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