Commit message: Prevent possible IMAP MITM via PREAUTH response.

This is similar to CVE-2014-2567 and CVE-2020-12398.  STARTTLS is not
allowed in the Authenticated state, so previously Mutt would
implicitly mark the connection as authenticated and skip any
encryption checking/enabling.

No credentials are exposed, but it does allow messages to be sent to
an attacker, via postpone or fcc'ing for instance.

Reuse the $ssl_starttls quadoption "in reverse" to prompt to abort the
connection if it is unencrypted.

Thanks very much to Damian Poddebniak and Fabian Ising from the
Münster University of Applied Sciences for reporting this issue, and
their help in testing the fix.
CVE: CVE-2020-14093
CWE: CWE-200
Vulnerability description: Mutt before 1.14.3 allows an IMAP fcc/postpone man-in-the-middle attack via a PREAUTH response.

Project: mutt
File: imap.c

int imap_open_connection (IMAP_DATA* idata)
{
  if (mutt_socket_open (idata->conn) < 0)
    return -1;

  idata->state = IMAP_CONNECTED;

  if (imap_cmd_step (idata) != IMAP_CMD_OK)
  {
    imap_close_connection (idata);
    return -1;
  }

  if (ascii_strncasecmp ("* OK", idata->buf, 4) == 0)
  {
    if (ascii_strncasecmp ("* OK [CAPABILITY", idata->buf, 16)
        && imap_check_capabilities (idata))
      goto bail;
#if defined(USE_SSL)
    /* Attempt STARTTLS if available and desired. */
    if (!idata->conn->ssf && (option(OPTSSLFORCETLS) ||
                              mutt_bit_isset (idata->capabilities, STARTTLS)))
    {
      int rc;

      if (option(OPTSSLFORCETLS))
        rc = MUTT_YES;
      else if ((rc = query_quadoption (OPT_SSLSTARTTLS,
                                       _("Secure connection with TLS?"))) == -1)
	goto err_close_conn;
      if (rc == MUTT_YES)
      {
	if ((rc = imap_exec (idata, "STARTTLS", IMAP_CMD_FAIL_OK)) == -1)
	  goto bail;
	if (rc != -2)
	{
	  if (mutt_ssl_starttls (idata->conn))
	  {
	    mutt_error (_("Could not negotiate TLS connection"));
	    mutt_sleep (1);
	    goto err_close_conn;
	  }
	  else
	  {
	    /* RFC 2595 demands we recheck CAPABILITY after TLS completes. */
	    if (imap_exec (idata, "CAPABILITY", 0))
	      goto bail;
	  }
	}
      }
    }

    if (option(OPTSSLFORCETLS) && ! idata->conn->ssf)
    {
      mutt_error _("Encrypted connection unavailable");
      mutt_sleep (1);
      goto err_close_conn;
    }
#endif
  }
  else if (ascii_strncasecmp ("* PREAUTH", idata->buf, 9) == 0)
  {
#if defined(USE_SSL)
    /* An unencrypted PREAUTH response is most likely a MITM attack.
     * Require a confirmation. */
    if (!idata->conn->ssf)
    {
      if (option(OPTSSLFORCETLS) ||
          (query_quadoption (OPT_SSLSTARTTLS,
                             _("Abort unencrypted PREAUTH connection?")) != MUTT_NO))
      {
        mutt_error _("Encrypted connection unavailable");
        mutt_sleep (1);
        goto err_close_conn;
      }
    }
#endif

    idata->state = IMAP_AUTHENTICATED;
    if (imap_check_capabilities (idata) != 0)
      goto bail;
    FREE (&idata->capstr);
  }
  else
  {
    imap_error ("imap_open_connection()", idata->buf);
    goto bail;
  }

  return 0;

#if defined(USE_SSL)
err_close_conn:
  imap_close_connection (idata);
#endif
bail:
  FREE (&idata->capstr);
  return -1;
}

Project: mutt
File: imap.c

static void imap_set_flag (IMAP_DATA* idata, int aclbit, int flag,
                           const char *str, char *flags, size_t flsize)
{
  if (mutt_bit_isset (idata->ctx->rights, aclbit))
    if (flag && imap_has_flag (idata->flags, str))
      safe_strcat (flags, flsize, str);
}

Project: mutt
File: imap.c

int imap_sync_mailbox (CONTEXT* ctx, int expunge, int* index_hint)
{
  IMAP_DATA* idata;
  CONTEXT* appendctx = NULL;
  HEADER* h;
  HEADER** hdrs = NULL;
  int oldsort;
  int n;
  int rc, quickdel_rc = 0;

  idata = (IMAP_DATA*) ctx->data;

  if (idata->state < IMAP_SELECTED)
  {
    dprint (2, (debugfile, "imap_sync_mailbox: no mailbox selected\n"));
    return -1;
  }

  /* This function is only called when the calling code expects the context
   * to be changed. */
  imap_allow_reopen (ctx);

  if ((rc = imap_check_mailbox (ctx, index_hint, 0)) != 0)
    goto out;

  /* if we are expunging anyway, we can do deleted messages very quickly... */
  if (expunge && mutt_bit_isset (ctx->rights, MUTT_ACL_DELETE))
  {
    if ((quickdel_rc = imap_exec_msgset (idata,
                                         "UID STORE", "+FLAGS.SILENT (\\Deleted)",
                                         MUTT_DELETED, 1, 0)) < 0)
    {
      rc = quickdel_rc;
      mutt_error (_("Expunge failed"));
      mutt_sleep (1);
      goto out;
    }

    if (quickdel_rc > 0)
    {
      /* mark these messages as unchanged so second pass ignores them. Done
       * here so BOGUS UW-IMAP 4.7 SILENT FLAGS updates are ignored. */
      for (n = 0; n < ctx->msgcount; n++)
        if (ctx->hdrs[n]->deleted && ctx->hdrs[n]->changed)
          ctx->hdrs[n]->active = 0;
      if (!ctx->quiet)
        mutt_message (_("Marking %d messages deleted..."), quickdel_rc);
    }
  }

#if USE_HCACHE
  idata->hcache = imap_hcache_open (idata, NULL);
#endif

  /* save messages with real (non-flag) changes */
  for (n = 0; n < ctx->msgcount; n++)
  {
    h = ctx->hdrs[n];

    if (h->deleted)
    {
      imap_cache_del (idata, h);
#if USE_HCACHE
      imap_hcache_del (idata, HEADER_DATA(h)->uid);
#endif
    }

    if (h->active && h->changed)
    {
#if USE_HCACHE
      imap_hcache_put (idata, h);
#endif
      /* if the message has been rethreaded or attachments have been deleted
       * we delete the message and reupload it.
       * This works better if we're expunging, of course. */
      /* TODO: why the h->env check? */
      if ((h->env && h->env->changed) || h->attach_del)
      {
        /* NOTE and TODO:
         *
         * The mx_open_mailbox() in append mode below merely hijacks an existing
         * idata; it doesn't reset idata->ctx.  imap_append_message() ends up
         * using (borrowing) the same idata we are using.
         *
         * Right after the APPEND operation finishes, the server can send an
         * EXISTS notifying of the new message.  Then, while still inside
         * imap_append_message(), imap_cmd_step() -> imap_cmd_finish() will
         * call imap_read_headers() to download those (because the idata's
         * reopen_allow is set).
         *
         * The imap_read_headers() will open (and clobber) the idata->hcache we
         * just opened above, then close it.
         *
         * The easy and less dangerous fix done here (for a stable branch bug
         * fix) is to close and reopen the header cache around the operation.
         *
         * A better fix would be allowing idata->hcache reuse.  When that is
         * done, the close/reopen in read_headers_condstore_qresync_updates()
         * can also be removed. */
#if USE_HCACHE
        imap_hcache_close (idata);
#endif
        if (!ctx->quiet)
          mutt_message (_("Saving changed messages... [%d/%d]"), n+1,
                        ctx->msgcount);
	if (!appendctx)
	  appendctx = mx_open_mailbox (ctx->path, MUTT_APPEND | MUTT_QUIET, NULL);
	if (!appendctx)
	  dprint (1, (debugfile, "imap_sync_mailbox: Error opening mailbox in append mode\n"));
	else
	  _mutt_save_message (h, appendctx, 1, 0, 0);
        /* TODO: why the check for h->env?  Is this possible? */
        if (h->env)
          h->env->changed = 0;
#if USE_HCACHE
        idata->hcache = imap_hcache_open (idata, NULL);
#endif
      }
    }
  }

#if USE_HCACHE
  imap_hcache_close (idata);
#endif

  /* presort here to avoid doing 10 resorts in imap_exec_msgset.
   *
   * Note: sync_helper() may trigger an imap_exec() if the queue fills
   * up.  Because IMAP_REOPEN_ALLOW is set, this may result in new
   * messages being downloaded or an expunge being processed.  For new
   * messages this would both result in memory corruption (since we're
   * alloc'ing msgcount instead of hdrmax pointers) and data loss of
   * the new messages.  For an expunge, the restored hdrs would point
   * to headers that have been freed.
   *
   * Since reopen is allowed, we could change this to call
   * mutt_sort_headers() before and after instead, but the double sort
   * is noticeably slower.
   *
   * So instead, just turn off reopen_allow for the duration of the
   * swapped hdrs.  The imap_exec() below flushes the queue out,
   * giving the opportunity to process any reopen events.
   */
  imap_disallow_reopen (ctx);
  oldsort = Sort;
  if (Sort != SORT_ORDER)
  {
    hdrs = ctx->hdrs;
    ctx->hdrs = safe_malloc (ctx->msgcount * sizeof (HEADER*));
    memcpy (ctx->hdrs, hdrs, ctx->msgcount * sizeof (HEADER*));

    Sort = SORT_ORDER;
    qsort (ctx->hdrs, ctx->msgcount, sizeof (HEADER*),
           mutt_get_sort_func (SORT_ORDER));
  }

  rc = sync_helper (idata, MUTT_ACL_DELETE, MUTT_DELETED, "\\Deleted");
  if (rc >= 0)
    rc |= sync_helper (idata, MUTT_ACL_WRITE, MUTT_FLAG, "\\Flagged");
  if (rc >= 0)
    rc |= sync_helper (idata, MUTT_ACL_WRITE, MUTT_OLD, "Old");
  if (rc >= 0)
    rc |= sync_helper (idata, MUTT_ACL_SEEN, MUTT_READ, "\\Seen");
  if (rc >= 0)
    rc |= sync_helper (idata, MUTT_ACL_WRITE, MUTT_REPLIED, "\\Answered");

  if ((oldsort != Sort) || hdrs)
  {
    Sort = oldsort;
    FREE (&ctx->hdrs);
    ctx->hdrs = hdrs;
  }
  imap_allow_reopen (ctx);

  /* Flush the queued flags if any were changed in sync_helper.
   * The real (non-flag) changes loop might have flushed quickdel_rc
   * queued commands, so we double check the cmdbuf isn't empty. */
  if (((rc > 0) || (quickdel_rc > 0)) && mutt_buffer_len (idata->cmdbuf))
    if (imap_exec (idata, NULL, 0) != IMAP_CMD_OK)
      rc = -1;

  if (rc < 0)
  {
    if (ctx->closing)
    {
      if (mutt_yesorno (_("Error saving flags. Close anyway?"), 0) == MUTT_YES)
      {
        rc = 0;
        idata->state = IMAP_AUTHENTICATED;
        goto out;
      }
    }
    else
      mutt_error _("Error saving flags");
    rc = -1;
    goto out;
  }

  /* Update local record of server state to reflect the synchronization just
   * completed.  imap_read_headers always overwrites hcache-origin flags, so
   * there is no need to mutate the hcache after flag-only changes. */
  for (n = 0; n < ctx->msgcount; n++)
  {
    HEADER_DATA(ctx->hdrs[n])->deleted = ctx->hdrs[n]->deleted;
    HEADER_DATA(ctx->hdrs[n])->flagged = ctx->hdrs[n]->flagged;
    HEADER_DATA(ctx->hdrs[n])->old = ctx->hdrs[n]->old;
    HEADER_DATA(ctx->hdrs[n])->read = ctx->hdrs[n]->read;
    HEADER_DATA(ctx->hdrs[n])->replied = ctx->hdrs[n]->replied;
    ctx->hdrs[n]->changed = 0;
  }
  ctx->changed = 0;

  /* We must send an EXPUNGE command if we're not closing. */
  if (expunge && !(ctx->closing) &&
      mutt_bit_isset(ctx->rights, MUTT_ACL_DELETE))
  {
    if (!ctx->quiet)
      mutt_message _("Expunging messages from server...");
    /* Set expunge bit so we don't get spurious reopened messages */
    idata->reopen |= IMAP_EXPUNGE_EXPECTED;
    if (imap_exec (idata, "EXPUNGE", 0) != 0)
    {
      idata->reopen &= ~IMAP_EXPUNGE_EXPECTED;
      imap_error (_("imap_sync_mailbox: EXPUNGE failed"), idata->buf);
      rc = -1;
      goto out;
    }
    idata->reopen &= ~IMAP_EXPUNGE_EXPECTED;
  }

  if (expunge && ctx->closing)
  {
    imap_exec (idata, "CLOSE", IMAP_CMD_QUEUE);
    idata->state = IMAP_AUTHENTICATED;
  }

  if (option (OPTMESSAGECACHECLEAN))
    imap_cache_clean (idata);

  rc = 0;

out:
  imap_disallow_reopen (ctx);
  if (appendctx)
  {
    mx_fastclose_mailbox (appendctx);
    FREE (&appendctx);
  }
  return rc;
}

