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