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