Project: mutt
File: imap.c

void imap_logout (IMAP_DATA** idata)
{
  /* we set status here to let imap_handle_untagged know we _expect_ to
   * receive a bye response (so it doesn't freak out and close the conn) */
  (*idata)->status = IMAP_BYE;
  imap_cmd_start (*idata, "LOGOUT");
  if (ImapPollTimeout <= 0 ||
      mutt_socket_poll ((*idata)->conn, ImapPollTimeout) != 0)
  {
    while (imap_cmd_step (*idata) == IMAP_CMD_CONTINUE)
      ;
  }

  mutt_socket_close ((*idata)->conn);
  imap_free_idata (idata);
}

Project: mutt
File: imap.c

int imap_access (const char* path)
{
  IMAP_DATA* idata;
  IMAP_MBOX mx;
  char buf[LONG_STRING*2];
  char mailbox[LONG_STRING];
  char mbox[LONG_STRING];
  int rc;

  if (imap_parse_path (path, &mx))
    return -1;

  if (!(idata = imap_conn_find (&mx.account,
                                option (OPTIMAPPASSIVE) ? MUTT_IMAP_CONN_NONEW : 0)))
  {
    FREE (&mx.mbox);
    return -1;
  }

  imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
  if (!*mailbox)
    strfcpy (mailbox, "INBOX", sizeof (mailbox));

  /* we may already be in the folder we're checking */
  if (!ascii_strcmp(idata->mailbox, mx.mbox))
  {
    FREE (&mx.mbox);
    return 0;
  }
  FREE (&mx.mbox);

  if (imap_mboxcache_get (idata, mailbox, 0))
  {
    dprint (3, (debugfile, "imap_access: found %s in cache\n", mailbox));
    return 0;
  }

  imap_munge_mbox_name (idata, mbox, sizeof (mbox), mailbox);

  if (mutt_bit_isset (idata->capabilities, IMAP4REV1))
    snprintf (buf, sizeof (buf), "STATUS %s (UIDVALIDITY)", mbox);
  else if (mutt_bit_isset (idata->capabilities, STATUS))
    snprintf (buf, sizeof (buf), "STATUS %s (UID-VALIDITY)", mbox);
  else
  {
    dprint (2, (debugfile, "imap_access: STATUS not supported?\n"));
    return -1;
  }

  if ((rc = imap_exec (idata, buf, IMAP_CMD_FAIL_OK)) < 0)
  {
    dprint (1, (debugfile, "imap_access: Can't check STATUS of %s\n", mbox));
    return rc;
  }

  return 0;
}

Project: mutt
File: imap.c

imap_complete_hosts (char *dest, size_t len)
{
  BUFFY* mailbox;
  CONNECTION* conn;
  int rc = -1;
  size_t matchlen;

  matchlen = mutt_strlen (dest);
  for (mailbox = Incoming; mailbox; mailbox = mailbox->next)
  {
    if (!mutt_strncmp (dest, mutt_b2s (mailbox->pathbuf), matchlen))
    {
      if (rc)
      {
        strfcpy (dest, mutt_b2s (mailbox->pathbuf), len);
        rc = 0;
      }
      else
        longest_common_prefix (dest, mutt_b2s (mailbox->pathbuf), matchlen, len);
    }
  }

  for (conn = mutt_socket_head (); conn; conn = conn->next)
  {
    ciss_url_t url;
    char urlstr[LONG_STRING];

    if (conn->account.type != MUTT_ACCT_TYPE_IMAP)
      continue;

    mutt_account_tourl (&conn->account, &url);
    /* FIXME: how to handle multiple users on the same host? */
    url.user = NULL;
    url.path = NULL;
    url_ciss_tostring (&url, urlstr, sizeof (urlstr), 0);
    if (!mutt_strncmp (dest, urlstr, matchlen))
    {
      if (rc)
      {
        strfcpy (dest, urlstr, len);
        rc = 0;
      }
      else
        longest_common_prefix (dest, urlstr, matchlen, len);
    }
  }

  return rc;
}

Project: mutt
File: imap.c

