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