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