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