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