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