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