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