Commit message: sanitise cache paths

Co-authored-by: JerikoOne <jeriko.one@gmx.us>
CVE: CVE-2018-14363
CWE: CWE-22
Vulnerability description: An issue was discovered in NeoMutt before 2018-07-16. newsrc.c does not properly restrict '/' characters that may have unsafe interaction with cache pathnames.

Project: neomutt
File: newsrc.c

static int active_get_cache(struct NntpServer *nserv)
{
  char buf[HUGE_STRING];
  char file[PATH_MAX];
  time_t t;

  cache_expand(file, sizeof(file), &nserv->conn->account, ".active");
  mutt_debug(1, "Parsing %s\n", file);
  FILE *fp = mutt_file_fopen(file, "r");
  if (!fp)
    return -1;

  if (fgets(buf, sizeof(buf), fp) == NULL || sscanf(buf, "%ld%s", &t, file) != 1 || t == 0)
  {
    mutt_file_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);
  mutt_file_fclose(&fp);
  mutt_clear_error();
  return 0;
}

Project: neomutt
File: newsrc.c

static void cache_expand(char *dst, size_t dstlen, struct Account *acct, char *src)
{
  char *c = NULL;
  char file[PATH_MAX];

  /* server subdirectory */
  if (acct)
  {
    struct Url url;

    mutt_account_tourl(acct, &url);
    url.path = src;
    url_tostring(&url, file, sizeof(file), U_PATH);
  }
  else
    mutt_str_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);
  mutt_encode_path(dst, dstlen, dst);
}

Project: neomutt
File: newsrc.c

struct NntpData *mutt_newsgroup_catchup(struct NntpServer *nserv, char *group)
{
  struct NntpData *nntp_data = NULL;

  if (!nserv || !nserv->groups_hash || !group || !*group)
    return NULL;

  nntp_data = mutt_hash_find(nserv->groups_hash, group);
  if (!nntp_data)
    return NULL;

  if (nntp_data->newsrc_ent)
  {
    mutt_mem_realloc(&nntp_data->newsrc_ent, sizeof(struct NewsrcEntry));
    nntp_data->newsrc_len = 1;
    nntp_data->newsrc_ent[0].first = 1;
    nntp_data->newsrc_ent[0].last = nntp_data->last_message;
  }
  nntp_data->unread = 0;
  if (Context && Context->data == nntp_data)
  {
    for (unsigned int i = 0; i < Context->msgcount; i++)
      mutt_set_flag(Context, Context->hdrs[i], MUTT_READ, 1);
  }
  return nntp_data;
}

Project: neomutt
File: newsrc.c

struct NntpData *mutt_newsgroup_uncatchup(struct NntpServer *nserv, char *group)
{
  struct NntpData *nntp_data = NULL;

  if (!nserv || !nserv->groups_hash || !group || !*group)
    return NULL;

  nntp_data = mutt_hash_find(nserv->groups_hash, group);
  if (!nntp_data)
    return NULL;

  if (nntp_data->newsrc_ent)
  {
    mutt_mem_realloc(&nntp_data->newsrc_ent, sizeof(struct NewsrcEntry));
    nntp_data->newsrc_len = 1;
    nntp_data->newsrc_ent[0].first = 1;
    nntp_data->newsrc_ent[0].last = nntp_data->first_message - 1;
  }
  if (Context && Context->data == nntp_data)
  {
    nntp_data->unread = Context->msgcount;
    for (unsigned int i = 0; i < Context->msgcount; i++)
      mutt_set_flag(Context, Context->hdrs[i], MUTT_READ, 0);
  }
  else
  {
    nntp_data->unread = nntp_data->last_message;
    if (nntp_data->newsrc_ent)
      nntp_data->unread -= nntp_data->newsrc_ent[0].last;
  }
  return nntp_data;
}

Project: neomutt
File: newsrc.c

struct NntpData *mutt_newsgroup_unsubscribe(struct NntpServer *nserv, char *group)
{
  struct NntpData *nntp_data = NULL;

