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