fclose -> m_fclose
[apps/madmutt.git] / nntp / nntp.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1998 Brandon Long <blong@fiction.net>
4  * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net>
5  * Copyright (C) 2000-2002 Vsevolod Volkov <vvv@mutt.org.ua>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #include <lib-lib/lib-lib.h>
13
14 #include <lib-mime/mime.h>
15 #include <lib-ui/curses.h>
16 #include <lib-ui/sidebar.h>
17 #include <lib-mx/mx.h>
18
19 #include "mutt.h"
20 #include "sort.h"
21 #include "nntp.h"
22 #include "buffy.h"
23
24 #include <lib-crypt/crypt.h>
25
26 #define WANT_LISTGROUP_COMMAND          0
27
28 static unsigned int _checked = 0;
29
30 void nntp_sync_sidebar (NNTP_DATA* data) {
31   int i = 0;
32   BUFFY* tmp = NULL;
33   char buf[STRING];
34
35   if (!Incoming.len)
36     return;
37
38   /* unfortunately, NNTP_DATA::group only is the plain
39    * group name, so for every single update, we need to
40    * compose the full string which must be defined via
41    * mailboxes command ;-((( FIXME
42    */
43   buf[0] = '\0';
44   snprintf (buf, sizeof (buf), "nntp%s://%s%s%s%s/%s",
45             (data->nserv->conn->account.flags & M_ACCT_SSL) ? "s" : "",
46             NONULL (data->nserv->conn->account.user),
47             *data->nserv->conn->account.pass ? ":" : "",
48             *data->nserv->conn->account.pass ? data->nserv->conn->account.pass : "",
49             data->nserv->conn->account.host,
50             data->group);
51
52   /* bail out if group not found via mailboxes */
53   if ((i = buffy_lookup (buf)) < 0)
54     return;
55
56   tmp = Incoming.arr[i];
57   /* copied from browser.c */
58   if (option (OPTMARKOLD) &&
59       data->lastCached >= data->firstMessage &&
60       data->lastCached <= data->lastMessage)
61     tmp->msg_unread = data->lastMessage - data->lastCached;
62   else
63     tmp->msg_unread = data->unread;
64   tmp->new = data->unread > 0;
65   /* this is closest to a "total" count we can get */
66   tmp->msgcount = data->lastMessage - data->firstMessage;
67 }
68
69 static int nntp_auth (NNTP_SERVER * serv)
70 {
71   CONNECTION *conn = serv->conn;
72   char buf[STRING];
73   unsigned char flags = conn->account.flags;
74
75   if (mutt_account_getuser (&conn->account) || !conn->account.user[0] ||
76       mutt_account_getpass (&conn->account) || !conn->account.pass[0]) {
77     conn->account.flags = flags;
78     return -2;
79   }
80
81   mutt_message _("Logging in...");
82
83   snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user);
84   mutt_socket_write (conn, buf);
85   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) {
86     conn->account.flags = flags;
87     return -1;
88   }
89
90   snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n", conn->account.pass);
91   mutt_socket_write(conn, buf);
92   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) {
93     conn->account.flags = flags;
94     return -1;
95   }
96
97   if (m_strncmp("281", buf, 3)) {
98     conn->account.flags = flags;
99     mutt_error _("Login failed.");
100
101     sleep (2);
102     return -3;
103   }
104
105   return 0;
106 }
107
108 static int nntp_connect_error (NNTP_SERVER * serv)
109 {
110   serv->status = NNTP_NONE;
111   mutt_socket_close (serv->conn);
112   mutt_error _("Server closed connection!");
113
114   sleep (2);
115   return -1;
116 }
117
118 static int nntp_connect_and_auth (NNTP_SERVER * serv)
119 {
120   CONNECTION *conn = serv->conn;
121   char buf[STRING];
122   int rc;
123
124   serv->status = NNTP_NONE;
125
126   if (mutt_socket_open (conn) < 0)
127     return -1;
128
129   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
130     return nntp_connect_error (serv);
131
132   if (!m_strncmp("200", buf, 3))
133     mutt_message (_("Connected to %s. Posting ok."), conn->account.host);
134   else if (!m_strncmp("201", buf, 3))
135     mutt_message (_("Connected to %s. Posting NOT ok."), conn->account.host);
136   else {
137     mutt_socket_close(conn);
138     m_strrtrim(buf);
139     mutt_error("%s", buf);
140     sleep (2);
141     return -1;
142   }
143
144   sleep (1);
145
146   /* Tell INN to switch to mode reader if it isn't so. Ignore all
147      returned codes and messages. */
148   mutt_socket_write (conn, "MODE READER\r\n");
149   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
150     return nntp_connect_error (serv);
151
152   mutt_socket_write (conn, "STAT\r\n");
153   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
154     return nntp_connect_error (serv);
155
156   if (!(conn->account.flags & M_ACCT_USER) && m_strncmp("480", buf, 3)) {
157     serv->status = NNTP_OK;
158     return 0;
159   }
160
161   rc = nntp_auth (serv);
162   if (rc == -1)
163     return nntp_connect_error (serv);
164   if (rc == -2) {
165     mutt_socket_close (conn);
166     serv->status = NNTP_BYE;
167     return -1;
168   }
169   if (rc < 0) {
170     mutt_socket_close (conn);
171     mutt_error _("Login failed.");
172
173     sleep (2);
174     return -1;
175   }
176   serv->status = NNTP_OK;
177   return 0;
178 }
179
180 static int nntp_attempt_features (NNTP_SERVER * serv)
181 {
182   char buf[LONG_STRING];
183   CONNECTION *conn = serv->conn;
184
185   mutt_socket_write (conn, "XOVER\r\n");
186   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
187     return nntp_connect_error (serv);
188   if (m_strncmp("500", buf, 3))
189     serv->hasXOVER = 1;
190
191   mutt_socket_write (conn, "XPAT\r\n");
192   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
193     return nntp_connect_error (serv);
194   if (m_strncmp("500", buf, 3))
195     serv->hasXPAT = 1;
196
197 #if WANT_LISTGROUP_COMMAND
198   mutt_socket_write (conn, "LISTGROUP\r\n");
199   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
200     return (nntp_connect_error (serv));
201   if (m_strncmp("500", buf, 3))
202     serv->hasLISTGROUP = 1;
203 #endif
204
205   mutt_socket_write (conn, "XGTITLE +\r\n");
206   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
207     return nntp_connect_error (serv);
208   if (m_strncmp("500", buf, 3))
209     serv->hasXGTITLE = 1;
210
211   if (!m_strncmp("282", buf, 3)) {
212     do {
213       if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
214         return nntp_connect_error (serv);
215     } while (!(buf[0] == '.' && buf[1] == '\0'));
216   }
217
218   return 0;
219 }
220
221 static int nntp_open_connection (NNTP_SERVER * serv)
222 {
223   if (serv->status == NNTP_OK)
224     return 0;
225   if (serv->status == NNTP_BYE)
226     return -1;
227   if (nntp_connect_and_auth (serv) < 0)
228     return -1;
229   if (nntp_attempt_features (serv) < 0)
230     return -1;
231   return 0;
232 }
233
234 static int nntp_reconnect (NNTP_SERVER * serv)
235 {
236   char buf[SHORT_STRING];
237
238   mutt_socket_close (serv->conn);
239
240   for (;;) {
241     if (nntp_connect_and_auth (serv) == 0)
242       return 0;
243
244     snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"),
245               serv->conn->account.host);
246     if (query_quadoption (OPT_NNTPRECONNECT, buf) != M_YES) {
247       serv->status = NNTP_BYE;
248       return -1;
249     }
250   }
251 }
252
253 /* Send data from line[LONG_STRING] and receive answer to same line */
254 static int mutt_nntp_query (NNTP_DATA * data, char *line, size_t linelen)
255 {
256   char buf[LONG_STRING];
257   int done = TRUE;
258
259   if (data->nserv->status == NNTP_BYE)
260     return -1;
261
262   do {
263     if (*line) {
264       mutt_socket_write (data->nserv->conn, line);
265     }
266     else if (data->group) {
267       snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group);
268       mutt_socket_write (data->nserv->conn, buf);
269     }
270
271     done = TRUE;
272     if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0) {
273       if (nntp_reconnect (data->nserv) < 0)
274         return -1;
275
276       if (data->group) {
277         snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group);
278         mutt_socket_write (data->nserv->conn, buf);
279         if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0)
280           return -1;
281       }
282       if (*line)
283         done = FALSE;
284     }
285     else if ((!m_strncmp("480", buf, 3)) && nntp_auth (data->nserv) < 0)
286       return -1;
287   } while (!done);
288
289   m_strcpy(line, linelen, buf);
290   return 0;
291 }
292
293 /*
294  * This function calls  funct(*line, *data)  for each received line,
295  * funct(NULL, *data)  if  rewind(*data)  needs, exits when fail or done.
296  * Returned codes:
297  *  0 - successful,
298  *  1 - correct but not performed (may be, have to be continued),
299  * -1 - conection lost,
300  * -2 - invalid command or execution error,
301  * -3 - error in funct(*line, *data).
302  */
303 static int mutt_nntp_fetch (NNTP_DATA * nntp_data, const char *query, char *msg,
304                             progress_t* bar, int (*funct) (char *, void *),
305                             void *data, int tagged)
306 {
307   char buf[LONG_STRING];
308   char *inbuf, *p;
309   int done = FALSE;
310   int chunk, line;
311   long pos = 0;
312   size_t lenbuf = 0;
313   int ret;
314
315   do {
316     m_strcpy(buf, sizeof(buf), query);
317     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0)
318       return -1;
319     if (buf[0] == '5')
320       return -2;
321     if (buf[0] != '2')
322       return 1;
323
324     ret = 0;
325     line = 0;
326     inbuf = p_new(char, sizeof(buf));
327
328     for (;;) {
329       chunk = mutt_socket_readln(buf, sizeof (buf), nntp_data->nserv->conn);
330       if (chunk < 0)
331         break;
332
333       p = buf;
334       if (!lenbuf && buf[0] == '.') {
335         if (buf[1] == '\0') {
336           done = TRUE;
337           break;
338         }
339         if (buf[1] == '.')
340           p++;
341       }
342
343       m_strcpy(inbuf + lenbuf, sizeof(buf), p);
344       pos += chunk;
345
346       if (chunk >= ssizeof (buf)) {
347         lenbuf += m_strlen(p);
348       }
349       else {
350         if (bar) {
351           mutt_progress_bar (bar, pos);
352         } else if (msg) {
353           line++;
354           if (ReadInc && (line % ReadInc == 0)) {
355             if (tagged)
356               mutt_message (_("%s (tagged: %d) %d"), msg, tagged, line);
357             else
358               mutt_message ("%s %d", msg, line);
359           }
360         }
361
362         if (ret == 0 && funct (inbuf, data) < 0)
363           ret = -3;
364         lenbuf = 0;
365       }
366
367       p_realloc(&inbuf, lenbuf + sizeof (buf));
368     }
369     p_delete(&inbuf);
370     funct (NULL, data);
371   }
372   while (!done);
373   return ret;
374 }
375
376 static int nntp_read_tempfile (char *line, void *file)
377 {
378   FILE *f = (FILE *) file;
379
380   if (!line)
381     rewind (f);
382   else {
383     fputs (line, f);
384     if (fputc ('\n', f) == EOF)
385       return -1;
386   }
387   return 0;
388 }
389
390 static void nntp_parse_xref (CONTEXT * ctx, char *group, char *xref,
391                              HEADER * h)
392 {
393   register char *p, *b;
394   register char *colon = NULL;
395
396   b = p = xref;
397   while (*p) {
398     /* skip to next word */
399     b = p;
400     while (*b && ((*b == ' ') || (*b == '\t')))
401       b++;
402     p = b;
403     colon = NULL;
404     /* skip to end of word */
405     while (*p && (*p != ' ') && (*p != '\t')) {
406       if (*p == ':')
407         colon = p;
408       p++;
409     }
410     if (*p) {
411       *p = '\0';
412       p++;
413     }
414     if (colon) {
415       *colon = '\0';
416       colon++;
417       nntp_get_status (ctx, h, b, atoi (colon));
418       if (h && h->article_num == 0 && m_strcmp(group, b) == 0)
419         h->article_num = atoi (colon);
420     }
421   }
422 }
423
424 /*
425  * returns:
426  *  0 on success
427  *  1 if article not found
428  * -1 if read or write error on tempfile or socket
429  */
430 static int nntp_read_header (CONTEXT * ctx, const char *msgid,
431                              int article_num)
432 {
433   NNTP_DATA *nntp_data = ((NNTP_DATA *) ctx->data);
434   FILE *f;
435   char buf[LONG_STRING];
436   char tempfile[_POSIX_PATH_MAX];
437   int ret;
438   HEADER *h = ctx->hdrs[ctx->msgcount];
439
440   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(Tempdir), NULL);
441   if (!f)
442     return -1;
443
444   if (!msgid)
445     snprintf (buf, sizeof (buf), "HEAD %d\r\n", article_num);
446   else
447     snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid);
448
449   ret = mutt_nntp_fetch (nntp_data, buf, NULL, NULL, nntp_read_tempfile, f, 0);
450   if (ret) {
451     m_fclose(&f);
452     unlink (tempfile);
453     return (ret == -1 ? -1 : 1);
454   }
455
456   h->article_num = article_num;
457   h->env = mutt_read_rfc822_header (f, h, 0, 0);
458   m_fclose(&f);
459   unlink (tempfile);
460
461   if (h->env->xref != NULL)
462     nntp_parse_xref (ctx, nntp_data->group, h->env->xref, h);
463   else if (h->article_num == 0 && msgid) {
464     snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid);
465     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) == 0)
466       h->article_num = atoi (buf + 4);
467   }
468
469   return 0;
470 }
471
472 static int parse_description (char *line, void *n)
473 {
474 #define news ((NNTP_SERVER *) n)
475   register char *d = line;
476   NNTP_DATA *data;
477
478   if (!line)
479     return 0;
480   while (*d && *d != '\t' && *d != ' ')
481     d++;
482   *d = 0;
483   d++;
484   while (*d && (*d == '\t' || *d == ' '))
485     d++;
486   if ((data = (NNTP_DATA *) hash_find (news->newsgroups, line)) != NULL &&
487       m_strcmp(d, data->desc)) {
488     p_delete(&data->desc);
489     data->desc = m_strdup(d);
490   }
491   return 0;
492 #undef news
493 }
494
495 static void nntp_get_desc (NNTP_DATA * data, const char *mask, char *msg, progress_t* bar)
496 {
497   char buf[STRING];
498
499   if (!option (OPTLOADDESC) || !data || !data->nserv)
500     return;
501
502   /* Get newsgroup description, if we can */
503   if (data->nserv->hasXGTITLE)
504     snprintf (buf, sizeof (buf), "XGTITLE %s\r\n", mask);
505   else
506     snprintf (buf, sizeof (buf), "string_list_t NEWSGROUPS %s\r\n", mask);
507   if (mutt_nntp_fetch (data, buf, msg, bar, parse_description, data->nserv, 0) !=
508       0) {
509   }
510 }
511
512 /*
513  * XOVER returns a tab separated list of:
514  * id|subject|from|date|Msgid|references|bytes|lines|xref
515  *
516  * This has to duplicate some of the functionality of 
517  * mutt_read_rfc822_header(), since it replaces the call to that (albeit with
518  * a limited number of headers which are "parsed" by placement in the list)
519  */
520 static int nntp_parse_xover (CONTEXT * ctx, char *buf, HEADER * hdr)
521 {
522   NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data;
523   char *p, *b;
524   int x, done = 0;
525
526   hdr->env = envelope_new();
527   hdr->env->newsgroups = m_strdup(nntp_data->group);
528   hdr->content = body_new();
529   hdr->content->type = TYPETEXT;
530   hdr->content->subtype = m_strdup("plain");
531   hdr->content->encoding = ENC7BIT;
532   hdr->content->disposition = DISPINLINE;
533   hdr->content->length = -1;
534   b = p = buf;
535
536   for (x = 0; !done && x < 9; x++) {
537     /* if from file, need to skip newline character */
538     while (*p && *p != '\n' && *p != '\t')
539       p++;
540     if (!*p)
541       done++;
542     *p = '\0';
543     p++;
544     switch (x) {
545     case 0:
546
547       hdr->article_num = atoi (b);
548       nntp_get_status (ctx, hdr, NULL, hdr->article_num);
549       break;
550     case 1:
551       hdr->env->subject = m_strdup(b);
552       break;
553     case 2:
554       address_list_wipe(&hdr->env->from);
555       hdr->env->from = rfc822_parse_adrlist (hdr->env->from, b);
556       /* same as for mutt_parse_rfc822_line():
557        * don't leave from info NULL if there's an invalid address (or
558        * whatever) in From: field; mutt would just display it as empty
559        * and mark mail/(esp.) news article as your own. aaargh! this
560        * bothered me for _years_ */
561       if (!hdr->env->from) {
562         hdr->env->from = address_new ();
563         hdr->env->from->personal = m_strdup(b);
564       }
565       break;
566     case 3:
567       hdr->date_sent = mutt_parse_date (b, hdr);
568       hdr->received = hdr->date_sent;
569       break;
570     case 4:
571       p_delete(&hdr->env->message_id);
572       hdr->env->message_id = m_strdup(b);
573       break;
574     case 5:
575       string_list_wipe(&hdr->env->references);
576       hdr->env->references = mutt_parse_references (b, 0);
577       break;
578     case 6:
579       hdr->content->length = atoi (b);
580       break;
581     case 7:
582       hdr->lines = atoi (b);
583       break;
584     case 8:
585       if (!hdr->read)
586         p_delete(&hdr->env->xref);
587       b = b + 6;                /* skips the "Xref: " */
588       hdr->env->xref = m_strdup(b);
589       nntp_parse_xref (ctx, nntp_data->group, b, hdr);
590     }
591     rfc2047_decode_envelope(hdr->env);
592     if (!*p)
593       return -1;
594     b = p;
595   }
596   return 0;
597 }
598
599 typedef struct {
600   CONTEXT *ctx;
601   unsigned int base;
602   unsigned int first;
603   unsigned int last;
604   unsigned short *messages;
605   char *msg;
606 } FETCH_CONTEXT;
607
608 #define fc ((FETCH_CONTEXT *) c)
609
610 #if WANT_LISTGROUP_COMMAND
611 static int _nntp_fetch_numbers (unsigned int num, void *c)
612 {
613   if (num < fc->base || num > fc->last)
614     return 0;
615   fc->messages[num - fc->base] = 1;
616   return 0;
617 }
618 static int nntp_fetch_numbers (char *line, void *c)
619 {
620   if (!line)
621     return 0;
622   return (_nntp_fetch_numbers ((unsigned int) atoi (line), c));
623 }
624 #endif
625
626 static int add_xover_line (char *line, void *c)
627 {
628   unsigned int num, total;
629   CONTEXT *ctx = fc->ctx;
630   NNTP_DATA *data = (NNTP_DATA *) ctx->data;
631
632   if (!line)
633     return 0;
634
635   if (ctx->msgcount >= ctx->hdrmax)
636     mx_alloc_memory (ctx);
637   ctx->hdrs[ctx->msgcount] = header_new();
638   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
639
640   nntp_parse_xover (ctx, line, ctx->hdrs[ctx->msgcount]);
641   num = ctx->hdrs[ctx->msgcount]->article_num;
642
643   if (num >= fc->first && num <= fc->last && fc->messages[num - fc->base]) {
644     ctx->msgcount++;
645     if (num > data->lastLoaded)
646       data->lastLoaded = num;
647     num = num - fc->first + 1;
648     total = fc->last - fc->first + 1;
649     if (!ctx->quiet && fc->msg && ReadInc && (num % ReadInc == 0))
650       mutt_message ("%s %d/%d", fc->msg, num, total);
651   }
652   else
653     header_delete(&ctx->hdrs[ctx->msgcount]);       /* skip it */
654
655   return 0;
656 }
657
658 #undef fc
659
660 static int nntp_fetch_headers (CONTEXT * ctx, unsigned int first,
661                                unsigned int last)
662 {
663   char buf[HUGE_STRING];
664   char *msg = _("Fetching message headers...");
665   char *msg2 = _("Fetching headers from cache...");
666   NNTP_DATA *nntp_data = ((NNTP_DATA *) ctx->data);
667   int ret;
668   int num;
669   int oldmsgcount;
670   unsigned int current;
671   FILE *f;
672   FETCH_CONTEXT fc;
673
674   /* if empty group or nothing to do */
675   if (!last || first > last)
676     return 0;
677
678   /* fetch list of articles */
679   mutt_message _("Fetching list of articles...");
680
681   fc.ctx = ctx;
682   fc.base = first;
683   fc.last = last;
684   fc.messages = p_new(unsigned short, last - first + 1);
685 #if WANT_LISTGROUP_COMMAND
686   if (nntp_data->nserv->hasLISTGROUP) {
687     snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group);
688     if (mutt_nntp_fetch (nntp_data, buf, NULL, NULL, nntp_fetch_numbers, &fc, 0) !=
689         0) {
690       mutt_error (_("LISTGROUP command failed: %s"), buf);
691       sleep (2);
692       p_delete(&fc.messages);
693       return -1;
694     }
695   }
696   else {
697 #endif
698     for (num = 0; num < last - first + 1; num++)
699       fc.messages[num] = 1;
700 #if WANT_LISTGROUP_COMMAND
701   }
702 #endif
703
704   /* CACHE: must be loaded xover cache here */
705   num = nntp_data->lastCached - first + 1;
706   if (option (OPTNEWSCACHE) && nntp_data->cache && num > 0) {
707     nntp_cache_expand (buf, nntp_data->cache);
708     mutt_message (msg2);
709
710     if ((f = safe_fopen (buf, "r"))) {
711       int r = 0, c = 0;
712
713       /* counting number of lines */
714       while (fgets (buf, sizeof (buf), f) != NULL)
715         r++;
716       rewind (f);
717       while (r > num && fgets (buf, sizeof (buf), f) != NULL)
718         r--;
719       oldmsgcount = ctx->msgcount;
720       fc.first = first;
721       fc.last = first + num - 1;
722       fc.msg = NULL;
723       while (fgets (buf, sizeof (buf), f) != NULL) {
724         if (ReadInc && ((++c) % ReadInc == 0))
725           mutt_message ("%s %d/%d", msg2, c, r);
726         add_xover_line (buf, &fc);
727       }
728       m_fclose(&f);
729       nntp_data->lastLoaded = fc.last;
730       first = fc.last + 1;
731       if (ctx->msgcount > oldmsgcount)
732         mx_update_context (ctx, ctx->msgcount - oldmsgcount);
733     }
734     else
735       nntp_delete_cache (nntp_data);
736   }
737   num = last - first + 1;
738   if (num <= 0) {
739     p_delete(&fc.messages);
740     return 0;
741   }
742
743   /*
744    * Without XOVER, we have to fetch each article header and parse
745    * it.  With XOVER, we ask for all of them
746    */
747   mutt_message (msg);
748   if (nntp_data->nserv->hasXOVER) {
749     oldmsgcount = ctx->msgcount;
750     fc.first = first;
751     fc.last = last;
752     fc.msg = msg;
753     snprintf (buf, sizeof (buf), "XOVER %d-%d\r\n", first, last);
754     ret = mutt_nntp_fetch (nntp_data, buf, NULL, NULL, add_xover_line, &fc, 0);
755     if (ctx->msgcount > oldmsgcount)
756       mx_update_context (ctx, ctx->msgcount - oldmsgcount);
757     if (ret != 0) {
758       mutt_error (_("XOVER command failed: %s"), buf);
759       p_delete(&fc.messages);
760       return -1;
761     }
762     /* fetched OK */
763   }
764   else
765     for (current = first; current <= last; current++) {
766       HEADER *h;
767
768       ret = current - first + 1;
769       mutt_message ("%s %d/%d", msg, ret, num);
770
771       if (!fc.messages[current - fc.base])
772         continue;
773
774       if (ctx->msgcount >= ctx->hdrmax)
775         mx_alloc_memory (ctx);
776       h = ctx->hdrs[ctx->msgcount] = header_new();
777       h->index = ctx->msgcount;
778
779       ret = nntp_read_header (ctx, NULL, current);
780       if (ret == 0) {           /* Got article. Fetch next header */
781         nntp_get_status (ctx, h, NULL, h->article_num);
782         ctx->msgcount++;
783         mx_update_context (ctx, 1);
784       }
785       else
786         header_delete(&h);  /* skip it */
787       if (ret == -1) {
788         p_delete(&fc.messages);
789         return -1;
790       }
791
792       if (current > nntp_data->lastLoaded)
793         nntp_data->lastLoaded = current;
794     }
795   p_delete(&fc.messages);
796   nntp_data->lastLoaded = last;
797   mutt_clear_error ();
798   return 0;
799 }
800
801 /* 
802  * currently, nntp "mailbox" is "newsgroup"
803  */
804 static int nntp_open_mailbox (CONTEXT * ctx)
805 {
806   NNTP_DATA *nntp_data;
807   NNTP_SERVER *serv;
808   char buf[HUGE_STRING];
809   char server[LONG_STRING];
810   int count = 0;
811   unsigned int first;
812   ACCOUNT act;
813
814   p_clear(&act, 1);
815
816   if (nntp_parse_url (ctx->path, &act, buf, sizeof (buf)) < 0 || !*buf) {
817     mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path);
818     mutt_sleep (2);
819     return -1;
820   }
821
822   server[0] = '\0';
823   nntp_expand_path (server, sizeof (server), &act);
824   if (!(serv = mutt_select_newsserver (server)) || serv->status != NNTP_OK)
825     return -1;
826
827   CurrentNewsSrv = serv;
828
829   /* create NNTP-specific state struct if nof found in list */
830   if ((nntp_data = (NNTP_DATA *) hash_find (serv->newsgroups, buf)) == NULL) {
831     nntp_data = xmalloc(sizeof(NNTP_DATA) + m_strlen(buf) + 1);
832     nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA);
833     strcpy (nntp_data->group, buf);
834     hash_insert (serv->newsgroups, nntp_data->group, nntp_data, 0);
835     nntp_add_to_list (serv, nntp_data);
836   }
837   ctx->data = nntp_data;
838   nntp_data->nserv = serv;
839
840   mutt_message (_("Selecting %s..."), nntp_data->group);
841
842   if (!nntp_data->desc) {
843     nntp_get_desc (nntp_data, nntp_data->group, NULL, NULL);
844     if (nntp_data->desc)
845       nntp_save_cache_index (serv);
846   }
847
848   buf[0] = 0;
849   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
850     return -1;
851   }
852
853   if (m_strncmp("211", buf, 3)) {
854     string_list_t *l = serv->list;
855
856     /* GROUP command failed */
857     if (!m_strncmp("411", buf, 3)) {
858       mutt_error (_("Newsgroup %s not found on server %s"),
859                   nntp_data->group, serv->conn->account.host);
860
861       /* CACHE: delete cache and line from .index */
862       nntp_delete_cache (nntp_data);
863       hash_delete (serv->newsgroups, nntp_data->group, NULL,
864                    nntp_delete_data);
865       while (l && l->data != (void *) nntp_data)
866         l = l->next;
867       if (l)
868         l->data = NULL;
869
870       sleep (2);
871     }
872
873     return -1;
874   }
875
876   sscanf (buf + 4, "%d %u %u %s", &count, &nntp_data->firstMessage,
877           &nntp_data->lastMessage, buf);
878
879   nntp_data->deleted = 0;
880
881   time (&serv->check_time);
882
883   /*
884    * Check for max adding context. If it is greater than $nntp_context,
885    * strip off extra articles
886    */
887   first = nntp_data->firstMessage;
888   if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext)
889     first = nntp_data->lastMessage - NntpContext + 1;
890   if (first)
891     nntp_data->lastLoaded = first - 1;
892   return nntp_fetch_headers (ctx, first, nntp_data->lastMessage);
893 }
894
895 int nntp_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno)
896 {
897   char buf[LONG_STRING];
898   char path[_POSIX_PATH_MAX];
899   NNTP_CACHE *cache;
900   int ret;
901   progress_t bar;
902
903   /* see if we already have the message in our cache */
904   cache =
905     &((NNTP_DATA *) ctx->data)->acache[ctx->hdrs[msgno]->index %
906                                        NNTP_CACHE_LEN];
907
908   /* if everything is fine, assign msg->fp and return */
909   if (cache->path && cache->index == ctx->hdrs[msgno]->index &&
910       (msg->fp = fopen (cache->path, "r")))
911     return 0;
912
913   /* clear the previous entry */
914   unlink (cache->path);
915   p_delete(&cache->path);
916
917   cache->index = ctx->hdrs[msgno]->index;
918   msg->fp = m_tempfile(path, sizeof(path), NONULL(Tempdir), NULL);
919   if (!msg->fp) {
920     return -1;
921   }
922   cache->path = m_strdup(path);
923
924   if (ctx->hdrs[msgno]->article_num == 0)
925     snprintf (buf, sizeof (buf), "ARTICLE %s\r\n",
926               ctx->hdrs[msgno]->env->message_id);
927   else
928     snprintf (buf, sizeof (buf), "ARTICLE %d\r\n",
929               ctx->hdrs[msgno]->article_num);
930
931   bar.msg = _("Fetching message...");
932   bar.size = 0;
933   mutt_progress_bar (&bar, 0);
934
935   ret = mutt_nntp_fetch ((NNTP_DATA *) ctx->data, buf, NULL, &bar, nntp_read_tempfile,
936                          msg->fp, ctx->tagged);
937   if (ret == 1) {
938     mutt_error (_("Article %d not found on server"),
939                 ctx->hdrs[msgno]->article_num);
940   }
941
942   if (ret) {
943     m_fclose(&msg->fp);
944     unlink (path);
945     p_delete(&cache->path);
946     return -1;
947   }
948
949   envelope_delete(&ctx->hdrs[msgno]->env);
950   ctx->hdrs[msgno]->env =
951     mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno], 0, 0);
952   /* fix content length */
953   fseeko (msg->fp, 0, SEEK_END);
954   ctx->hdrs[msgno]->content->length = ftello (msg->fp) -
955     ctx->hdrs[msgno]->content->offset;
956
957   /* this is called in mutt before the open which fetches the message, 
958    * which is probably wrong, but we just call it again here to handle
959    * the problem instead of fixing it.
960    */
961   mutt_parse_mime_message (ctx, ctx->hdrs[msgno]);
962
963   /* These would normally be updated in mx_update_context(), but the 
964    * full headers aren't parsed with XOVER, so the information wasn't
965    * available then.
966    */
967   ctx->hdrs[msgno]->security = crypt_query (ctx->hdrs[msgno]->content);
968
969   mutt_clear_error ();
970   rewind (msg->fp);
971
972   return 0;
973 }
974
975 /* Post article */
976 int nntp_post (const char *msg)
977 {
978   char buf[LONG_STRING];
979   size_t len;
980   FILE *f;
981   NNTP_DATA *nntp_data;
982
983   if (Context && Context->magic == M_NNTP)
984     nntp_data = (NNTP_DATA *) Context->data;
985   else {
986     if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer)) ||
987         !CurrentNewsSrv->list || !CurrentNewsSrv->list->data) {
988       mutt_error (_("Can't post article. No connection to news server."));
989       return -1;
990     }
991     nntp_data = (NNTP_DATA *) CurrentNewsSrv->list->data;
992   }
993
994   if (!(f = safe_fopen (msg, "r"))) {
995     mutt_error (_("Can't post article. Unable to open %s"), msg);
996     return -1;
997   }
998
999   m_strcpy(buf, sizeof(buf), "POST\r\n");
1000   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1001     mutt_error (_("Can't post article. Connection to %s lost."),
1002                 nntp_data->nserv->conn->account.host);
1003     return -1;
1004   }
1005   if (buf[0] != '3') {
1006     mutt_error (_("Can't post article: %s"), buf);
1007     return -1;
1008   }
1009
1010   buf[0] = '.';
1011   buf[1] = '\0';
1012   while (fgets (buf + 1, sizeof (buf) - 2, f) != NULL) {
1013     len = m_strlen(buf);
1014     if (buf[len - 1] == '\n') {
1015       buf[len - 1] = '\r';
1016       buf[len] = '\n';
1017       len++;
1018       buf[len] = '\0';
1019     }
1020     if (buf[1] == '.')
1021       mutt_socket_write(nntp_data->nserv->conn, buf);
1022     else
1023       mutt_socket_write(nntp_data->nserv->conn, buf + 1);
1024   }
1025   m_fclose(&f);
1026
1027   if (buf[m_strlen(buf) - 1] != '\n')
1028     mutt_socket_write(nntp_data->nserv->conn, "\r\n");
1029   mutt_socket_write(nntp_data->nserv->conn, ".\r\n");
1030   if (mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) {
1031     mutt_error (_("Can't post article. Connection to %s lost."),
1032                 nntp_data->nserv->conn->account.host);
1033     return -1;
1034   }
1035   if (buf[0] != '2') {
1036     mutt_error (_("Can't post article: %s"), buf);
1037     return -1;
1038   }
1039
1040   return 0;
1041 }
1042
1043 /* nntp_logout_all: close all open connections. */
1044 void nntp_logout_all (void)
1045 {
1046   char buf[LONG_STRING];
1047   CONNECTION *conn;
1048
1049   conn = mutt_socket_head ();
1050
1051   while (conn) {
1052     CONNECTION* next = conn->next;
1053     if (conn->account.type == M_ACCT_TYPE_NNTP) {
1054       mutt_message (_("Closing connection to %s..."), conn->account.host);
1055       mutt_socket_write (conn, "QUIT\r\n");
1056       mutt_socket_readln (buf, sizeof (buf), conn);
1057       mutt_clear_error ();
1058       mutt_socket_close (conn);
1059       mutt_socket_free (conn);
1060     }
1061     conn = next;
1062   }
1063 }
1064
1065 static void nntp_free_acache (NNTP_DATA * data)
1066 {
1067   int i;
1068
1069   for (i = 0; i < NNTP_CACHE_LEN; i++) {
1070     if (data->acache[i].path) {
1071       unlink (data->acache[i].path);
1072       p_delete(&data->acache[i].path);
1073     }
1074   }
1075 }
1076
1077 void nntp_delete_data (void *p)
1078 {
1079   NNTP_DATA *data = (NNTP_DATA *)p;
1080
1081   if (!p)
1082     return;
1083   p_delete(&data->entries);
1084   p_delete(&data->desc);
1085   p_delete(&data->cache);
1086   nntp_free_acache (data);
1087   p_delete(&data);
1088 }
1089
1090 static int nntp_sync_mailbox (CONTEXT * ctx, int unused1, int* unused2)
1091 {
1092   NNTP_DATA *data = ctx->data;
1093
1094   /* CACHE: update cache and .index files */
1095   if ((option (OPTSAVEUNSUB) || data->subscribed))
1096     nntp_save_cache_group (ctx);
1097   nntp_free_acache (data);
1098
1099   data->nserv->check_time = 0;  /* next nntp_check_mailbox() will really check */
1100   return 0;
1101 }
1102
1103 static void nntp_fastclose_mailbox (CONTEXT * ctx)
1104 {
1105   NNTP_DATA *data = (NNTP_DATA *) ctx->data, *tmp;
1106
1107   if (!data)
1108     return;
1109   nntp_free_acache (data);
1110   if (!data->nserv || !data->nserv->newsgroups || !data->group)
1111     return;
1112   nntp_save_cache_index (data->nserv);
1113   if ((tmp = hash_find (data->nserv->newsgroups, data->group)) == NULL
1114       || tmp != data)
1115     nntp_delete_data (data);
1116   else
1117     nntp_sync_sidebar (data);
1118 }
1119
1120 /* commit changes and terminate connection */
1121 int nntp_close_mailbox (CONTEXT * ctx)
1122 {
1123   if (!ctx)
1124     return -1;
1125   mutt_message _("Quitting newsgroup...");
1126
1127   if (ctx->data) {
1128     NNTP_DATA *data = (NNTP_DATA *) ctx->data;
1129     int ret;
1130
1131     if (data->nserv && data->nserv->conn && ctx->unread) {
1132       ret = query_quadoption (OPT_CATCHUP, _("Mark all articles read?"));
1133       if (ret == M_YES)
1134         mutt_newsgroup_catchup (data->nserv, data->group);
1135       else if (ret < 0)
1136         return -1;
1137     }
1138   }
1139   nntp_sync_mailbox (ctx, 0, NULL);
1140   if (ctx->data && ((NNTP_DATA *) ctx->data)->nserv) {
1141     NNTP_SERVER *news;
1142
1143     news = ((NNTP_DATA *) ctx->data)->nserv;
1144     newsrc_gen_entries (ctx);
1145     ((NNTP_DATA *) ctx->data)->unread = ctx->unread;
1146     mutt_newsrc_update (news);
1147   }
1148   mutt_clear_error ();
1149   return 0;
1150 }
1151
1152 /* use the GROUP command to poll for new mail */
1153 static int _nntp_check_mailbox (CONTEXT * ctx, NNTP_DATA * nntp_data)
1154 {
1155   char buf[LONG_STRING];
1156   int count = 0;
1157
1158   if (nntp_data->nserv->check_time + NewsPollTimeout > time (NULL))
1159     return 0;
1160
1161   buf[0] = 0;
1162   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1163     return -1;
1164   }
1165   if (m_strncmp("211", buf, 3)) {
1166     buf[0] = 0;
1167     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1168       return -1;
1169     }
1170   }
1171   if (!m_strncmp("211", buf, 3)) {
1172     int first;
1173     int last;
1174
1175     sscanf (buf + 4, "%d %d %d", &count, &first, &last);
1176     nntp_data->firstMessage = first;
1177     nntp_data->lastMessage = last;
1178     if (ctx && last > nntp_data->lastLoaded) {
1179       nntp_fetch_headers (ctx, nntp_data->lastLoaded + 1, last);
1180       time (&nntp_data->nserv->check_time);
1181       return 1;
1182     }
1183     if (!last || (!nntp_data->rc && !nntp_data->lastCached))
1184       nntp_data->unread = count;
1185     else
1186       mutt_newsgroup_stat (nntp_data);
1187     /* active was renumbered? */
1188     if (last < nntp_data->lastLoaded) {
1189       if (!nntp_data->max) {
1190         nntp_data->entries = p_new(NEWSRC_ENTRY, 5);
1191         nntp_data->max = 5;
1192       }
1193       nntp_data->lastCached = 0;
1194       nntp_data->num = 1;
1195       nntp_data->entries[0].first = 1;
1196       nntp_data->entries[0].last = 0;
1197     }
1198     nntp_sync_sidebar (nntp_data);
1199   }
1200
1201   time (&nntp_data->nserv->check_time);
1202   return 0;
1203 }
1204
1205 static int nntp_check_mailbox (CONTEXT * ctx, int* unused1, int unused2)
1206 {
1207   return _nntp_check_mailbox (ctx, (NNTP_DATA *) ctx->data);
1208 }
1209
1210 static int add_group (char *buf, void *serv)
1211 {
1212 #define s ((NNTP_SERVER *) serv)
1213   char group[LONG_STRING], mod, desc[HUGE_STRING];
1214   int first, last;
1215   NNTP_DATA *nntp_data;
1216   static int n = 0;
1217
1218   _checked = n;                 /* _checked have N, where N = number of groups */
1219   if (!buf)                     /* at EOF must be zerouth */
1220     n = 0;
1221
1222   if (!s || !buf)
1223     return 0;
1224
1225   *desc = 0;
1226   sscanf (buf, "%s %d %d %c %[^\n]", group, &last, &first, &mod, desc);
1227   if (!group)
1228     return 0;
1229   if ((nntp_data = (NNTP_DATA *) hash_find (s->newsgroups, group)) == NULL) {
1230     n++;
1231     nntp_data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1);
1232     nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA);
1233     strcpy (nntp_data->group, group);
1234     nntp_data->nserv = s;
1235     if (s->newsgroups->nelem < s->newsgroups->curnelem * 2)
1236       s->newsgroups = hash_resize (s->newsgroups, s->newsgroups->nelem * 2);
1237     hash_insert (s->newsgroups, nntp_data->group, nntp_data, 0);
1238     nntp_add_to_list (s, nntp_data);
1239   }
1240   nntp_data->deleted = 0;
1241   nntp_data->firstMessage = first;
1242   nntp_data->lastMessage = last;
1243   if (mod == 'y')
1244     nntp_data->allowed = 1;
1245   else
1246     nntp_data->allowed = 0;
1247   if (nntp_data->desc)
1248     p_delete(&nntp_data->desc);
1249   if (*desc)
1250     nntp_data->desc = m_strdup(desc);
1251   if (nntp_data->rc || nntp_data->lastCached)
1252     mutt_newsgroup_stat (nntp_data);
1253   else if (nntp_data->lastMessage &&
1254            nntp_data->firstMessage <= nntp_data->lastMessage)
1255     nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1;
1256   else
1257     nntp_data->unread = 0;
1258
1259   return 0;
1260 #undef s
1261 }
1262
1263 int nntp_check_newgroups (NNTP_SERVER * serv, int force)
1264 {
1265   char buf[LONG_STRING];
1266   NNTP_DATA nntp_data;
1267   string_list_t *l;
1268   string_list_t emp;
1269   time_t now;
1270   struct tm *t;
1271
1272   if (!serv || !serv->newgroups_time)
1273     return -1;
1274
1275   if (nntp_open_connection (serv) < 0)
1276     return -1;
1277
1278   /* check subscribed groups for new news */
1279   if (option (OPTSHOWNEWNEWS)) {
1280     mutt_message _("Checking for new messages...");
1281
1282     for (l = serv->list; l; l = l->next) {
1283       serv->check_time = 0;     /* really check! */
1284       if (l->data && ((NNTP_DATA *) l->data)->subscribed)
1285         _nntp_check_mailbox (NULL, (NNTP_DATA *) l->data);
1286     }
1287     sidebar_draw ();
1288   }
1289   else if (!force)
1290     return 0;
1291
1292   mutt_message _("Checking for new newsgroups...");
1293
1294   now = serv->newgroups_time;
1295   time (&serv->newgroups_time);
1296   t = gmtime (&now);
1297   snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
1298             (t->tm_year % 100), t->tm_mon + 1, t->tm_mday, t->tm_hour,
1299             t->tm_min, t->tm_sec);
1300   nntp_data.nserv = serv;
1301   if (Context && Context->magic == M_NNTP)
1302     nntp_data.group = ((NNTP_DATA *) Context->data)->group;
1303   else
1304     nntp_data.group = NULL;
1305   l = serv->tail;
1306   if (mutt_nntp_fetch (&nntp_data, buf, _("Adding new newsgroups..."), NULL,
1307                        add_group, serv, 0) != 0) {
1308     return -1;
1309   }
1310
1311   mutt_message _("Loading descriptions...");
1312
1313   if (l)
1314     emp.next = l->next;
1315   else
1316     emp.next = serv->list;
1317   l = &emp;
1318   while (l->next) {
1319     l = l->next;
1320     ((NNTP_DATA *) l->data)->new = 1;
1321     nntp_get_desc ((NNTP_DATA *) l->data, ((NNTP_DATA *) l->data)->group,
1322                    NULL, NULL);
1323   }
1324   if (emp.next)
1325     nntp_save_cache_index (serv);
1326   mutt_clear_error ();
1327   return _checked;
1328 }
1329
1330 /* Load list of all newsgroups from cache ALL */
1331 int nntp_get_cache_all (NNTP_SERVER * serv)
1332 {
1333   char buf[HUGE_STRING];
1334   FILE *f;
1335
1336   nntp_cache_expand (buf, serv->cache);
1337   if ((f = safe_fopen (buf, "r"))) {
1338     int i = 0;
1339
1340     while (fgets (buf, sizeof (buf), f) != NULL) {
1341       if (ReadInc && (i % ReadInc == 0))
1342         mutt_message (_("Loading list from cache... %d"), i);
1343       add_group (buf, serv);
1344       i++;
1345     }
1346     add_group (NULL, NULL);
1347     m_fclose(&f);
1348     mutt_clear_error ();
1349     return 0;
1350   }
1351   else {
1352     p_delete(&serv->cache);
1353     return -1;
1354   }
1355 }
1356
1357 /* Load list of all newsgroups from active */
1358 int nntp_get_active (NNTP_SERVER * serv)
1359 {
1360   char msg[SHORT_STRING];
1361   NNTP_DATA nntp_data;
1362   string_list_t *tmp;
1363
1364   if (nntp_open_connection (serv) < 0)
1365     return -1;
1366
1367   snprintf (msg, sizeof (msg),
1368             _("Loading list of all newsgroups on server %s..."),
1369             serv->conn->account.host);
1370   mutt_message (msg);
1371   time (&serv->newgroups_time);
1372   nntp_data.nserv = serv;
1373   nntp_data.group = NULL;
1374
1375   if (mutt_nntp_fetch (&nntp_data, "string_list_t\r\n", msg, NULL, add_group, serv, 0) < 0) {
1376     return -1;
1377   }
1378
1379   m_strcpy(msg, sizeof(msg), _("Loading descriptions..."));
1380   mutt_message (msg);
1381   nntp_get_desc (&nntp_data, "*", msg, NULL);
1382
1383   for (tmp = serv->list; tmp; tmp = tmp->next) {
1384     NNTP_DATA *data = (NNTP_DATA *) tmp->data;
1385
1386     if (data && data->deleted && !data->rc) {
1387       nntp_delete_cache (data);
1388       hash_delete (serv->newsgroups, data->group, NULL, nntp_delete_data);
1389       tmp->data = NULL;
1390     }
1391   }
1392   nntp_save_cache_index (serv);
1393
1394   mutt_clear_error ();
1395   return _checked;
1396 }
1397
1398 /*
1399  * returns -1 if error ocurred while retrieving header,
1400  * number of articles which ones exist in context on success.
1401  */
1402 int nntp_check_msgid (CONTEXT * ctx, const char *msgid)
1403 {
1404   int ret;
1405
1406   /* if msgid is already in context, don't reload them */
1407   if (hash_find (ctx->id_hash, msgid))
1408     return 1;
1409   if (ctx->msgcount == ctx->hdrmax)
1410     mx_alloc_memory (ctx);
1411   ctx->hdrs[ctx->msgcount] = header_new();
1412   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
1413
1414   mutt_message (_("Fetching %s from server..."), msgid);
1415   ret = nntp_read_header (ctx, msgid, 0);
1416   /* since nntp_read_header() may set read flag, we must reset it */
1417   ctx->hdrs[ctx->msgcount]->read = 0;
1418   if (ret != 0)
1419     header_delete(&ctx->hdrs[ctx->msgcount]);
1420   else {
1421     ctx->msgcount++;
1422     mx_update_context (ctx, 1);
1423     ctx->changed = 1;
1424   }
1425   return ret;
1426 }
1427
1428 typedef struct {
1429   CONTEXT *ctx;
1430   unsigned int num;
1431   unsigned int max;
1432   unsigned int *child;
1433 } CHILD_CONTEXT;
1434
1435 static int check_children (char *s, void *c)
1436 {
1437 #define cc ((CHILD_CONTEXT *) c)
1438   unsigned int i, n;
1439
1440   if (!s || (n = atoi (s)) == 0)
1441     return 0;
1442   for (i = 0; i < cc->ctx->msgcount; i++)
1443     if (cc->ctx->hdrs[i]->article_num == n)
1444       return 0;
1445   if (cc->num >= cc->max)
1446     p_realloc(&cc->child, cc->max += 25);
1447   cc->child[cc->num++] = n;
1448
1449   return 0;
1450 #undef cc
1451 }
1452
1453 int nntp_check_children (CONTEXT * ctx, const char *msgid)
1454 {
1455   NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data;
1456   char buf[STRING];
1457   int i, ret = 0, tmp = 0;
1458   CHILD_CONTEXT cc;
1459
1460   if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->conn ||
1461       !nntp_data->nserv->conn->account.host)
1462     return -1;
1463   if (nntp_data->firstMessage > nntp_data->lastLoaded)
1464     return 0;
1465   if (!nntp_data->nserv->hasXPAT) {
1466     mutt_error (_("Server %s does not support this operation!"),
1467                 nntp_data->nserv->conn->account.host);
1468     return -1;
1469   }
1470
1471   snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n",
1472             nntp_data->firstMessage, nntp_data->lastLoaded, msgid);
1473
1474   cc.ctx = ctx;
1475   cc.num = 0;
1476   cc.max = 25;
1477   cc.child = p_new(unsigned int, 25);
1478   if (mutt_nntp_fetch (nntp_data, buf, NULL, NULL, check_children, &cc, 0)) {
1479     p_delete(&cc.child);
1480     return -1;
1481   }
1482   /* dont try to read the xover cache. check_children() already
1483    * made sure that we dont have the article, so we need to visit
1484    * the server. Reading the cache at this point is also bad
1485    * because it would duplicate messages */
1486   if (option (OPTNEWSCACHE)) {
1487     tmp++;
1488     unset_option (OPTNEWSCACHE);
1489   }
1490   for (i = 0; i < cc.num; i++) {
1491     if ((ret = nntp_fetch_headers (ctx, cc.child[i], cc.child[i])))
1492       break;
1493     if (ctx->msgcount &&
1494         ctx->hdrs[ctx->msgcount - 1]->article_num == cc.child[i])
1495       ctx->hdrs[ctx->msgcount - 1]->read = 0;
1496   }
1497   if (tmp)
1498     set_option (OPTNEWSCACHE);
1499   p_delete(&cc.child);
1500   return ret;
1501 }
1502
1503 static int nntp_is_magic (const char* path, struct stat* st) {
1504   url_scheme_t s = url_check_scheme (NONULL (path));
1505   return ((s == U_NNTP || s == U_NNTPS) ? M_NNTP : -1);
1506 }
1507
1508 static int acl_check_nntp (CONTEXT* ctx, int bit) {
1509   switch (bit) {
1510     case ACL_INSERT:    /* editing messages */
1511     case ACL_WRITE:     /* change importance */
1512       return (0);
1513     case ACL_DELETE:    /* (un)deletion */
1514     case ACL_SEEN:      /* mark as read */
1515       return (1);
1516     default:
1517       return (0);
1518   }
1519 }
1520
1521 mx_t const nntp_mx = {
1522     M_NNTP,
1523     0,
1524     nntp_is_magic,
1525     NULL,
1526     NULL,
1527     nntp_open_mailbox,
1528     NULL,
1529     acl_check_nntp,
1530     nntp_check_mailbox,
1531     nntp_fastclose_mailbox,
1532     nntp_sync_mailbox,
1533     NULL,
1534 };