  if (!nserv || !nserv->groups_hash || !group || !*group)
    return NULL;

  nntp_data = mutt_hash_find(nserv->groups_hash, group);
  if (!nntp_data)
    return NULL;

  nntp_data->subscribed = false;
  if (!SaveUnsubscribed)
  {
    nntp_data->newsrc_len = 0;
    FREE(&nntp_data->newsrc_ent);
  }
  return nntp_data;
}

Project: neomutt
File: newsrc.c

void nntp_acache_free(struct NntpData *nntp_data)
{
  for (int 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);
    }
  }
}

Project: neomutt
File: newsrc.c

int nntp_active_save_cache(struct NntpServer *nserv)
{
  char file[PATH_MAX];
  char *buf = NULL;
  size_t buflen, off;
  int rc;

  if (!nserv->cacheable)
    return 0;

  buflen = 10 * LONG_STRING;
  buf = mutt_mem_calloc(1, buflen);
  snprintf(buf, buflen, "%lu\n", (unsigned long) nserv->newgroups_time);
  off = strlen(buf);

  for (unsigned int i = 0; i < nserv->groups_num; i++)
  {
    struct NntpData *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;
      mutt_mem_realloc(&buf, buflen);
    }
    snprintf(buf + off, buflen - off, "%s %u %u %c%s%s\n", nntp_data->group,
             nntp_data->last_message, nntp_data->first_message,
             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");
  mutt_debug(1, "Updating %s\n", file);
  rc = update_file(file, buf);
  FREE(&buf);
  return rc;
}

Project: neomutt
File: newsrc.c

int nntp_add_group(char *line, void *data)
{
  struct NntpServer *nserv = data;
  struct NntpData *nntp_data = NULL;
  char group[LONG_STRING] = "";
  char desc[HUGE_STRING] = "";
  char mod;
  anum_t first, last;

  if (!nserv || !line)
    return 0;

  /* These sscanf limits must match the sizes of the group and desc arrays */
  if (sscanf(line, "%1023s " ANUM " " ANUM " %c %8191[^\n]", group, &last, &first, &mod, desc) < 4)
  {
    mutt_debug(4, "Cannot parse server line: %s\n", line);
    return 0;
  }

  nntp_data = nntp_data_find(nserv, group);
  nntp_data->deleted = false;
  nntp_data->first_message = first;
  nntp_data->last_message = last;
  nntp_data->allowed = (mod == 'y') || (mod == 'm');
  mutt_str_replace(&nntp_data->desc, desc);
  if (nntp_data->newsrc_ent || nntp_data->last_cached)
    nntp_group_unread_stat(nntp_data);
  else if (nntp_data->last_message && nntp_data->first_message <= nntp_data->last_message)
    nntp_data->unread = nntp_data->last_message - nntp_data->first_message + 1;
  else
    nntp_data->unread = 0;
  return 0;
}

Project: neomutt
File: newsrc.c

void nntp_article_status(struct Context *ctx, struct Header *hdr, char *group, anum_t anum)
{
  struct NntpData *nntp_data = ctx->data;

  if (group)
    nntp_data = mutt_hash_find(nntp_data->nserv->groups_hash, group);

  if (!nntp_data)
    return;

  for (unsigned int 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 = true;
      return;
    }
  }

  /* article was not cached yet, it's new */
  if (anum > nntp_data->last_cached)
    return;

  /* article isn't read but cached, it's old */
  if (MarkOld)
    hdr->old = true;
}

Project: neomutt
File: newsrc.c

static int nntp_bcache_delete(const char *id, struct BodyCache *bcache, void *data)
{
  struct NntpData *nntp_data = data;
  anum_t anum;
  char c;

  if (!nntp_data || sscanf(id, ANUM "%c", &anum, &c) != 1 ||
      anum < nntp_data->first_message || anum > nntp_data->last_message)
  {
    if (nntp_data)
      mutt_debug(2, "mutt_bcache_del %s\n", id);
    mutt_bcache_del(bcache, id);
  }
  return 0;
}

Project: neomutt
File: newsrc.c

