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