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