a533933ff0c92b1bf4c3a5d698234f1f1c9e3d78
[apps/madmutt.git] / 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 <libgen.h>
15
16 #include <lib-mime/mime.h>
17 #include <lib-ui/curses.h>
18 #include <lib-ui/sidebar.h>
19 #include <lib-mx/mx.h>
20
21 #include "mutt.h"
22 #include "sort.h"
23 #include "nntp.h"
24 #include "buffy.h"
25 #include "crypt.h"
26
27 #define NNTP_PORT      119
28 #define NNTP_SSL_PORT  563
29
30 static struct {
31     unsigned use_cache : 1;
32
33     int  checked;
34 } nntp = { true, 0 };
35
36 static int nntp_check_newgroups (nntp_server_t *, int);
37
38 /* newsrc {{{ */
39
40 static void mutt_newsgroup_stat(nntp_data_t *data)
41 {
42     data->unread = 0;
43     if (data->lastMessage == 0 || data->firstMessage > data->lastMessage)
44         return;
45
46     data->unread = data->lastMessage - data->firstMessage + 1;
47     for (int i = 0; i < data->num; i++) {
48         int first = MAX(data->entries[i].first, data->firstMessage);
49         int last  = MIN(data->entries[i].last,  data->lastMessage);
50         if (first <= last)
51             data->unread -= last - first + 1;
52     }
53 }
54
55 static int nntp_parse_newsrc_line(nntp_server_t * news, const char *line)
56 {
57     nntp_data_t *data;
58     char group[LONG_STRING];
59     const char *p;
60     int x = 1;
61
62     for (p = line; *p; p++) {
63         x += *p == ',';
64     }
65
66     p = strpbrk(line, ":!");
67     if (!p)
68         return -1;
69
70     m_strncpy(group, ssizeof(group), line, p - line);
71     data = hash_find(news->newsgroups, group);
72     if (!data) {
73         data = nntp_data_new();
74         data->group = p_dupstr(line, p - line);
75         data->nserv = news;
76         data->deleted = 1;
77         if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
78             hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
79         hash_insert(news->newsgroups, data->group, data);
80         news->tail = nntp_data_list_append(news->tail, data);
81     } else {
82         p_delete(&data->entries);
83     }
84
85     data->rc = 1;
86     data->entries = p_new(NEWSRC_ENTRY, x * 2);
87     data->max = x * 2;
88     data->subscribed = (*p++ == ':');
89
90     for (x = 0; *p; p++) {
91         p = skipspaces(p);
92         data->entries[x].first = strtol(p, (char **)&p, 10);
93         p = skipspaces(p);
94         if (*p == '-') {
95             data->entries[x].last = strtol(p + 1, (char **)&p, 10);
96         } else {
97             data->entries[x].last = data->entries[x].first;
98         }
99
100         p  = strchrnul(p, ',');
101         x += data->entries[x].last != 0;
102     }
103
104     if (x && !data->lastMessage)
105         data->lastMessage = data->entries[x - 1].last;
106     data->num = x;
107     mutt_newsgroup_stat(data);
108     return 0;
109 }
110
111 static int slurp_newsrc (nntp_server_t * news)
112 {
113     FILE *fp;
114     char *buf = NULL;
115     size_t n  = 0;
116     struct stat sb;
117
118     news->stat  = stat(news->newsrc, &sb);
119     news->size  = sb.st_size;
120     news->mtime = sb.st_mtime;
121
122     if ((fp = safe_fopen(news->newsrc, "r")) == NULL)
123         return -1;
124
125     /* hmm, should we use dotlock? */
126     if (mx_lock_file(news->newsrc, fileno (fp), 0, 0, 1)) {
127         m_fclose(&fp);
128         return -1;
129     }
130
131     while (getline(&buf, &n, fp) >= 0) {
132         nntp_parse_newsrc_line(news, buf);
133     }
134     p_delete(&buf);
135
136     mx_unlock_file (news->newsrc, fileno (fp), 0);
137     m_fclose(&fp);
138     return 0;
139 }
140
141 #define nntp_cache_expand(dst, dlen, fmt, ...)     \
142     do {                                                                     \
143         snprintf((dst), (dlen), "%s/" fmt, mod_core.cachedir, ##__VA_ARGS__);\
144         mutt_expand_path((dst), (dlen));                                     \
145     } while (0)
146
147 /* Loads $news_cache_dir/.index into memory, loads newsserver data
148  * and newsgroup cache names */
149 static int nntp_parse_cacheindex(nntp_server_t *news)
150 {
151     char buf[HUGE_STRING];
152     FILE *idx;
153
154     p_delete(&news->cache);
155     if (m_strisempty(mod_core.cachedir))
156         return 0;
157
158     nntp_cache_expand(buf, sizeof(buf), "%s.index", news->conn->account.host);
159     if (!(idx = safe_fopen(buf, "a+")))
160         return 0;
161     rewind(idx);
162
163     while (fgets(buf, sizeof(buf), idx)) {
164         buf[m_strlen(buf) - 1] = 0;  /* strip ending '\n' */
165         if (!m_strncmp(buf, "#: ", 3)
166         &&  !m_strcasecmp(buf + 3, news->conn->account.host)) {
167             break;
168         }
169     }
170
171     while (fgets(buf, sizeof(buf), idx)) {
172         char *p, *q;
173         int l, m, t;
174
175         if (m_strstart(buf, "#:", NULL))
176             break;
177
178         p = strchrnul(buf, ' ');
179         if (!*p)
180             continue;
181         *p++ = '\0';
182         p = vskipspaces(p);
183         q = strchrnul(p, ' ');
184         if (!*q)
185             continue;
186         *q++ = '\0';
187         l = strtol(q, &q, 10);
188         m = strtol(q, &q, 10);
189
190         if (!m_strcmp(buf, "ALL")) {
191             m_strreplace(&news->cache, m_strdup(p));
192             news->newgroups_time = m;
193             continue;
194         }
195
196         if (news->newsgroups) {
197             nntp_data_t *data = hash_find(news->newsgroups, buf);
198
199             if (!data) {
200                 data = nntp_data_new();
201                 data->group = m_strdup(buf);
202                 data->nserv = news;
203                 data->deleted = 1;
204                 if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
205                     hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
206                 hash_insert (news->newsgroups, data->group, data);
207                 news->tail = nntp_data_list_append(news->tail, data);
208             }
209             m_strreplace(&data->cache, p);
210
211             t = !data->firstMessage || data->lastMessage < m;
212             if (!data->firstMessage)
213                 data->firstMessage = l;
214             if (data->lastMessage < m)
215                 data->lastMessage = m;
216             data->lastCached = m;
217             if (t || !data->unread)
218                 mutt_newsgroup_stat(data);
219         }
220     }
221
222     m_fclose(&idx);
223     return 0;
224 }
225
226 /* nntp_parse_url: given an NNPT URL, return host, port,
227  * username, password and newsgroup will recognise. */
228 static int nntp_parse_url(const char *server, ACCOUNT * act, char *group,
229                           ssize_t group_len)
230 {
231   ciss_url_t url;
232   char *c;
233   int ret = -1;
234
235   /* Defaults */
236   act->flags = 0;
237   act->port = NNTP_PORT;
238   act->type = M_ACCT_TYPE_NNTP;
239
240   c = m_strdup(server);
241   url_parse_ciss (&url, c);
242
243   if (url.scheme == U_NNTP || url.scheme == U_NNTPS) {
244     if (url.scheme == U_NNTPS) {
245       act->has_ssl = 1;
246       act->port = NNTP_SSL_PORT;
247     }
248
249     *group = '\0';
250     if (url.path)
251       m_strcpy(group, group_len, url.path);
252
253     ret = mutt_account_fromurl (act, &url);
254   }
255
256   p_delete(&c);
257   return ret;
258 }
259
260 void nntp_expand_path (char *line, ssize_t len, ACCOUNT * act)
261 {
262   ciss_url_t url;
263
264   url.path = m_strdup(line);
265   mutt_account_tourl (act, &url);
266   url_ciss_tostring (&url, line, len, 0);
267   p_delete(&url.path);
268 }
269
270 static int add_group (char *buf, void *serv)
271 {
272   nntp_server_t *s = serv;
273   char group[LONG_STRING], mod, desc[HUGE_STRING];
274   int first, last;
275   nntp_data_t *nntp_data;
276   static int n = 0;
277
278   nntp.checked = n;       /* nntp.checked have N, where N = number of groups */
279   if (!buf)               /* at EOF must be zerouth */
280     n = 0;
281
282   if (!s || !buf)
283     return 0;
284
285   *desc = 0;
286   sscanf (buf, "%s %d %d %c %[^\n]", group, &last, &first, &mod, desc);
287   if (!group)
288     return 0;
289   if ((nntp_data = hash_find(s->newsgroups, group)) == NULL) {
290     n++;
291     nntp_data = nntp_data_new();
292     nntp_data->group = m_strdup(group);
293     nntp_data->nserv = s;
294     if (s->newsgroups->nelem < s->newsgroups->curnelem * 2)
295       hash_resize (s->newsgroups, s->newsgroups->nelem * 2);
296     hash_insert(s->newsgroups, nntp_data->group, nntp_data);
297     s->tail = nntp_data_list_append(s->tail, nntp_data);
298   }
299   nntp_data->deleted = 0;
300   nntp_data->firstMessage = first;
301   nntp_data->lastMessage = last;
302   if (mod == 'y')
303     nntp_data->allowed = 1;
304   else
305     nntp_data->allowed = 0;
306   if (nntp_data->desc)
307     p_delete(&nntp_data->desc);
308   if (*desc)
309     nntp_data->desc = m_strdup(desc);
310   if (nntp_data->rc || nntp_data->lastCached)
311     mutt_newsgroup_stat (nntp_data);
312   else if (nntp_data->lastMessage &&
313            nntp_data->firstMessage <= nntp_data->lastMessage)
314     nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1;
315   else
316     nntp_data->unread = 0;
317
318   return 0;
319 }
320
321 /* Load list of all newsgroups from cache ALL */
322 static int nntp_get_cache_all (nntp_server_t * serv)
323 {
324   char buf[HUGE_STRING];
325   FILE *f;
326
327   nntp_cache_expand(buf, ssizeof(buf), "%s", serv->cache);
328   if ((f = safe_fopen (buf, "r"))) {
329     int i = 0;
330
331     while (fgets (buf, sizeof (buf), f) != NULL) {
332       if (ReadInc && (i % ReadInc == 0))
333         mutt_message (_("Loading list from cache... %d"), i);
334       add_group (buf, serv);
335       i++;
336     }
337     add_group (NULL, NULL);
338     m_fclose(&f);
339     mutt_clear_error ();
340     return 0;
341   }
342   else {
343     p_delete(&serv->cache);
344     return -1;
345   }
346 }
347
348 /*
349  * Automatically loads a newsrc into memory, if necessary.
350  * Checks the size/mtime of a newsrc file, if it doesn't match, load
351  * again.  Hmm, if a system has broken mtimes, this might mean the file
352  * is reloaded every time, which we'd have to fix.
353  *
354  * a newsrc file is a line per newsgroup, with the newsgroup, then a 
355  * ':' denoting subscribed or '!' denoting unsubscribed, then a 
356  * comma separated list of article numbers and ranges.
357  */
358 nntp_server_t *mutt_select_newsserver (char *server)
359 {
360   char file[_POSIX_PATH_MAX];
361   char *buf, *p;
362   nntp_data_t *list;
363   ACCOUNT act;
364   nntp_server_t *serv;
365   CONNECTION *conn;
366
367   p_clear(&act, 1);
368
369   if (!server || !*server) {
370     mutt_error _("No newsserver defined!");
371
372     return NULL;
373   }
374
375   buf = p = p_new(char, m_strlen(server) + 10);
376   if (url_check_scheme (server) == U_UNKNOWN) {
377     strcpy (buf, "nntp://");
378     p = strchr (buf, '\0');
379   }
380   strcpy (p, server);
381
382   if ((nntp_parse_url (buf, &act, file, sizeof (file))) < 0 || *file) {
383     p_delete(&buf);
384     mutt_error (_("%s is an invalid newsserver specification!"), server);
385     return NULL;
386   }
387   p_delete(&buf);
388
389   conn = mutt_conn_find (NULL, &act);
390   if (!conn)
391     return NULL;
392
393   nntp_cache_expand(file, sizeof(file), "%s.newsrc", conn->account.host);
394   serv = (nntp_server_t *) conn->data;
395   if (serv) {
396     struct stat sb;
397
398     /* externally modified? */
399     if (serv->stat != stat(file, &sb) || (!serv->stat &&
400                                            (serv->size != sb.st_size
401                                             || serv->mtime != sb.st_mtime)))
402     {
403       for (list = serv->list; list; list = list->next) {
404         list->subscribed = list->rc = list->num = 0;
405       }
406       slurp_newsrc (serv);
407     }
408
409     if (serv->status == NNTP_BYE)
410       serv->status = NNTP_NONE;
411     nntp_check_newgroups (serv, 0);
412     return serv;
413   }
414
415   /* New newsserver */
416   serv = p_new(nntp_server_t, 1);
417   serv->tail = &serv->list;
418   serv->conn = conn;
419   serv->newsrc = m_strdup(file);
420   serv->newsgroups = hash_new(SHRT_MAX, false);
421   slurp_newsrc (serv);          /* load .newsrc */
422   nntp_parse_cacheindex (serv); /* load .index */
423   if (nntp.use_cache && serv->cache && nntp_get_cache_all (serv) >= 0)
424     nntp_check_newgroups (serv, 1);
425   else if (nntp_get_active (serv) < 0) {
426     hash_delete(&serv->newsgroups, NULL);
427     nntp_data_list_wipe(&serv->list);
428     p_delete(&serv->newsrc);
429     p_delete(&serv->cache);
430     p_delete(&serv);
431     return NULL;
432   }
433   conn->data = (void *) serv;
434
435   return serv;
436 }
437
438 /* 
439  * full status flags are not supported by nntp, but we can fake some
440  * of them.  This is how:
441  * Read = a read message number is in the .newsrc
442  * New = a message is new since we last read this newsgroup
443  * Old = anything else
444  * So, Read is marked as such in the newsrc, old is anything that is 
445  * "skipped" in the newsrc, and new is anything not in the newsrc nor
446  * in the cache. By skipped, I mean before the last unread message
447  */
448 static void
449 nntp_get_status(CONTEXT *ctx, HEADER *h, const char *group, int article)
450 {
451     nntp_data_t *data = ctx->data;
452
453     if (group)
454         data = hash_find(data->nserv->newsgroups, group);
455     if (!data)
456         return;
457
458     for (int i = 0; i < data->num; i++) {
459         if ((article >= data->entries[i].first) &&
460             (article <= data->entries[i].last))
461         {
462             /* we cannot use mutt_set_flag() because mx_update_context()
463                didn't called yet */
464             h->read = 1;
465             return;
466         }
467     }
468
469     /* If article was not cached yet, it is new! :) */
470     if (!data->cache || article > data->lastCached)
471         return;
472
473     /* Old articles are articles which aren't read but an article after them
474      * has been cached */
475     if (option(OPTMARKOLD))
476         h->old = 1;
477 }
478
479 static void nntp_create_newsrc_line(nntp_data_t *data, buffer_t *buf)
480 {
481     buffer_addstr(buf, data->group);
482     buffer_addch(buf, data->subscribed ? ':' : '!');
483     buffer_addch(buf, ' ');
484
485     for (int x = 0; x < data->num; x++) {
486         if (x) {
487             buffer_addch(buf, ',');
488         }
489
490         buffer_addf(buf, "%d-%d", data->entries[x].first,
491                     data->entries[x].last);
492     }
493     buffer_addch(buf, '\n');
494 }
495
496 static void newsrc_gen_entries (CONTEXT * ctx)
497 {
498   nntp_data_t *data = (nntp_data_t *) ctx->data;
499   int series, x;
500   int last = 0, first = 1;
501   int save_sort = SORT_ORDER;
502
503   if (Sort != SORT_ORDER) {
504     save_sort = Sort;
505     Sort = SORT_ORDER;
506     mutt_sort_headers (ctx, 0);
507   }
508
509   if (!data->max) {
510     data->entries = p_new(NEWSRC_ENTRY, 5);
511     data->max = 5;
512   }
513
514   /*
515    * Set up to fake initial sequence from 1 to the article before the 
516    * first article in our list
517    */
518   data->num = 0;
519   series = 1;
520
521   for (x = 0; x < ctx->msgcount; x++) {
522     if (series) {               /* search for first unread */
523       /*
524        * We don't actually check sequential order, since we mark 
525        * "missing" entries as read/deleted
526        */
527       last = ctx->hdrs[x]->article_num;
528       if (last >= data->firstMessage && !ctx->hdrs[x]->deleted &&
529           !ctx->hdrs[x]->read) {
530         if (data->num >= data->max) {
531           data->max = data->max * 2;
532           p_realloc(&data->entries, data->max);
533         }
534         data->entries[data->num].first = first;
535         data->entries[data->num].last = last - 1;
536         data->num++;
537         series = 0;
538       }
539     }
540     else {                      /* search for first read */
541
542       if (ctx->hdrs[x]->deleted || ctx->hdrs[x]->read) {
543         first = last + 1;
544         series = 1;
545       }
546       last = ctx->hdrs[x]->article_num;
547     }
548   }
549   if (series && first <= data->lastLoaded) {
550     if (data->num >= data->max) {
551       data->max = data->max * 2;
552       p_realloc(&data->entries, data->max);
553     }
554     data->entries[data->num].first = first;
555     data->entries[data->num].last = data->lastLoaded;
556     data->num++;
557   }
558
559   if (save_sort != Sort) {
560     Sort = save_sort;
561     mutt_sort_headers (ctx, 0);
562   }
563 }
564
565 static int mutt_update_list_file (char *filename, char *section,
566                                   const char *key, const char *line)
567 {
568   FILE *ifp;
569   FILE *ofp;
570   char buf[HUGE_STRING];
571   char tmpf[_POSIX_PATH_MAX], lnk[_POSIX_PATH_MAX];
572   char *c;
573   int ext = 0, done = 0, r = 0, l = 0;
574
575   /* if file not exist, create it */
576   if ((ifp = safe_fopen (filename, "a")))
577     m_fclose(&ifp);
578   if (!(ifp = safe_fopen (filename, "r"))) {
579     mutt_error (_("Unable to open %s for reading"), filename);
580     return -1;
581   }
582   if (mx_lock_file (filename, fileno (ifp), 0, 0, 1)) {
583     m_fclose(&ifp);
584     mutt_error (_("Unable to lock %s"), filename);
585     return -1;
586   }
587
588   /* use m_tempfile() to get a tempfile in the same
589    * directory as filename is so that we can follow symlinks
590    * via rename(2); as dirname(2) may modify its argument,
591    * temporarily use buf as copy of it
592    */
593   m_strcpy(buf, sizeof(buf), filename);
594   ofp = m_tempfile(tmpf, sizeof(tmpf), dirname(buf), filename);
595   if (!ofp) {
596     m_fclose(&ifp);
597     mutt_error (_("Unable to open %s for writing"), tmpf);
598     return -1;
599   }
600
601   if (section) {
602     while (r != EOF && !done && fgets (buf, sizeof (buf), ifp)) {
603       r = fputs (buf, ofp);
604       c = buf;
605       while (*c && *c != '\n') c++;
606       c[0] = 0; /* strip EOL */
607       if (!strncmp (buf, "#: ", 3) && !m_strcasecmp(buf+3, section))
608         done++;
609     }
610     if (r != EOF && !done) {
611       snprintf (buf, sizeof(buf), "#: %s\n", section);
612       r = fputs (buf, ofp);
613     }
614     done = 0;
615   }
616
617   while (r != EOF && fgets (buf, sizeof (buf), ifp)) {
618     if (ext) {
619       c = buf;
620       while (*c && (*c != '\r') && (*c != '\n')) c++;
621       c--;
622       if (*c != '\\') ext = 0;
623     } else if ((section && !strncmp (buf, "#: ", 3))) {
624       if (!done && line) {
625         fputs (line, ofp);
626         fputc ('\n', ofp);
627       }
628       r = fputs (buf, ofp);
629       done++;
630       break;
631     } else if (key && !strncmp (buf, key, strlen(key)) &&
632                (!*key || buf[strlen(key)] == ' ')) {
633       c = buf;
634       ext = 0;
635       while (*c && (*c != '\r') && (*c != '\n')) c++;
636       c--;
637       if (*c == '\\') ext = 1;
638       if (!done && line) {
639         r = fputs (line, ofp);
640         if (*key)
641           r = fputc ('\n', ofp);
642         done++;
643       }
644     } else {
645       r = fputs (buf, ofp);
646     }
647   }
648
649   while (r != EOF && fgets (buf, sizeof (buf), ifp))
650     r = fputs (buf, ofp);
651
652   /* If there wasn't a line to replace, put it on the end of the file */
653   if (r != EOF && !done && line) {
654     fputs (line, ofp);
655     r = fputc ('\n', ofp);
656   }
657   mx_unlock_file (filename, fileno (ifp), 0);
658   m_fclose(&ofp);
659   m_fclose(&ifp);
660   if (r == EOF) {
661     unlink (tmpf);
662     mutt_error (_("Can't write %s"), tmpf);
663     return -1;
664   }
665   lnk[0] = '\0';
666   if ((l = readlink (filename, lnk, sizeof(lnk)-1)) > 0)
667     lnk[l] = '\0';
668   if (rename (tmpf, l > 0 ? lnk : filename) < 0) {
669     unlink (tmpf);
670     mutt_error (_("Can't rename %s to %s"), tmpf, l > 0 ? lnk : filename);
671     return -1;
672   }
673   return 0;
674 }
675
676 int mutt_newsrc_update (nntp_server_t * news)
677 {
678     buffer_t buf;
679     nntp_data_t *data;
680     int r = -1;
681
682     if (!news)
683         return -1;
684
685     buffer_init(&buf);
686
687     /* we will generate full newsrc here */
688     for (data = news->list; data; data = data->next) {
689         if (!data || !data->rc)
690             continue;
691         nntp_create_newsrc_line(data, &buf);
692     }
693     /* newrc being fully rewritten */
694     if (news->newsrc
695     &&  (r = mutt_update_list_file(news->newsrc, NULL, "", buf.data)) == 0)
696     {
697         struct stat st;
698
699         stat (news->newsrc, &st);
700         news->size = st.st_size;
701         news->mtime = st.st_mtime;
702     }
703
704     buffer_wipe(&buf);
705     return r;
706 }
707
708 static FILE *mutt_mkname (char *s)
709 {
710   char buf[_POSIX_PATH_MAX], *pc;
711   int fd;
712   FILE *fp;
713
714   nntp_cache_expand(buf, ssizeof(buf), "%s", s);
715   if ((fp = safe_fopen (buf, "w")))
716     return fp;
717
718   nntp_cache_expand(buf, ssizeof(buf), "cache-XXXXXX");
719   pc = buf + m_strlen(buf) - 12; /* positioning to "cache-XXXXXX" */
720   if ((fd = mkstemp (buf)) == -1)
721     return NULL;
722   strcpy (s, pc);               /* generated name */
723   return fdopen (fd, "w");
724 }
725
726 /* Updates info into .index file: ALL or about selected newsgroup */
727 static int nntp_update_cacheindex (nntp_server_t * serv, nntp_data_t * data)
728 {
729   char buf[LONG_STRING];
730   char file[_POSIX_PATH_MAX];
731   const char *key = "ALL";
732
733   if (!serv || !serv->conn || !serv->conn->account.host)
734     return -1;
735
736   if (data && data->group) {
737     key = data->group;
738     snprintf (buf, sizeof (buf), "%s %s %d %d", key, data->cache,
739               data->firstMessage, data->lastLoaded);
740   }
741   else {
742     m_strcpy(file, sizeof(file), serv->cache);
743     snprintf (buf, sizeof (buf), "ALL %s 0 %d", file,
744               (int) serv->newgroups_time);
745   }
746   nntp_cache_expand(file, ssizeof(file), "%s.index", serv->conn->account.host);
747   return mutt_update_list_file(file, serv->conn->account.host, key, buf);
748 }
749
750 static void nntp_delete_cache (nntp_data_t * data)
751 {
752   char buf[_POSIX_PATH_MAX];
753
754   if (!nntp.use_cache || !data || !data->cache || !data->nserv)
755     return;
756
757   nntp_cache_expand(buf, ssizeof(buf), "%s", data->cache);
758   unlink (buf);
759   p_delete(&data->cache);
760   data->lastCached = 0;
761   nntp_cache_expand(buf, ssizeof(buf), "%s.index",
762                     data->nserv->conn->account.host);
763   mutt_update_list_file(buf, data->nserv->conn->account.host, data->group,
764                         NULL);
765 }
766
767 static int nntp_save_cache_index (nntp_server_t * news)
768 {
769   char buf[HUGE_STRING];
770   char file[_POSIX_PATH_MAX];
771   nntp_data_t *d;
772   FILE *f;
773
774   if (!news || !news->newsgroups)
775     return -1;
776   if (!nntp.use_cache)
777     return 0;
778
779   if (news->cache) {
780     nntp_cache_expand(file, ssizeof(file), "%s", news->cache);
781     unlink(file);
782     f = safe_fopen(file, "w");
783   } else {
784     m_strcpy(buf, ssizeof(buf), news->conn->account.host);
785     f = mutt_mkname(buf);
786     news->cache = m_strdup(buf);
787     nntp_cache_expand(file, ssizeof(file), "%s", buf);
788   }
789   if (!f)
790     return -1;
791
792   for (d = news->list; d; d = d->next) {
793     if (!d->deleted) {
794       if (d->desc)
795         snprintf (buf, sizeof (buf), "%s %d %d %c %s\n", d->group,
796                   d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n',
797                   d->desc);
798       else
799         snprintf (buf, sizeof (buf), "%s %d %d %c\n", d->group,
800                   d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n');
801       if (fputs (buf, f) == EOF) {
802         m_fclose(&f);
803         unlink (file);
804         return -1;
805       }
806     }
807   }
808   m_fclose(&f);
809
810   if (nntp_update_cacheindex (news, NULL)) {
811     unlink (file);
812     return -1;
813   }
814   return 0;
815 }
816
817 static int nntp_save_cache_group (CONTEXT * ctx)
818 {
819   char buf[HUGE_STRING], addr[STRING];
820   char file[_POSIX_PATH_MAX];
821   FILE *f;
822   HEADER *h;
823   struct tm *tm;
824   int i = 0, save = SORT_ORDER;
825   int prev = 0;
826
827   if (!nntp.use_cache)
828     return 0;
829   if (!ctx || !ctx->data || ctx->magic != M_NNTP)
830     return -1;
831
832   if (((nntp_data_t *) ctx->data)->cache) {
833     nntp_cache_expand(file, ssizeof(file), "%s",
834                       ((nntp_data_t *)ctx->data)->cache);
835     unlink (file);
836     f = safe_fopen (file, "w");
837   }
838   else {
839     snprintf(buf, ssizeof(buf), "%s-%s",
840              ((nntp_data_t *)ctx->data)->nserv->conn->account.host,
841              ((nntp_data_t *)ctx->data)->group);
842     f = mutt_mkname (buf);
843     ((nntp_data_t *)ctx->data)->cache = m_strdup(buf);
844     nntp_cache_expand(file, ssizeof(file), "%s", buf);
845   }
846   if (!f)
847     return -1;
848
849   if (Sort != SORT_ORDER) {
850     save = Sort;
851     Sort = SORT_ORDER;
852     mutt_sort_headers (ctx, 0);
853   }
854
855   /* Save only $nntp_context messages... */
856   ((nntp_data_t *) ctx->data)->lastCached = 0;
857   if (NntpContext && ctx->msgcount > NntpContext)
858     i = ctx->msgcount - NntpContext;
859   for (; i < ctx->msgcount; i++) {
860     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->article_num != prev) {
861       h = ctx->hdrs[i];
862       addr[0] = 0;
863       rfc822_addrcat(addr, sizeof(addr), h->env->from, 0);
864       tm = gmtime (&h->date_sent);
865       snprintf (buf, sizeof (buf),
866                 "%d\t%s\t%s\t%d %s %d %02d:%02d:%02d GMT\t%s\t",
867                 h->article_num, h->env->subject, addr, tm->tm_mday,
868                 Months[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour,
869                 tm->tm_min, tm->tm_sec, h->env->message_id);
870       fputs (buf, f);
871       if (h->env->references)
872         mutt_write_references (h->env->references, f);
873       snprintf (buf, sizeof (buf), "\t%zd\t%d\tXref: %s\n",
874                 h->content->length, h->lines, NONULL (h->env->xref));
875       if (fputs (buf, f) == EOF) {
876         m_fclose(&f);
877         unlink (file);
878         return -1;
879       }
880     }
881     prev = ctx->hdrs[i]->article_num;
882   }
883
884   if (save != Sort) {
885     Sort = save;
886     mutt_sort_headers (ctx, 0);
887   }
888   m_fclose(&f);
889
890   if (nntp_update_cacheindex (((nntp_data_t *) ctx->data)->nserv,
891                               (nntp_data_t *) ctx->data)) {
892     unlink (file);
893     return -1;
894   }
895   ((nntp_data_t *) ctx->data)->lastCached =
896     ((nntp_data_t *) ctx->data)->lastLoaded;
897   return 0;
898 }
899
900 nntp_data_t *mutt_newsgroup_subscribe (nntp_server_t * news, char *group)
901 {
902   nntp_data_t *data;
903
904   if (!news || !news->newsgroups || !group || !*group)
905     return NULL;
906   if (!(data = (nntp_data_t *) hash_find (news->newsgroups, group))) {
907     data = nntp_data_new();
908     data->group = m_strdup(group);
909     data->nserv = news;
910     data->deleted = 1;
911     if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
912         hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
913     hash_insert (news->newsgroups, data->group, data);
914     news->tail = nntp_data_list_append(news->tail, data);
915   }
916   if (!data->subscribed) {
917     data->subscribed = 1;
918     data->rc = 1;
919   }
920   return data;
921 }
922
923 nntp_data_t *mutt_newsgroup_unsubscribe (nntp_server_t * news, char *group)
924 {
925   nntp_data_t *data;
926
927   if (!news || !news->newsgroups || !group || !*group ||
928       !(data = (nntp_data_t *) hash_find (news->newsgroups, group)))
929     return NULL;
930   if (data->subscribed) {
931     data->subscribed = 0;
932   }
933   return data;
934 }
935
936 nntp_data_t *mutt_newsgroup_catchup (nntp_server_t * news, char *group)
937 {
938   nntp_data_t *data;
939
940   if (!news || !news->newsgroups || !group || !*group ||
941       !(data = (nntp_data_t *) hash_find (news->newsgroups, group)))
942     return NULL;
943   if (!data->max) {
944     data->entries = p_new(NEWSRC_ENTRY, 5);
945     data->max = 5;
946   }
947   data->num = 1;
948   data->entries[0].first = 1;
949   data->unread = 0;
950   data->entries[0].last = data->lastMessage;
951   if (Context && Context->data == data) {
952     int x;
953
954     for (x = 0; x < Context->msgcount; x++)
955       mutt_set_flag (Context, Context->hdrs[x], M_READ, 1);
956   }
957   return data;
958 }
959
960 nntp_data_t *mutt_newsgroup_uncatchup (nntp_server_t * news, char *group)
961 {
962   nntp_data_t *data;
963
964   if (!news || !news->newsgroups || !group || !*group ||
965       !(data = (nntp_data_t *) hash_find (news->newsgroups, group)))
966     return NULL;
967   if (!data->max) {
968     data->entries = p_new(NEWSRC_ENTRY, 5);
969     data->max = 5;
970   }
971   data->num = 1;
972   data->entries[0].first = 1;
973   data->entries[0].last = data->firstMessage - 1;
974   if (Context && Context->data == data) {
975     int x;
976
977     data->unread = Context->msgcount;
978     for (x = 0; x < Context->msgcount; x++)
979       mutt_set_flag (Context, Context->hdrs[x], M_READ, 0);
980   }
981   else
982     data->unread = data->lastMessage - data->entries[0].last;
983   return data;
984 }
985
986 /* this routine gives the first newsgroup with new messages */
987 void nntp_buffy (char* dst, ssize_t dstlen) {
988   nntp_data_t *list;
989   int count = 0;
990
991   /* forward to current group */
992   for (list = CurrentNewsSrv->list; list; list = list->next) {
993     if (list->subscribed && list->unread
994     &&  Context && Context->magic == M_NNTP
995     &&  m_strcmp(list->group, ((nntp_data_t *)Context->data)->group) == 0)
996     {
997       list = list->next;
998       break;
999     }
1000   }
1001
1002   *dst = '\0';
1003
1004   while (count < 2) {
1005
1006     if (!list)
1007       list = CurrentNewsSrv->list;
1008
1009     for (; list; list = list->next) {
1010       if (list->subscribed && list->unread) {
1011         if (Context && Context->magic == M_NNTP &&
1012             !m_strcmp(list->group, ((nntp_data_t *)Context->data)->group)) {
1013           int i, unread = 0;
1014
1015           for (i = 0; i < Context->msgcount; i++)
1016             if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted)
1017               unread++;
1018           if (!unread)
1019             continue;
1020         }
1021         m_strcpy(dst, dstlen, list->group);
1022         break;
1023       }
1024     }
1025     /* done if found */
1026     if (dst && *dst)
1027       return;
1028     count++;
1029   }
1030   *dst = '\0';
1031 }
1032
1033 /* }}} */
1034 /* nntp protocol {{{ */
1035
1036 void nntp_sync_sidebar (nntp_data_t* data) {
1037   int i = 0;
1038   BUFFY* tmp = NULL;
1039   char buf[STRING];
1040
1041   if (!Incoming.len)
1042     return;
1043
1044   snprintf(buf, sizeof (buf), "nntp%s://%s%s%s%s/%s",
1045            data->nserv->conn->account.has_ssl ? "s" : "",
1046            NONULL(data->nserv->conn->account.user),
1047            *data->nserv->conn->account.pass ? ":" : "",
1048            *data->nserv->conn->account.pass ? data->nserv->conn->account.pass : "",
1049            data->nserv->conn->account.host,
1050            data->group);
1051
1052   /* bail out if group not found via mailboxes */
1053   if ((i = buffy_lookup (buf)) < 0)
1054     return;
1055
1056   tmp = Incoming.arr[i];
1057   /* copied from browser.c */
1058   if (option (OPTMARKOLD) &&
1059       data->lastCached >= data->firstMessage &&
1060       data->lastCached <= data->lastMessage)
1061     tmp->msg_unread = data->lastMessage - data->lastCached;
1062   else
1063     tmp->msg_unread = data->unread;
1064   tmp->new = data->unread > 0;
1065   /* this is closest to a "total" count we can get */
1066   tmp->msgcount = data->lastMessage - data->firstMessage;
1067 }
1068
1069 static int nntp_auth (nntp_server_t * serv)
1070 {
1071   CONNECTION *conn = serv->conn;
1072   char buf[STRING];
1073   unsigned char flags = conn->account.flags;
1074
1075   if (mutt_account_getuser(&conn->account) || !conn->account.user[0] ||
1076       mutt_account_getpass(&conn->account) || !conn->account.pass[0]) {
1077     conn->account.flags = flags;
1078     return -2;
1079   }
1080
1081   mutt_message _("Logging in...");
1082
1083   snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user);
1084   mutt_socket_write (conn, buf);
1085   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) {
1086     conn->account.flags = flags;
1087     return -1;
1088   }
1089
1090   snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n", conn->account.pass);
1091   mutt_socket_write(conn, buf);
1092   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) {
1093     conn->account.flags = flags;
1094     return -1;
1095   }
1096
1097   if (m_strncmp("281", buf, 3)) {
1098     conn->account.flags = flags;
1099     mutt_error _("Login failed.");
1100
1101     sleep (2);
1102     return -3;
1103   }
1104
1105   return 0;
1106 }
1107
1108 static int nntp_connect_error (nntp_server_t * serv)
1109 {
1110   serv->status = NNTP_NONE;
1111   mutt_socket_close (serv->conn);
1112   mutt_error _("Server closed connection!");
1113
1114   sleep (2);
1115   return -1;
1116 }
1117
1118 static int nntp_connect_and_auth (nntp_server_t * serv)
1119 {
1120   CONNECTION *conn = serv->conn;
1121   char buf[STRING];
1122   int rc;
1123
1124   serv->status = NNTP_NONE;
1125
1126   if (mutt_socket_open (conn) < 0)
1127     return -1;
1128
1129   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1130     return nntp_connect_error (serv);
1131
1132   if (!m_strncmp("200", buf, 3))
1133     mutt_message (_("Connected to %s. Posting ok."), conn->account.host);
1134   else if (!m_strncmp("201", buf, 3))
1135     mutt_message (_("Connected to %s. Posting NOT ok."), conn->account.host);
1136   else {
1137     mutt_socket_close(conn);
1138     m_strrtrim(buf);
1139     mutt_error("%s", buf);
1140     sleep (2);
1141     return -1;
1142   }
1143
1144   /* Tell INN to switch to mode reader if it isn't so. Ignore all
1145      returned codes and messages. */
1146   mutt_socket_write (conn, "MODE READER\r\n");
1147   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1148     return nntp_connect_error (serv);
1149
1150   mutt_socket_write (conn, "STAT\r\n");
1151   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1152     return nntp_connect_error (serv);
1153
1154   if (!conn->account.has_user && m_strncmp("480", buf, 3)) {
1155     serv->status = NNTP_OK;
1156     return 0;
1157   }
1158
1159   rc = nntp_auth (serv);
1160   if (rc == -1)
1161     return nntp_connect_error (serv);
1162   if (rc == -2) {
1163     mutt_socket_close (conn);
1164     serv->status = NNTP_BYE;
1165     return -1;
1166   }
1167   if (rc < 0) {
1168     mutt_socket_close (conn);
1169     mutt_error _("Login failed.");
1170
1171     sleep (2);
1172     return -1;
1173   }
1174   serv->status = NNTP_OK;
1175   return 0;
1176 }
1177
1178 static int nntp_attempt_features (nntp_server_t * serv)
1179 {
1180   char buf[LONG_STRING];
1181   CONNECTION *conn = serv->conn;
1182
1183   if (serv->feat_known)
1184       return 0;
1185
1186   mutt_socket_write (conn, "XOVER\r\n");
1187   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1188     return nntp_connect_error (serv);
1189   if (m_strncmp("500", buf, 3))
1190     serv->hasXOVER = 1;
1191
1192   mutt_socket_write (conn, "XPAT\r\n");
1193   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1194     return nntp_connect_error (serv);
1195   if (m_strncmp("500", buf, 3))
1196     serv->hasXPAT = 1;
1197
1198   mutt_socket_write (conn, "LISTGROUP\r\n");
1199   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1200     return (nntp_connect_error (serv));
1201   if (m_strncmp("500", buf, 3))
1202     serv->hasLISTGROUP = 1;
1203
1204   mutt_socket_write (conn, "XGTITLE +\r\n");
1205   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1206     return nntp_connect_error (serv);
1207   if (m_strncmp("500", buf, 3))
1208     serv->hasXGTITLE = 1;
1209
1210   if (!m_strncmp("282", buf, 3)) {
1211     do {
1212       if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
1213         return nntp_connect_error (serv);
1214     } while (!(buf[0] == '.' && buf[1] == '\0'));
1215   }
1216
1217   serv->feat_known = 1;
1218   return 0;
1219 }
1220
1221 static int nntp_open_connection (nntp_server_t * serv)
1222 {
1223   if (serv->status == NNTP_OK)
1224     return 0;
1225   if (serv->status == NNTP_BYE)
1226     return -1;
1227   if (nntp_connect_and_auth (serv) < 0)
1228     return -1;
1229   if (nntp_attempt_features (serv) < 0)
1230     return -1;
1231   return 0;
1232 }
1233
1234 static int nntp_reconnect (nntp_server_t * serv)
1235 {
1236   char buf[STRING];
1237
1238   mutt_socket_close (serv->conn);
1239
1240   for (;;) {
1241     if (nntp_connect_and_auth (serv) == 0)
1242       return 0;
1243
1244     snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"),
1245               serv->conn->account.host);
1246     if (query_quadoption (OPT_NNTPRECONNECT, buf) != M_YES) {
1247       serv->status = NNTP_BYE;
1248       return -1;
1249     }
1250   }
1251 }
1252
1253 /* Send data from line[LONG_STRING] and receive answer to same line */
1254 static int mutt_nntp_query (nntp_data_t * data, char *line, size_t linelen)
1255 {
1256   char buf[LONG_STRING];
1257   int done = TRUE;
1258
1259   if (data->nserv->status == NNTP_BYE)
1260     return -1;
1261
1262   do {
1263     if (*line) {
1264       mutt_socket_write (data->nserv->conn, line);
1265     }
1266     else if (data->group) {
1267       snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group);
1268       mutt_socket_write (data->nserv->conn, buf);
1269     }
1270
1271     done = TRUE;
1272     if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0) {
1273       if (nntp_reconnect (data->nserv) < 0)
1274         return -1;
1275
1276       if (data->group) {
1277         snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group);
1278         mutt_socket_write (data->nserv->conn, buf);
1279         if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0)
1280           return -1;
1281       }
1282       if (*line)
1283         done = FALSE;
1284     }
1285     else if ((!m_strncmp("480", buf, 3)) && nntp_auth (data->nserv) < 0)
1286       return -1;
1287   } while (!done);
1288
1289   m_strcpy(line, linelen, buf);
1290   return 0;
1291 }
1292
1293 /*
1294  * This function calls  funct(*line, *data)  for each received line,
1295  * funct(NULL, *data)  if  rewind(*data)  needs, exits when fail or done.
1296  * Returned codes:
1297  *  0 - successful,
1298  *  1 - correct but not performed (may be, have to be continued),
1299  * -1 - conection lost,
1300  * -2 - invalid command or execution error,
1301  * -3 - error in funct(*line, *data).
1302  */
1303 static int mutt_nntp_fetch (nntp_data_t * nntp_data, const char *query,
1304                             const char *msg, progress_t* bar,
1305                             int (*funct) (char *, void *),
1306                             void *data, int tagged)
1307 {
1308   char buf[LONG_STRING];
1309   char *inbuf, *p;
1310   int done = FALSE;
1311   int chunk, line;
1312   long pos = 0;
1313   size_t lenbuf = 0;
1314   int ret;
1315
1316   do {
1317     m_strcpy(buf, sizeof(buf), query);
1318     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0)
1319       return -1;
1320     if (buf[0] == '5')
1321       return -2;
1322     if (buf[0] != '2')
1323       return 1;
1324
1325     ret = 0;
1326     line = 0;
1327     inbuf = p_new(char, sizeof(buf));
1328
1329     for (;;) {
1330       chunk = mutt_socket_readln(buf, sizeof (buf), nntp_data->nserv->conn);
1331       if (chunk < 0)
1332         break;
1333
1334       p = buf;
1335       if (!lenbuf && buf[0] == '.') {
1336         if (buf[1] == '\0') {
1337           done = TRUE;
1338           break;
1339         }
1340         if (buf[1] == '.')
1341           p++;
1342       }
1343
1344       m_strcpy(inbuf + lenbuf, sizeof(buf), p);
1345       pos += chunk;
1346
1347       if (chunk >= ssizeof (buf)) {
1348         lenbuf += m_strlen(p);
1349       }
1350       else {
1351         if (bar) {
1352           mutt_progress_bar (bar, pos);
1353         } else if (msg) {
1354           line++;
1355           if (ReadInc && (line % ReadInc == 0)) {
1356             if (tagged)
1357               mutt_message (_("%s (tagged: %d) %d"), msg, tagged, line);
1358             else
1359               mutt_message ("%s %d", msg, line);
1360           }
1361         }
1362
1363         if (ret == 0 && funct (inbuf, data) < 0)
1364           ret = -3;
1365         lenbuf = 0;
1366       }
1367
1368       p_realloc(&inbuf, lenbuf + sizeof (buf));
1369     }
1370     p_delete(&inbuf);
1371     funct (NULL, data);
1372   }
1373   while (!done);
1374   return ret;
1375 }
1376
1377 static int nntp_read_tempfile (char *line, void *file)
1378 {
1379   FILE *f = (FILE *) file;
1380
1381   if (!line)
1382     rewind (f);
1383   else {
1384     fputs (line, f);
1385     if (fputc ('\n', f) == EOF)
1386       return -1;
1387   }
1388   return 0;
1389 }
1390
1391 static void nntp_parse_xref (CONTEXT * ctx, char *group, char *xref,
1392                              HEADER * h)
1393 {
1394   register char *p, *b;
1395   register char *colon = NULL;
1396
1397   b = p = xref;
1398   while (*p) {
1399     /* skip to next word */
1400     b = p;
1401     while (*b && ((*b == ' ') || (*b == '\t')))
1402       b++;
1403     p = b;
1404     colon = NULL;
1405     /* skip to end of word */
1406     while (*p && (*p != ' ') && (*p != '\t')) {
1407       if (*p == ':')
1408         colon = p;
1409       p++;
1410     }
1411     if (*p) {
1412       *p = '\0';
1413       p++;
1414     }
1415     if (colon) {
1416       *colon = '\0';
1417       colon++;
1418       nntp_get_status (ctx, h, b, atoi (colon));
1419       if (h && h->article_num == 0 && m_strcmp(group, b) == 0)
1420         h->article_num = atoi (colon);
1421     }
1422   }
1423 }
1424
1425 /*
1426  * returns:
1427  *  0 on success
1428  *  1 if article not found
1429  * -1 if read or write error on tempfile or socket
1430  */
1431 static int nntp_read_header (CONTEXT * ctx, const char *msgid,
1432                              int article_num)
1433 {
1434   nntp_data_t *nntp_data = ((nntp_data_t *) ctx->data);
1435   FILE *f;
1436   char buf[LONG_STRING];
1437   char tempfile[_POSIX_PATH_MAX];
1438   int ret;
1439   HEADER *h = ctx->hdrs[ctx->msgcount];
1440
1441   f = m_tempfile(tempfile, sizeof(tempfile), NONULL(mod_core.tmpdir), NULL);
1442   if (!f)
1443     return -1;
1444
1445   if (!msgid)
1446     snprintf (buf, sizeof (buf), "HEAD %d\r\n", article_num);
1447   else
1448     snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid);
1449
1450   ret = mutt_nntp_fetch (nntp_data, buf, NULL, NULL, nntp_read_tempfile, f, 0);
1451   if (ret) {
1452     m_fclose(&f);
1453     unlink (tempfile);
1454     return (ret == -1 ? -1 : 1);
1455   }
1456
1457   h->article_num = article_num;
1458   h->env = mutt_read_rfc822_header (f, h, 0, 0);
1459   m_fclose(&f);
1460   unlink (tempfile);
1461
1462   if (h->env->xref != NULL)
1463     nntp_parse_xref (ctx, nntp_data->group, h->env->xref, h);
1464   else if (h->article_num == 0 && msgid) {
1465     snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid);
1466     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) == 0)
1467       h->article_num = atoi (buf + 4);
1468   }
1469
1470   return 0;
1471 }
1472
1473 static int parse_description (char *line, void *n)
1474 {
1475   register char *d = line;
1476   nntp_data_t *data;
1477   nntp_server_t *news = n;
1478
1479   if (!line)
1480     return 0;
1481   while (*d && *d != '\t' && *d != ' ')
1482     d++;
1483   *d = 0;
1484   d++;
1485   while (*d && (*d == '\t' || *d == ' '))
1486     d++;
1487   if ((data = (nntp_data_t *) hash_find (news->newsgroups, line)) != NULL &&
1488       m_strcmp(d, data->desc)) {
1489     p_delete(&data->desc);
1490     data->desc = m_strdup(d);
1491   }
1492   return 0;
1493 }
1494
1495 static void nntp_get_desc (nntp_data_t * data, const char *mask, char *msg, progress_t* bar)
1496 {
1497   char buf[STRING];
1498
1499   if (!option (OPTLOADDESC) || !data || !data->nserv)
1500     return;
1501
1502   /* Get newsgroup description, if we can */
1503   if (data->nserv->hasXGTITLE)
1504     snprintf (buf, sizeof (buf), "XGTITLE %s\r\n", mask);
1505   else
1506     snprintf (buf, sizeof (buf), "LIST NEWSGROUPS %s\r\n", mask);
1507   mutt_nntp_fetch (data, buf, msg, bar, parse_description, data->nserv, 0);
1508 }
1509
1510 /*
1511  * XOVER returns a tab separated list of:
1512  * id|subject|from|date|Msgid|references|bytes|lines|xref
1513  *
1514  * This has to duplicate some of the functionality of 
1515  * mutt_read_rfc822_header(), since it replaces the call to that (albeit with
1516  * a limited number of headers which are "parsed" by placement in the list)
1517  */
1518 static int nntp_parse_xover (CONTEXT * ctx, char *buf, HEADER * hdr)
1519 {
1520   nntp_data_t *nntp_data = (nntp_data_t *) ctx->data;
1521   char *p, *b;
1522   int x, done = 0;
1523
1524   hdr->env = envelope_new();
1525   hdr->env->newsgroups = m_strdup(nntp_data->group);
1526   hdr->content = body_new();
1527   hdr->content->type = TYPETEXT;
1528   hdr->content->subtype = m_strdup("plain");
1529   hdr->content->encoding = ENC7BIT;
1530   hdr->content->disposition = DISPINLINE;
1531   hdr->content->length = -1;
1532   b = p = buf;
1533
1534   for (x = 0; !done && x < 9; x++) {
1535     /* if from file, need to skip newline character */
1536     while (*p && *p != '\n' && *p != '\t')
1537       p++;
1538     if (!*p)
1539       done++;
1540     *p = '\0';
1541     p++;
1542     switch (x) {
1543     case 0:
1544       hdr->article_num = atoi (b);
1545       nntp_get_status (ctx, hdr, NULL, hdr->article_num);
1546       break;
1547     case 1:
1548       hdr->env->subject = m_strdup(b);
1549       break;
1550     case 2:
1551       address_list_wipe(&hdr->env->from);
1552       hdr->env->from = rfc822_parse_adrlist (hdr->env->from, b);
1553       /* same as for mutt_parse_rfc822_line():
1554        * don't leave from info NULL if there's an invalid address (or
1555        * whatever) in From: field; mutt would just display it as empty
1556        * and mark mail/(esp.) news article as your own. aaargh! this
1557        * bothered me for _years_ */
1558       if (!hdr->env->from) {
1559         hdr->env->from = address_new ();
1560         hdr->env->from->personal = m_strdup(b);
1561       }
1562       break;
1563     case 3:
1564       hdr->date_sent = mutt_parse_date (b, hdr);
1565       hdr->received = hdr->date_sent;
1566       break;
1567     case 4:
1568       p_delete(&hdr->env->message_id);
1569       hdr->env->message_id = m_strdup(b);
1570       break;
1571     case 5:
1572       string_list_wipe(&hdr->env->references);
1573       hdr->env->references = mutt_parse_references (b, 0);
1574       break;
1575     case 6:
1576       hdr->content->length = atoi (b);
1577       break;
1578     case 7:
1579       hdr->lines = atoi (b);
1580       break;
1581     case 8:
1582       if (!hdr->read)
1583         p_delete(&hdr->env->xref);
1584       b = b + 6;                /* skips the "Xref: " */
1585       hdr->env->xref = m_strdup(b);
1586       nntp_parse_xref (ctx, nntp_data->group, b, hdr);
1587     }
1588     rfc2047_decode_envelope(hdr->env);
1589     if (!*p)
1590       return -1;
1591     b = p;
1592   }
1593   return 0;
1594 }
1595
1596 typedef struct {
1597   CONTEXT *ctx;
1598   int   first;
1599   int   last;
1600   bits_t messages;
1601   const char *msg;
1602 } FETCH_CONTEXT;
1603
1604 static int nntp_fetch_numbers (char *line, void *c)
1605 {
1606     FETCH_CONTEXT *fc = c;
1607
1608     if (line) {
1609         int num = atoi(line);
1610         if (num < fc->first || num > fc->last)
1611             return 0;
1612
1613         bit_set(&fc->messages, num);
1614     }
1615     return 0;
1616 }
1617
1618 static int add_xover_line (char *line, void *c)
1619 {
1620   int num, total;
1621   FETCH_CONTEXT *fc = c;
1622   CONTEXT *ctx = fc->ctx;
1623   nntp_data_t *data = (nntp_data_t *) ctx->data;
1624
1625   if (!line)
1626     return 0;
1627
1628   if (ctx->msgcount >= ctx->hdrmax)
1629     mx_alloc_memory (ctx);
1630   ctx->hdrs[ctx->msgcount] = header_new();
1631   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
1632
1633   nntp_parse_xover (ctx, line, ctx->hdrs[ctx->msgcount]);
1634   num = ctx->hdrs[ctx->msgcount]->article_num;
1635
1636   if (num >= fc->first && num <= fc->last) {
1637     ctx->msgcount++;
1638     if (num > data->lastLoaded)
1639       data->lastLoaded = num;
1640     num = num - fc->first + 1;
1641     total = fc->last - fc->first + 1;
1642     if (!ctx->quiet && fc->msg && ReadInc && (num % ReadInc == 0))
1643       mutt_message ("%s %d/%d", fc->msg, num, total);
1644   } else {
1645     header_delete(&ctx->hdrs[ctx->msgcount]);       /* skip it */
1646   }
1647
1648   return 0;
1649 }
1650
1651
1652 static int nntp_fetch_headers(CONTEXT * ctx, int first, int last)
1653 {
1654   char buf[HUGE_STRING];
1655   const char *msg = _("Fetching message headers...");
1656   const char *msg2 = _("Fetching headers from cache...");
1657   nntp_data_t *nntp_data = ((nntp_data_t *) ctx->data);
1658   int ret, num, oldmsgcount, current;
1659   FILE *f;
1660   FETCH_CONTEXT fc;
1661
1662   /* if empty group or nothing to do */
1663   if (!last || first > last)
1664     return 0;
1665
1666   /* fetch list of articles */
1667   mutt_message _("Fetching list of articles...");
1668
1669   fc.ctx   = ctx;
1670   fc.first = first;
1671   fc.last  = last;
1672
1673   /* CACHE: must be loaded xover cache here */
1674   num = nntp_data->lastCached - first + 1;
1675   if (nntp.use_cache && nntp_data->cache && num > 0) {
1676   nntp_cache_expand(buf, ssizeof(buf), "%s", nntp_data->cache);
1677   mutt_message (msg2);
1678
1679   if ((f = safe_fopen (buf, "r"))) {
1680     int r = 0, c = 0;
1681
1682     /* counting number of lines */
1683     while (fgets (buf, sizeof (buf), f) != NULL)
1684       r++;
1685     rewind (f);
1686     while (r > num && fgets (buf, sizeof (buf), f) != NULL)
1687       r--;
1688     oldmsgcount = ctx->msgcount;
1689     fc.first = first;
1690     fc.last = first + num - 1;
1691     fc.msg = NULL;
1692     while (fgets (buf, sizeof (buf), f) != NULL) {
1693       if (ReadInc && ((++c) % ReadInc == 0))
1694         mutt_message ("%s %d/%d", msg2, c, r);
1695       add_xover_line (buf, &fc);
1696     }
1697     m_fclose(&f);
1698     nntp_data->lastLoaded = fc.last;
1699     first = fc.last + 1;
1700     if (ctx->msgcount > oldmsgcount)
1701       mx_update_context (ctx, ctx->msgcount - oldmsgcount);
1702   }
1703   else
1704     nntp_delete_cache (nntp_data);
1705   }
1706   num = last - first + 1;
1707   if (num <= 0) {
1708     return 0;
1709   }
1710
1711   /*
1712   * Without XOVER, we have to fetch each article header and parse
1713   * it.  With XOVER, we ask for all of them
1714   */
1715   mutt_message (msg);
1716   if (nntp_data->nserv->hasXOVER) {
1717     oldmsgcount = ctx->msgcount;
1718     fc.first = first;
1719     fc.last = last;
1720     fc.msg = msg;
1721     snprintf (buf, sizeof (buf), "XOVER %d-%d\r\n", first, last);
1722     ret = mutt_nntp_fetch (nntp_data, buf, NULL, NULL, add_xover_line, &fc, 0);
1723     if (ctx->msgcount > oldmsgcount)
1724       mx_update_context (ctx, ctx->msgcount - oldmsgcount);
1725     if (ret != 0) {
1726       mutt_error (_("XOVER command failed: %s"), buf);
1727       return -1;
1728     }
1729   } else {
1730     bits_init(&fc.messages);
1731
1732     if (nntp_data->nserv->hasLISTGROUP) {
1733       snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group);
1734       if (mutt_nntp_fetch(nntp_data, buf, NULL, NULL,
1735                           nntp_fetch_numbers, &fc, 0))
1736       {
1737         mutt_error (_("LISTGROUP command failed: %s"), buf);
1738         sleep (2);
1739         bits_wipe(&fc.messages);
1740         return -1;
1741       }
1742     } else {
1743       for (num = first; num <= last; num++)
1744         bit_set(&fc.messages, num);
1745     }
1746
1747     for (current = first; current <= last; current++) {
1748       HEADER *h;
1749
1750       ret = current - first + 1;
1751       mutt_message ("%s %d/%d", msg, ret, num);
1752
1753       if (!bit_isset(&fc.messages, current))
1754         continue;
1755
1756       if (ctx->msgcount >= ctx->hdrmax)
1757         mx_alloc_memory (ctx);
1758       h = ctx->hdrs[ctx->msgcount] = header_new();
1759       h->index = ctx->msgcount;
1760
1761       ret = nntp_read_header (ctx, NULL, current);
1762       if (ret == 0) {           /* Got article. Fetch next header */
1763         nntp_get_status (ctx, h, NULL, h->article_num);
1764         ctx->msgcount++;
1765         mx_update_context (ctx, 1);
1766       }
1767       else
1768         header_delete(&h);  /* skip it */
1769       if (ret == -1) {
1770         bits_wipe(&fc.messages);
1771         return -1;
1772       }
1773
1774       if (current > nntp_data->lastLoaded)
1775         nntp_data->lastLoaded = current;
1776     }
1777     bits_wipe(&fc.messages);
1778   }
1779
1780   nntp_data->lastLoaded = last;
1781   mutt_clear_error ();
1782   return 0;
1783 }
1784
1785 /*
1786  * currently, nntp "mailbox" is "newsgroup"
1787  */
1788 static int nntp_open_mailbox (CONTEXT * ctx)
1789 {
1790   nntp_data_t *nntp_data;
1791   nntp_server_t *serv;
1792   char buf[HUGE_STRING];
1793   char server[LONG_STRING];
1794   int count = 0, first;
1795   ACCOUNT act;
1796
1797   p_clear(&act, 1);
1798
1799   if (nntp_parse_url (ctx->path, &act, buf, sizeof (buf)) < 0 || !*buf) {
1800     mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path);
1801     mutt_sleep (2);
1802     return -1;
1803   }
1804
1805   server[0] = '\0';
1806   nntp_expand_path (server, sizeof (server), &act);
1807   if (!(serv = mutt_select_newsserver (server)) || serv->status != NNTP_OK)
1808     return -1;
1809
1810   CurrentNewsSrv = serv;
1811
1812   /* create NNTP-specific state struct if nof found in list */
1813   if ((nntp_data = hash_find(serv->newsgroups, buf)) == NULL) {
1814     nntp_data = nntp_data_new();
1815     nntp_data->group = m_strdup(buf);
1816     hash_insert(serv->newsgroups, nntp_data->group, nntp_data);
1817     serv->tail = nntp_data_list_append(serv->tail, nntp_data);
1818   }
1819   ctx->data = nntp_data;
1820   nntp_data->nserv = serv;
1821
1822   mutt_message (_("Selecting %s..."), nntp_data->group);
1823
1824   if (!nntp_data->desc) {
1825     nntp_get_desc (nntp_data, nntp_data->group, NULL, NULL);
1826     if (nntp_data->desc)
1827       nntp_save_cache_index (serv);
1828   }
1829
1830   buf[0] = 0;
1831   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1832     return -1;
1833   }
1834
1835   if (m_strncmp("211", buf, 3)) {
1836     nntp_data_t **l;
1837
1838     /* GROUP command failed */
1839     if (!m_strncmp("411", buf, 3)) {
1840       mutt_error (_("Newsgroup %s not found on server %s"),
1841                   nntp_data->group, serv->conn->account.host);
1842
1843       /* CACHE: delete cache and line from .index */
1844       nntp_delete_cache (nntp_data);
1845       hash_remove(serv->newsgroups, nntp_data->group, NULL, NULL);
1846       for (l = &serv->list; *l; l = &(*l)->next) {
1847           if ((*l) == nntp_data) {
1848               nntp_data_list_pop(l);
1849               nntp_data_delete(&nntp_data);
1850               if (!*l)
1851                   serv->tail = l;
1852               break;
1853           }
1854       }
1855       nntp_data_delete(&nntp_data);
1856       sleep (2);
1857     }
1858
1859     return -1;
1860   }
1861
1862   sscanf (buf + 4, "%d %u %u %s", &count, &nntp_data->firstMessage,
1863           &nntp_data->lastMessage, buf);
1864
1865   nntp_data->deleted = 0;
1866
1867   time (&serv->check_time);
1868
1869   /*
1870    * Check for max adding context. If it is greater than $nntp_context,
1871    * strip off extra articles
1872    */
1873   first = nntp_data->firstMessage;
1874   if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext)
1875     first = nntp_data->lastMessage - NntpContext + 1;
1876   if (first)
1877     nntp_data->lastLoaded = first - 1;
1878   return nntp_fetch_headers (ctx, first, nntp_data->lastMessage);
1879 }
1880
1881 int nntp_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno)
1882 {
1883   char buf[LONG_STRING];
1884   char path[_POSIX_PATH_MAX];
1885   NNTP_CACHE *cache;
1886   int ret;
1887   progress_t bar;
1888
1889   /* see if we already have the message in our cache */
1890   cache =
1891     &((nntp_data_t *) ctx->data)->acache[ctx->hdrs[msgno]->index %
1892                                        NNTP_CACHE_LEN];
1893
1894   /* if everything is fine, assign msg->fp and return */
1895   if (cache->path && cache->index == ctx->hdrs[msgno]->index &&
1896       (msg->fp = fopen (cache->path, "r")))
1897     return 0;
1898
1899   /* clear the previous entry */
1900   unlink (cache->path);
1901   p_delete(&cache->path);
1902
1903   cache->index = ctx->hdrs[msgno]->index;
1904   msg->fp = m_tempfile(path, sizeof(path), NONULL(mod_core.tmpdir), NULL);
1905   if (!msg->fp) {
1906     return -1;
1907   }
1908   cache->path = m_strdup(path);
1909
1910   if (ctx->hdrs[msgno]->article_num == 0)
1911     snprintf (buf, sizeof (buf), "ARTICLE %s\r\n",
1912               ctx->hdrs[msgno]->env->message_id);
1913   else
1914     snprintf (buf, sizeof (buf), "ARTICLE %d\r\n",
1915               ctx->hdrs[msgno]->article_num);
1916
1917   bar.msg = _("Fetching message...");
1918   bar.size = 0;
1919   mutt_progress_bar (&bar, 0);
1920
1921   ret = mutt_nntp_fetch ((nntp_data_t *) ctx->data, buf, NULL, &bar, nntp_read_tempfile,
1922                          msg->fp, ctx->tagged);
1923   if (ret == 1) {
1924     mutt_error (_("Article %d not found on server"),
1925                 ctx->hdrs[msgno]->article_num);
1926   }
1927
1928   if (ret) {
1929     m_fclose(&msg->fp);
1930     unlink (path);
1931     p_delete(&cache->path);
1932     return -1;
1933   }
1934
1935   envelope_delete(&ctx->hdrs[msgno]->env);
1936   ctx->hdrs[msgno]->env =
1937     mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno], 0, 0);
1938   /* fix content length */
1939   fseeko (msg->fp, 0, SEEK_END);
1940   ctx->hdrs[msgno]->content->length = ftello (msg->fp) -
1941     ctx->hdrs[msgno]->content->offset;
1942
1943   /* this is called in mutt before the open which fetches the message, 
1944    * which is probably wrong, but we just call it again here to handle
1945    * the problem instead of fixing it.
1946    */
1947   mutt_parse_mime_message (ctx, ctx->hdrs[msgno]);
1948
1949   /* These would normally be updated in mx_update_context(), but the 
1950    * full headers aren't parsed with XOVER, so the information wasn't
1951    * available then.
1952    */
1953   ctx->hdrs[msgno]->security = crypt_query (ctx->hdrs[msgno]->content);
1954
1955   mutt_clear_error ();
1956   rewind (msg->fp);
1957
1958   return 0;
1959 }
1960
1961 /* Post article */
1962 int nntp_post (const char *msg)
1963 {
1964   char buf[LONG_STRING];
1965   size_t len;
1966   FILE *f;
1967   nntp_data_t *nntp_data;
1968
1969   if (Context && Context->magic == M_NNTP)
1970     nntp_data = (nntp_data_t *)Context->data;
1971   else {
1972     if (!(CurrentNewsSrv = mutt_select_newsserver(NewsServer)) ||
1973         !CurrentNewsSrv->list)
1974     {
1975       mutt_error (_("Can't post article. No connection to news server."));
1976       return -1;
1977     }
1978     nntp_data = CurrentNewsSrv->list;
1979   }
1980
1981   if (!(f = safe_fopen (msg, "r"))) {
1982     mutt_error (_("Can't post article. Unable to open %s"), msg);
1983     return -1;
1984   }
1985
1986   m_strcpy(buf, sizeof(buf), "POST\r\n");
1987   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1988     mutt_error (_("Can't post article. Connection to %s lost."),
1989                 nntp_data->nserv->conn->account.host);
1990     return -1;
1991   }
1992   if (buf[0] != '3') {
1993     mutt_error (_("Can't post article: %s"), buf);
1994     return -1;
1995   }
1996
1997   buf[0] = '.';
1998   buf[1] = '\0';
1999   while (fgets (buf + 1, sizeof (buf) - 2, f) != NULL) {
2000     len = m_strlen(buf);
2001     if (buf[len - 1] == '\n') {
2002       buf[len - 1] = '\r';
2003       buf[len] = '\n';
2004       len++;
2005       buf[len] = '\0';
2006     }
2007     if (buf[1] == '.')
2008       mutt_socket_write(nntp_data->nserv->conn, buf);
2009     else
2010       mutt_socket_write(nntp_data->nserv->conn, buf + 1);
2011   }
2012   m_fclose(&f);
2013
2014   if (buf[m_strlen(buf) - 1] != '\n')
2015     mutt_socket_write(nntp_data->nserv->conn, "\r\n");
2016   mutt_socket_write(nntp_data->nserv->conn, ".\r\n");
2017   if (mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) {
2018     mutt_error (_("Can't post article. Connection to %s lost."),
2019                 nntp_data->nserv->conn->account.host);
2020     return -1;
2021   }
2022   if (buf[0] != '2') {
2023     mutt_error (_("Can't post article: %s"), buf);
2024     return -1;
2025   }
2026
2027   return 0;
2028 }
2029
2030 /* nntp_logout_all: close all open connections. */
2031 void nntp_logout_all (void)
2032 {
2033   char buf[LONG_STRING];
2034   CONNECTION *conn;
2035
2036   conn = mutt_socket_head ();
2037
2038   while (conn) {
2039     CONNECTION* next = conn->next;
2040     if (conn->account.type == M_ACCT_TYPE_NNTP) {
2041       mutt_message (_("Closing connection to %s..."), conn->account.host);
2042       mutt_socket_write (conn, "QUIT\r\n");
2043       mutt_socket_readln (buf, sizeof (buf), conn);
2044       mutt_clear_error ();
2045       mutt_socket_close (conn);
2046       mutt_socket_free (conn);
2047     }
2048     conn = next;
2049   }
2050 }
2051
2052 static void nntp_free_acache (nntp_data_t * data)
2053 {
2054   int i;
2055
2056   for (i = 0; i < NNTP_CACHE_LEN; i++) {
2057     if (data->acache[i].path) {
2058       unlink (data->acache[i].path);
2059       p_delete(&data->acache[i].path);
2060     }
2061   }
2062 }
2063
2064 void nntp_data_wipe(nntp_data_t *data)
2065 {
2066     p_delete(&data->entries);
2067     p_delete(&data->desc);
2068     p_delete(&data->cache);
2069     p_delete(&data->group);
2070     nntp_free_acache(data);
2071 }
2072
2073 static int nntp_sync_mailbox (CONTEXT * ctx, int unused1, int* unused2)
2074 {
2075   nntp_data_t *data = ctx->data;
2076
2077   nntp_save_cache_group(ctx);
2078   nntp_free_acache(data);
2079
2080   data->nserv->check_time = 0;  /* next nntp_check_mailbox() will really check */
2081   return 0;
2082 }
2083
2084 static void nntp_fastclose_mailbox (CONTEXT * ctx)
2085 {
2086   nntp_data_t *data = (nntp_data_t *) ctx->data, *tmp;
2087
2088   if (!data)
2089     return;
2090   nntp_free_acache (data);
2091   if (!data->nserv || !data->nserv->newsgroups || !data->group)
2092     return;
2093   nntp_save_cache_index (data->nserv);
2094   if ((tmp = hash_find (data->nserv->newsgroups, data->group)) == NULL
2095       || tmp != data)
2096     nntp_data_delete(&data);
2097   else
2098     nntp_sync_sidebar (data);
2099 }
2100
2101 /* commit changes and terminate connection */
2102 int nntp_close_mailbox (CONTEXT * ctx)
2103 {
2104   if (!ctx)
2105     return -1;
2106   mutt_message _("Quitting newsgroup...");
2107
2108   if (ctx->data) {
2109     nntp_data_t *data = (nntp_data_t *) ctx->data;
2110     int ret;
2111
2112     if (data->nserv && data->nserv->conn && ctx->unread) {
2113       ret = query_quadoption (OPT_CATCHUP, _("Mark all articles read?"));
2114       if (ret == M_YES)
2115         mutt_newsgroup_catchup (data->nserv, data->group);
2116       else if (ret < 0)
2117         return -1;
2118     }
2119   }
2120   nntp_sync_mailbox (ctx, 0, NULL);
2121   if (ctx->data && ((nntp_data_t *) ctx->data)->nserv) {
2122     nntp_server_t *news;
2123
2124     news = ((nntp_data_t *) ctx->data)->nserv;
2125     newsrc_gen_entries (ctx);
2126     ((nntp_data_t *) ctx->data)->unread = ctx->unread;
2127     mutt_newsrc_update (news);
2128   }
2129   mutt_clear_error ();
2130   return 0;
2131 }
2132
2133 /* use the GROUP command to poll for new mail */
2134 static int _nntp_check_mailbox (CONTEXT * ctx, nntp_data_t * nntp_data)
2135 {
2136   char buf[LONG_STRING];
2137   int count = 0;
2138
2139   if (nntp_data->nserv->check_time + NewsPollTimeout > time (NULL))
2140     return 0;
2141
2142   buf[0] = 0;
2143   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
2144     return -1;
2145   }
2146   if (m_strncmp("211", buf, 3)) {
2147     buf[0] = 0;
2148     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
2149       return -1;
2150     }
2151   }
2152   if (!m_strncmp("211", buf, 3)) {
2153     int first;
2154     int last;
2155
2156     sscanf (buf + 4, "%d %d %d", &count, &first, &last);
2157     nntp_data->firstMessage = first;
2158     nntp_data->lastMessage = last;
2159     if (ctx && last > nntp_data->lastLoaded) {
2160       nntp_fetch_headers (ctx, nntp_data->lastLoaded + 1, last);
2161       time (&nntp_data->nserv->check_time);
2162       return 1;
2163     }
2164     if (!last || (!nntp_data->rc && !nntp_data->lastCached))
2165       nntp_data->unread = count;
2166     else
2167       mutt_newsgroup_stat (nntp_data);
2168     /* active was renumbered? */
2169     if (last < nntp_data->lastLoaded) {
2170       if (!nntp_data->max) {
2171         nntp_data->entries = p_new(NEWSRC_ENTRY, 5);
2172         nntp_data->max = 5;
2173       }
2174       nntp_data->lastCached = 0;
2175       nntp_data->num = 1;
2176       nntp_data->entries[0].first = 1;
2177       nntp_data->entries[0].last = 0;
2178     }
2179     nntp_sync_sidebar (nntp_data);
2180   }
2181
2182   time (&nntp_data->nserv->check_time);
2183   return 0;
2184 }
2185
2186 static int nntp_check_mailbox (CONTEXT * ctx, int* unused1, int unused2)
2187 {
2188   return _nntp_check_mailbox (ctx, (nntp_data_t *) ctx->data);
2189 }
2190
2191 static int nntp_check_newgroups (nntp_server_t * serv, int force)
2192 {
2193   char buf[LONG_STRING];
2194   nntp_data_t nntp_data;
2195   nntp_data_t *l, **emp;
2196   time_t now;
2197   struct tm *t;
2198
2199   if (!serv || !serv->newgroups_time)
2200     return -1;
2201
2202   if (nntp_open_connection (serv) < 0)
2203     return -1;
2204
2205   /* check subscribed groups for new news */
2206   if (option (OPTSHOWNEWNEWS)) {
2207     mutt_message _("Checking for new messages...");
2208
2209     for (l = serv->list; l; l = l->next) {
2210       serv->check_time = 0;     /* really check! */
2211       if (l->subscribed)
2212         _nntp_check_mailbox (NULL, l);
2213     }
2214     sidebar_draw ();
2215   }
2216   else if (!force)
2217     return 0;
2218
2219   mutt_message _("Checking for new newsgroups...");
2220
2221   now = serv->newgroups_time;
2222   time (&serv->newgroups_time);
2223   t = gmtime (&now);
2224   snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2225             (t->tm_year % 100), t->tm_mon + 1, t->tm_mday, t->tm_hour,
2226             t->tm_min, t->tm_sec);
2227   nntp_data.nserv = serv;
2228   if (Context && Context->magic == M_NNTP)
2229     nntp_data.group = ((nntp_data_t *) Context->data)->group;
2230   else
2231     nntp_data.group = NULL;
2232
2233   emp = nntp_data_list_last(&serv->list);
2234   if (mutt_nntp_fetch (&nntp_data, buf, _("Adding new newsgroups..."), NULL,
2235                        add_group, serv, 0) != 0) {
2236     return -1;
2237   }
2238
2239   mutt_message _("Loading descriptions...");
2240
2241   for (l = *emp; l; l = l->next) {
2242     l->new = 1;
2243     nntp_get_desc(l, l->group, NULL, NULL);
2244   }
2245   if (*emp)
2246     nntp_save_cache_index(serv);
2247   mutt_clear_error();
2248   return nntp.checked;
2249 }
2250
2251 /* Load list of all newsgroups from active */
2252 int nntp_get_active (nntp_server_t * serv)
2253 {
2254   char msg[STRING];
2255   nntp_data_t nntp_data, **tmp = &serv->list;
2256
2257   if (nntp_open_connection (serv) < 0)
2258     return -1;
2259
2260   snprintf (msg, sizeof (msg),
2261             _("Loading list of all newsgroups on server %s..."),
2262             serv->conn->account.host);
2263   mutt_message (msg);
2264   time (&serv->newgroups_time);
2265   nntp_data.nserv = serv;
2266   nntp_data.group = NULL;
2267
2268   if (mutt_nntp_fetch (&nntp_data, "LIST\r\n", msg, NULL, add_group, serv, 0) < 0) {
2269     return -1;
2270   }
2271
2272   m_strcpy(msg, sizeof(msg), _("Loading descriptions..."));
2273   mutt_message (msg);
2274   nntp_get_desc (&nntp_data, "*", msg, NULL);
2275
2276   while (*tmp) {
2277     if ((*tmp)->deleted && !(*tmp)->rc) {
2278       nntp_data_t *d = nntp_data_list_pop(tmp);
2279       nntp_delete_cache(d);
2280       hash_remove(serv->newsgroups, d->group, NULL, NULL);
2281       nntp_data_delete(&d);
2282     } else {
2283       tmp = &(*tmp)->next;
2284     }
2285   }
2286   serv->tail = tmp;
2287   nntp_save_cache_index(serv);
2288
2289   mutt_clear_error ();
2290   return nntp.checked;
2291 }
2292
2293 /*
2294  * returns -1 if error ocurred while retrieving header,
2295  * number of articles which ones exist in context on success.
2296  */
2297 int nntp_check_msgid (CONTEXT * ctx, const char *msgid)
2298 {
2299   int ret;
2300
2301   /* if msgid is already in context, don't reload them */
2302   if (hash_find (ctx->id_hash, msgid))
2303     return 1;
2304   if (ctx->msgcount == ctx->hdrmax)
2305     mx_alloc_memory (ctx);
2306   ctx->hdrs[ctx->msgcount] = header_new();
2307   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
2308
2309   mutt_message (_("Fetching %s from server..."), msgid);
2310   ret = nntp_read_header (ctx, msgid, 0);
2311   /* since nntp_read_header() may set read flag, we must reset it */
2312   ctx->hdrs[ctx->msgcount]->read = 0;
2313   if (ret != 0)
2314     header_delete(&ctx->hdrs[ctx->msgcount]);
2315   else {
2316     ctx->msgcount++;
2317     mx_update_context (ctx, 1);
2318     ctx->changed = 1;
2319   }
2320   return ret;
2321 }
2322
2323 typedef struct {
2324   CONTEXT *ctx;
2325   int num;
2326   int max;
2327   int *child;
2328 } CHILD_CONTEXT;
2329
2330 static int check_children (char *s, void *c)
2331 {
2332   CHILD_CONTEXT *cc = c;
2333   int i, n;
2334
2335   if (!s || (n = atoi (s)) == 0)
2336     return 0;
2337   for (i = 0; i < cc->ctx->msgcount; i++)
2338     if (cc->ctx->hdrs[i]->article_num == n)
2339       return 0;
2340   if (cc->num >= cc->max)
2341     p_realloc(&cc->child, cc->max += 25);
2342   cc->child[cc->num++] = n;
2343
2344   return 0;
2345 }
2346
2347 int nntp_check_children (CONTEXT * ctx, const char *msgid)
2348 {
2349   nntp_data_t *nntp_data = (nntp_data_t *) ctx->data;
2350   char buf[STRING];
2351   int i, ret = 0, tmp = 0;
2352   CHILD_CONTEXT cc = { ctx, 0, 0, NULL };
2353
2354   if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->conn ||
2355       !nntp_data->nserv->conn->account.host)
2356     return -1;
2357   if (nntp_data->firstMessage > nntp_data->lastLoaded)
2358     return 0;
2359   if (!nntp_data->nserv->hasXPAT) {
2360     mutt_error (_("Server %s does not support this operation!"),
2361                 nntp_data->nserv->conn->account.host);
2362     return -1;
2363   }
2364
2365   snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n",
2366             nntp_data->firstMessage, nntp_data->lastLoaded, msgid);
2367
2368   if (mutt_nntp_fetch (nntp_data, buf, NULL, NULL, check_children, &cc, 0)) {
2369     p_delete(&cc.child);
2370     return -1;
2371   }
2372
2373   /* dont try to read the xover cache. check_children() already
2374    * made sure that we dont have the article, so we need to visit
2375    * the server. Reading the cache at this point is also bad
2376    * because it would duplicate messages */
2377   if (nntp.use_cache) {
2378     tmp++;
2379     nntp.use_cache = false;
2380   }
2381   for (i = 0; i < cc.num; i++) {
2382     if ((ret = nntp_fetch_headers (ctx, cc.child[i], cc.child[i])))
2383       break;
2384     if (ctx->msgcount &&
2385         ctx->hdrs[ctx->msgcount - 1]->article_num == cc.child[i])
2386       ctx->hdrs[ctx->msgcount - 1]->read = 0;
2387   }
2388   if (tmp)
2389     nntp.use_cache = true;
2390   p_delete(&cc.child);
2391   return ret;
2392 }
2393
2394 static int nntp_is_magic (const char* path, struct stat* st) {
2395   url_scheme_t s = url_check_scheme(NONULL(path));
2396   return s == U_NNTP || s == U_NNTPS ? M_NNTP : -1;
2397 }
2398
2399 static int acl_check_nntp (CONTEXT* ctx, int bit) {
2400   switch (bit) {
2401     case ACL_INSERT:    /* editing messages */
2402     case ACL_WRITE:     /* change importance */
2403       return (0);
2404     case ACL_DELETE:    /* (un)deletion */
2405     case ACL_SEEN:      /* mark as read */
2406       return (1);
2407     default:
2408       return (0);
2409   }
2410 }
2411
2412 mx_t const nntp_mx = {
2413     M_NNTP,
2414     0,
2415     nntp_is_magic,
2416     NULL,
2417     NULL,
2418     nntp_open_mailbox,
2419     NULL,
2420     acl_check_nntp,
2421     nntp_check_mailbox,
2422     nntp_fastclose_mailbox,
2423     nntp_sync_mailbox,
2424     NULL,
2425 };
2426
2427 /* }}} */