void nntp_bcache_update(struct NntpData *nntp_data)
{
  mutt_bcache_list(nntp_data->bcache, nntp_bcache_delete, nntp_data);
}

Project: neomutt
File: newsrc.c

void nntp_buffy(char *buf, size_t len)
{
  for (unsigned int i = 0; i < CurrentNewsSrv->groups_num; i++)
  {
    struct NntpData *nntp_data = CurrentNewsSrv->groups_list[i];

    if (!nntp_data || !nntp_data->subscribed || !nntp_data->unread)
      continue;

    if (Context && Context->magic == MUTT_NNTP &&
        (mutt_str_strcmp(nntp_data->group, ((struct NntpData *) Context->data)->group) == 0))
    {
      unsigned int unread = 0;

      for (unsigned int j = 0; j < Context->msgcount; j++)
        if (!Context->hdrs[j]->read && !Context->hdrs[j]->deleted)
          unread++;
      if (!unread)
        continue;
    }
    mutt_str_strfcpy(buf, nntp_data->group, len);
    break;
  }
}

Project: neomutt
File: newsrc.c

void nntp_clear_cache(struct NntpServer *nserv)
{
  char file[PATH_MAX];
  char *fp = NULL;
  struct dirent *entry = NULL;
  DIR *dp = NULL;

  if (!nserv || !nserv->cacheable)
    return;

  cache_expand(file, sizeof(file), &nserv->conn->account, NULL);
  dp = opendir(file);
  if (dp)
  {
    mutt_str_strncat(file, sizeof(file), "/", 1);
    fp = file + strlen(file);
    while ((entry = readdir(dp)))
    {
      char *group = entry->d_name;
      struct stat sb;
      struct NntpData *nntp_data = NULL;
      struct NntpData nntp_tmp;

      if ((mutt_str_strcmp(group, ".") == 0) || (mutt_str_strcmp(group, "..") == 0))
        continue;
      *fp = '\0';
      mutt_str_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_str_strcmp(ext, ".hcache") != 0))
          continue;
        *ext = '\0';
      }
      else
#endif
          if (!S_ISDIR(sb.st_mode))
        continue;

      nntp_data = mutt_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 || SaveUnsubscribed)
        continue;

      nntp_delete_group_cache(nntp_data);
      if (S_ISDIR(sb.st_mode))
      {
        rmdir(file);
        mutt_debug(2, "%s\n", file);
      }
    }
    closedir(dp);
  }
}

Project: neomutt
File: newsrc.c

static struct NntpData *nntp_data_find(struct NntpServer *nserv, const char *group)
{
  struct NntpData *nntp_data = mutt_hash_find(nserv->groups_hash, group);
  if (nntp_data)
    return nntp_data;

  size_t len = strlen(group) + 1;
  /* create NntpData structure and add it to hash */
  nntp_data = mutt_mem_calloc(1, sizeof(struct NntpData) + len);
  nntp_data->group = (char *) nntp_data + sizeof(struct NntpData);
  mutt_str_strfcpy(nntp_data->group, group, len);
  nntp_data->nserv = nserv;
  nntp_data->deleted = true;
  mutt_hash_insert(nserv->groups_hash, nntp_data->group, nntp_data);

  /* add NntpData to list */
  if (nserv->groups_num >= nserv->groups_max)
  {
    nserv->groups_max *= 2;
    mutt_mem_realloc(&nserv->groups_list, nserv->groups_max * sizeof(nntp_data));
  }
  nserv->groups_list[nserv->groups_num++] = nntp_data;

  return nntp_data;
}

Project: neomutt
File: newsrc.c

void nntp_delete_group_cache(struct NntpData *nntp_data)
{
  if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->cacheable)
    return;

#ifdef USE_HCACHE
  char file[PATH_MAX];
  nntp_hcache_namer(nntp_data->group, file, sizeof(file));
  cache_expand(file, sizeof(file), &nntp_data->nserv->conn->account, file);
  unlink(file);
  nntp_data->last_cached = 0;
  mutt_debug(2, "%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)
  {
    mutt_debug(2, "%s/*\n", nntp_data->group);
    mutt_bcache_list(nntp_data->bcache, nntp_bcache_delete, NULL);
    mutt_bcache_close(&nntp_data->bcache);
  }
}

