Rocco Rutte:
[apps/madmutt.git] / nntp / newsrc.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-2001 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 #ifdef HAVE_CONFIG_H
13 #include <config.h>
14 #endif
15
16 #include "mutt.h"
17 #include "mutt_curses.h"
18 #include "sort.h"
19 #include "mx.h"
20 #include "mime.h"
21 #include "nntp.h"
22 #include "rfc822.h"
23 #include "rfc1524.h"
24 #include "rfc2047.h"
25
26 #include "lib/mem.h"
27 #include "lib/str.h"
28 #include "lib/intl.h"
29 #include "lib/debug.h"
30
31 #include <unistd.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <sys/stat.h>
36
37 void nntp_add_to_list (NNTP_SERVER * s, NNTP_DATA * d)
38 {
39   LIST *l;
40
41   if (!s || !d)
42     return;
43
44   l = mem_calloc (1, sizeof (LIST));
45   if (s->list)
46     s->tail->next = l;
47   else
48     s->list = l;
49   s->tail = l;
50   l->data = (void *) d;
51 }
52
53 static int nntp_parse_newsrc_line (NNTP_SERVER * news, char *line)
54 {
55   NNTP_DATA *data;
56   char group[LONG_STRING];
57   int x = 1;
58   char *p = line, *b, *h;
59   size_t len;
60
61   while (*p) {
62     if (*p++ == ',')
63       x++;
64   }
65
66   p = line;
67   while (*p && (*p != ':' && *p != '!'))
68     p++;
69   if (!*p)
70     return -1;
71   len = p + 1 - line;
72   if (len > sizeof (group))
73     len = sizeof (group);
74   strfcpy (group, line, len);
75   if ((data = (NNTP_DATA *) hash_find (news->newsgroups, group)) == NULL) {
76     data =
77       (NNTP_DATA *) mem_calloc (1, sizeof (NNTP_DATA) + str_len (group) + 1);
78     data->group = (char *) data + sizeof (NNTP_DATA);
79     strcpy (data->group, group);
80     data->nserv = news;
81     data->deleted = 1;
82     if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
83       news->newsgroups =
84         hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
85     hash_insert (news->newsgroups, data->group, data, 0);
86     nntp_add_to_list (news, data);
87   }
88   else
89     mem_free ((void **) &data->entries);
90
91   data->rc = 1;
92   data->entries = mem_calloc (x * 2, sizeof (NEWSRC_ENTRY));
93   data->max = x * 2;
94
95   if (*p == ':')
96     data->subscribed = 1;
97   else
98     data->subscribed = 0;
99
100   p++;
101   b = p;
102   x = 0;
103   while (*b) {
104     while (*p && *p != ',' && *p != '\n')
105       p++;
106     if (*p) {
107       *p = '\0';
108       p++;
109     }
110     if ((h = strchr (b, '-'))) {
111       *h = '\0';
112       h++;
113       data->entries[x].first = atoi (b);
114       data->entries[x].last = atoi (h);
115     }
116     else {
117       data->entries[x].first = atoi (b);
118       data->entries[x].last = data->entries[x].first;
119     }
120     b = p;
121     if (data->entries[x].last != 0)
122       x++;
123   }
124   if (x && !data->lastMessage)
125     data->lastMessage = data->entries[x - 1].last;
126   data->num = x;
127   mutt_newsgroup_stat (data);
128   debug_print (2, ("Newsgroup %s\n", data->group));
129
130   return 0;
131 }
132
133 static int slurp_newsrc (NNTP_SERVER * news)
134 {
135   FILE *fp;
136   char *buf;
137   struct stat sb;
138
139   news->stat = stat (news->newsrc, &sb);
140   news->size = sb.st_size;
141   news->mtime = sb.st_mtime;
142
143   if ((fp = safe_fopen (news->newsrc, "r")) == NULL)
144     return -1;
145   /* hmm, should we use dotlock? */
146   if (mx_lock_file (news->newsrc, fileno (fp), 0, 0, 1)) {
147     fclose (fp);
148     return -1;
149   }
150
151   buf = mem_malloc (sb.st_size + 1);
152   while (fgets (buf, sb.st_size + 1, fp))
153     nntp_parse_newsrc_line (news, buf);
154   mem_free (&buf);
155
156   mx_unlock_file (news->newsrc, fileno (fp), 0);
157   fclose (fp);
158   return 0;
159 }
160
161 void nntp_cache_expand (char *dst, const char *src)
162 {
163   snprintf (dst, _POSIX_PATH_MAX, "%s/%s", NewsCacheDir, src);
164   mutt_expand_path (dst, _POSIX_PATH_MAX);
165 }
166
167 /* Loads $news_cache_dir/.index into memory, loads newsserver data
168  * and newsgroup cache names */
169 static int nntp_parse_cacheindex (NNTP_SERVER * news)
170 {
171   struct stat st;
172   char buf[HUGE_STRING], *cp;
173   char dir[_POSIX_PATH_MAX], file[_POSIX_PATH_MAX];
174   FILE *index;
175   NNTP_DATA *data;
176   int l, m, t;
177
178   /* check is server name defined or not */
179   if (!news || !news->conn || !news->conn->account.host)
180     return -1;
181   unset_option (OPTNEWSCACHE);
182   if (!NewsCacheDir || !*NewsCacheDir)
183     return 0;
184
185   strfcpy (dir, NewsCacheDir, sizeof (dir));
186   mutt_expand_path (dir, sizeof (dir));
187
188   if (lstat (dir, &st) || (st.st_mode & S_IFDIR) == 0) {
189     snprintf (buf, sizeof (buf), _("Directory %s not exist. Create it?"),
190               dir);
191     if (mutt_yesorno (buf, M_YES) != M_YES
192         || mkdir (dir, (S_IRWXU + S_IRWXG + S_IRWXO))) {
193       mutt_error _("Cache directory not created!");
194
195       return -1;
196     }
197     mutt_clear_error ();
198   }
199
200   set_option (OPTNEWSCACHE);
201
202   mem_free (&news->cache);
203   snprintf (buf, sizeof (buf), "%s/.index", dir);
204   if (!(index = safe_fopen (buf, "a+")))
205     return 0;
206   rewind (index);
207   while (fgets (buf, sizeof (buf), index)) {
208     buf[str_len (buf) - 1] = 0;  /* strip ending '\n' */
209     if (!str_ncmp (buf, "#: ", 3) &&
210         !str_casecmp (buf + 3, news->conn->account.host))
211       break;
212   }
213   while (fgets (buf, sizeof (buf), index)) {
214     cp = buf;
215     while (*cp && *cp != ' ')
216       cp++;
217     if (!*cp)
218       continue;
219     cp[0] = 0;
220     if (!str_cmp (buf, "#:"))
221       break;
222     sscanf (cp + 1, "%s %d %d", file, &l, &m);
223     if (!str_cmp (buf, "ALL")) {
224       news->cache = str_dup (file);
225       news->newgroups_time = m;
226     }
227     else if (news->newsgroups) {
228       if ((data = (NNTP_DATA *) hash_find (news->newsgroups, buf)) == NULL) {
229         data =
230           (NNTP_DATA *) mem_calloc (1,
231                                      sizeof (NNTP_DATA) + str_len (buf) + 1);
232         data->group = (char *) data + sizeof (NNTP_DATA);
233         strcpy (data->group, buf);
234         data->nserv = news;
235         data->deleted = 1;
236         if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
237           news->newsgroups =
238             hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
239         hash_insert (news->newsgroups, data->group, data, 0);
240         nntp_add_to_list (news, data);
241       }
242       data->cache = str_dup (file);
243       t = 0;
244       if (!data->firstMessage || data->lastMessage < m)
245         t = 1;
246       if (!data->firstMessage)
247         data->firstMessage = l;
248       if (data->lastMessage < m)
249         data->lastMessage = m;
250       data->lastCached = m;
251       if (t || !data->unread)
252         mutt_newsgroup_stat (data);
253     }
254   }
255   fclose (index);
256   return 0;
257 }
258
259 const char *nntp_format_str (char *dest, size_t destlen, char op,
260                              const char *src, const char *fmt,
261                              const char *ifstring, const char *elsestring,
262                              unsigned long data, format_flag flags)
263 {
264   char fn[SHORT_STRING], tmp[SHORT_STRING];
265
266   switch (op) {
267   case 's':
268     strncpy (fn, NewsServer, sizeof (fn) - 1);
269     str_tolower (fn);
270     snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
271     snprintf (dest, destlen, tmp, fn);
272     break;
273   }
274   return (src);
275 }
276
277 /* nntp_parse_url: given an NNPT URL, return host, port,
278  * username, password and newsgroup will recognise. */
279 int nntp_parse_url (const char *server, ACCOUNT * acct,
280                     char *group, size_t group_len)
281 {
282   ciss_url_t url;
283   char *c;
284   int ret = -1;
285
286   /* Defaults */
287   acct->flags = 0;
288   acct->port = NNTP_PORT;
289   acct->type = M_ACCT_TYPE_NNTP;
290
291   c = str_dup (server);
292   url_parse_ciss (&url, c);
293
294   if (url.scheme == U_NNTP || url.scheme == U_NNTPS) {
295     if (url.scheme == U_NNTPS) {
296       acct->flags |= M_ACCT_SSL;
297       acct->port = NNTP_SSL_PORT;
298     }
299
300     *group = '\0';
301     if (url.path)
302       strfcpy (group, url.path, group_len);
303
304     ret = mutt_account_fromurl (acct, &url);
305   }
306
307   mem_free (&c);
308   return ret;
309 }
310
311 void nntp_expand_path (char *line, size_t len, ACCOUNT * acct)
312 {
313   ciss_url_t url;
314
315   url.path = str_dup (line);
316   mutt_account_tourl (acct, &url);
317   url_ciss_tostring (&url, line, len, 0);
318   mem_free (&url.path);
319 }
320
321 /*
322  * Automatically loads a newsrc into memory, if necessary.
323  * Checks the size/mtime of a newsrc file, if it doesn't match, load
324  * again.  Hmm, if a system has broken mtimes, this might mean the file
325  * is reloaded every time, which we'd have to fix.
326  *
327  * a newsrc file is a line per newsgroup, with the newsgroup, then a 
328  * ':' denoting subscribed or '!' denoting unsubscribed, then a 
329  * comma separated list of article numbers and ranges.
330  */
331 NNTP_SERVER *mutt_select_newsserver (char *server)
332 {
333   char file[_POSIX_PATH_MAX];
334   char *buf, *p;
335   LIST *list;
336   ACCOUNT acct;
337   NNTP_SERVER *serv;
338   CONNECTION *conn;
339
340   memset (&acct, 0, sizeof (ACCOUNT));
341
342   if (!server || !*server) {
343     mutt_error _("No newsserver defined!");
344
345     return NULL;
346   }
347
348   buf = p = mem_calloc (str_len (server) + 10, sizeof (char));
349   if (url_check_scheme (server) == U_UNKNOWN) {
350     strcpy (buf, "nntp://");
351     p = strchr (buf, '\0');
352   }
353   strcpy (p, server);
354
355   if ((nntp_parse_url (buf, &acct, file, sizeof (file))) < 0 || *file) {
356     mem_free (&buf);
357     mutt_error (_("%s is an invalid newsserver specification!"), server);
358     return NULL;
359   }
360   mem_free (&buf);
361
362   conn = mutt_conn_find (NULL, &acct);
363   if (!conn)
364     return NULL;
365
366   mutt_FormatString (file, sizeof (file), NONULL (NewsRc), nntp_format_str, 0,
367                      0);
368   mutt_expand_path (file, sizeof (file));
369
370   serv = (NNTP_SERVER *) conn->data;
371   if (serv) {
372     struct stat sb;
373
374     /* externally modified? */
375     if (serv->stat != stat (file, &sb) || (!serv->stat &&
376                                            (serv->size != sb.st_size
377                                             || serv->mtime != sb.st_mtime))) {
378       for (list = serv->list; list; list = list->next) {
379         NNTP_DATA *data = (NNTP_DATA *) list->data;
380
381         if (data) {
382           data->subscribed = 0;
383           data->rc = 0;
384           data->num = 0;
385         }
386       }
387       slurp_newsrc (serv);
388       nntp_clear_cacheindex (serv);
389     }
390
391     if (serv->status == NNTP_BYE)
392       serv->status = NNTP_NONE;
393     nntp_check_newgroups (serv, 0);
394     return serv;
395   }
396
397   /* New newsserver */
398   serv = mem_calloc (1, sizeof (NNTP_SERVER));
399   serv->conn = conn;
400   serv->newsrc = str_dup (file);
401   serv->newsgroups = hash_create (1009);
402   slurp_newsrc (serv);          /* load .newsrc */
403   nntp_parse_cacheindex (serv); /* load .index */
404   if (option (OPTNEWSCACHE) && serv->cache && nntp_get_cache_all (serv) >= 0)
405     nntp_check_newgroups (serv, 1);
406   else if (nntp_get_active (serv) < 0) {
407     hash_destroy (&serv->newsgroups, nntp_delete_data);
408     for (list = serv->list; list; list = list->next)
409       list->data = NULL;
410     mutt_free_list (&serv->list);
411     mem_free (&serv->newsrc);
412     mem_free (&serv->cache);
413     mem_free (&serv);
414     return NULL;
415   }
416   nntp_clear_cacheindex (serv);
417   conn->data = (void *) serv;
418
419   return serv;
420 }
421
422 /* 
423  * full status flags are not supported by nntp, but we can fake some
424  * of them.  This is how:
425  * Read = a read message number is in the .newsrc
426  * New = a message is new since we last read this newsgroup
427  * Old = anything else
428  * So, Read is marked as such in the newsrc, old is anything that is 
429  * "skipped" in the newsrc, and new is anything not in the newsrc nor
430  * in the cache. By skipped, I mean before the last unread message
431  */
432 void nntp_get_status (CONTEXT * ctx, HEADER * h, char *group, int article)
433 {
434   NNTP_DATA *data = (NNTP_DATA *) ctx->data;
435   int x;
436
437   if (group)
438     data = (NNTP_DATA *) hash_find (data->nserv->newsgroups, group);
439
440   if (!data) {
441 #ifdef DEBUG
442     if (group)
443       debug_print (3, ("newsgroup %s not found\n", group));
444 #endif
445     return;
446   }
447
448   for (x = 0; x < data->num; x++) {
449     if ((article >= data->entries[x].first) &&
450         (article <= data->entries[x].last)) {
451       /* we cannot use mutt_set_flag() because mx_update_context()
452          didn't called yet */
453       h->read = 1;
454       return;
455     }
456   }
457   /* If article was not cached yet, it is new! :) */
458   if (!data->cache || article > data->lastCached)
459     return;
460   /* Old articles are articles which aren't read but an article after them
461    * has been cached */
462   if (option (OPTMARKOLD))
463     h->old = 1;
464 }
465
466 void mutt_newsgroup_stat (NNTP_DATA * data)
467 {
468   int i;
469   unsigned int first, last;
470
471   data->unread = 0;
472   if (data->lastMessage == 0 || data->firstMessage > data->lastMessage)
473     return;
474
475   data->unread = data->lastMessage - data->firstMessage + 1;
476   for (i = 0; i < data->num; i++) {
477     first = data->entries[i].first;
478     if (first < data->firstMessage)
479       first = data->firstMessage;
480     last = data->entries[i].last;
481     if (last > data->lastMessage)
482       last = data->lastMessage;
483     if (first <= last)
484       data->unread -= last - first + 1;
485   }
486 }
487
488 static int puti (char *line, int num)
489 {
490   char *p, s[32];
491
492   for (p = s; num;) {
493     *p++ = '0' + num % 10;
494     num /= 10;
495   }
496   while (p > s)
497     *line++ = *--p, num++;
498   *line = '\0';
499   return num;
500 }
501
502 static void nntp_create_newsrc_line (NNTP_DATA * data, char **buf,
503                                      char **pline, size_t * buflen)
504 {
505   char *line = *pline;
506   size_t len = *buflen - (*pline - *buf);
507   int x, i;
508
509   if (len < LONG_STRING * 10) {
510     len += *buflen;
511     *buflen *= 2;
512     line = *buf;
513     mem_realloc (buf, *buflen);
514     line = *buf + (*pline - line);
515   }
516   strcpy (line, data->group);
517   len -= str_len (line) + 1;
518   line += str_len (line);
519   *line++ = data->subscribed ? ':' : '!';
520   *line++ = ' ';
521   *line = '\0';
522
523   for (x = 0; x < data->num; x++) {
524     if (len < LONG_STRING) {
525       len += *buflen;
526       *buflen *= 2;
527       *pline = line;
528       line = *buf;
529       mem_realloc (buf, *buflen);
530       line = *buf + (*pline - line);
531     }
532     if (x) {
533       *line++ = ',';
534       len--;
535     }
536
537 #if 0
538     if (data->entries[x].first == data->entries[x].last)
539       snprintf (line, len, "%d%n", data->entries[x].first, &i);
540     else
541       snprintf (line, len, "%d-%d%n",
542                 data->entries[x].first, data->entries[x].last, &i);
543     len -= i;
544     line += i;
545 #else
546     i = puti (line, data->entries[x].first);
547     line += i;
548     len -= i;
549     if (data->entries[x].first != data->entries[x].last) {
550       *line++ = '-';
551       len--;
552       i = puti (line, data->entries[x].last);
553       line += i;
554       len -= i;
555     }
556 #endif
557   }
558   *line++ = '\n';
559   *line = '\0';
560   *pline = line;
561 }
562
563 void newsrc_gen_entries (CONTEXT * ctx)
564 {
565   NNTP_DATA *data = (NNTP_DATA *) ctx->data;
566   int series, x;
567   unsigned int last = 0, first = 1;
568   int save_sort = SORT_ORDER;
569
570   if (Sort != SORT_ORDER) {
571     save_sort = Sort;
572     Sort = SORT_ORDER;
573     mutt_sort_headers (ctx, 0);
574   }
575
576   if (!data->max) {
577     data->entries = mem_calloc (5, sizeof (NEWSRC_ENTRY));
578     data->max = 5;
579   }
580
581   /*
582    * Set up to fake initial sequence from 1 to the article before the 
583    * first article in our list
584    */
585   data->num = 0;
586   series = 1;
587
588   for (x = 0; x < ctx->msgcount; x++) {
589     if (series) {               /* search for first unread */
590       /*
591        * We don't actually check sequential order, since we mark 
592        * "missing" entries as read/deleted
593        */
594       last = ctx->hdrs[x]->article_num;
595       if (last >= data->firstMessage && !ctx->hdrs[x]->deleted &&
596           !ctx->hdrs[x]->read) {
597         if (data->num >= data->max) {
598           data->max = data->max * 2;
599           mem_realloc (&data->entries, data->max * sizeof (NEWSRC_ENTRY));
600         }
601         data->entries[data->num].first = first;
602         data->entries[data->num].last = last - 1;
603         data->num++;
604         series = 0;
605       }
606     }
607     else {                      /* search for first read */
608
609       if (ctx->hdrs[x]->deleted || ctx->hdrs[x]->read) {
610         first = last + 1;
611         series = 1;
612       }
613       last = ctx->hdrs[x]->article_num;
614     }
615   }
616   if (series && first <= data->lastLoaded) {
617     if (data->num >= data->max) {
618       data->max = data->max * 2;
619       mem_realloc (&data->entries, data->max * sizeof (NEWSRC_ENTRY));
620     }
621     data->entries[data->num].first = first;
622     data->entries[data->num].last = data->lastLoaded;
623     data->num++;
624   }
625
626   if (save_sort != Sort) {
627     Sort = save_sort;
628     mutt_sort_headers (ctx, 0);
629   }
630 }
631
632 static int mutt_update_list_file (char *filename, char *section,
633                                   char *key, char *line) {
634   FILE *ifp;
635   FILE *ofp;
636   char buf[HUGE_STRING];
637   char tmpfile[_POSIX_PATH_MAX];
638   char *c;
639   int ext = 0, done = 0, r = 0;
640
641   /* if file not exist, create it */
642   if ((ifp = safe_fopen (filename, "a")))
643     fclose (ifp);
644   debug_print (1, ("Opening %s\n", filename));
645   if (!(ifp = safe_fopen (filename, "r"))) {
646     mutt_error (_("Unable to open %s for reading"), filename);
647     return -1;
648   }
649   if (mx_lock_file (filename, fileno (ifp), 0, 0, 1)) {
650     fclose (ifp);
651     mutt_error (_("Unable to lock %s"), filename);
652     return -1;
653   }
654   snprintf (tmpfile, sizeof(tmpfile), "%s.tmp", filename);
655   debug_print (1, ("Opening %s\n", tmpfile));
656   if (!(ofp = fopen (tmpfile, "w"))) {
657     fclose (ifp);
658     mutt_error (_("Unable to open %s for writing"), tmpfile);
659     return -1;
660   }
661
662   if (section) {
663     while (r != EOF && !done && fgets (buf, sizeof (buf), ifp)) {
664       r = fputs (buf, ofp);
665       c = buf;
666       while (*c && *c != '\n') c++;
667       c[0] = 0; /* strip EOL */
668       if (!strncmp (buf, "#: ", 3) && !str_casecmp (buf+3, section))
669         done++;
670     }
671     if (r != EOF && !done) {
672       snprintf (buf, sizeof(buf), "#: %s\n", section);
673       r = fputs (buf, ofp);
674     }
675     done = 0;
676   }
677
678   while (r != EOF && fgets (buf, sizeof (buf), ifp)) {
679     if (ext) {
680       c = buf;
681       while (*c && (*c != '\r') && (*c != '\n')) c++;
682       c--;
683       if (*c != '\\') ext = 0;
684     } else if ((section && !strncmp (buf, "#: ", 3))) {
685       if (!done && line) {
686         fputs (line, ofp);
687         fputc ('\n', ofp);
688       }
689       r = fputs (buf, ofp);
690       done++;
691       break;
692     } else if (key && !strncmp (buf, key, strlen(key)) &&
693                (!*key || buf[strlen(key)] == ' ')) {
694       c = buf;
695       ext = 0;
696       while (*c && (*c != '\r') && (*c != '\n')) c++;
697       c--;
698       if (*c == '\\') ext = 1;
699       if (!done && line) {
700         r = fputs (line, ofp);
701         if (*key)
702           r = fputc ('\n', ofp);
703         done++;
704       }
705     } else {
706       r = fputs (buf, ofp);
707     }
708   }
709
710   while (r != EOF && fgets (buf, sizeof (buf), ifp))
711     r = fputs (buf, ofp);
712
713   /* If there wasn't a line to replace, put it on the end of the file */
714   if (r != EOF && !done && line) {
715     fputs (line, ofp);
716     r = fputc ('\n', ofp);
717   }
718   mx_unlock_file (filename, fileno (ifp), 0);
719   fclose (ofp);
720   fclose (ifp);
721   if (r == EOF) {
722     unlink (tmpfile);
723     mutt_error (_("Can't write %s"), tmpfile);
724     return -1;
725   }
726   if (rename (tmpfile, filename) < 0) {
727     unlink (tmpfile);
728     mutt_error (_("Can't rename %s to %s"), tmpfile, filename);
729     return -1;
730   }
731   return 0;
732 }
733
734 int mutt_newsrc_update (NNTP_SERVER * news)
735 {
736   char *buf, *line;
737   NNTP_DATA *data;
738   LIST *tmp;
739   int r = -1;
740   size_t len, llen;
741
742   if (!news)
743     return -1;
744   llen = len = 10 * LONG_STRING;
745   line = buf = mem_calloc (1, len);
746   /* we will generate full newsrc here */
747   for (tmp = news->list; tmp; tmp = tmp->next) {
748     data = (NNTP_DATA *) tmp->data;
749     if (!data || !data->rc)
750       continue;
751     nntp_create_newsrc_line (data, &buf, &line, &llen);
752     debug_print (2, ("Added to newsrc: %s\n", line));
753     line += str_len (line);
754   }
755   /* newrc being fully rewritten */
756   if (news->newsrc &&
757       (r = mutt_update_list_file (news->newsrc, NULL, "", buf)) == 0) {
758     struct stat st;
759
760     stat (news->newsrc, &st);
761     news->size = st.st_size;
762     news->mtime = st.st_mtime;
763   }
764   mem_free (&buf);
765   return r;
766 }
767
768 static FILE *mutt_mkname (char *s)
769 {
770   char buf[_POSIX_PATH_MAX], *pc;
771   int fd;
772   FILE *fp;
773
774   nntp_cache_expand (buf, s);
775   if ((fp = safe_fopen (buf, "w")))
776     return fp;
777
778   nntp_cache_expand (buf, "cache-XXXXXX");
779   pc = buf + str_len (buf) - 12; /* positioning to "cache-XXXXXX" */
780   if ((fd = mkstemp (buf)) == -1)
781     return NULL;
782   strcpy (s, pc);               /* generated name */
783   return fdopen (fd, "w");
784 }
785
786 /* Updates info into .index file: ALL or about selected newsgroup */
787 static int nntp_update_cacheindex (NNTP_SERVER * serv, NNTP_DATA * data)
788 {
789   char buf[LONG_STRING], *key = "ALL";
790   char file[_POSIX_PATH_MAX];
791
792   if (!serv || !serv->conn || !serv->conn->account.host)
793     return -1;
794
795   if (data && data->group) {
796     key = data->group;
797     snprintf (buf, sizeof (buf), "%s %s %d %d", key, data->cache,
798               data->firstMessage, data->lastLoaded);
799   }
800   else {
801     strfcpy (file, serv->cache, sizeof (file));
802     snprintf (buf, sizeof (buf), "ALL %s 0 %d", file,
803               (int) serv->newgroups_time);
804   }
805   nntp_cache_expand (file, ".index");
806   return mutt_update_list_file (file, serv->conn->account.host, key, buf);
807 }
808
809 /* Remove cache files of unsubscribed newsgroups */
810 void nntp_clear_cacheindex (NNTP_SERVER * news)
811 {
812   NNTP_DATA *data;
813   LIST *tmp;
814
815   if (option (OPTSAVEUNSUB) || !news)
816     return;
817
818   for (tmp = news->list; tmp; tmp = tmp->next) {
819     data = (NNTP_DATA *) tmp->data;
820     if (!data || data->subscribed || !data->cache)
821       continue;
822     nntp_delete_cache (data);
823     debug_print (2, ("Removed from .index: %s\n", data->group));
824   }
825   return;
826 }
827
828 int nntp_save_cache_index (NNTP_SERVER * news)
829 {
830   char buf[HUGE_STRING];
831   char file[_POSIX_PATH_MAX];
832   NNTP_DATA *d;
833   FILE *f;
834   LIST *l;
835
836   if (!news || !news->newsgroups)
837     return -1;
838   if (!option (OPTNEWSCACHE))
839     return 0;
840
841   if (news->cache) {
842     nntp_cache_expand (file, news->cache);
843     unlink (file);
844     f = safe_fopen (file, "w");
845   }
846   else {
847     strfcpy (buf, news->conn->account.host, sizeof (buf));
848     f = mutt_mkname (buf);
849     news->cache = str_dup (buf);
850     nntp_cache_expand (file, buf);
851   }
852   if (!f)
853     return -1;
854
855   for (l = news->list; l; l = l->next) {
856     if ((d = (NNTP_DATA *) l->data) && !d->deleted) {
857       if (d->desc)
858         snprintf (buf, sizeof (buf), "%s %d %d %c %s\n", d->group,
859                   d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n',
860                   d->desc);
861       else
862         snprintf (buf, sizeof (buf), "%s %d %d %c\n", d->group,
863                   d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n');
864       if (fputs (buf, f) == EOF) {
865         fclose (f);
866         unlink (file);
867         return -1;
868       }
869     }
870   }
871   fclose (f);
872
873   if (nntp_update_cacheindex (news, NULL)) {
874     unlink (file);
875     return -1;
876   }
877   return 0;
878 }
879
880 int nntp_save_cache_group (CONTEXT * ctx)
881 {
882   char buf[HUGE_STRING], addr[STRING];
883   char file[_POSIX_PATH_MAX];
884   FILE *f;
885   HEADER *h;
886   struct tm *tm;
887   int i = 0, save = SORT_ORDER;
888   int prev = 0;
889
890   if (!option (OPTNEWSCACHE))
891     return 0;
892   if (!ctx || !ctx->data || ctx->magic != M_NNTP)
893     return -1;
894
895   if (((NNTP_DATA *) ctx->data)->cache) {
896     nntp_cache_expand (file, ((NNTP_DATA *) ctx->data)->cache);
897     unlink (file);
898     f = safe_fopen (file, "w");
899   }
900   else {
901     snprintf (buf, sizeof (buf), "%s-%s",
902               ((NNTP_DATA *) ctx->data)->nserv->conn->account.host,
903               ((NNTP_DATA *) ctx->data)->group);
904     f = mutt_mkname (buf);
905     ((NNTP_DATA *) ctx->data)->cache = str_dup (buf);
906     nntp_cache_expand (file, buf);
907   }
908   if (!f)
909     return -1;
910
911   if (Sort != SORT_ORDER) {
912     save = Sort;
913     Sort = SORT_ORDER;
914     mutt_sort_headers (ctx, 0);
915   }
916
917   /* Save only $nntp_context messages... */
918   ((NNTP_DATA *) ctx->data)->lastCached = 0;
919   if (NntpContext && ctx->msgcount > NntpContext)
920     i = ctx->msgcount - NntpContext;
921   for (; i < ctx->msgcount; i++) {
922     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->article_num != prev) {
923       h = ctx->hdrs[i];
924       addr[0] = 0;
925       rfc822_write_address (addr, sizeof (addr), h->env->from, 0);
926       tm = gmtime (&h->date_sent);
927       snprintf (buf, sizeof (buf),
928                 "%d\t%s\t%s\t%d %s %d %02d:%02d:%02d GMT\t%s\t",
929                 h->article_num, h->env->subject, addr, tm->tm_mday,
930                 Months[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour,
931                 tm->tm_min, tm->tm_sec, h->env->message_id);
932       fputs (buf, f);
933       if (h->env->references)
934         mutt_write_references (h->env->references, f);
935       snprintf (buf, sizeof (buf), "\t%ld\t%d\tXref: %s\n",
936                 h->content->length, h->lines, NONULL (h->env->xref));
937       if (fputs (buf, f) == EOF) {
938         fclose (f);
939         unlink (file);
940         return -1;
941       }
942     }
943     prev = ctx->hdrs[i]->article_num;
944   }
945
946   if (save != Sort) {
947     Sort = save;
948     mutt_sort_headers (ctx, 0);
949   }
950   fclose (f);
951
952   if (nntp_update_cacheindex (((NNTP_DATA *) ctx->data)->nserv,
953                               (NNTP_DATA *) ctx->data)) {
954     unlink (file);
955     return -1;
956   }
957   ((NNTP_DATA *) ctx->data)->lastCached =
958     ((NNTP_DATA *) ctx->data)->lastLoaded;
959   return 0;
960 }
961
962 void nntp_delete_cache (NNTP_DATA * data)
963 {
964   char buf[_POSIX_PATH_MAX];
965
966   if (!option (OPTNEWSCACHE) || !data || !data->cache || !data->nserv)
967     return;
968
969   nntp_cache_expand (buf, data->cache);
970   unlink (buf);
971   mem_free (&data->cache);
972   data->lastCached = 0;
973   nntp_cache_expand (buf, ".index");
974   mutt_update_list_file (buf, data->nserv->conn->account.host, data->group,
975                          NULL);
976 }
977
978 NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER * news, char *group)
979 {
980   NNTP_DATA *data;
981
982   if (!news || !news->newsgroups || !group || !*group)
983     return NULL;
984   if (!(data = (NNTP_DATA *) hash_find (news->newsgroups, group))) {
985     data =
986       (NNTP_DATA *) mem_calloc (1, sizeof (NNTP_DATA) + str_len (group) + 1);
987     data->group = (char *) data + sizeof (NNTP_DATA);
988     strcpy (data->group, group);
989     data->nserv = news;
990     data->deleted = 1;
991     if (news->newsgroups->nelem < news->newsgroups->curnelem * 2)
992       news->newsgroups =
993         hash_resize (news->newsgroups, news->newsgroups->nelem * 2);
994     hash_insert (news->newsgroups, data->group, data, 0);
995     nntp_add_to_list (news, data);
996   }
997   if (!data->subscribed) {
998     data->subscribed = 1;
999     data->rc = 1;
1000   }
1001   return data;
1002 }
1003
1004 NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER * news, char *group)
1005 {
1006   NNTP_DATA *data;
1007
1008   if (!news || !news->newsgroups || !group || !*group ||
1009       !(data = (NNTP_DATA *) hash_find (news->newsgroups, group)))
1010     return NULL;
1011   if (data->subscribed) {
1012     data->subscribed = 0;
1013     if (!option (OPTSAVEUNSUB))
1014       data->rc = 0;
1015   }
1016   return data;
1017 }
1018
1019 NNTP_DATA *mutt_newsgroup_catchup (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 = mem_calloc (5, sizeof (NEWSRC_ENTRY));
1028     data->max = 5;
1029   }
1030   data->num = 1;
1031   data->entries[0].first = 1;
1032   data->unread = 0;
1033   data->entries[0].last = data->lastMessage;
1034   if (Context && Context->data == data) {
1035     int x;
1036
1037     for (x = 0; x < Context->msgcount; x++)
1038       mutt_set_flag (Context, Context->hdrs[x], M_READ, 1);
1039   }
1040   return data;
1041 }
1042
1043 NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER * news, char *group)
1044 {
1045   NNTP_DATA *data;
1046
1047   if (!news || !news->newsgroups || !group || !*group ||
1048       !(data = (NNTP_DATA *) hash_find (news->newsgroups, group)))
1049     return NULL;
1050   if (!data->max) {
1051     data->entries = mem_calloc (5, sizeof (NEWSRC_ENTRY));
1052     data->max = 5;
1053   }
1054   data->num = 1;
1055   data->entries[0].first = 1;
1056   data->entries[0].last = data->firstMessage - 1;
1057   if (Context && Context->data == data) {
1058     int x;
1059
1060     data->unread = Context->msgcount;
1061     for (x = 0; x < Context->msgcount; x++)
1062       mutt_set_flag (Context, Context->hdrs[x], M_READ, 0);
1063   }
1064   else
1065     data->unread = data->lastMessage - data->entries[0].last;
1066   return data;
1067 }
1068
1069 /* this routine gives the first newsgroup with new messages */
1070 void nntp_buffy (char* dst, size_t dstlen) {
1071   LIST *list;
1072   int count = 0;
1073
1074   /* forward to current group */
1075   for (list = CurrentNewsSrv->list; list; list = list->next) {
1076     NNTP_DATA *data = (NNTP_DATA *) list->data;
1077     if (data && data->subscribed && data->unread && 
1078         Context && Context->magic == M_NNTP &&
1079         str_cmp (data->group, ((NNTP_DATA *) Context->data)->group) == 0) {
1080       list = list->next;
1081       break;
1082     }
1083   }
1084
1085   *dst = '\0';
1086
1087   while (count < 2) {
1088
1089     if (!list)
1090       list = CurrentNewsSrv->list;
1091
1092     for (; list; list = list->next) {
1093       NNTP_DATA *data = (NNTP_DATA *) list->data;
1094
1095       if (data && data->subscribed && data->unread) {
1096         if (Context && Context->magic == M_NNTP &&
1097             !str_cmp (data->group, ((NNTP_DATA *) Context->data)->group)) {
1098           unsigned int i, unread = 0;
1099
1100           for (i = 0; i < Context->msgcount; i++)
1101             if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted)
1102               unread++;
1103           if (!unread)
1104             continue;
1105         }
1106         strncpy (dst, data->group, dstlen);
1107         break;
1108       }
1109     }
1110     /* done if found */
1111     if (dst && *dst)
1112       return;
1113     count++;
1114   }
1115   *dst = '\0';
1116 }