int imap_fast_trash (CONTEXT* ctx, char* dest)
{
  IMAP_DATA* idata;
  char mbox[LONG_STRING];
  char mmbox[LONG_STRING];
  char prompt[LONG_STRING];
  int n, rc;
  IMAP_MBOX mx;
  int triedcreate = 0;
  BUFFER *sync_cmd = NULL;
  int err_continue = MUTT_NO;

  idata = (IMAP_DATA*) ctx->data;

  if (imap_parse_path (dest, &mx))
  {
    dprint (1, (debugfile, "imap_fast_trash: bad destination %s\n", dest));
    return -1;
  }

  /* check that the save-to folder is in the same account */
  if (!mutt_account_match (&(CTX_DATA->conn->account), &(mx.account)))
  {
    dprint (3, (debugfile, "imap_fast_trash: %s not same server as %s\n",
                dest, ctx->path));
    return 1;
  }

  imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox));
  if (!*mbox)
    strfcpy (mbox, "INBOX", sizeof (mbox));
  imap_munge_mbox_name (idata, mmbox, sizeof (mmbox), mbox);

  sync_cmd = mutt_buffer_new ();
  for (n = 0; n < ctx->msgcount; n++)
  {
    if (ctx->hdrs[n]->active && ctx->hdrs[n]->changed &&
        ctx->hdrs[n]->deleted && !ctx->hdrs[n]->purge)
    {
      rc = imap_sync_message_for_copy (idata, ctx->hdrs[n], sync_cmd, &err_continue);
      if (rc < 0)
      {
        dprint (1, (debugfile, "imap_fast_trash: could not sync\n"));
        goto out;
      }
    }
  }

  /* loop in case of TRYCREATE */
  do
  {
    rc = imap_exec_msgset (idata, "UID COPY", mmbox, MUTT_TRASH, 0, 0);
    if (!rc)
    {
      dprint (1, (debugfile, "imap_fast_trash: No messages to trash\n"));
      rc = -1;
      goto out;
    }
    else if (rc < 0)
    {
      dprint (1, (debugfile, "could not queue copy\n"));
      goto out;
    }
    else if (!ctx->quiet)
      mutt_message (_("Copying %d messages to %s..."), rc, mbox);

    /* let's get it on */
    rc = imap_exec (idata, NULL, IMAP_CMD_FAIL_OK);
    if (rc == -2)
    {
      if (triedcreate)
      {
        dprint (1, (debugfile, "Already tried to create mailbox %s\n", mbox));
        break;
      }
      /* bail out if command failed for reasons other than nonexistent target */
      if (ascii_strncasecmp (imap_get_qualifier (idata->buf), "[TRYCREATE]", 11))
        break;
      dprint (3, (debugfile, "imap_fast_trash: server suggests TRYCREATE\n"));
      snprintf (prompt, sizeof (prompt), _("Create %s?"), mbox);
      if (option (OPTCONFIRMCREATE) && mutt_yesorno (prompt, 1) < 1)
      {
        mutt_clear_error ();
        goto out;
      }
      if (imap_create_mailbox (idata, mbox) < 0)
        break;
      triedcreate = 1;
    }
  }
  while (rc == -2);

  if (rc != 0)
  {
    imap_error ("imap_fast_trash", idata->buf);
    goto out;
  }

  rc = 0;

out:
  mutt_buffer_free (&sync_cmd);
  FREE (&mx.mbox);

  return rc < 0 ? -1 : rc;
}

Project: mutt
File: imap.c

longest_common_prefix (char *dest, const char* src, size_t start, size_t dlen)
{
  size_t pos = start;

  while (pos < dlen && dest[pos] && dest[pos] == src[pos])
    pos++;
  dest[pos] = '\0';

  return pos;
}

Project: mutt
File: imap.c

int imap_close_mailbox (CONTEXT* ctx)
{
  IMAP_DATA* idata;
  int i;

  idata = (IMAP_DATA*) ctx->data;
  /* Check to see if the mailbox is actually open */
  if (!idata)
    return 0;

  /* imap_open_mailbox_append() borrows the IMAP_DATA temporarily,
   * just for the connection, but does not set idata->ctx to the
   * open-append ctx.
   *
   * So when these are equal, it means we are actually closing the
   * mailbox and should clean up idata.  Otherwise, we don't want to
   * touch idata - it's still being used.
   */
  if (ctx == idata->ctx)
  {
    if (idata->status != IMAP_FATAL && idata->state >= IMAP_SELECTED)
    {
      /* mx_close_mailbox won't sync if there are no deleted messages
       * and the mailbox is unchanged, so we may have to close here */
      if (!ctx->deleted)
        imap_exec (idata, "CLOSE", IMAP_CMD_QUEUE);
      idata->state = IMAP_AUTHENTICATED;
    }

    idata->reopen = 0;
    FREE (&(idata->mailbox));
    mutt_free_list (&idata->flags);
    idata->ctx = NULL;

    hash_destroy (&idata->uid_hash, NULL);
    FREE (&idata->msn_index);
    idata->msn_index_size = 0;
    idata->max_msn = 0;

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

    mutt_bcache_close (&idata->bcache);
  }

  /* free IMAP part of headers */
  for (i = 0; i < ctx->msgcount; i++)
    /* mailbox may not have fully loaded */
    if (ctx->hdrs[i] && ctx->hdrs[i]->data)
      imap_free_header_data ((IMAP_HEADER_DATA**)&(ctx->hdrs[i]->data));

  return 0;
}

Project: mutt
File: imap.c

static char* imap_get_flags (LIST** hflags, char* s)
{
  LIST* flags;
  char* flag_word;
  char ctmp;

  /* sanity-check string */
  if (ascii_strncasecmp ("FLAGS", s, 5) != 0)
  {
    dprint (1, (debugfile, "imap_get_flags: not a FLAGS response: %s\n",
                s));
    return NULL;
  }
  s += 5;
  SKIPWS(s);
  if (*s != '(')
  {
    dprint (1, (debugfile, "imap_get_flags: bogus FLAGS response: %s\n",
                s));
    return NULL;
  }

  /* create list, update caller's flags handle */
  flags = mutt_new_list();
  *hflags = flags;

  while (*s && *s != ')')
  {
    s++;
    SKIPWS(s);
    flag_word = s;
    while (*s && (*s != ')') && !ISSPACE (*s))
      s++;
    ctmp = *s;
    *s = '\0';
    if (*flag_word)
      mutt_add_list (flags, flag_word);
    *s = ctmp;
  }

  /* note bad flags response */
  if (*s != ')')
  {
    dprint (1, (debugfile,
                "imap_get_flags: Unterminated FLAGS response: %s\n", s));
    mutt_free_list (hflags);

    return NULL;
  }

  s++;

  return s;
}

Project: mutt
File: imap.c

int imap_exec_msgset (IMAP_DATA* idata, const char* pre, const char* post,
                      int flag, int changed, int invert)
{
  HEADER** hdrs = NULL;
  short oldsort;
  BUFFER* cmd;
  int pos;
  int rc;
  int count = 0, reopen_set = 0;

  cmd = mutt_buffer_new ();

  /* Unlike imap_sync_mailbox(), this function can be called when
   * IMAP_REOPEN_ALLOW is not set.  In that case, the caller isn't
   * prepared to handle context changes.  Resorting may not always
   * give the same order, so we must make a copy.
   *
   * See the comment in imap_sync_mailbox() for the dangers of running
   * even queued execs while reopen is set.  To prevent memory
   * corruption and data loss we must disable reopen for the duration
   * of the swapped hdrs.
   */
  if (idata->reopen & IMAP_REOPEN_ALLOW)
  {
    idata->reopen &= ~IMAP_REOPEN_ALLOW;
    reopen_set = 1;
  }
  oldsort = Sort;
  if (Sort != SORT_ORDER)
  {
    hdrs = idata->ctx->hdrs;
    idata->ctx->hdrs = safe_malloc (idata->ctx->msgcount * sizeof (HEADER*));
    memcpy (idata->ctx->hdrs, hdrs, idata->ctx->msgcount * sizeof (HEADER*));

    Sort = SORT_ORDER;
    qsort (idata->ctx->hdrs, idata->ctx->msgcount, sizeof (HEADER*),
           mutt_get_sort_func (SORT_ORDER));
  }

  pos = 0;

  do
  {
    mutt_buffer_clear (cmd);
    mutt_buffer_add_printf (cmd, "%s ", pre);
    rc = imap_make_msg_set (idata, cmd, flag, changed, invert, &pos);
    if (rc > 0)
    {
      mutt_buffer_add_printf (cmd, " %s", post);
      if (imap_exec (idata, cmd->data, IMAP_CMD_QUEUE))
      {
        rc = -1;
        goto out;
      }
      count += rc;
    }
  }
  while (rc > 0);

  rc = count;

out:
  mutt_buffer_free (&cmd);
  if ((oldsort != Sort) || hdrs)
  {
    Sort = oldsort;
    FREE (&idata->ctx->hdrs);
    idata->ctx->hdrs = hdrs;
  }
  if (reopen_set)
    idata->reopen |= IMAP_REOPEN_ALLOW;

  return rc;
}

Project: mutt
File: imap.c

IMAP_STATUS* imap_mboxcache_get (IMAP_DATA* idata, const char* mbox, int create)
{
  LIST* cur;
  IMAP_STATUS* status;
  IMAP_STATUS scache;
#ifdef USE_HCACHE
  header_cache_t *hc = NULL;
  void *puidvalidity = NULL;
  void *puidnext = NULL;
  void *pmodseq = NULL;
#endif

  for (cur = idata->mboxcache; cur; cur = cur->next)
  {
    status = (IMAP_STATUS*)cur->data;

    if (!imap_mxcmp (mbox, status->name))
      return status;
  }
  status = NULL;

  /* lame */
  if (create)
  {
    memset (&scache, 0, sizeof (scache));
    scache.name = (char*)mbox;
    idata->mboxcache = mutt_add_list_n (idata->mboxcache, &scache,
                                        sizeof (scache));
    status = imap_mboxcache_get (idata, mbox, 0);
    status->name = safe_strdup (mbox);
  }

#ifdef USE_HCACHE
  hc = imap_hcache_open (idata, mbox);
  if (hc)
  {
    puidvalidity = mutt_hcache_fetch_raw (hc, "/UIDVALIDITY", imap_hcache_keylen);
    puidnext = mutt_hcache_fetch_raw (hc, "/UIDNEXT", imap_hcache_keylen);
    pmodseq = mutt_hcache_fetch_raw (hc, "/MODSEQ", imap_hcache_keylen);
    if (puidvalidity)
    {
      if (!status)
      {
        mutt_hcache_free ((void **)&puidvalidity);
        mutt_hcache_free ((void **)&puidnext);
        mutt_hcache_free ((void **)&pmodseq);
        mutt_hcache_close (hc);
        return imap_mboxcache_get (idata, mbox, 1);
      }
      memcpy (&status->uidvalidity, puidvalidity, sizeof(unsigned int));

      if (puidnext)
        memcpy (&status->uidnext, puidnext, sizeof(unsigned int));
      else
        status->uidnext = 0;

      if (pmodseq)
        memcpy (&status->modseq, pmodseq, sizeof(unsigned long long));
      else
        status->modseq = 0;
      dprint (3, (debugfile, "mboxcache: hcache uidvalidity %u, uidnext %u, modseq %llu\n",
                  status->uidvalidity, status->uidnext, status->modseq));
    }
    mutt_hcache_free ((void **)&puidvalidity);
    mutt_hcache_free ((void **)&puidnext);
    mutt_hcache_free ((void **)&pmodseq);
    mutt_hcache_close (hc);
  }
#endif

  return status;
}

