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