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