Project: mutt
File: imap.c

int imap_subscribe (char *path, int subscribe)
{
  IMAP_DATA *idata;
  char buf[LONG_STRING*2];
  char mbox[LONG_STRING];
  int mblen;
  BUFFER err;
  IMAP_MBOX mx;

  if (!mx_is_imap (path) || imap_parse_path (path, &mx) || !mx.mbox)
  {
    mutt_error (_("Bad mailbox name"));
    return -1;
  }
  if (!(idata = imap_conn_find (&(mx.account), 0)))
    goto fail;

  imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
  if (!*buf)
    strfcpy (buf, "INBOX", sizeof (buf));

  if (option (OPTIMAPCHECKSUBSCRIBED))
  {
    mutt_buffer_init (&err);
    err.dsize = STRING;
    err.data = safe_malloc (err.dsize);
    mblen = snprintf (mbox, sizeof (mbox), "%smailboxes ",
                      subscribe ? "" : "un");
    imap_quote_string_and_backquotes (mbox + mblen, sizeof(mbox) - mblen,
                                      path);
    if (mutt_parse_rc_line (mbox, &err))
      dprint (1, (debugfile, "Error adding subscribed mailbox: %s\n", err.data));
    FREE (&err.data);
  }

  if (subscribe)
    mutt_message (_("Subscribing to %s..."), buf);
  else
    mutt_message (_("Unsubscribing from %s..."), buf);
  imap_munge_mbox_name (idata, mbox, sizeof(mbox), buf);

  snprintf (buf, sizeof (buf), "%sSUBSCRIBE %s", subscribe ? "" : "UN", mbox);

  if (imap_exec (idata, buf, 0) < 0)
    goto fail;

  imap_unmunge_mbox_name(idata, mx.mbox);
  if (subscribe)
    mutt_message (_("Subscribed to %s"), mx.mbox);
  else
    mutt_message (_("Unsubscribed from %s"), mx.mbox);
  FREE (&mx.mbox);
  return 0;

fail:
  FREE (&mx.mbox);
  return -1;
}

Project: mutt
File: imap.c

int imap_status (const char* path, int queue)
{
  static int queued = 0;

  IMAP_DATA *idata;
  char buf[LONG_STRING*2];
  char mbox[LONG_STRING];
  IMAP_STATUS* status;

  if (imap_get_mailbox (path, &idata, buf, sizeof (buf)) < 0)
    return -1;

  /* We are in the folder we're polling - just return the mailbox count.
   *
   * Note that imap_mxcmp() converts NULL to "INBOX", so we need to
   * make sure the idata really is open to a folder. */
  if (idata->ctx && !imap_mxcmp (buf, idata->mailbox))
    return idata->ctx->msgcount;
  else if (mutt_bit_isset(idata->capabilities,IMAP4REV1) ||
	   mutt_bit_isset(idata->capabilities,STATUS))
  {
    imap_munge_mbox_name (idata, mbox, sizeof(mbox), buf);
    snprintf (buf, sizeof (buf), "STATUS %s (%s)", mbox, "MESSAGES");
    imap_unmunge_mbox_name (idata, mbox);
  }
  else
    /* Server does not support STATUS, and this is not the current mailbox.
     * There is no lightweight way to check recent arrivals */
    return -1;

  if (queue)
  {
    imap_exec (idata, buf, IMAP_CMD_QUEUE);
    queued = 1;
    return 0;
  }
  else if (!queued)
    imap_exec (idata, buf, 0);

  queued = 0;
  if ((status = imap_mboxcache_get (idata, mbox, 0)))
    return status->messages;

  return 0;
}

Project: mutt
File: imap.c

static int imap_check_mailbox_reopen (CONTEXT *ctx, int *index_hint)
{
  int rc;

  imap_allow_reopen (ctx);
  rc = imap_check_mailbox (ctx, index_hint, 0);
  imap_disallow_reopen (ctx);

  return rc;
}

Project: mutt
File: imap.c

static int compare_flags_for_copy (HEADER* h)
{
  IMAP_HEADER_DATA* hd = (IMAP_HEADER_DATA*)h->data;

  if (h->read != hd->read)
    return 1;
  if (h->old != hd->old)
    return 1;
  if (h->flagged != hd->flagged)
    return 1;
  if (h->replied != hd->replied)
    return 1;

  return 0;
}

Project: mutt
File: imap.c

static int imap_open_mailbox (CONTEXT* ctx)
{
  IMAP_DATA *idata;
  IMAP_STATUS* status;
  char buf[LONG_STRING];
  char bufout[LONG_STRING*2];
  int count = 0;
  IMAP_MBOX mx, pmx;
  int rc;
  const char *condstore;

  if (imap_parse_path (ctx->path, &mx))
  {
    mutt_error (_("%s is an invalid IMAP path"), ctx->path);
    return -1;
  }

  /* we require a connection which isn't currently in IMAP_SELECTED state */
  if (!(idata = imap_conn_find (&(mx.account), MUTT_IMAP_CONN_NOSELECT)))
    goto fail_noidata;

  /* once again the context is new */
  ctx->data = idata;

  /* Clean up path and replace the one in the ctx */
  imap_fix_path (idata, mx.mbox, buf, sizeof (buf));
  if (!*buf)
    strfcpy (buf, "INBOX", sizeof (buf));
  FREE(&(idata->mailbox));
  idata->mailbox = safe_strdup (buf);
  imap_qualify_path (buf, sizeof (buf), &mx, idata->mailbox);

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

  idata->ctx = ctx;

  /* clear mailbox status */
  idata->status = 0;
  memset (idata->ctx->rights, 0, sizeof (idata->ctx->rights));
  idata->newMailCount = 0;
  idata->max_msn = 0;

  if (!ctx->quiet)
    mutt_message (_("Selecting %s..."), idata->mailbox);
  imap_munge_mbox_name (idata, buf, sizeof(buf), idata->mailbox);

  /* pipeline ACL test */
  if (mutt_bit_isset (idata->capabilities, ACL))
  {
    snprintf (bufout, sizeof (bufout), "MYRIGHTS %s", buf);
    imap_exec (idata, bufout, IMAP_CMD_QUEUE);
  }
  /* assume we have all rights if ACL is unavailable */
  else
  {
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_LOOKUP);
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_READ);
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_SEEN);
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_WRITE);
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_INSERT);
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_POST);
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_CREATE);
    mutt_bit_set (idata->ctx->rights, MUTT_ACL_DELETE);
  }
  /* pipeline the postponed count if possible */
  pmx.mbox = NULL;
  if (mx_is_imap (Postponed) && !imap_parse_path (Postponed, &pmx)
      && mutt_account_match (&pmx.account, &mx.account))
    imap_status (Postponed, 1);
  FREE (&pmx.mbox);

#if USE_HCACHE
  if (mutt_bit_isset (idata->capabilities, CONDSTORE) &&
      option (OPTIMAPCONDSTORE))
    condstore = " (CONDSTORE)";
  else
