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