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