#endif
    condstore = "";

  snprintf (bufout, sizeof (bufout), "%s %s%s",
            ctx->readonly ? "EXAMINE" : "SELECT",
            buf, condstore);

  idata->state = IMAP_SELECTED;

  imap_cmd_start (idata, bufout);

  status = imap_mboxcache_get (idata, idata->mailbox, 1);

  do
  {
    char *pc;

    if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
      break;

    pc = idata->buf + 2;

    /* Obtain list of available flags here, may be overridden by a
     * PERMANENTFLAGS tag in the OK response */
    if (ascii_strncasecmp ("FLAGS", pc, 5) == 0)
    {
      /* don't override PERMANENTFLAGS */
      if (!idata->flags)
      {
	dprint (3, (debugfile, "Getting mailbox FLAGS\n"));
	if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
	  goto fail;
      }
    }
    /* PERMANENTFLAGS are massaged to look like FLAGS, then override FLAGS */
    else if (ascii_strncasecmp ("OK [PERMANENTFLAGS", pc, 18) == 0)
    {
      dprint (3, (debugfile, "Getting mailbox PERMANENTFLAGS\n"));
      /* safe to call on NULL */
      mutt_free_list (&(idata->flags));
      /* skip "OK [PERMANENT" so syntax is the same as FLAGS */
      pc += 13;
      if ((pc = imap_get_flags (&(idata->flags), pc)) == NULL)
	goto fail;
    }
    /* save UIDVALIDITY for the header cache */
    else if (ascii_strncasecmp ("OK [UIDVALIDITY", pc, 14) == 0)
    {
      dprint (3, (debugfile, "Getting mailbox UIDVALIDITY\n"));
      pc += 3;
      pc = imap_next_word (pc);
      if (mutt_atoui (pc, &idata->uid_validity) < 0)
        goto fail;
      status->uidvalidity = idata->uid_validity;
    }
    else if (ascii_strncasecmp ("OK [UIDNEXT", pc, 11) == 0)
    {
      dprint (3, (debugfile, "Getting mailbox UIDNEXT\n"));
      pc += 3;
      pc = imap_next_word (pc);
      if (mutt_atoui (pc, &idata->uidnext) < 0)
        goto fail;
      status->uidnext = idata->uidnext;
    }
    else if (ascii_strncasecmp ("OK [HIGHESTMODSEQ", pc, 17) == 0)
    {
      dprint (3, (debugfile, "Getting mailbox HIGHESTMODSEQ\n"));
      pc += 3;
      pc = imap_next_word (pc);
      if (mutt_atoull (pc, &idata->modseq) < 0)
        goto fail;
      status->modseq = idata->modseq;
    }
    else if (ascii_strncasecmp ("OK [NOMODSEQ", pc, 12) == 0)
    {
      dprint (3, (debugfile, "Mailbox has NOMODSEQ set\n"));
      status->modseq = idata->modseq = 0;
    }
    else
    {
      pc = imap_next_word (pc);
      if (!ascii_strncasecmp ("EXISTS", pc, 6))
      {
	count = idata->newMailCount;
	idata->newMailCount = 0;
      }
    }
  }
  while (rc == IMAP_CMD_CONTINUE);

  if (rc == IMAP_CMD_NO)
  {
    char *s;
    s = imap_next_word (idata->buf); /* skip seq */
    s = imap_next_word (s); /* Skip response */
    mutt_error ("%s", s);
    mutt_sleep (2);
    goto fail;
  }

  if (rc != IMAP_CMD_OK)
    goto fail;

  /* check for READ-ONLY notification */
  if (!ascii_strncasecmp (imap_get_qualifier (idata->buf), "[READ-ONLY]", 11) &&
      !mutt_bit_isset (idata->capabilities, ACL))
  {
    dprint (2, (debugfile, "Mailbox is read-only.\n"));
    ctx->readonly = 1;
  }

#ifdef DEBUG
  /* dump the mailbox flags we've found */
  if (debuglevel > 2)
  {
    if (!idata->flags)
      dprint (3, (debugfile, "No folder flags found\n"));
    else
    {
      LIST* t = idata->flags;

      dprint (3, (debugfile, "Mailbox flags: "));

      t = t->next;
      while (t)
      {
        dprint (3, (debugfile, "[%s] ", t->data));
        t = t->next;
      }
      dprint (3, (debugfile, "\n"));
    }
  }
#endif

  if (!(mutt_bit_isset(idata->ctx->rights, MUTT_ACL_DELETE) ||
        mutt_bit_isset(idata->ctx->rights, MUTT_ACL_SEEN) ||
        mutt_bit_isset(idata->ctx->rights, MUTT_ACL_WRITE) ||
        mutt_bit_isset(idata->ctx->rights, MUTT_ACL_INSERT)))
    ctx->readonly = 1;

  ctx->hdrmax = count;
  ctx->hdrs = safe_calloc (count, sizeof (HEADER *));
  ctx->v2r = safe_calloc (count, sizeof (int));
  ctx->msgcount = 0;

  if (count && (imap_read_headers (idata, 1, count, 1) < 0))
  {
    mutt_error _("Error opening mailbox");
    mutt_sleep (1);
    goto fail;
  }

  imap_disallow_reopen (ctx);

  dprint (2, (debugfile, "imap_open_mailbox: msgcount is %d\n", ctx->msgcount));
  FREE (&mx.mbox);
  return 0;

fail:
  if (idata->state == IMAP_SELECTED)
    idata->state = IMAP_AUTHENTICATED;
fail_noidata:
  FREE (&mx.mbox);
  return -1;
}

Project: mutt
File: imap.c

int imap_buffy_check (int force, int check_stats)
{
  IMAP_DATA* idata;
  IMAP_DATA* lastdata = NULL;
  BUFFY* mailbox;
  char name[LONG_STRING];
  char command[LONG_STRING*2];
  char munged[LONG_STRING];
  int buffies = 0;

  for (mailbox = Incoming; mailbox; mailbox = mailbox->next)
  {
    /* Init newly-added mailboxes */
    if (! mailbox->magic)
    {
      if (mx_is_imap (mutt_b2s (mailbox->pathbuf)))
        mailbox->magic = MUTT_IMAP;
    }

    if (mailbox->magic != MUTT_IMAP)
      continue;

    if (mailbox->nopoll)
      continue;

    if (imap_get_mailbox (mutt_b2s (mailbox->pathbuf), &idata, name, sizeof (name)) < 0)
    {
      mailbox->new = 0;
      continue;
    }

    /* Don't issue STATUS on the selected mailbox, it will be NOOPed or
     * IDLEd elsewhere.
     * idata->mailbox may be NULL for connections other than the current
     * mailbox's, and shouldn't expand to INBOX in that case. #3216. */
    if (idata->mailbox && !imap_mxcmp (name, idata->mailbox))
    {
      mailbox->new = 0;
      continue;
    }

    if (!mutt_bit_isset (idata->capabilities, IMAP4REV1) &&
        !mutt_bit_isset (idata->capabilities, STATUS))
    {
      dprint (2, (debugfile, "Server doesn't support STATUS\n"));
      continue;
    }

    if (lastdata && idata != lastdata)
    {
      /* Send commands to previous server. Sorting the buffy list
       * may prevent some infelicitous interleavings */
      if (imap_exec (lastdata, NULL, IMAP_CMD_FAIL_OK | IMAP_CMD_POLL) == -1)
        dprint (1, (debugfile, "Error polling mailboxes\n"));

      lastdata = NULL;
    }

    if (!lastdata)
      lastdata = idata;

    imap_munge_mbox_name (idata, munged, sizeof (munged), name);
    if (check_stats)
      snprintf (command, sizeof (command),
                "STATUS %s (UIDNEXT UIDVALIDITY UNSEEN RECENT MESSAGES)", munged);
    else
      snprintf (command, sizeof (command),
                "STATUS %s (UIDNEXT UIDVALIDITY UNSEEN RECENT)", munged);

    if (imap_exec (idata, command, IMAP_CMD_QUEUE | IMAP_CMD_POLL) < 0)
    {
      dprint (1, (debugfile, "Error queueing command\n"));
      return 0;
    }
  }

  if (lastdata && (imap_exec (lastdata, NULL, IMAP_CMD_FAIL_OK | IMAP_CMD_POLL) == -1))
  {
    dprint (1, (debugfile, "Error polling mailboxes\n"));
    return 0;
  }

  /* collect results */
  for (mailbox = Incoming; mailbox; mailbox = mailbox->next)
  {
    if (mailbox->magic == MUTT_IMAP && mailbox->new)
      buffies++;
  }

  return buffies;
}

Project: mutt
File: imap.c

static int sync_helper (IMAP_DATA* idata, int right, int flag, const char* name)
{
  int count = 0;
  int rc;
  char buf[LONG_STRING];

  if (!idata->ctx)
    return -1;

  if (!mutt_bit_isset (idata->ctx->rights, right))
    return 0;

  if (right == MUTT_ACL_WRITE && !imap_has_flag (idata->flags, name))
    return 0;

  snprintf (buf, sizeof(buf), "+FLAGS.SILENT (%s)", name);
  if ((rc = imap_exec_msgset (idata, "UID STORE", buf, flag, 1, 0)) < 0)
    return rc;
  count += rc;

  buf[0] = '-';
  if ((rc = imap_exec_msgset (idata, "UID STORE", buf, flag, 1, 1)) < 0)
    return rc;
  count += rc;

  return count;
}

Project: mutt
File: imap.c

int imap_check_mailbox (CONTEXT *ctx, int *index_hint, int force)
{
  /* overload keyboard timeout to avoid many mailbox checks in a row.
   * Most users don't like having to wait exactly when they press a key. */
  IMAP_DATA* idata;
  int result = 0;

  idata = (IMAP_DATA*) ctx->data;

  /* try IDLE first, unless force is set */
  if (!force && option (OPTIMAPIDLE) && mutt_bit_isset (idata->capabilities, IDLE)
      && (idata->state != IMAP_IDLE || time(NULL) >= idata->lastread + ImapKeepalive))
  {
    if (imap_cmd_idle (idata) < 0)
      return -1;
  }
  if (idata->state == IMAP_IDLE)
  {
    while ((result = mutt_socket_poll (idata->conn, 0)) > 0)
    {
      if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE)
      {
        dprint (1, (debugfile, "Error reading IDLE response\n"));
        return -1;
      }
    }
    if (result < 0)
    {
      dprint (1, (debugfile, "Poll failed, disabling IDLE\n"));
      mutt_bit_unset (idata->capabilities, IDLE);
    }
  }

  if ((force ||
       (idata->state != IMAP_IDLE && time(NULL) >= idata->lastread + Timeout))
      && imap_exec (idata, "NOOP", IMAP_CMD_POLL) != 0)
    return -1;

  /* We call this even when we haven't run NOOP in case we have pending
   * changes to process, since we can reopen here. */
  imap_cmd_finish (idata);

  if (idata->check_status & IMAP_EXPUNGE_PENDING)
    result = MUTT_REOPENED;
  else if (idata->check_status & IMAP_NEWMAIL_PENDING)
    result = MUTT_NEW_MAIL;
  else if (idata->check_status & IMAP_FLAGS_PENDING)
    result = MUTT_FLAGS;

  idata->check_status = 0;

  return result;
}