Project: neomutt
File: newsrc.c

const char *nntp_format_str(char *buf, size_t buflen, size_t col, int cols, char op,
                            const char *src, const char *prec, const char *if_str,
                            const char *else_str, unsigned long data, enum FormatFlag flags)
{
  struct NntpServer *nserv = (struct NntpServer *) data;
  struct Account *acct = &nserv->conn->account;
  struct Url url;
  char fn[SHORT_STRING], fmt[SHORT_STRING], *p = NULL;

  switch (op)
  {
    case 'a':
      mutt_account_tourl(acct, &url);
      url_tostring(&url, fn, sizeof(fn), U_PATH);
      p = strchr(fn, '/');
      if (p)
        *p = '\0';
      snprintf(fmt, sizeof(fmt), "%%%ss", prec);
      snprintf(buf, buflen, fmt, fn);
      break;
    case 'p':
      snprintf(fmt, sizeof(fmt), "%%%su", prec);
      snprintf(buf, buflen, fmt, acct->port);
      break;
    case 'P':
      *buf = '\0';
      if (acct->flags & MUTT_ACCT_PORT)
      {
        snprintf(fmt, sizeof(fmt), "%%%su", prec);
        snprintf(buf, buflen, fmt, acct->port);
      }
      break;
    case 's':
      strncpy(fn, acct->host, sizeof(fn) - 1);
      mutt_str_strlower(fn);
      snprintf(fmt, sizeof(fmt), "%%%ss", prec);
      snprintf(buf, buflen, fmt, fn);
      break;
    case 'S':
      mutt_account_tourl(acct, &url);
      url_tostring(&url, fn, sizeof(fn), U_PATH);
      p = strchr(fn, ':');
      if (p)
        *p = '\0';
      snprintf(fmt, sizeof(fmt), "%%%ss", prec);
      snprintf(buf, buflen, fmt, fn);
      break;
    case 'u':
      snprintf(fmt, sizeof(fmt), "%%%ss", prec);
      snprintf(buf, buflen, fmt, acct->user);
      break;
  }
  return src;
}

Project: neomutt
File: newsrc.c

void nntp_hash_destructor(int type, void *obj, intptr_t data)
{
  nntp_data_free(obj);
}

Project: neomutt
File: newsrc.c

void nntp_hcache_update(struct NntpData *nntp_data, header_cache_t *hc)
{
  char buf[16];
  bool old = false;
  void *hdata = NULL;
  anum_t first = 0, last = 0;

  if (!hc)
    return;

  /* fetch previous values of first and last */
  hdata = mutt_hcache_fetch_raw(hc, "index", 5);
  if (hdata)
  {
    mutt_debug(2, "mutt_hcache_fetch index: %s\n", (char *) hdata);
    if (sscanf(hdata, ANUM " " ANUM, &first, &last) == 2)
    {
      old = true;
      nntp_data->last_cached = last;

      /* clean removed headers from cache */
      for (anum_t current = first; current <= last; current++)
      {
        if (current >= nntp_data->first_message && current <= nntp_data->last_message)
          continue;

        snprintf(buf, sizeof(buf), "%u", current);
        mutt_debug(2, "mutt_hcache_delete %s\n", buf);
        mutt_hcache_delete(hc, buf, strlen(buf));
      }
    }
    mutt_hcache_free(hc, &hdata);
  }

  /* store current values of first and last */
  if (!old || nntp_data->first_message != first || nntp_data->last_message != last)
  {
    snprintf(buf, sizeof(buf), "%u %u", nntp_data->first_message, nntp_data->last_message);
    mutt_debug(2, "mutt_hcache_store index: %s\n", buf);
    mutt_hcache_store_raw(hc, "index", 5, buf, strlen(buf));
  }
}

Project: neomutt
File: newsrc.c

