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