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