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