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