/* * Copyright (C) 1998 Brandon Long * Copyright (C) 1999 Andrej Gritsenko * Copyright (C) 2000-2012 Vsevolod Volkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #if HAVE_CONFIG_H #include "config.h" #endif #include "mutt.h" #include "mutt_curses.h" #include "sort.h" #include "mx.h" #include "mime.h" #include "mailbox.h" #include "nntp.h" #include "rfc822.h" #include "rfc1524.h" #include "rfc2047.h" #include "bcache.h" #if USE_HCACHE #include "hcache.h" #endif #include #include #include #include #include #include #include #include /* Find NNTP_DATA for given newsgroup or add it */ static NNTP_DATA *nntp_data_find (NNTP_SERVER *nserv, const char *group) { NNTP_DATA *nntp_data = hash_find (nserv->groups_hash, group); if (!nntp_data) { /* create NNTP_DATA structure and add it to hash */ nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); nntp_data->group = (char *)nntp_data + sizeof (NNTP_DATA); strcpy (nntp_data->group, group); nntp_data->nserv = nserv; nntp_data->deleted = 1; if (nserv->groups_hash->nelem < nserv->groups_hash->curnelem * 2) nserv->groups_hash = hash_resize (nserv->groups_hash, nserv->groups_hash->nelem * 2, 0); hash_insert (nserv->groups_hash, nntp_data->group, nntp_data, 0); /* add NNTP_DATA to list */ if (nserv->groups_num >= nserv->groups_max) { nserv->groups_max *= 2; safe_realloc (&nserv->groups_list, nserv->groups_max * sizeof (nntp_data)); } nserv->groups_list[nserv->groups_num++] = nntp_data; } return nntp_data; } /* Remove all temporarily cache files */ void nntp_acache_free (NNTP_DATA *nntp_data) { int i; for (i = 0; i < NNTP_ACACHE_LEN; i++) { if (nntp_data->acache[i].path) { unlink (nntp_data->acache[i].path); FREE (&nntp_data->acache[i].path); } } } /* Free NNTP_DATA, used to destroy hash elements */ void nntp_data_free (void *data) { NNTP_DATA *nntp_data = data; if (!nntp_data) return; nntp_acache_free (nntp_data); mutt_bcache_close (&nntp_data->bcache); FREE (&nntp_data->newsrc_ent); FREE (&nntp_data->desc); FREE (&data); } /* Unlock and close .newsrc file */ void nntp_newsrc_close (NNTP_SERVER *nserv) { if (!nserv->newsrc_fp) return; dprint (1, (debugfile, "Unlocking %s\n", nserv->newsrc_file)); mx_unlock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0); safe_fclose (&nserv->newsrc_fp); } /* Parse .newsrc file: * 0 - not changed * 1 - parsed * -1 - error */ int nntp_newsrc_parse (NNTP_SERVER *nserv) { unsigned int i; char *line; struct stat sb; /* if file doesn't exist, create it */ nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "a"); safe_fclose (&nserv->newsrc_fp); /* open .newsrc */ nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "r"); if (!nserv->newsrc_fp) { mutt_perror (nserv->newsrc_file); mutt_sleep (2); return -1; } /* lock it */ dprint (1, (debugfile, "Locking %s\n", nserv->newsrc_file)); if (mx_lock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0, 0, 1)) { safe_fclose (&nserv->newsrc_fp); return -1; } if (stat (nserv->newsrc_file, &sb)) { mutt_perror (nserv->newsrc_file); nntp_newsrc_close (nserv); mutt_sleep (2); return -1; } if (nserv->size == sb.st_size && nserv->mtime == sb.st_mtime) return 0; nserv->size = sb.st_size; nserv->mtime = sb.st_mtime; nserv->newsrc_modified = 1; dprint (1, (debugfile, "Parsing %s\n", nserv->newsrc_file)); /* .newsrc has been externally modified or hasn't been loaded yet */ for (i = 0; i < nserv->groups_num; i++) { NNTP_DATA *nntp_data = nserv->groups_list[i]; if (!nntp_data) continue; nntp_data->subscribed = 0; nntp_data->newsrc_len = 0; FREE (&nntp_data->newsrc_ent); } line = safe_malloc (sb.st_size + 1); while (sb.st_size && fgets (line, sb.st_size + 1, nserv->newsrc_fp)) { char *b, *h, *p; unsigned int subs = 0, i = 1; NNTP_DATA *nntp_data; /* find end of newsgroup name */ p = strpbrk (line, ":!"); if (!p) continue; /* ":" - subscribed, "!" - unsubscribed */ if (*p == ':') subs++; *p++ = '\0'; /* get newsgroup data */ nntp_data = nntp_data_find (nserv, line); FREE (&nntp_data->newsrc_ent); /* count number of entries */ b = p; while (*b) if (*b++ == ',') i++; nntp_data->newsrc_ent = safe_calloc (i, sizeof (NEWSRC_ENTRY)); nntp_data->subscribed = subs; /* parse entries */ i = 0; while (p) { b = p; /* find end of entry */ p = strchr (p, ','); if (p) *p++ = '\0'; /* first-last or single number */ h = strchr (b, '-'); if (h) *h++ = '\0'; else h = b; if (sscanf (b, ANUM, &nntp_data->newsrc_ent[i].first) == 1 && sscanf (h, ANUM, &nntp_data->newsrc_ent[i].last) == 1) i++; } if (i == 0) { nntp_data->newsrc_ent[i].first = 1; nntp_data->newsrc_ent[i].last = 0; i++; } if (nntp_data->lastMessage == 0) nntp_data->lastMessage = nntp_data->newsrc_ent[i - 1].last; nntp_data->newsrc_len = i; safe_realloc (&nntp_data->newsrc_ent, i * sizeof (NEWSRC_ENTRY)); nntp_group_unread_stat (nntp_data); dprint (2, (debugfile, "nntp_newsrc_parse: %s\n", nntp_data->group)); } FREE (&line); return 1; } /* Generate array of .newsrc entries */ void nntp_newsrc_gen_entries (CONTEXT *ctx) { NNTP_DATA *nntp_data = ctx->data; anum_t last = 0, first = 1; int series, i; int save_sort = SORT_ORDER; unsigned int entries; if (Sort != SORT_ORDER) { save_sort = Sort; Sort = SORT_ORDER; mutt_sort_headers (ctx, 0); } entries = nntp_data->newsrc_len; if (!entries) { entries = 5; nntp_data->newsrc_ent = safe_calloc (entries, sizeof (NEWSRC_ENTRY)); } /* Set up to fake initial sequence from 1 to the article before the * first article in our list */ nntp_data->newsrc_len = 0; series = 1; for (i = 0; i < ctx->msgcount; i++) { /* search for first unread */ if (series) { /* We don't actually check sequential order, since we mark * "missing" entries as read/deleted */ last = NHDR (ctx->hdrs[i])->article_num; if (last >= nntp_data->firstMessage && !ctx->hdrs[i]->deleted && !ctx->hdrs[i]->read) { if (nntp_data->newsrc_len >= entries) { entries *= 2; safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY)); } nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first; nntp_data->newsrc_ent[nntp_data->newsrc_len].last = last - 1; nntp_data->newsrc_len++; series = 0; } } /* search for first read */ else { if (ctx->hdrs[i]->deleted || ctx->hdrs[i]->read) { first = last + 1; series = 1; } last = NHDR (ctx->hdrs[i])->article_num; } } if (series && first <= nntp_data->lastLoaded) { if (nntp_data->newsrc_len >= entries) { entries++; safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY)); } nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first; nntp_data->newsrc_ent[nntp_data->newsrc_len].last = nntp_data->lastLoaded; nntp_data->newsrc_len++; } safe_realloc (&nntp_data->newsrc_ent, nntp_data->newsrc_len * sizeof (NEWSRC_ENTRY)); if (save_sort != Sort) { Sort = save_sort; mutt_sort_headers (ctx, 0); } } /* Update file with new contents */ static int update_file (char *filename, char *buf) { FILE *fp; char tmpfile[_POSIX_PATH_MAX]; int rc = -1; while (1) { snprintf (tmpfile, sizeof (tmpfile), "%s.tmp", filename); fp = fopen (tmpfile, "w"); if (!fp) { mutt_perror (tmpfile); *tmpfile = '\0'; break; } if (fputs (buf, fp) == EOF) { mutt_perror (tmpfile); break; } if (fclose (fp) == EOF) { mutt_perror (tmpfile); fp = NULL; break; } fp = NULL; if (rename (tmpfile, filename) < 0) { mutt_perror (filename); break; } *tmpfile = '\0'; rc = 0; break; } if (fp) fclose (fp); if (*tmpfile) unlink (tmpfile); if (rc) mutt_sleep (2); return rc; } /* Update .newsrc file */ int nntp_newsrc_update (NNTP_SERVER *nserv) { char *buf; size_t buflen, off; unsigned int i; int rc = -1; if (!nserv) return -1; buflen = 10 * LONG_STRING; buf = safe_calloc (1, buflen); off = 0; /* we will generate full newsrc here */ for (i = 0; i < nserv->groups_num; i++) { NNTP_DATA *nntp_data = nserv->groups_list[i]; unsigned int n; if (!nntp_data || !nntp_data->newsrc_ent) continue; /* write newsgroup name */ if (off + strlen (nntp_data->group) + 3 > buflen) { buflen *= 2; safe_realloc (&buf, buflen); } snprintf (buf + off, buflen - off, "%s%c ", nntp_data->group, nntp_data->subscribed ? ':' : '!'); off += strlen (buf + off); /* write entries */ for (n = 0; n < nntp_data->newsrc_len; n++) { if (off + LONG_STRING > buflen) { buflen *= 2; safe_realloc (&buf, buflen); } if (n) buf[off++] = ','; if (nntp_data->newsrc_ent[n].first == nntp_data->newsrc_ent[n].last) snprintf (buf + off, buflen - off, "%d", nntp_data->newsrc_ent[n].first); else if (nntp_data->newsrc_ent[n].first < nntp_data->newsrc_ent[n].last) snprintf (buf + off, buflen - off, "%d-%d", nntp_data->newsrc_ent[n].first, nntp_data->newsrc_ent[n].last); off += strlen (buf + off); } buf[off++] = '\n'; } buf[off] = '\0'; /* newrc being fully rewritten */ dprint (1, (debugfile, "Updating %s\n", nserv->newsrc_file)); if (nserv->newsrc_file && update_file (nserv->newsrc_file, buf) == 0) { struct stat sb; rc = stat (nserv->newsrc_file, &sb); if (rc == 0) { nserv->size = sb.st_size; nserv->mtime = sb.st_mtime; } else { mutt_perror (nserv->newsrc_file); mutt_sleep (2); } } FREE (&buf); return rc; } /* Make fully qualified cache file name */ static void cache_expand (char *dst, size_t dstlen, ACCOUNT *acct, char *src) { char *c; char file[_POSIX_PATH_MAX]; /* server subdirectory */ if (acct) { ciss_url_t url; mutt_account_tourl (acct, &url); url.path = src; url_ciss_tostring (&url, file, sizeof (file), U_PATH); } else strfcpy (file, src ? src : "", sizeof (file)); snprintf (dst, dstlen, "%s/%s", NewsCacheDir, file); /* remove trailing slash */ c = dst + strlen (dst) - 1; if (*c == '/') *c = '\0'; mutt_expand_path (dst, dstlen); } /* Make fully qualified url from newsgroup name */ void nntp_expand_path (char *line, size_t len, ACCOUNT *acct) { ciss_url_t url; url.path = safe_strdup (line); mutt_account_tourl (acct, &url); url_ciss_tostring (&url, line, len, 0); FREE (&url.path); } /* Parse newsgroup */ int nntp_add_group (char *line, void *data) { NNTP_SERVER *nserv = data; NNTP_DATA *nntp_data; char group[LONG_STRING]; char desc[HUGE_STRING] = ""; char mod; anum_t first, last; if (!nserv || !line) return 0; if (sscanf (line, "%s " ANUM " " ANUM " %c %[^\n]", group, &last, &first, &mod, desc) < 4) return 0; nntp_data = nntp_data_find (nserv, group); nntp_data->deleted = 0; nntp_data->firstMessage = first; nntp_data->lastMessage = last; nntp_data->allowed = mod == 'y' || mod == 'm' ? 1 : 0; mutt_str_replace (&nntp_data->desc, desc); if (nntp_data->newsrc_ent || nntp_data->lastCached) nntp_group_unread_stat (nntp_data); else if (nntp_data->lastMessage && nntp_data->firstMessage <= nntp_data->lastMessage) nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1; else nntp_data->unread = 0; return 0; } /* Load list of all newsgroups from cache */ static int active_get_cache (NNTP_SERVER *nserv) { char buf[HUGE_STRING]; char file[_POSIX_PATH_MAX]; time_t t; FILE *fp; cache_expand (file, sizeof (file), &nserv->conn->account, ".active"); dprint (1, (debugfile, "Parsing %s\n", file)); fp = safe_fopen (file, "r"); if (!fp) return -1; if (fgets (buf, sizeof (buf), fp) == NULL || sscanf (buf, "%ld%s", &t, file) != 1 || t == 0) { fclose (fp); return -1; } nserv->newgroups_time = t; mutt_message _("Loading list of groups from cache..."); while (fgets (buf, sizeof (buf), fp)) nntp_add_group (buf, nserv); nntp_add_group (NULL, NULL); fclose (fp); mutt_clear_error (); return 0; } /* Save list of all newsgroups to cache */ int nntp_active_save_cache (NNTP_SERVER *nserv) { char file[_POSIX_PATH_MAX]; char *buf; size_t buflen, off; unsigned int i; int rc; if (!nserv->cacheable) return 0; buflen = 10 * LONG_STRING; buf = safe_calloc (1, buflen); snprintf (buf, buflen, "%lu\n", (unsigned long)nserv->newgroups_time); off = strlen (buf); for (i = 0; i < nserv->groups_num; i++) { NNTP_DATA *nntp_data = nserv->groups_list[i]; if (!nntp_data || nntp_data->deleted) continue; if (off + strlen (nntp_data->group) + (nntp_data->desc ? strlen (nntp_data->desc) : 0) + 50 > buflen) { buflen *= 2; safe_realloc (&buf, buflen); } snprintf (buf + off, buflen - off, "%s %d %d %c%s%s\n", nntp_data->group, nntp_data->lastMessage, nntp_data->firstMessage, nntp_data->allowed ? 'y' : 'n', nntp_data->desc ? " " : "", nntp_data->desc ? nntp_data->desc : ""); off += strlen (buf + off); } cache_expand (file, sizeof (file), &nserv->conn->account, ".active"); dprint (1, (debugfile, "Updating %s\n", file)); rc = update_file (file, buf); FREE (&buf); return rc; } #ifdef USE_HCACHE /* Used by mutt_hcache_open() to compose hcache file name */ static int nntp_hcache_namer (const char *path, char *dest, size_t destlen) { return snprintf (dest, destlen, "%s.hcache", path); } /* Open newsgroup hcache */ header_cache_t *nntp_hcache_open (NNTP_DATA *nntp_data) { ciss_url_t url; char file[_POSIX_PATH_MAX]; if (!nntp_data->nserv || !nntp_data->nserv->cacheable || !nntp_data->nserv->conn || !nntp_data->group || !(nntp_data->newsrc_ent || nntp_data->subscribed || option (OPTSAVEUNSUB))) return NULL; mutt_account_tourl (&nntp_data->nserv->conn->account, &url); url.path = nntp_data->group; url_ciss_tostring (&url, file, sizeof (file), U_PATH); return mutt_hcache_open (NewsCacheDir, file, nntp_hcache_namer); } /* Remove stale cached headers */ void nntp_hcache_update (NNTP_DATA *nntp_data, header_cache_t *hc) { char buf[16]; int old = 0; void *hdata; anum_t first, last, current; if (!hc) return; /* fetch previous values of first and last */ hdata = mutt_hcache_fetch_raw (hc, "index", strlen); if (hdata) { dprint (2, (debugfile, "nntp_hcache_update: mutt_hcache_fetch index: %s\n", hdata)); if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2) { old = 1; nntp_data->lastCached = last; /* clean removed headers from cache */ for (current = first; current <= last; current++) { if (current >= nntp_data->firstMessage && current <= nntp_data->lastMessage) continue; snprintf (buf, sizeof (buf), "%d", current); dprint (2, (debugfile, "nntp_hcache_update: mutt_hcache_delete %s\n", buf)); mutt_hcache_delete (hc, buf, strlen); } } } /* store current values of first and last */ if (!old || nntp_data->firstMessage != first || nntp_data->lastMessage != last) { snprintf (buf, sizeof (buf), "%u %u", nntp_data->firstMessage, nntp_data->lastMessage); dprint (2, (debugfile, "nntp_hcache_update: mutt_hcache_store index: %s\n", buf)); mutt_hcache_store_raw (hc, "index", buf, strlen (buf) + 1, strlen); } } #endif /* Remove bcache file */ static int nntp_bcache_delete (const char *id, body_cache_t *bcache, void *data) { NNTP_DATA *nntp_data = data; anum_t anum; char c; if (!nntp_data || sscanf (id, ANUM "%c", &anum, &c) != 1 || anum < nntp_data->firstMessage || anum > nntp_data->lastMessage) { if (nntp_data) dprint (2, (debugfile, "nntp_bcache_delete: mutt_bcache_del %s\n", id)); mutt_bcache_del (bcache, id); } return 0; } /* Remove stale cached messages */ void nntp_bcache_update (NNTP_DATA *nntp_data) { mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, nntp_data); } /* Remove hcache and bcache of newsgroup */ void nntp_delete_group_cache (NNTP_DATA *nntp_data) { char file[_POSIX_PATH_MAX]; if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->cacheable) return; #ifdef USE_HCACHE nntp_hcache_namer (nntp_data->group, file, sizeof (file)); cache_expand (file, sizeof (file), &nntp_data->nserv->conn->account, file); unlink (file); nntp_data->lastCached = 0; dprint (2, (debugfile, "nntp_delete_group_cache: %s\n", file)); #endif if (!nntp_data->bcache) nntp_data->bcache = mutt_bcache_open (&nntp_data->nserv->conn->account, nntp_data->group); if (nntp_data->bcache) { dprint (2, (debugfile, "nntp_delete_group_cache: %s/*\n", nntp_data->group)); mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, NULL); mutt_bcache_close (&nntp_data->bcache); } } /* Remove hcache and bcache of all unexistent and unsubscribed newsgroups */ void nntp_clear_cache (NNTP_SERVER *nserv) { char file[_POSIX_PATH_MAX]; char *fp; struct dirent *entry; DIR *dp; if (!nserv || !nserv->cacheable) return; cache_expand (file, sizeof (file), &nserv->conn->account, NULL); dp = opendir (file); if (dp) { safe_strncat (file, sizeof (file), "/", 1); fp = file + strlen (file); while ((entry = readdir (dp))) { char *group = entry->d_name; struct stat sb; NNTP_DATA *nntp_data; NNTP_DATA nntp_tmp; if (mutt_strcmp (group, ".") == 0 || mutt_strcmp (group, "..") == 0) continue; *fp = '\0'; safe_strncat (file, sizeof (file), group, strlen (group)); if (stat (file, &sb)) continue; #ifdef USE_HCACHE if (S_ISREG (sb.st_mode)) { char *ext = group + strlen (group) - 7; if (strlen (group) < 8 || mutt_strcmp (ext, ".hcache")) continue; *ext = '\0'; } else #endif if (!S_ISDIR (sb.st_mode)) continue; nntp_data = hash_find (nserv->groups_hash, group); if (!nntp_data) { nntp_data = &nntp_tmp; nntp_data->nserv = nserv; nntp_data->group = group; nntp_data->bcache = NULL; } else if (nntp_data->newsrc_ent || nntp_data->subscribed || option (OPTSAVEUNSUB)) continue; nntp_delete_group_cache (nntp_data); if (S_ISDIR (sb.st_mode)) { rmdir (file); dprint (2, (debugfile, "nntp_clear_cache: %s\n", file)); } } closedir (dp); } return; } /* %a = account url * %p = port * %P = port if specified * %s = news server name * %S = url schema * %u = username */ const char * nntp_format_str (char *dest, size_t destlen, size_t col, char op, const char *src, const char *fmt, const char *ifstring, const char *elsestring, unsigned long data, format_flag flags) { NNTP_SERVER *nserv = (NNTP_SERVER *)data; ACCOUNT *acct = &nserv->conn->account; ciss_url_t url; char fn[SHORT_STRING], tmp[SHORT_STRING], *p; switch (op) { case 'a': mutt_account_tourl (acct, &url); url_ciss_tostring (&url, fn, sizeof (fn), U_PATH); p = strchr (fn, '/'); if (p) *p = '\0'; snprintf (tmp, sizeof (tmp), "%%%ss", fmt); snprintf (dest, destlen, tmp, fn); break; case 'p': snprintf (tmp, sizeof (tmp), "%%%su", fmt); snprintf (dest, destlen, tmp, acct->port); break; case 'P': *dest = '\0'; if (acct->flags & M_ACCT_PORT) { snprintf (tmp, sizeof (tmp), "%%%su", fmt); snprintf (dest, destlen, tmp, acct->port); } break; case 's': strncpy (fn, acct->host, sizeof (fn) - 1); mutt_strlower (fn); snprintf (tmp, sizeof (tmp), "%%%ss", fmt); snprintf (dest, destlen, tmp, fn); break; case 'S': mutt_account_tourl (acct, &url); url_ciss_tostring (&url, fn, sizeof (fn), U_PATH); p = strchr (fn, ':'); if (p) *p = '\0'; snprintf (tmp, sizeof (tmp), "%%%ss", fmt); snprintf (dest, destlen, tmp, fn); break; case 'u': snprintf (tmp, sizeof (tmp), "%%%ss", fmt); snprintf (dest, destlen, tmp, acct->user); break; } return (src); } /* Automatically loads a newsrc into memory, if necessary. * Checks the size/mtime of a newsrc file, if it doesn't match, load * again. Hmm, if a system has broken mtimes, this might mean the file * is reloaded every time, which we'd have to fix. */ NNTP_SERVER *nntp_select_server (char *server, int leave_lock) { char file[_POSIX_PATH_MAX]; char *p; int rc; struct stat sb; ACCOUNT acct; NNTP_SERVER *nserv; NNTP_DATA *nntp_data; CONNECTION *conn; ciss_url_t url; if (!server || !*server) { mutt_error _("No news server defined!"); mutt_sleep (2); return NULL; } /* create account from news server url */ acct.flags = 0; acct.port = NNTP_PORT; acct.type = M_ACCT_TYPE_NNTP; snprintf (file, sizeof (file), "%s%s", strstr (server, "://") ? "" : "news://", server); if (url_parse_ciss (&url, file) < 0 || (url.path && *url.path) || !(url.scheme == U_NNTP || url.scheme == U_NNTPS) || mutt_account_fromurl (&acct, &url) < 0) { mutt_error (_("%s is an invalid news server specification!"), server); mutt_sleep (2); return NULL; } if (url.scheme == U_NNTPS) { acct.flags |= M_ACCT_SSL; acct.port = NNTP_SSL_PORT; } /* find connection by account */ conn = mutt_conn_find (NULL, &acct); if (!conn) return NULL; if (!(conn->account.flags & M_ACCT_USER) && acct.flags & M_ACCT_USER) { conn->account.flags |= M_ACCT_USER; conn->account.user[0] = '\0'; } /* news server already exists */ nserv = conn->data; if (nserv) { if (nserv->status == NNTP_BYE) nserv->status = NNTP_NONE; if (nntp_open_connection (nserv) < 0) return NULL; rc = nntp_newsrc_parse (nserv); if (rc < 0) return NULL; /* check for new newsgroups */ if (!leave_lock && nntp_check_new_groups (nserv) < 0) rc = -1; /* .newsrc has been externally modified */ if (rc > 0) nntp_clear_cache (nserv); if (rc < 0 || !leave_lock) nntp_newsrc_close (nserv); return rc < 0 ? NULL : nserv; } /* new news server */ nserv = safe_calloc (1, sizeof (NNTP_SERVER)); nserv->conn = conn; nserv->groups_hash = hash_create (1009, 0); nserv->groups_max = 16; nserv->groups_list = safe_malloc (nserv->groups_max * sizeof (nntp_data)); rc = nntp_open_connection (nserv); /* try to create cache directory and enable caching */ nserv->cacheable = 0; if (rc >= 0 && NewsCacheDir && *NewsCacheDir) { cache_expand (file, sizeof (file), &conn->account, NULL); p = *file == '/' ? file + 1 : file; while (1) { p = strchr (p, '/'); if (p) *p = '\0'; if ((stat (file, &sb) || (sb.st_mode & S_IFDIR) == 0) && mkdir (file, 0700)) { mutt_error (_("Can't create %s: %s."), file, strerror (errno)); mutt_sleep (2); break; } if (!p) { nserv->cacheable = 1; break; } *p++ = '/'; } } /* load .newsrc */ if (rc >= 0) { mutt_FormatString (file, sizeof (file), 0, NONULL (NewsRc), nntp_format_str, (unsigned long)nserv, 0); mutt_expand_path (file, sizeof (file)); nserv->newsrc_file = safe_strdup (file); rc = nntp_newsrc_parse (nserv); } if (rc >= 0) { /* try to load list of newsgroups from cache */ if (nserv->cacheable && active_get_cache (nserv) == 0) rc = nntp_check_new_groups (nserv); /* load list of newsgroups from server */ else rc = nntp_active_fetch (nserv); } if (rc >= 0) nntp_clear_cache (nserv); #ifdef USE_HCACHE /* check cache files */ if (rc >= 0 && nserv->cacheable) { struct dirent *entry; DIR *dp = opendir (file); if (dp) { while ((entry = readdir (dp))) { header_cache_t *hc; void *hdata; char *group = entry->d_name; p = group + strlen (group) - 7; if (strlen (group) < 8 || strcmp (p, ".hcache")) continue; *p = '\0'; nntp_data = hash_find (nserv->groups_hash, group); if (!nntp_data) continue; hc = nntp_hcache_open (nntp_data); if (!hc) continue; /* fetch previous values of first and last */ hdata = mutt_hcache_fetch_raw (hc, "index", strlen); if (hdata) { anum_t first, last; if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2) { if (nntp_data->deleted) { nntp_data->firstMessage = first; nntp_data->lastMessage = last; } if (last >= nntp_data->firstMessage && last <= nntp_data->lastMessage) { nntp_data->lastCached = last; dprint (2, (debugfile, "nntp_select_server: %s lastCached=%u\n", nntp_data->group, last)); } } } mutt_hcache_close (hc); } closedir (dp); } } #endif if (rc < 0 || !leave_lock) nntp_newsrc_close (nserv); if (rc < 0) { hash_destroy (&nserv->groups_hash, nntp_data_free); FREE (&nserv->groups_list); FREE (&nserv->newsrc_file); FREE (&nserv->authenticators); FREE (&nserv); mutt_socket_close (conn); mutt_socket_free (conn); return NULL; } conn->data = nserv; return nserv; } /* Full status flags are not supported by nntp, but we can fake some of them: * Read = a read message number is in the .newsrc * New = not read and not cached * Old = not read but cached */ void nntp_article_status (CONTEXT *ctx, HEADER *hdr, char *group, anum_t anum) { NNTP_DATA *nntp_data = ctx->data; unsigned int i; if (group) nntp_data = hash_find (nntp_data->nserv->groups_hash, group); if (!nntp_data) return; for (i = 0; i < nntp_data->newsrc_len; i++) { if ((anum >= nntp_data->newsrc_ent[i].first) && (anum <= nntp_data->newsrc_ent[i].last)) { /* can't use mutt_set_flag() because mx_update_context() didn't called yet */ hdr->read = 1; return; } } /* article was not cached yet, it's new */ if (anum > nntp_data->lastCached) return; /* article isn't read but cached, it's old */ if (option (OPTMARKOLD)) hdr->old = 1; } /* calculate number of unread articles using .newsrc data */ void nntp_group_unread_stat (NNTP_DATA *nntp_data) { unsigned int i; anum_t first, last; nntp_data->unread = 0; if (nntp_data->lastMessage == 0 || nntp_data->firstMessage > nntp_data->lastMessage) return; nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1; for (i = 0; i < nntp_data->newsrc_len; i++) { first = nntp_data->newsrc_ent[i].first; if (first < nntp_data->firstMessage) first = nntp_data->firstMessage; last = nntp_data->newsrc_ent[i].last; if (last > nntp_data->lastMessage) last = nntp_data->lastMessage; if (first <= last) nntp_data->unread -= last - first + 1; } } /* Subscribe newsgroup */ NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *nserv, char *group) { NNTP_DATA *nntp_data; if (!nserv || !nserv->groups_hash || !group || !*group) return NULL; nntp_data = nntp_data_find (nserv, group); nntp_data->subscribed = 1; if (!nntp_data->newsrc_ent) { nntp_data->newsrc_ent = safe_calloc (1, sizeof (NEWSRC_ENTRY)); nntp_data->newsrc_len = 1; nntp_data->newsrc_ent[0].first = 1; nntp_data->newsrc_ent[0].last = 0; } return nntp_data; } /* Unsubscribe newsgroup */ NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *nserv, char *group) { NNTP_DATA *nntp_data; if (!nserv || !nserv->groups_hash || !group || !*group) return NULL; nntp_data = hash_find (nserv->groups_hash, group); if (!nntp_data) return NULL; nntp_data->subscribed = 0; if (!option (OPTSAVEUNSUB)) { nntp_data->newsrc_len = 0; FREE (&nntp_data->newsrc_ent); } return nntp_data; } /* Catchup newsgroup */ NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *nserv, char *group) { NNTP_DATA *nntp_data; if (!nserv || !nserv->groups_hash || !group || !*group) return NULL; nntp_data = hash_find (nserv->groups_hash, group); if (!nntp_data) return NULL; if (nntp_data->newsrc_ent) { safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); nntp_data->newsrc_len = 1; nntp_data->newsrc_ent[0].first = 1; nntp_data->newsrc_ent[0].last = nntp_data->lastMessage; } nntp_data->unread = 0; if (Context && Context->data == nntp_data) { unsigned int i; for (i = 0; i < Context->msgcount; i++) mutt_set_flag (Context, Context->hdrs[i], M_READ, 1); } return nntp_data; } /* Uncatchup newsgroup */ NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *nserv, char *group) { NNTP_DATA *nntp_data; if (!nserv || !nserv->groups_hash || !group || !*group) return NULL; nntp_data = hash_find (nserv->groups_hash, group); if (!nntp_data) return NULL; if (nntp_data->newsrc_ent) { safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); nntp_data->newsrc_len = 1; nntp_data->newsrc_ent[0].first = 1; nntp_data->newsrc_ent[0].last = nntp_data->firstMessage - 1; } if (Context && Context->data == nntp_data) { unsigned int i; nntp_data->unread = Context->msgcount; for (i = 0; i < Context->msgcount; i++) mutt_set_flag (Context, Context->hdrs[i], M_READ, 0); } else nntp_data->unread = nntp_data->lastMessage - nntp_data->newsrc_ent[0].last; return nntp_data; } /* Get first newsgroup with new messages */ void nntp_buffy (char *buf, size_t len) { unsigned int i; for (i = 0; i < CurrentNewsSrv->groups_num; i++) { NNTP_DATA *nntp_data = CurrentNewsSrv->groups_list[i]; if (!nntp_data || !nntp_data->subscribed || !nntp_data->unread) continue; if (Context && Context->magic == M_NNTP && !mutt_strcmp (nntp_data->group, ((NNTP_DATA *)Context->data)->group)) { unsigned int i, unread = 0; for (i = 0; i < Context->msgcount; i++) if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted) unread++; if (!unread) continue; } strfcpy (buf, nntp_data->group, len); break; } }