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