void nntp_newsrc_close(struct NntpServer *nserv)
{
  if (!nserv->newsrc_fp)
    return;

  mutt_debug(1, "Unlocking %s\n", nserv->newsrc_file);
  mutt_file_unlock(fileno(nserv->newsrc_fp));
  mutt_file_fclose(&nserv->newsrc_fp);
}

Project: neomutt
File: newsrc.c

void nntp_newsrc_gen_entries(struct Context *ctx)
{
  struct NntpData *nntp_data = ctx->data;
  anum_t last = 0, first = 1;
  bool series;
  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 = mutt_mem_calloc(entries, sizeof(struct NewsrcEntry));
  }

  /* Set up to fake initial sequence from 1 to the article before the
   * first article in our list */
  nntp_data->newsrc_len = 0;
  series = true;
  for (int 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->first_message && !ctx->hdrs[i]->deleted &&
          !ctx->hdrs[i]->read)
      {
        if (nntp_data->newsrc_len >= entries)
        {
          entries *= 2;
          mutt_mem_realloc(&nntp_data->newsrc_ent, entries * sizeof(struct NewsrcEntry));
        }
        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 = false;
      }
    }

    /* search for first read */
    else
    {
      if (ctx->hdrs[i]->deleted || ctx->hdrs[i]->read)
      {
        first = last + 1;
        series = true;
      }
      last = NHDR(ctx->hdrs[i])->article_num;
    }
  }

  if (series && first <= nntp_data->last_loaded)
  {
    if (nntp_data->newsrc_len >= entries)
    {
      entries++;
      mutt_mem_realloc(&nntp_data->newsrc_ent, entries * sizeof(struct NewsrcEntry));
    }
    nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first;
    nntp_data->newsrc_ent[nntp_data->newsrc_len].last = nntp_data->last_loaded;
    nntp_data->newsrc_len++;
  }
  mutt_mem_realloc(&nntp_data->newsrc_ent, nntp_data->newsrc_len * sizeof(struct NewsrcEntry));

  if (save_sort != Sort)
  {
    Sort = save_sort;
    mutt_sort_headers(ctx, 0);
  }
}

Project: neomutt
File: newsrc.c

int nntp_newsrc_parse(struct NntpServer *nserv)
{
  char *line = NULL;
  struct stat sb;

  if (nserv->newsrc_fp)
  {
    /* if we already have a handle, close it and reopen */
    mutt_file_fclose(&nserv->newsrc_fp);
  }
  else
  {
    /* if file doesn't exist, create it */
    nserv->newsrc_fp = mutt_file_fopen(nserv->newsrc_file, "a");
    mutt_file_fclose(&nserv->newsrc_fp);
  }

  /* open .newsrc */
  nserv->newsrc_fp = mutt_file_fopen(nserv->newsrc_file, "r");
  if (!nserv->newsrc_fp)
  {
    mutt_perror(nserv->newsrc_file);
    return -1;
  }

  /* lock it */
  mutt_debug(1, "Locking %s\n", nserv->newsrc_file);
  if (mutt_file_lock(fileno(nserv->newsrc_fp), 0, 1))
  {
    mutt_file_fclose(&nserv->newsrc_fp);
    return -1;
  }

  if (stat(nserv->newsrc_file, &sb))
  {
    mutt_perror(nserv->newsrc_file);
    nntp_newsrc_close(nserv);
    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 = true;
  mutt_debug(1, "Parsing %s\n", nserv->newsrc_file);

  /* .newsrc has been externally modified or hasn't been loaded yet */
  for (unsigned int i = 0; i < nserv->groups_num; i++)
  {
    struct NntpData *nntp_data = nserv->groups_list[i];

    if (!nntp_data)
      continue;

    nntp_data->subscribed = false;
    nntp_data->newsrc_len = 0;
    FREE(&nntp_data->newsrc_ent);
  }

  line = mutt_mem_malloc(sb.st_size + 1);
  while (sb.st_size && fgets(line, sb.st_size + 1, nserv->newsrc_fp))
  {
    char *b = NULL, *h = NULL;
    unsigned int j = 1;
    bool subs = false;

    /* find end of newsgroup name */
    char *p = strpbrk(line, ":!");
    if (!p)
      continue;

    /* ":" - subscribed, "!" - unsubscribed */
    if (*p == ':')
      subs = true;
    *p++ = '\0';

    /* get newsgroup data */
    struct NntpData *nntp_data = nntp_data_find(nserv, line);
    FREE(&nntp_data->newsrc_ent);

    /* count number of entries */
    b = p;
    while (*b)
      if (*b++ == ',')
        j++;
    nntp_data->newsrc_ent = mutt_mem_calloc(j, sizeof(struct NewsrcEntry));
    nntp_data->subscribed = subs;

    /* parse entries */
    j = 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[j].first) == 1 &&
          sscanf(h, ANUM, &nntp_data->newsrc_ent[j].last) == 1)
      {
        j++;
      }
    }
    if (j == 0)
    {
      nntp_data->newsrc_ent[j].first = 1;
      nntp_data->newsrc_ent[j].last = 0;
      j++;
    }
    if (nntp_data->last_message == 0)
      nntp_data->last_message = nntp_data->newsrc_ent[j - 1].last;
    nntp_data->newsrc_len = j;
    mutt_mem_realloc(&nntp_data->newsrc_ent, j * sizeof(struct NewsrcEntry));
    nntp_group_unread_stat(nntp_data);
    mutt_debug(2, "%s\n", nntp_data->group);
  }
  FREE(&line);
  return 1;
}