Project: mutt
File: imap.c

int imap_create_mailbox (IMAP_DATA* idata, char* mailbox)
{
  char buf[LONG_STRING*2], mbox[LONG_STRING];

  imap_munge_mbox_name (idata, mbox, sizeof (mbox), mailbox);
  snprintf (buf, sizeof (buf), "CREATE %s", mbox);

  if (imap_exec (idata, buf, 0) != 0)
  {
    mutt_error (_("CREATE failed: %s"), imap_cmd_trailer (idata));
    return -1;
  }

  return 0;
}

Project: mutt
File: imap.c

int imap_read_literal (FILE* fp, IMAP_DATA* idata, unsigned int bytes, progress_t* pbar)
{
  unsigned int pos;
  char c;

  int r = 0;

  dprint (2, (debugfile, "imap_read_literal: reading %ld bytes\n", bytes));

  for (pos = 0; pos < bytes; pos++)
  {
    if (mutt_socket_readchar (idata->conn, &c) != 1)
    {
      dprint (1, (debugfile, "imap_read_literal: error during read, %ld bytes read\n", pos));
      idata->status = IMAP_FATAL;

      return -1;
    }

#if 1
    if (r == 1 && c != '\n')
      fputc ('\r', fp);

    if (c == '\r')
    {
      r = 1;
      continue;
    }
    else
      r = 0;
#endif
    fputc (c, fp);

    if (pbar && !(pos % 1024))
      mutt_progress_update (pbar, pos, -1);
#ifdef DEBUG
    if (debuglevel >= IMAP_LOG_LTRL)
      fputc (c, debugfile);
#endif
  }

  return 0;
}

Project: mutt
File: imap.c

int imap_delete_mailbox (CONTEXT* ctx, IMAP_MBOX mx)
{
  char buf[LONG_STRING*2], mbox[LONG_STRING];
  IMAP_DATA *idata;

  if (!ctx || !ctx->data)
  {
    if (!(idata = imap_conn_find (&mx.account,
                                  option (OPTIMAPPASSIVE) ? MUTT_IMAP_CONN_NONEW : 0)))
    {
      FREE (&mx.mbox);
      return -1;
    }
  }
  else
  {
    idata = ctx->data;
  }

  imap_munge_mbox_name (idata, mbox, sizeof (mbox), mx.mbox);
  snprintf (buf, sizeof (buf), "DELETE %s", mbox);

  if (imap_exec ((IMAP_DATA*) idata, buf, 0) != 0)
    return -1;

  return 0;
}

Project: mutt
File: imap.c

static int imap_compile_search (const pattern_t* pat, BUFFER* buf)
{
  if (! do_search (pat, 0))
    return 0;

  if (pat->not)
    mutt_buffer_addstr (buf, "NOT ");

  if (pat->child)
  {
    int clauses;

    if ((clauses = do_search (pat->child, 1)) > 0)
    {
      const pattern_t* clause = pat->child;

      mutt_buffer_addch (buf, '(');

      while (clauses)
      {
        if (do_search (clause, 0))
        {
          if (pat->op == MUTT_OR && clauses > 1)
            mutt_buffer_addstr (buf, "OR ");
          clauses--;

          if (imap_compile_search (clause, buf) < 0)
            return -1;

          if (clauses)
            mutt_buffer_addch (buf, ' ');

        }
        clause = clause->next;
      }

      mutt_buffer_addch (buf, ')');
    }
  }
  else
  {
    char term[STRING];
    char *delim;

    switch (pat->op)
    {
      case MUTT_HEADER:
        mutt_buffer_addstr (buf, "HEADER ");

        /* extract header name */
        if (! (delim = strchr (pat->p.str, ':')))
        {
          mutt_error (_("Header search without header name: %s"), pat->p.str);
          return -1;
        }
        *delim = '\0';
        imap_quote_string (term, sizeof (term), pat->p.str);
        mutt_buffer_addstr (buf, term);
        mutt_buffer_addch (buf, ' ');

        /* and field */
        *delim = ':';
        delim++;
        SKIPWS(delim);
        imap_quote_string (term, sizeof (term), delim);
        mutt_buffer_addstr (buf, term);
        break;
      case MUTT_BODY:
        mutt_buffer_addstr (buf, "BODY ");
        imap_quote_string (term, sizeof (term), pat->p.str);
        mutt_buffer_addstr (buf, term);
        break;
      case MUTT_WHOLE_MSG:
        mutt_buffer_addstr (buf, "TEXT ");
        imap_quote_string (term, sizeof (term), pat->p.str);
        mutt_buffer_addstr (buf, term);
        break;
    }
  }

  return 0;
}

Project: mutt
File: imap.c

static int imap_check_capabilities (IMAP_DATA* idata)
{
  if (imap_exec (idata, "CAPABILITY", 0) != 0)
  {
    imap_error ("imap_check_capabilities", idata->buf);
    return -1;
  }

  if (!(mutt_bit_isset(idata->capabilities,IMAP4) ||
        mutt_bit_isset(idata->capabilities,IMAP4REV1)))
  {
    mutt_error _("This IMAP server is ancient. Mutt does not work with it.");
    mutt_sleep (2);	/* pause a moment to let the user see the error */

    return -1;
  }

  return 0;
}

Project: mutt
File: imap.c

void imap_mboxcache_free (IMAP_DATA* idata)
{
  LIST* cur;
  IMAP_STATUS* status;

  for (cur = idata->mboxcache; cur; cur = cur->next)
  {
    status = (IMAP_STATUS*)cur->data;

    FREE (&status->name);
  }

  mutt_free_list (&idata->mboxcache);
}

Project: mutt
File: imap.c

static int imap_make_msg_set (IMAP_DATA* idata, BUFFER* buf, int flag,
                              int changed, int invert, int* pos)
{
  HEADER** hdrs = idata->ctx->hdrs;
  int count = 0;	/* number of messages in message set */
  int match = 0;	/* whether current message matches flag condition */
  unsigned int setstart = 0;	/* start of current message range */
  int n;
  int started = 0;

  hdrs = idata->ctx->hdrs;

  for (n = *pos;
       (n < idata->ctx->msgcount) && (mutt_buffer_len (buf) < IMAP_MAX_CMDLEN);
       n++)
  {
    match = 0;
    /* don't include pending expunged messages.
     *
     * TODO: can we unset active in cmd_parse_expunge() and
     * cmd_parse_vanished() instead of checking for index != INT_MAX. */
    if (hdrs[n]->active && (hdrs[n]->index != INT_MAX))
      switch (flag)
      {
        case MUTT_DELETED:
          if (hdrs[n]->deleted != HEADER_DATA(hdrs[n])->deleted)
            match = invert ^ hdrs[n]->deleted;
	  break;
        case MUTT_FLAG:
          if (hdrs[n]->flagged != HEADER_DATA(hdrs[n])->flagged)
            match = invert ^ hdrs[n]->flagged;
	  break;
        case MUTT_OLD:
          if (hdrs[n]->old != HEADER_DATA(hdrs[n])->old)
            match = invert ^ hdrs[n]->old;
	  break;
        case MUTT_READ:
          if (hdrs[n]->read != HEADER_DATA(hdrs[n])->read)
            match = invert ^ hdrs[n]->read;
	  break;
        case MUTT_REPLIED:
          if (hdrs[n]->replied != HEADER_DATA(hdrs[n])->replied)
            match = invert ^ hdrs[n]->replied;
	  break;

        case MUTT_TAG:
	  if (hdrs[n]->tagged)
	    match = 1;
	  break;
        case MUTT_TRASH:
          if (hdrs[n]->deleted && !hdrs[n]->purge)
            match = 1;
	  break;
      }

    if (match && (!changed || hdrs[n]->changed))
    {
      count++;
      if (setstart == 0)
      {
        setstart = HEADER_DATA (hdrs[n])->uid;
        if (started == 0)
	{
	  mutt_buffer_add_printf (buf, "%u", HEADER_DATA (hdrs[n])->uid);
	  started = 1;
	}
        else
	  mutt_buffer_add_printf (buf, ",%u", HEADER_DATA (hdrs[n])->uid);
      }
      /* tie up if the last message also matches */
      else if (n == idata->ctx->msgcount-1)
	mutt_buffer_add_printf (buf, ":%u", HEADER_DATA (hdrs[n])->uid);
    }
    /* End current set if message doesn't match or we've reached the end
     * of the mailbox via inactive messages following the last match. */
    else if (setstart && (hdrs[n]->active || n == idata->ctx->msgcount-1))
    {
      if (HEADER_DATA (hdrs[n-1])->uid > setstart)
	mutt_buffer_add_printf (buf, ":%u", HEADER_DATA (hdrs[n-1])->uid);
      setstart = 0;
    }
  }

  *pos = n;

  return count;
}

Project: mutt
File: imap.c

