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