Project: neomutt
File: newsrc.c

int nntp_newsrc_update(struct NntpServer *nserv)
{
  char *buf = NULL;
  size_t buflen, off;
  int rc = -1;

  if (!nserv)
    return -1;

  buflen = 10 * LONG_STRING;
  buf = mutt_mem_calloc(1, buflen);
  off = 0;

  /* we will generate full newsrc here */
  for (unsigned int i = 0; i < nserv->groups_num; i++)
  {
    struct NntpData *nntp_data = nserv->groups_list[i];

    if (!nntp_data || !nntp_data->newsrc_ent)
      continue;

    /* write newsgroup name */
    if (off + strlen(nntp_data->group) + 3 > buflen)
    {
      buflen *= 2;
      mutt_mem_realloc(&buf, buflen);
    }
    snprintf(buf + off, buflen - off, "%s%c ", nntp_data->group,
             nntp_data->subscribed ? ':' : '!');
    off += strlen(buf + off);

    /* write entries */
    for (unsigned int j = 0; j < nntp_data->newsrc_len; j++)
    {
      if (off + LONG_STRING > buflen)
      {
        buflen *= 2;
        mutt_mem_realloc(&buf, buflen);
      }
      if (j)
        buf[off++] = ',';
      if (nntp_data->newsrc_ent[j].first == nntp_data->newsrc_ent[j].last)
        snprintf(buf + off, buflen - off, "%u", nntp_data->newsrc_ent[j].first);
      else if (nntp_data->newsrc_ent[j].first < nntp_data->newsrc_ent[j].last)
      {
        snprintf(buf + off, buflen - off, "%u-%u",
                 nntp_data->newsrc_ent[j].first, nntp_data->newsrc_ent[j].last);
      }
      off += strlen(buf + off);
    }
    buf[off++] = '\n';
  }
  buf[off] = '\0';

  /* newrc being fully rewritten */
  mutt_debug(1, "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);
    }
  }
  FREE(&buf);
  return rc;
}

Project: neomutt
File: pop.c

static int fetch_message(char *line, void *file)
{
  FILE *f = (FILE *) file;

  fputs(line, f);
  if (fputc('\n', f) == EOF)
    return -1;

  return 0;
}

Project: neomutt
File: pop.c

