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