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