static int pop_check_mailbox(struct Context *ctx, int *index_hint)
{
  int ret;
  struct PopData *pop_data = (struct PopData *) ctx->data;

  if ((pop_data->check_time + PopCheckinterval) > time(NULL))
    return 0;

  pop_logout(ctx);

  mutt_socket_close(pop_data->conn);

  if (pop_open_connection(pop_data) < 0)
    return -1;

  ctx->size = pop_data->size;

  mutt_message(_("Checking for new messages..."));

  ret = pop_fetch_headers(ctx);
  pop_clear_cache(pop_data);

  if (ret < 0)
    return -1;

  if (ret > 0)
    return MUTT_NEW_MAIL;

  return 0;
}

Project: neomutt
File: pop.c

static void pop_clear_cache(struct PopData *pop_data)
{
  if (!pop_data->clear_cache)
    return;

  mutt_debug(1, "delete cached messages\n");

  for (int i = 0; i < POP_CACHE_LEN; i++)
  {
    if (pop_data->cache[i].path)
    {
      unlink(pop_data->cache[i].path);
      FREE(&pop_data->cache[i].path);
    }
  }
}

Project: neomutt
File: pop.c

static int pop_close_mailbox(struct Context *ctx)
{
  struct PopData *pop_data = (struct PopData *) ctx->data;

  if (!pop_data)
    return 0;

  pop_logout(ctx);

  if (pop_data->status != POP_NONE)
    mutt_socket_close(pop_data->conn);

  pop_data->status = POP_NONE;

  pop_data->clear_cache = true;
  pop_clear_cache(pop_data);

  if (!pop_data->conn->data)
    mutt_socket_free(pop_data->conn);

  mutt_bcache_close(&pop_data->bcache);

  return 0;
}

Project: neomutt
File: pop.c

static header_cache_t *pop_hcache_open(struct PopData *pop_data, const char *path)
{
  struct Url url;
  char p[LONG_STRING];

  if (!pop_data || !pop_data->conn)
    return mutt_hcache_open(HeaderCache, path, NULL);

  mutt_account_tourl(&pop_data->conn->account, &url);
  url.path = HC_FNAME;
  url_tostring(&url, p, sizeof(p), U_PATH);
  return mutt_hcache_open(HeaderCache, p, pop_hcache_namer);
}

Project: neomutt
File: pop.c

static int pop_open_mailbox(struct Context *ctx)
{
  char buf[PATH_MAX];
  struct Connection *conn = NULL;
  struct Account acct;
  struct PopData *pop_data = NULL;
  struct Url url;

  if (pop_parse_path(ctx->path, &acct))
  {
    mutt_error(_("%s is an invalid POP path"), ctx->path);
    return -1;
  }

  mutt_account_tourl(&acct, &url);
  url.path = NULL;
  url_tostring(&url, buf, sizeof(buf), 0);
  conn = mutt_conn_find(NULL, &acct);
  if (!conn)
    return -1;

  FREE(&ctx->path);
  FREE(&ctx->realpath);
  ctx->path = mutt_str_strdup(buf);
  ctx->realpath = mutt_str_strdup(ctx->path);

  pop_data = mutt_mem_calloc(1, sizeof(struct PopData));
  pop_data->conn = conn;
  ctx->data = pop_data;

  if (pop_open_connection(pop_data) < 0)
    return -1;

  conn->data = pop_data;
  pop_data->bcache = mutt_bcache_open(&acct, NULL);

  /* init (hard-coded) ACL rights */
  memset(ctx->rights, 0, sizeof(ctx->rights));
  mutt_bit_set(ctx->rights, MUTT_ACL_SEEN);
  mutt_bit_set(ctx->rights, MUTT_ACL_DELETE);
#ifdef USE_HCACHE
  /* flags are managed using header cache, so it only makes sense to
   * enable them in that case */
  mutt_bit_set(ctx->rights, MUTT_ACL_WRITE);
#endif

  while (true)
  {
    if (pop_reconnect(ctx) < 0)
      return -1;

    ctx->size = pop_data->size;

    mutt_message(_("Fetching list of messages..."));

    const int ret = pop_fetch_headers(ctx);

    if (ret >= 0)
      return 0;

    if (ret < -1)
    {
      mutt_sleep(2);
      return -1;
    }
  }
}