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