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