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