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