6b1888e0dbb94122076cbbd6ea40edca4d4c950a
[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   register char *d = line;
467   NNTP_DATA *data;
468   NNTP_SERVER *news = n;
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 }
485
486 static void nntp_get_desc (NNTP_DATA * data, const char *mask, char *msg, progress_t* bar)
487 {
488   char buf[STRING];
489
490   if (!option (OPTLOADDESC) || !data || !data->nserv)
491     return;
492
493   /* Get newsgroup description, if we can */
494   if (data->nserv->hasXGTITLE)
495     snprintf (buf, sizeof (buf), "XGTITLE %s\r\n", mask);
496   else
497     snprintf (buf, sizeof (buf), "LIST NEWSGROUPS %s\r\n", mask);
498   if (mutt_nntp_fetch (data, buf, msg, bar, parse_description, data->nserv, 0) !=
499       0) {
500   }
501 }
502
503 /*
504  * XOVER returns a tab separated list of:
505  * id|subject|from|date|Msgid|references|bytes|lines|xref
506  *
507  * This has to duplicate some of the functionality of 
508  * mutt_read_rfc822_header(), since it replaces the call to that (albeit with
509  * a limited number of headers which are "parsed" by placement in the list)
510  */
511 static int nntp_parse_xover (CONTEXT * ctx, char *buf, HEADER * hdr)
512 {
513   NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data;
514   char *p, *b;
515   int x, done = 0;
516
517   hdr->env = envelope_new();
518   hdr->env->newsgroups = m_strdup(nntp_data->group);
519   hdr->content = body_new();
520   hdr->content->type = TYPETEXT;
521   hdr->content->subtype = m_strdup("plain");
522   hdr->content->encoding = ENC7BIT;
523   hdr->content->disposition = DISPINLINE;
524   hdr->content->length = -1;
525   b = p = buf;
526
527   for (x = 0; !done && x < 9; x++) {
528     /* if from file, need to skip newline character */
529     while (*p && *p != '\n' && *p != '\t')
530       p++;
531     if (!*p)
532       done++;
533     *p = '\0';
534     p++;
535     switch (x) {
536     case 0:
537
538       hdr->article_num = atoi (b);
539       nntp_get_status (ctx, hdr, NULL, hdr->article_num);
540       break;
541     case 1:
542       hdr->env->subject = m_strdup(b);
543       break;
544     case 2:
545       address_list_wipe(&hdr->env->from);
546       hdr->env->from = rfc822_parse_adrlist (hdr->env->from, b);
547       /* same as for mutt_parse_rfc822_line():
548        * don't leave from info NULL if there's an invalid address (or
549        * whatever) in From: field; mutt would just display it as empty
550        * and mark mail/(esp.) news article as your own. aaargh! this
551        * bothered me for _years_ */
552       if (!hdr->env->from) {
553         hdr->env->from = address_new ();
554         hdr->env->from->personal = m_strdup(b);
555       }
556       break;
557     case 3:
558       hdr->date_sent = mutt_parse_date (b, hdr);
559       hdr->received = hdr->date_sent;
560       break;
561     case 4:
562       p_delete(&hdr->env->message_id);
563       hdr->env->message_id = m_strdup(b);
564       break;
565     case 5:
566       string_list_wipe(&hdr->env->references);
567       hdr->env->references = mutt_parse_references (b, 0);
568       break;
569     case 6:
570       hdr->content->length = atoi (b);
571       break;
572     case 7:
573       hdr->lines = atoi (b);
574       break;
575     case 8:
576       if (!hdr->read)
577         p_delete(&hdr->env->xref);
578       b = b + 6;                /* skips the "Xref: " */
579       hdr->env->xref = m_strdup(b);
580       nntp_parse_xref (ctx, nntp_data->group, b, hdr);
581     }
582     rfc2047_decode_envelope(hdr->env);
583     if (!*p)
584       return -1;
585     b = p;
586   }
587   return 0;
588 }
589
590 typedef struct {
591   CONTEXT *ctx;
592   int   first;
593   int   last;
594   bits_t messages;
595   const char *msg;
596 } FETCH_CONTEXT;
597
598 static int nntp_fetch_numbers (char *line, void *c)
599 {
600     FETCH_CONTEXT *fc = c;
601
602     if (line) {
603         int num = atoi(line);
604         if (num < fc->first || num > fc->last)
605             return 0;
606
607         bit_set(&fc->messages, num);
608     }
609     return 0;
610 }
611
612 static int add_xover_line (char *line, void *c)
613 {
614   int num, total;
615   FETCH_CONTEXT *fc = c;
616   CONTEXT *ctx = fc->ctx;
617   NNTP_DATA *data = (NNTP_DATA *) ctx->data;
618
619   if (!line)
620     return 0;
621
622   if (ctx->msgcount >= ctx->hdrmax)
623     mx_alloc_memory (ctx);
624   ctx->hdrs[ctx->msgcount] = header_new();
625   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
626
627   nntp_parse_xover (ctx, line, ctx->hdrs[ctx->msgcount]);
628   num = ctx->hdrs[ctx->msgcount]->article_num;
629
630   if (bit_isset(&fc->messages, num)) {
631     ctx->msgcount++;
632     if (num > data->lastLoaded)
633       data->lastLoaded = num;
634     num = num - fc->first + 1;
635     total = fc->last - fc->first + 1;
636     if (!ctx->quiet && fc->msg && ReadInc && (num % ReadInc == 0))
637       mutt_message ("%s %d/%d", fc->msg, num, total);
638   }
639   else
640     header_delete(&ctx->hdrs[ctx->msgcount]);       /* skip it */
641
642   return 0;
643 }
644
645
646 static int nntp_fetch_headers(CONTEXT * ctx, int first, int last)
647 {
648   char buf[HUGE_STRING];
649   const char *msg = _("Fetching message headers...");
650   const char *msg2 = _("Fetching headers from cache...");
651   NNTP_DATA *nntp_data = ((NNTP_DATA *) ctx->data);
652   int ret, num, oldmsgcount, current;
653   FILE *f;
654   FETCH_CONTEXT fc;
655
656   /* if empty group or nothing to do */
657   if (!last || first > last)
658     return 0;
659
660   /* fetch list of articles */
661   mutt_message _("Fetching list of articles...");
662
663   fc.ctx   = ctx;
664   fc.first = first;
665   fc.last  = last;
666   bits_init(&fc.messages);
667
668   if (nntp_data->nserv->hasLISTGROUP) {
669     snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group);
670     if (mutt_nntp_fetch(nntp_data, buf, NULL, NULL,
671                         nntp_fetch_numbers, &fc, 0))
672     {
673       mutt_error (_("LISTGROUP command failed: %s"), buf);
674       sleep (2);
675       bits_wipe(&fc.messages);
676       return -1;
677     }
678   } else {
679     for (num = first; num <= last; num++)
680       bit_set(&fc.messages, num);
681   }
682
683   /* CACHE: must be loaded xover cache here */
684   num = nntp_data->lastCached - first + 1;
685   if (option (OPTNEWSCACHE) && nntp_data->cache && num > 0) {
686   nntp_cache_expand (buf, nntp_data->cache);
687   mutt_message (msg2);
688
689   if ((f = safe_fopen (buf, "r"))) {
690     int r = 0, c = 0;
691
692     /* counting number of lines */
693     while (fgets (buf, sizeof (buf), f) != NULL)
694       r++;
695     rewind (f);
696     while (r > num && fgets (buf, sizeof (buf), f) != NULL)
697       r--;
698     oldmsgcount = ctx->msgcount;
699     fc.first = first;
700     fc.last = first + num - 1;
701     fc.msg = NULL;
702     while (fgets (buf, sizeof (buf), f) != NULL) {
703       if (ReadInc && ((++c) % ReadInc == 0))
704         mutt_message ("%s %d/%d", msg2, c, r);
705       add_xover_line (buf, &fc);
706     }
707     m_fclose(&f);
708     nntp_data->lastLoaded = fc.last;
709     first = fc.last + 1;
710     if (ctx->msgcount > oldmsgcount)
711       mx_update_context (ctx, ctx->msgcount - oldmsgcount);
712   }
713   else
714     nntp_delete_cache (nntp_data);
715   }
716   num = last - first + 1;
717   if (num <= 0) {
718     bits_wipe(&fc.messages);
719     return 0;
720   }
721
722   /*
723   * Without XOVER, we have to fetch each article header and parse
724   * it.  With XOVER, we ask for all of them
725   */
726   mutt_message (msg);
727   if (nntp_data->nserv->hasXOVER) {
728     oldmsgcount = ctx->msgcount;
729     fc.first = first;
730     fc.last = last;
731     fc.msg = msg;
732     snprintf (buf, sizeof (buf), "XOVER %d-%d\r\n", first, last);
733     ret = mutt_nntp_fetch (nntp_data, buf, NULL, NULL, add_xover_line, &fc, 0);
734     if (ctx->msgcount > oldmsgcount)
735       mx_update_context (ctx, ctx->msgcount - oldmsgcount);
736     if (ret != 0) {
737       mutt_error (_("XOVER command failed: %s"), buf);
738       bits_wipe(&fc.messages);
739       return -1;
740     }
741   /* fetched OK */
742   } else {
743     for (current = first; current <= last; current++) {
744       HEADER *h;
745
746       ret = current - first + 1;
747       mutt_message ("%s %d/%d", msg, ret, num);
748
749       if (!bit_isset(&fc.messages, current))
750         continue;
751
752       if (ctx->msgcount >= ctx->hdrmax)
753         mx_alloc_memory (ctx);
754       h = ctx->hdrs[ctx->msgcount] = header_new();
755       h->index = ctx->msgcount;
756
757       ret = nntp_read_header (ctx, NULL, current);
758       if (ret == 0) {           /* Got article. Fetch next header */
759         nntp_get_status (ctx, h, NULL, h->article_num);
760         ctx->msgcount++;
761         mx_update_context (ctx, 1);
762       }
763       else
764         header_delete(&h);  /* skip it */
765       if (ret == -1) {
766         bits_wipe(&fc.messages);
767         return -1;
768       }
769
770       if (current > nntp_data->lastLoaded)
771         nntp_data->lastLoaded = current;
772     }
773   }
774   bits_wipe(&fc.messages);
775   nntp_data->lastLoaded = last;
776   mutt_clear_error ();
777   return 0;
778 }
779
780 /* 
781  * currently, nntp "mailbox" is "newsgroup"
782  */
783 static int nntp_open_mailbox (CONTEXT * ctx)
784 {
785   NNTP_DATA *nntp_data;
786   NNTP_SERVER *serv;
787   char buf[HUGE_STRING];
788   char server[LONG_STRING];
789   int count = 0, first;
790   ACCOUNT act;
791
792   p_clear(&act, 1);
793
794   if (nntp_parse_url (ctx->path, &act, buf, sizeof (buf)) < 0 || !*buf) {
795     mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path);
796     mutt_sleep (2);
797     return -1;
798   }
799
800   server[0] = '\0';
801   nntp_expand_path (server, sizeof (server), &act);
802   if (!(serv = mutt_select_newsserver (server)) || serv->status != NNTP_OK)
803     return -1;
804
805   CurrentNewsSrv = serv;
806
807   /* create NNTP-specific state struct if nof found in list */
808   if ((nntp_data = (NNTP_DATA *) hash_find (serv->newsgroups, buf)) == NULL) {
809     nntp_data = xmalloc(sizeof(NNTP_DATA) + m_strlen(buf) + 1);
810     nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA);
811     strcpy (nntp_data->group, buf);
812     hash_insert (serv->newsgroups, nntp_data->group, nntp_data);
813     nntp_add_to_list (serv, nntp_data);
814   }
815   ctx->data = nntp_data;
816   nntp_data->nserv = serv;
817
818   mutt_message (_("Selecting %s..."), nntp_data->group);
819
820   if (!nntp_data->desc) {
821     nntp_get_desc (nntp_data, nntp_data->group, NULL, NULL);
822     if (nntp_data->desc)
823       nntp_save_cache_index (serv);
824   }
825
826   buf[0] = 0;
827   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
828     return -1;
829   }
830
831   if (m_strncmp("211", buf, 3)) {
832     string_list_t *l = serv->list;
833
834     /* GROUP command failed */
835     if (!m_strncmp("411", buf, 3)) {
836       mutt_error (_("Newsgroup %s not found on server %s"),
837                   nntp_data->group, serv->conn->account.host);
838
839       /* CACHE: delete cache and line from .index */
840       nntp_delete_cache (nntp_data);
841       hash_remove (serv->newsgroups, nntp_data->group, NULL,
842                    nntp_delete_data);
843       while (l && l->data != (void *) nntp_data)
844         l = l->next;
845       if (l)
846         l->data = NULL;
847
848       sleep (2);
849     }
850
851     return -1;
852   }
853
854   sscanf (buf + 4, "%d %u %u %s", &count, &nntp_data->firstMessage,
855           &nntp_data->lastMessage, buf);
856
857   nntp_data->deleted = 0;
858
859   time (&serv->check_time);
860
861   /*
862    * Check for max adding context. If it is greater than $nntp_context,
863    * strip off extra articles
864    */
865   first = nntp_data->firstMessage;
866   if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext)
867     first = nntp_data->lastMessage - NntpContext + 1;
868   if (first)
869     nntp_data->lastLoaded = first - 1;
870   return nntp_fetch_headers (ctx, first, nntp_data->lastMessage);
871 }
872
873 int nntp_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno)
874 {
875   char buf[LONG_STRING];
876   char path[_POSIX_PATH_MAX];
877   NNTP_CACHE *cache;
878   int ret;
879   progress_t bar;
880
881   /* see if we already have the message in our cache */
882   cache =
883     &((NNTP_DATA *) ctx->data)->acache[ctx->hdrs[msgno]->index %
884                                        NNTP_CACHE_LEN];
885
886   /* if everything is fine, assign msg->fp and return */
887   if (cache->path && cache->index == ctx->hdrs[msgno]->index &&
888       (msg->fp = fopen (cache->path, "r")))
889     return 0;
890
891   /* clear the previous entry */
892   unlink (cache->path);
893   p_delete(&cache->path);
894
895   cache->index = ctx->hdrs[msgno]->index;
896   msg->fp = m_tempfile(path, sizeof(path), NONULL(mod_core.tmpdir), NULL);
897   if (!msg->fp) {
898     return -1;
899   }
900   cache->path = m_strdup(path);
901
902   if (ctx->hdrs[msgno]->article_num == 0)
903     snprintf (buf, sizeof (buf), "ARTICLE %s\r\n",
904               ctx->hdrs[msgno]->env->message_id);
905   else
906     snprintf (buf, sizeof (buf), "ARTICLE %d\r\n",
907               ctx->hdrs[msgno]->article_num);
908
909   bar.msg = _("Fetching message...");
910   bar.size = 0;
911   mutt_progress_bar (&bar, 0);
912
913   ret = mutt_nntp_fetch ((NNTP_DATA *) ctx->data, buf, NULL, &bar, nntp_read_tempfile,
914                          msg->fp, ctx->tagged);
915   if (ret == 1) {
916     mutt_error (_("Article %d not found on server"),
917                 ctx->hdrs[msgno]->article_num);
918   }
919
920   if (ret) {
921     m_fclose(&msg->fp);
922     unlink (path);
923     p_delete(&cache->path);
924     return -1;
925   }
926
927   envelope_delete(&ctx->hdrs[msgno]->env);
928   ctx->hdrs[msgno]->env =
929     mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno], 0, 0);
930   /* fix content length */
931   fseeko (msg->fp, 0, SEEK_END);
932   ctx->hdrs[msgno]->content->length = ftello (msg->fp) -
933     ctx->hdrs[msgno]->content->offset;
934
935   /* this is called in mutt before the open which fetches the message, 
936    * which is probably wrong, but we just call it again here to handle
937    * the problem instead of fixing it.
938    */
939   mutt_parse_mime_message (ctx, ctx->hdrs[msgno]);
940
941   /* These would normally be updated in mx_update_context(), but the 
942    * full headers aren't parsed with XOVER, so the information wasn't
943    * available then.
944    */
945   ctx->hdrs[msgno]->security = crypt_query (ctx->hdrs[msgno]->content);
946
947   mutt_clear_error ();
948   rewind (msg->fp);
949
950   return 0;
951 }
952
953 /* Post article */
954 int nntp_post (const char *msg)
955 {
956   char buf[LONG_STRING];
957   size_t len;
958   FILE *f;
959   NNTP_DATA *nntp_data;
960
961   if (Context && Context->magic == M_NNTP)
962     nntp_data = (NNTP_DATA *) Context->data;
963   else {
964     if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer)) ||
965         !CurrentNewsSrv->list || !CurrentNewsSrv->list->data) {
966       mutt_error (_("Can't post article. No connection to news server."));
967       return -1;
968     }
969     nntp_data = (NNTP_DATA *) CurrentNewsSrv->list->data;
970   }
971
972   if (!(f = safe_fopen (msg, "r"))) {
973     mutt_error (_("Can't post article. Unable to open %s"), msg);
974     return -1;
975   }
976
977   m_strcpy(buf, sizeof(buf), "POST\r\n");
978   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
979     mutt_error (_("Can't post article. Connection to %s lost."),
980                 nntp_data->nserv->conn->account.host);
981     return -1;
982   }
983   if (buf[0] != '3') {
984     mutt_error (_("Can't post article: %s"), buf);
985     return -1;
986   }
987
988   buf[0] = '.';
989   buf[1] = '\0';
990   while (fgets (buf + 1, sizeof (buf) - 2, f) != NULL) {
991     len = m_strlen(buf);
992     if (buf[len - 1] == '\n') {
993       buf[len - 1] = '\r';
994       buf[len] = '\n';
995       len++;
996       buf[len] = '\0';
997     }
998     if (buf[1] == '.')
999       mutt_socket_write(nntp_data->nserv->conn, buf);
1000     else
1001       mutt_socket_write(nntp_data->nserv->conn, buf + 1);
1002   }
1003   m_fclose(&f);
1004
1005   if (buf[m_strlen(buf) - 1] != '\n')
1006     mutt_socket_write(nntp_data->nserv->conn, "\r\n");
1007   mutt_socket_write(nntp_data->nserv->conn, ".\r\n");
1008   if (mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) {
1009     mutt_error (_("Can't post article. Connection to %s lost."),
1010                 nntp_data->nserv->conn->account.host);
1011     return -1;
1012   }
1013   if (buf[0] != '2') {
1014     mutt_error (_("Can't post article: %s"), buf);
1015     return -1;
1016   }
1017
1018   return 0;
1019 }
1020
1021 /* nntp_logout_all: close all open connections. */
1022 void nntp_logout_all (void)
1023 {
1024   char buf[LONG_STRING];
1025   CONNECTION *conn;
1026
1027   conn = mutt_socket_head ();
1028
1029   while (conn) {
1030     CONNECTION* next = conn->next;
1031     if (conn->account.type == M_ACCT_TYPE_NNTP) {
1032       mutt_message (_("Closing connection to %s..."), conn->account.host);
1033       mutt_socket_write (conn, "QUIT\r\n");
1034       mutt_socket_readln (buf, sizeof (buf), conn);
1035       mutt_clear_error ();
1036       mutt_socket_close (conn);
1037       mutt_socket_free (conn);
1038     }
1039     conn = next;
1040   }
1041 }
1042
1043 static void nntp_free_acache (NNTP_DATA * data)
1044 {
1045   int i;
1046
1047   for (i = 0; i < NNTP_CACHE_LEN; i++) {
1048     if (data->acache[i].path) {
1049       unlink (data->acache[i].path);
1050       p_delete(&data->acache[i].path);
1051     }
1052   }
1053 }
1054
1055 void nntp_delete_data (void *p)
1056 {
1057   NNTP_DATA *data = (NNTP_DATA *)p;
1058
1059   if (!p)
1060     return;
1061   p_delete(&data->entries);
1062   p_delete(&data->desc);
1063   p_delete(&data->cache);
1064   nntp_free_acache (data);
1065   p_delete(&data);
1066 }
1067
1068 static int nntp_sync_mailbox (CONTEXT * ctx, int unused1, int* unused2)
1069 {
1070   NNTP_DATA *data = ctx->data;
1071
1072   /* CACHE: update cache and .index files */
1073   if ((option (OPTSAVEUNSUB) || data->subscribed))
1074     nntp_save_cache_group (ctx);
1075   nntp_free_acache (data);
1076
1077   data->nserv->check_time = 0;  /* next nntp_check_mailbox() will really check */
1078   return 0;
1079 }
1080
1081 static void nntp_fastclose_mailbox (CONTEXT * ctx)
1082 {
1083   NNTP_DATA *data = (NNTP_DATA *) ctx->data, *tmp;
1084
1085   if (!data)
1086     return;
1087   nntp_free_acache (data);
1088   if (!data->nserv || !data->nserv->newsgroups || !data->group)
1089     return;
1090   nntp_save_cache_index (data->nserv);
1091   if ((tmp = hash_find (data->nserv->newsgroups, data->group)) == NULL
1092       || tmp != data)
1093     nntp_delete_data (data);
1094   else
1095     nntp_sync_sidebar (data);
1096 }
1097
1098 /* commit changes and terminate connection */
1099 int nntp_close_mailbox (CONTEXT * ctx)
1100 {
1101   if (!ctx)
1102     return -1;
1103   mutt_message _("Quitting newsgroup...");
1104
1105   if (ctx->data) {
1106     NNTP_DATA *data = (NNTP_DATA *) ctx->data;
1107     int ret;
1108
1109     if (data->nserv && data->nserv->conn && ctx->unread) {
1110       ret = query_quadoption (OPT_CATCHUP, _("Mark all articles read?"));
1111       if (ret == M_YES)
1112         mutt_newsgroup_catchup (data->nserv, data->group);
1113       else if (ret < 0)
1114         return -1;
1115     }
1116   }
1117   nntp_sync_mailbox (ctx, 0, NULL);
1118   if (ctx->data && ((NNTP_DATA *) ctx->data)->nserv) {
1119     NNTP_SERVER *news;
1120
1121     news = ((NNTP_DATA *) ctx->data)->nserv;
1122     newsrc_gen_entries (ctx);
1123     ((NNTP_DATA *) ctx->data)->unread = ctx->unread;
1124     mutt_newsrc_update (news);
1125   }
1126   mutt_clear_error ();
1127   return 0;
1128 }
1129
1130 /* use the GROUP command to poll for new mail */
1131 static int _nntp_check_mailbox (CONTEXT * ctx, NNTP_DATA * nntp_data)
1132 {
1133   char buf[LONG_STRING];
1134   int count = 0;
1135
1136   if (nntp_data->nserv->check_time + NewsPollTimeout > time (NULL))
1137     return 0;
1138
1139   buf[0] = 0;
1140   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1141     return -1;
1142   }
1143   if (m_strncmp("211", buf, 3)) {
1144     buf[0] = 0;
1145     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1146       return -1;
1147     }
1148   }
1149   if (!m_strncmp("211", buf, 3)) {
1150     int first;
1151     int last;
1152
1153     sscanf (buf + 4, "%d %d %d", &count, &first, &last);
1154     nntp_data->firstMessage = first;
1155     nntp_data->lastMessage = last;
1156     if (ctx && last > nntp_data->lastLoaded) {
1157       nntp_fetch_headers (ctx, nntp_data->lastLoaded + 1, last);
1158       time (&nntp_data->nserv->check_time);
1159       return 1;
1160     }
1161     if (!last || (!nntp_data->rc && !nntp_data->lastCached))
1162       nntp_data->unread = count;
1163     else
1164       mutt_newsgroup_stat (nntp_data);
1165     /* active was renumbered? */
1166     if (last < nntp_data->lastLoaded) {
1167       if (!nntp_data->max) {
1168         nntp_data->entries = p_new(NEWSRC_ENTRY, 5);
1169         nntp_data->max = 5;
1170       }
1171       nntp_data->lastCached = 0;
1172       nntp_data->num = 1;
1173       nntp_data->entries[0].first = 1;
1174       nntp_data->entries[0].last = 0;
1175     }
1176     nntp_sync_sidebar (nntp_data);
1177   }
1178
1179   time (&nntp_data->nserv->check_time);
1180   return 0;
1181 }
1182
1183 static int nntp_check_mailbox (CONTEXT * ctx, int* unused1, int unused2)
1184 {
1185   return _nntp_check_mailbox (ctx, (NNTP_DATA *) ctx->data);
1186 }
1187
1188 static int add_group (char *buf, void *serv)
1189 {
1190   NNTP_SERVER *s = serv;
1191   char group[LONG_STRING], mod, desc[HUGE_STRING];
1192   int first, last;
1193   NNTP_DATA *nntp_data;
1194   static int n = 0;
1195
1196   _checked = n;                 /* _checked have N, where N = number of groups */
1197   if (!buf)                     /* at EOF must be zerouth */
1198     n = 0;
1199
1200   if (!s || !buf)
1201     return 0;
1202
1203   *desc = 0;
1204   sscanf (buf, "%s %d %d %c %[^\n]", group, &last, &first, &mod, desc);
1205   if (!group)
1206     return 0;
1207   if ((nntp_data = (NNTP_DATA *) hash_find (s->newsgroups, group)) == NULL) {
1208     n++;
1209     nntp_data = xmalloc(sizeof(NNTP_DATA) + m_strlen(group) + 1);
1210     nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA);
1211     strcpy (nntp_data->group, group);
1212     nntp_data->nserv = s;
1213     if (s->newsgroups->nelem < s->newsgroups->curnelem * 2)
1214       hash_resize (s->newsgroups, s->newsgroups->nelem * 2);
1215     hash_insert (s->newsgroups, nntp_data->group, nntp_data);
1216     nntp_add_to_list (s, nntp_data);
1217   }
1218   nntp_data->deleted = 0;
1219   nntp_data->firstMessage = first;
1220   nntp_data->lastMessage = last;
1221   if (mod == 'y')
1222     nntp_data->allowed = 1;
1223   else
1224     nntp_data->allowed = 0;
1225   if (nntp_data->desc)
1226     p_delete(&nntp_data->desc);
1227   if (*desc)
1228     nntp_data->desc = m_strdup(desc);
1229   if (nntp_data->rc || nntp_data->lastCached)
1230     mutt_newsgroup_stat (nntp_data);
1231   else if (nntp_data->lastMessage &&
1232            nntp_data->firstMessage <= nntp_data->lastMessage)
1233     nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1;
1234   else
1235     nntp_data->unread = 0;
1236
1237   return 0;
1238 }
1239
1240 int nntp_check_newgroups (NNTP_SERVER * serv, int force)
1241 {
1242   char buf[LONG_STRING];
1243   NNTP_DATA nntp_data;
1244   string_list_t *l;
1245   string_list_t emp;
1246   time_t now;
1247   struct tm *t;
1248
1249   if (!serv || !serv->newgroups_time)
1250     return -1;
1251
1252   if (nntp_open_connection (serv) < 0)
1253     return -1;
1254
1255   /* check subscribed groups for new news */
1256   if (option (OPTSHOWNEWNEWS)) {
1257     mutt_message _("Checking for new messages...");
1258
1259     for (l = serv->list; l; l = l->next) {
1260       serv->check_time = 0;     /* really check! */
1261       if (l->data && ((NNTP_DATA *) l->data)->subscribed)
1262         _nntp_check_mailbox (NULL, (NNTP_DATA *) l->data);
1263     }
1264     sidebar_draw ();
1265   }
1266   else if (!force)
1267     return 0;
1268
1269   mutt_message _("Checking for new newsgroups...");
1270
1271   now = serv->newgroups_time;
1272   time (&serv->newgroups_time);
1273   t = gmtime (&now);
1274   snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
1275             (t->tm_year % 100), t->tm_mon + 1, t->tm_mday, t->tm_hour,
1276             t->tm_min, t->tm_sec);
1277   nntp_data.nserv = serv;
1278   if (Context && Context->magic == M_NNTP)
1279     nntp_data.group = ((NNTP_DATA *) Context->data)->group;
1280   else
1281     nntp_data.group = NULL;
1282   l = serv->tail;
1283   if (mutt_nntp_fetch (&nntp_data, buf, _("Adding new newsgroups..."), NULL,
1284                        add_group, serv, 0) != 0) {
1285     return -1;
1286   }
1287
1288   mutt_message _("Loading descriptions...");
1289
1290   if (l)
1291     emp.next = l->next;
1292   else
1293     emp.next = serv->list;
1294   l = &emp;
1295   while (l->next) {
1296     l = l->next;
1297     ((NNTP_DATA *) l->data)->new = 1;
1298     nntp_get_desc ((NNTP_DATA *) l->data, ((NNTP_DATA *) l->data)->group,
1299                    NULL, NULL);
1300   }
1301   if (emp.next)
1302     nntp_save_cache_index (serv);
1303   mutt_clear_error ();
1304   return _checked;
1305 }
1306
1307 /* Load list of all newsgroups from cache ALL */
1308 int nntp_get_cache_all (NNTP_SERVER * serv)
1309 {
1310   char buf[HUGE_STRING];
1311   FILE *f;
1312
1313   nntp_cache_expand (buf, serv->cache);
1314   if ((f = safe_fopen (buf, "r"))) {
1315     int i = 0;
1316
1317     while (fgets (buf, sizeof (buf), f) != NULL) {
1318       if (ReadInc && (i % ReadInc == 0))
1319         mutt_message (_("Loading list from cache... %d"), i);
1320       add_group (buf, serv);
1321       i++;
1322     }
1323     add_group (NULL, NULL);
1324     m_fclose(&f);
1325     mutt_clear_error ();
1326     return 0;
1327   }
1328   else {
1329     p_delete(&serv->cache);
1330     return -1;
1331   }
1332 }
1333
1334 /* Load list of all newsgroups from active */
1335 int nntp_get_active (NNTP_SERVER * serv)
1336 {
1337   char msg[STRING];
1338   NNTP_DATA nntp_data;
1339   string_list_t *tmp;
1340
1341   if (nntp_open_connection (serv) < 0)
1342     return -1;
1343
1344   snprintf (msg, sizeof (msg),
1345             _("Loading list of all newsgroups on server %s..."),
1346             serv->conn->account.host);
1347   mutt_message (msg);
1348   time (&serv->newgroups_time);
1349   nntp_data.nserv = serv;
1350   nntp_data.group = NULL;
1351
1352   if (mutt_nntp_fetch (&nntp_data, "LIST\r\n", msg, NULL, add_group, serv, 0) < 0) {
1353     return -1;
1354   }
1355
1356   m_strcpy(msg, sizeof(msg), _("Loading descriptions..."));
1357   mutt_message (msg);
1358   nntp_get_desc (&nntp_data, "*", msg, NULL);
1359
1360   for (tmp = serv->list; tmp; tmp = tmp->next) {
1361     NNTP_DATA *data = (NNTP_DATA *) tmp->data;
1362
1363     if (data && data->deleted && !data->rc) {
1364       nntp_delete_cache (data);
1365       hash_remove (serv->newsgroups, data->group, NULL, nntp_delete_data);
1366       tmp->data = NULL;
1367     }
1368   }
1369   nntp_save_cache_index (serv);
1370
1371   mutt_clear_error ();
1372   return _checked;
1373 }
1374
1375 /*
1376  * returns -1 if error ocurred while retrieving header,
1377  * number of articles which ones exist in context on success.
1378  */
1379 int nntp_check_msgid (CONTEXT * ctx, const char *msgid)
1380 {
1381   int ret;
1382
1383   /* if msgid is already in context, don't reload them */
1384   if (hash_find (ctx->id_hash, msgid))
1385     return 1;
1386   if (ctx->msgcount == ctx->hdrmax)
1387     mx_alloc_memory (ctx);
1388   ctx->hdrs[ctx->msgcount] = header_new();
1389   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
1390
1391   mutt_message (_("Fetching %s from server..."), msgid);
1392   ret = nntp_read_header (ctx, msgid, 0);
1393   /* since nntp_read_header() may set read flag, we must reset it */
1394   ctx->hdrs[ctx->msgcount]->read = 0;
1395   if (ret != 0)
1396     header_delete(&ctx->hdrs[ctx->msgcount]);
1397   else {
1398     ctx->msgcount++;
1399     mx_update_context (ctx, 1);
1400     ctx->changed = 1;
1401   }
1402   return ret;
1403 }
1404
1405 typedef struct {
1406   CONTEXT *ctx;
1407   int num;
1408   int max;
1409   int *child;
1410 } CHILD_CONTEXT;
1411
1412 static int check_children (char *s, void *c)
1413 {
1414   CHILD_CONTEXT *cc = c;
1415   int i, n;
1416
1417   if (!s || (n = atoi (s)) == 0)
1418     return 0;
1419   for (i = 0; i < cc->ctx->msgcount; i++)
1420     if (cc->ctx->hdrs[i]->article_num == n)
1421       return 0;
1422   if (cc->num >= cc->max)
1423     p_realloc(&cc->child, cc->max += 25);
1424   cc->child[cc->num++] = n;
1425
1426   return 0;
1427 }
1428
1429 int nntp_check_children (CONTEXT * ctx, const char *msgid)
1430 {
1431   NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data;
1432   char buf[STRING];
1433   int i, ret = 0, tmp = 0;
1434   CHILD_CONTEXT cc;
1435
1436   if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->conn ||
1437       !nntp_data->nserv->conn->account.host)
1438     return -1;
1439   if (nntp_data->firstMessage > nntp_data->lastLoaded)
1440     return 0;
1441   if (!nntp_data->nserv->hasXPAT) {
1442     mutt_error (_("Server %s does not support this operation!"),
1443                 nntp_data->nserv->conn->account.host);
1444     return -1;
1445   }
1446
1447   snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n",
1448             nntp_data->firstMessage, nntp_data->lastLoaded, msgid);
1449
1450   cc.ctx = ctx;
1451   cc.num = 0;
1452   cc.max = 25;
1453   cc.child = p_new(int, 25);
1454   if (mutt_nntp_fetch (nntp_data, buf, NULL, NULL, check_children, &cc, 0)) {
1455     p_delete(&cc.child);
1456     return -1;
1457   }
1458   /* dont try to read the xover cache. check_children() already
1459    * made sure that we dont have the article, so we need to visit
1460    * the server. Reading the cache at this point is also bad
1461    * because it would duplicate messages */
1462   if (option (OPTNEWSCACHE)) {
1463     tmp++;
1464     unset_option (OPTNEWSCACHE);
1465   }
1466   for (i = 0; i < cc.num; i++) {
1467     if ((ret = nntp_fetch_headers (ctx, cc.child[i], cc.child[i])))
1468       break;
1469     if (ctx->msgcount &&
1470         ctx->hdrs[ctx->msgcount - 1]->article_num == cc.child[i])
1471       ctx->hdrs[ctx->msgcount - 1]->read = 0;
1472   }
1473   if (tmp)
1474     set_option (OPTNEWSCACHE);
1475   p_delete(&cc.child);
1476   return ret;
1477 }
1478
1479 static int nntp_is_magic (const char* path, struct stat* st) {
1480   url_scheme_t s = url_check_scheme (NONULL (path));
1481   return ((s == U_NNTP || s == U_NNTPS) ? M_NNTP : -1);
1482 }
1483
1484 static int acl_check_nntp (CONTEXT* ctx, int bit) {
1485   switch (bit) {
1486     case ACL_INSERT:    /* editing messages */
1487     case ACL_WRITE:     /* change importance */
1488       return (0);
1489     case ACL_DELETE:    /* (un)deletion */
1490     case ACL_SEEN:      /* mark as read */
1491       return (1);
1492     default:
1493       return (0);
1494   }
1495 }
1496
1497 mx_t const nntp_mx = {
1498     M_NNTP,
1499     0,
1500     nntp_is_magic,
1501     NULL,
1502     NULL,
1503     nntp_open_mailbox,
1504     NULL,
1505     acl_check_nntp,
1506     nntp_check_mailbox,
1507     nntp_fastclose_mailbox,
1508     nntp_sync_mailbox,
1509     NULL,
1510 };