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