static int imap_save_to_header_cache (CONTEXT *ctx, HEADER *h)
{
  int rc = 0;
#ifdef USE_HCACHE
  int close_hc = 1;
  IMAP_DATA* idata;

  idata = (IMAP_DATA *)ctx->data;
  if (idata->hcache)
    close_hc = 0;
  else
    idata->hcache = imap_hcache_open (idata, NULL);
  rc = imap_hcache_put (idata, h);
  if (close_hc)
    imap_hcache_close (idata);
#endif
  return rc;
}

Project: mutt
File: imap.c

void imap_expunge_mailbox (IMAP_DATA* idata)
{
  HEADER* h;
  int i, cacheno;
  short old_sort;

#ifdef USE_HCACHE
  idata->hcache = imap_hcache_open (idata, NULL);
#endif

  old_sort = Sort;
  Sort = SORT_ORDER;
  mutt_sort_headers (idata->ctx, 0);

  for (i = 0; i < idata->ctx->msgcount; i++)
  {
    h = idata->ctx->hdrs[i];

    if (h->index == INT_MAX)
    {
      dprint (2, (debugfile, "Expunging message UID %u.\n", HEADER_DATA (h)->uid));

      h->active = 0;
      idata->ctx->size -= h->content->length;

      imap_cache_del (idata, h);
#if USE_HCACHE
      imap_hcache_del (idata, HEADER_DATA(h)->uid);
#endif

      /* free cached body from disk, if necessary */
      cacheno = HEADER_DATA(h)->uid % IMAP_CACHE_LEN;
      if (idata->cache[cacheno].uid == HEADER_DATA(h)->uid &&
	  idata->cache[cacheno].path)
      {
	unlink (idata->cache[cacheno].path);
	FREE (&idata->cache[cacheno].path);
      }

      int_hash_delete (idata->uid_hash, HEADER_DATA(h)->uid, h, NULL);

      imap_free_header_data ((IMAP_HEADER_DATA**)&h->data);
    }
    else
    {
      h->index = i;
      /* Mutt has several places where it turns off h->active as a
       * hack.  For example to avoid FLAG updates, or to exclude from
       * imap_exec_msgset.
       *
       * Unfortunately, when a reopen is allowed and the IMAP_EXPUNGE_PENDING
       * flag becomes set (e.g. a flag update to a modified header),
       * this function will be called by imap_cmd_finish().
       *
       * The mx_update_tables() will free and remove these "inactive" headers,
       * despite that an EXPUNGE was not received for them.
       * This would result in memory leaks and segfaults due to dangling
       * pointers in the msn_index and uid_hash.
       *
       * So this is another hack to work around the hacks.  We don't want to
       * remove the messages, so make sure active is on.
       */
      h->active = 1;
    }
  }

#if USE_HCACHE
  imap_hcache_close (idata);
#endif

  /* We may be called on to expunge at any time. We can't rely on the caller
   * to always know to rethread */
  mx_update_tables (idata->ctx, 0);
  Sort = old_sort;
  mutt_sort_headers (idata->ctx, 1);
}

Project: mutt
File: imap.c

static int imap_open_mailbox_append (CONTEXT *ctx, int flags)
{
  IMAP_DATA *idata;
  char buf[LONG_STRING];
  char mailbox[LONG_STRING];
  IMAP_MBOX mx;
  int rc;

  if (imap_parse_path (ctx->path, &mx))
    return -1;

  /* in APPEND mode, we appear to hijack an existing IMAP connection -
   * ctx is brand new and mostly empty */

  if (!(idata = imap_conn_find (&(mx.account), 0)))
  {
    FREE (&mx.mbox);
    return -1;
  }

  ctx->data = idata;

  imap_fix_path (idata, mx.mbox, mailbox, sizeof (mailbox));
  if (!*mailbox)
    strfcpy (mailbox, "INBOX", sizeof (mailbox));
  FREE (&mx.mbox);

  if ((rc = imap_access (ctx->path)) == 0)
    return 0;

  if (rc == -1)
    return -1;

  snprintf (buf, sizeof (buf), _("Create %s?"), mailbox);
  if (option (OPTCONFIRMCREATE) && mutt_yesorno (buf, 1) < 1)
    return -1;

  if (imap_create_mailbox (idata, mailbox) < 0)
    return -1;

  return 0;
}

Project: mutt
File: imap.c

int imap_has_flag (LIST* flag_list, const char* flag)
{
  if (!flag_list)
    return 0;

  flag_list = flag_list->next;
  while (flag_list)
  {
    if (!ascii_strncasecmp (flag_list->data, flag, strlen (flag_list->data)))
      return 1;

    if (!ascii_strncmp (flag_list->data, "\\*", strlen (flag_list->data)))
      return 1;

    flag_list = flag_list->next;
  }

  return 0;
}

Project: mutt
File: imap.c

int imap_search (CONTEXT* ctx, const pattern_t* pat)
{
  BUFFER buf;
  IMAP_DATA* idata = (IMAP_DATA*)ctx->data;
  int i;

  for (i = 0; i < ctx->msgcount; i++)
    ctx->hdrs[i]->matched = 0;

  if (!do_search (pat, 1))
    return 0;

  mutt_buffer_init (&buf);
  mutt_buffer_addstr (&buf, "UID SEARCH ");
  if (imap_compile_search (pat, &buf) < 0)
  {
    FREE (&buf.data);
    return -1;
  }
  if (imap_exec (idata, buf.data, 0) < 0)
  {
    FREE (&buf.data);
    return -1;
  }

  FREE (&buf.data);
  return 0;
}

Project: mutt
File: imap.c

IMAP_DATA* imap_conn_find (const ACCOUNT* account, int flags)
{
  CONNECTION* conn = NULL;
  ACCOUNT* creds = NULL;
  IMAP_DATA* idata = NULL;
  int new = 0;

  while ((conn = mutt_conn_find (conn, account)))
  {
    if (!creds)
      creds = &conn->account;
    else
      memcpy (&conn->account, creds, sizeof (ACCOUNT));

    idata = (IMAP_DATA*)conn->data;
    if (flags & MUTT_IMAP_CONN_NONEW)
    {
      if (!idata)
      {
        /* This should only happen if we've come to the end of the list */
        mutt_socket_free (conn);
        return NULL;
      }
      else if (idata->state < IMAP_AUTHENTICATED)
        continue;
    }
    if (flags & MUTT_IMAP_CONN_NOSELECT && idata && idata->state >= IMAP_SELECTED)
      continue;
    if (idata && idata->status == IMAP_FATAL)
      continue;
    break;
  }
  if (!conn)
    return NULL; /* this happens when the initial connection fails */

  /* The current connection is a new connection */
  if (!idata)
  {
    idata = imap_new_idata ();
    conn->data = idata;
    idata->conn = conn;
    new = 1;
  }

  if (idata->state == IMAP_DISCONNECTED)
    imap_open_connection (idata);
  if (idata->state == IMAP_CONNECTED)
  {
    if (!imap_authenticate (idata))
    {
      idata->state = IMAP_AUTHENTICATED;
      FREE (&idata->capstr);
      new = 1;
      if (idata->conn->ssf)
	dprint (2, (debugfile, "Communication encrypted at %d bits\n",
		    idata->conn->ssf));
    }
    else
      mutt_account_unsetpass (&idata->conn->account);
  }
  if (new && idata->state == IMAP_AUTHENTICATED)
  {
    /* capabilities may have changed */
    imap_exec (idata, "CAPABILITY", IMAP_CMD_FAIL_OK);

#if defined(USE_ZLIB)
    /* RFC 4978 */
    if (mutt_bit_isset (idata->capabilities, COMPRESS_DEFLATE))
    {
      if (option (OPTIMAPDEFLATE) &&
	  imap_exec (idata, "COMPRESS DEFLATE", IMAP_CMD_FAIL_OK) == 0)
	mutt_zstrm_wrap_conn (idata->conn);
    }
#endif

    /* enable RFC6855, if the server supports that */
    if (mutt_bit_isset (idata->capabilities, ENABLE))
      imap_exec (idata, "ENABLE UTF8=ACCEPT", IMAP_CMD_QUEUE);

    /* enable QRESYNC.  Advertising QRESYNC also means CONDSTORE
     * is supported (even if not advertised), so flip that bit. */
    if (mutt_bit_isset (idata->capabilities, QRESYNC))
    {
      mutt_bit_set (idata->capabilities, CONDSTORE);
      if (option (OPTIMAPQRESYNC))
        imap_exec (idata, "ENABLE QRESYNC", IMAP_CMD_QUEUE);
    }

    /* get root delimiter, '/' as default */
    idata->delim = '/';
    imap_exec (idata, "LIST \"\" \"\"", IMAP_CMD_QUEUE);
    if (option (OPTIMAPCHECKSUBSCRIBED))
      imap_exec (idata, "LSUB \"\" \"*\"", IMAP_CMD_QUEUE);

    /* we may need the root delimiter before we open a mailbox */
    imap_exec (idata, NULL, IMAP_CMD_FAIL_OK);
  }

  if (idata->state < IMAP_AUTHENTICATED)
    return NULL;

  return idata;
}

Project: mutt
File: imap.c

