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