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