static int imap_get_mailbox (const char* path, IMAP_DATA** hidata, char* buf, size_t blen)
{
  IMAP_MBOX mx;

  if (imap_parse_path (path, &mx))
  {
    dprint (1, (debugfile, "imap_get_mailbox: Error parsing %s\n", path));
    return -1;
  }
  if (!(*hidata = imap_conn_find (&(mx.account), option (OPTIMAPPASSIVE) ? MUTT_IMAP_CONN_NONEW : 0)))
  {
    FREE (&mx.mbox);
    return -1;
  }

  imap_fix_path (*hidata, mx.mbox, buf, blen);
  if (!*buf)
    strfcpy (buf, "INBOX", blen);
  FREE (&mx.mbox);

  return 0;
}

Project: mutt
File: imap.c

int imap_rename_mailbox (IMAP_DATA* idata, IMAP_MBOX* mx, const char* newname)
{
  char oldmbox[LONG_STRING];
  char newmbox[LONG_STRING];
  BUFFER *b;
  int rc = 0;

  imap_munge_mbox_name (idata, oldmbox, sizeof (oldmbox), mx->mbox);
  imap_munge_mbox_name (idata, newmbox, sizeof (newmbox), newname);

  b = mutt_buffer_pool_get ();
  mutt_buffer_printf (b, "RENAME %s %s", oldmbox, newmbox);

  if (imap_exec (idata, mutt_b2s (b), 0) != 0)
    rc = -1;

  mutt_buffer_pool_release (&b);

  return rc;
}

Project: mutt
File: imap.c

void imap_close_connection(IMAP_DATA* idata)
{
  if (idata->state != IMAP_DISCONNECTED)
  {
    mutt_socket_close (idata->conn);
    idata->state = IMAP_DISCONNECTED;
  }
  idata->seqno = idata->nextcmd = idata->lastcmd = idata->status = 0;
  memset (idata->cmds, 0, sizeof (IMAP_COMMAND) * idata->cmdslots);
}

Project: mutt
File: imap.c

void imap_logout_all (void)
{
  CONNECTION* conn;
  CONNECTION* tmp;

  conn = mutt_socket_head ();

  while (conn)
  {
    tmp = conn->next;

    if (conn->account.type == MUTT_ACCT_TYPE_IMAP && conn->fd >= 0)
    {
      mutt_message (_("Closing connection to %s..."), conn->account.host);
      imap_logout ((IMAP_DATA**) (void*) &conn->data);
      mutt_clear_error ();
      mutt_socket_free (conn);
    }

    conn = tmp;
  }
}

Project: mutt
File: imap.c

int imap_complete(char* dest, size_t dlen, const char* path)
{
  IMAP_DATA* idata;
  char list[LONG_STRING];
  char buf[LONG_STRING*2];
  IMAP_LIST listresp;
  char completion[LONG_STRING];
  int clen;
  size_t matchlen = 0;
  int completions = 0;
  IMAP_MBOX mx;
  int rc;

  if (imap_parse_path (path, &mx))
  {
    strfcpy (dest, path, dlen);
    return imap_complete_hosts (dest, dlen);
  }

  /* don't open a new socket just for completion. Instead complete over
   * known mailboxes/hooks/etc */
  if (!(idata = imap_conn_find (&(mx.account), MUTT_IMAP_CONN_NONEW)))
  {
    FREE (&mx.mbox);
    strfcpy (dest, path, dlen);
    return imap_complete_hosts (dest, dlen);
  }

  /* reformat path for IMAP list, and append wildcard */
  /* don't use INBOX in place of "" */
  if (mx.mbox && mx.mbox[0])
    imap_fix_path (idata, mx.mbox, list, sizeof(list));
  else
    list[0] = '\0';

  /* fire off command */
  snprintf (buf, sizeof(buf), "%s \"\" \"%s%%\"",
            option (OPTIMAPLSUB) ? "LSUB" : "LIST", list);

  imap_cmd_start (idata, buf);

  /* and see what the results are */
  strfcpy (completion, NONULL(mx.mbox), sizeof(completion));
  idata->cmdtype = IMAP_CT_LIST;
  idata->cmddata = &listresp;
  do
  {
    listresp.name = NULL;
    rc = imap_cmd_step (idata);

    if (rc == IMAP_CMD_CONTINUE && listresp.name)
    {
      /* if the folder isn't selectable, append delimiter to force browse
       * to enter it on second tab. */
      if (listresp.noselect)
      {
        clen = strlen(listresp.name);
        listresp.name[clen++] = listresp.delim;
        listresp.name[clen] = '\0';
      }
      /* copy in first word */
      if (!completions)
      {
        strfcpy (completion, listresp.name, sizeof(completion));
        matchlen = strlen (completion);
        completions++;
        continue;
      }

      matchlen = longest_common_prefix (completion, listresp.name, 0, matchlen);
      completions++;
    }
  }
  while (rc == IMAP_CMD_CONTINUE);
  idata->cmddata = NULL;

  if (completions)
  {
    /* reformat output */
    imap_qualify_path (dest, dlen, &mx, completion);
    mutt_pretty_mailbox (dest, dlen);

    FREE (&mx.mbox);
    return 0;
  }

  return -1;
}

Project: mutt
File: imap.c

static int imap_open_new_message (MESSAGE *msg, CONTEXT *dest, HEADER *hdr)
{
  BUFFER *tmp = NULL;
  int rc = -1;

  tmp = mutt_buffer_pool_get ();
  mutt_buffer_mktemp (tmp);
  if ((msg->fp = safe_fopen (mutt_b2s (tmp), "w")) == NULL)
  {
    mutt_perror (mutt_b2s (tmp));
    goto cleanup;
  }

  msg->path = safe_strdup (mutt_b2s (tmp));
  rc = 0;

cleanup:
  mutt_buffer_pool_release (&tmp);
  return rc;
}

Project: mutt
File: imap.c

static int do_search (const pattern_t* search, int allpats)
{
  int rc = 0;
  const pattern_t* pat;

  for (pat = search; pat; pat = pat->next)
  {
    switch (pat->op)
    {
      case MUTT_BODY:
      case MUTT_HEADER:
      case MUTT_WHOLE_MSG:
        if (pat->stringmatch)
          rc++;
        break;
      default:
        if (pat->child && do_search (pat->child, 1))
          rc++;
    }

    if (!allpats)
      break;
  }

  return rc;
}

Project: mutt
File: imap.c

int imap_sync_message_for_copy (IMAP_DATA *idata, HEADER *hdr, BUFFER *cmd,
                                int *err_continue)
{
  char flags[LONG_STRING];
  char uid[11];

  if (!compare_flags_for_copy (hdr))
  {
    if (hdr->deleted == HEADER_DATA(hdr)->deleted)
      hdr->changed = 0;
    return 0;
  }

  snprintf (uid, sizeof (uid), "%u", HEADER_DATA(hdr)->uid);
  mutt_buffer_clear (cmd);
  mutt_buffer_addstr (cmd, "UID STORE ");
  mutt_buffer_addstr (cmd, uid);

  flags[0] = '\0';

  imap_set_flag (idata, MUTT_ACL_SEEN, hdr->read, "\\Seen ",
		 flags, sizeof (flags));
  imap_set_flag (idata, MUTT_ACL_WRITE, hdr->old,
                 "Old ", flags, sizeof (flags));
  imap_set_flag (idata, MUTT_ACL_WRITE, hdr->flagged,
		 "\\Flagged ", flags, sizeof (flags));
  imap_set_flag (idata, MUTT_ACL_WRITE, hdr->replied,
		 "\\Answered ", flags, sizeof (flags));
  imap_set_flag (idata, MUTT_ACL_DELETE, HEADER_DATA(hdr)->deleted,
                 "\\Deleted ", flags, sizeof (flags));

  /* now make sure we don't lose custom tags */
  if (mutt_bit_isset (idata->ctx->rights, MUTT_ACL_WRITE))
    imap_add_keywords (flags, hdr, idata->flags, sizeof (flags));

  mutt_remove_trailing_ws (flags);

  /* UW-IMAP is OK with null flags, Cyrus isn't. The only solution is to
   * explicitly revoke all system flags (if we have permission) */
  if (!*flags)
  {
    imap_set_flag (idata, MUTT_ACL_SEEN, 1, "\\Seen ", flags, sizeof (flags));
    imap_set_flag (idata, MUTT_ACL_WRITE, 1, "Old ", flags, sizeof (flags));
    imap_set_flag (idata, MUTT_ACL_WRITE, 1, "\\Flagged ", flags, sizeof (flags));
    imap_set_flag (idata, MUTT_ACL_WRITE, 1, "\\Answered ", flags, sizeof (flags));
    imap_set_flag (idata, MUTT_ACL_DELETE, !HEADER_DATA(hdr)->deleted,
                   "\\Deleted ", flags, sizeof (flags));

    mutt_remove_trailing_ws (flags);

    mutt_buffer_addstr (cmd, " -FLAGS.SILENT (");
  }
  else
    mutt_buffer_addstr (cmd, " FLAGS.SILENT (");

  mutt_buffer_addstr (cmd, flags);
  mutt_buffer_addstr (cmd, ")");

  /* after all this it's still possible to have no flags, if you
   * have no ACL rights */
  if (*flags && (imap_exec (idata, cmd->data, 0) != 0) &&
      err_continue && (*err_continue != MUTT_YES))
  {
    *err_continue = imap_continue ("imap_sync_message: STORE failed",
				   idata->buf);
    if (*err_continue != MUTT_YES)
      return -1;
  }

  if (hdr->deleted == HEADER_DATA(hdr)->deleted)
    hdr->changed = 0;

  return 0;
}