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