Commit message: mboxlist: fix uninitialised memory use where pattern is "Other Users"
CVE: CVE-2017-14230
CWE: CWE-20
Vulnerability description: In the mboxlist_do_find function in imap/mboxlist.c in Cyrus IMAP before 3.0.4, an off-by-one error in prefix calculation for the LIST command caused use of uninitialized memory, which might allow remote attackers to obtain sensitive information or cause a denial of service (daemon crash) via a 'LIST "" "Other Users"' command.

Project: cyrus-imapd
File: mboxlist.c

static void _write_acl(struct dlist *dl, const char *aclstr)
{
    const char *p, *q;
    struct dlist *al = dlist_newkvlist(dl, "A");

    p = aclstr;

    while (p && *p) {
        char *name,*val;

        q = strchr(p, '\t');
        if (!q) break;

        name = xstrndup(p, q-p);
        q++;

        p = strchr(q, '\t');
        if (p) {
            val = xstrndup(q, p-q);
            p++;
        }
        else
            val = xstrdup(q);

        dlist_setatom(al, name, val);

        free(name);
        free(val);
    }
}

Project: cyrus-imapd
File: mboxlist.c

EXPORTED int mboxlist_createmailbox(const char *name, int mbtype,
                           const char *partition,
                           int isadmin, const char *userid,
                           const struct auth_state *auth_state,
                           int localonly, int forceuser, int dbonly,
                           int notify, struct mailbox **mailboxptr)
{
    int options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS)
                  | OPT_POP3_NEW_UIDL;
    int r;
    struct mailbox *mailbox = NULL;
    uint32_t uidvalidity = 0;

    /* check if a previous deleted mailbox existed */
    mbentry_t *oldmbentry = NULL;
    r = mboxlist_lookup_allow_all(name, &oldmbentry, NULL);
    if (!r && oldmbentry->mbtype == MBTYPE_DELETED) {
        /* then the UIDVALIDITY must be higher than before */
        if (uidvalidity <= oldmbentry->uidvalidity)
            uidvalidity = oldmbentry->uidvalidity+1;
    }
    mboxlist_entry_free(&oldmbentry);

    r = mboxlist_createmailbox_full(name, mbtype, partition,
                                    isadmin, userid, auth_state,
                                    options, uidvalidity, 0, NULL, NULL, localonly,
                                    forceuser, dbonly, &mailbox);

    if (notify && !r) {
        /* send a MailboxCreate event notification */
        struct mboxevent *mboxevent = mboxevent_new(EVENT_MAILBOX_CREATE);
        mboxevent_extract_mailbox(mboxevent, mailbox);
        mboxevent_set_access(mboxevent, NULL, NULL, userid, mailbox->name, 1);

        mboxevent_notify(&mboxevent);
        mboxevent_free(&mboxevent);
    }

    if (mailboxptr && !r) *mailboxptr = mailbox;
    else mailbox_close(&mailbox);

    return r;
}

Project: cyrus-imapd
File: mboxlist.c

EXPORTED int mboxlist_createsync(const char *name, int mbtype,
                        const char *partition,
                        const char *userid, const struct auth_state *auth_state,
                        int options, unsigned uidvalidity,
                        modseq_t highestmodseq,
                        const char *acl, const char *uniqueid,
                        int local_only, struct mailbox **mboxptr)
{
    return mboxlist_createmailbox_full(name, mbtype, partition,
                                       1, userid, auth_state,
                                       options, uidvalidity,
                                       highestmodseq, acl, uniqueid,
                                       local_only, 1, 0, mboxptr);
}

Project: cyrus-imapd
File: mboxlist.c

EXPORTED mbentry_t *mboxlist_entry_create(void)
{
    mbentry_t *ret = xzmalloc(sizeof(mbentry_t));
    /* xxx - initialiser functions here? */
    return ret;
}

Project: cyrus-imapd
File: mboxlist.c

static int mboxlist_find_category(struct find_rock *rock, const char *prefix, size_t len)
{
    int r = 0;
    if (!rock->issubs && !rock->isadmin && !cyrusdb_fetch(rock->db, "$RACL", 5, NULL, NULL, NULL)) {
        /* we're using reverse ACLs */
        struct buf buf = BUF_INITIALIZER;
        strarray_t matches = STRARRAY_INITIALIZER;
        mboxlist_racl_key(rock->mb_category == MBNAME_OTHERUSER, rock->userid, NULL, &buf);
        /* this is the prefix */
        struct raclrock raclrock = { buf.len, &matches };
        /* we only need to look inside the prefix still, but we keep the length
         * in raclrock pointing to the start of the mboxname part of the key so
         * we get correct names in matches */
        if (len) buf_appendmap(&buf, prefix, len);
        r = cyrusdb_foreach(rock->db, buf.s, buf.len, NULL, racl_cb, &raclrock, NULL);
        /* XXX - later we need to sort the array when we've added groups */
        int i;
        for (i = 0; !r && i < strarray_size(&matches); i++) {
            const char *key = strarray_nth(&matches, i);
            r = cyrusdb_forone(rock->db, key, strlen(key), &find_p, &find_cb, rock, NULL);
        }
        strarray_fini(&matches);
    }
    else {
        r = cyrusdb_foreach(rock->db, prefix, len, &find_p, &find_cb, rock, NULL);
    }

    if (r == CYRUSDB_DONE) r = 0;
    return r;
}

Project: cyrus-imapd
File: mboxlist.c

EXPORTED int mboxlist_findsubmulti(struct namespace *namespace,
                                   const strarray_t *patterns, int isadmin,
                                   const char *userid, const struct auth_state *auth_state,
                                   findall_cb *proc, void *rock,
                                   int force)
{
    int r = 0;

    if (!namespace) namespace = mboxname_get_adminnamespace();

    struct find_rock cbrock;
    memset(&cbrock, 0, sizeof(struct find_rock));

    /* open the subscription file that contains the mailboxes the
       user is subscribed to */
    struct db *subs = NULL;
    r = mboxlist_opensubs(userid, &subs);
    if (r) return r;

    cbrock.auth_state = auth_state;
    cbrock.checkmboxlist = !force;
    cbrock.db = subs;
    cbrock.isadmin = isadmin;
    cbrock.issubs = 1;
    cbrock.namespace = namespace;
    cbrock.proc = proc;
    cbrock.procrock = rock;
    cbrock.userid = userid;
    if (userid) {
        const char *domp = strchr(userid, '@');
        if (domp) cbrock.domain = domp + 1;
    }

    r = mboxlist_do_find(&cbrock, patterns);

    mboxlist_closesubs(subs);

    return r;
}

Project: cyrus-imapd
File: mboxlist.c

static int mboxlist_have_admin_rights(const char *rights) {
    int access, have_admin_access;

    cyrus_acl_strtomask(rights, &access);
    have_admin_access = access & ACL_ADMIN;

    return have_admin_access;
}

Project: cyrus-imapd
File: mboxlist.c

static int mboxlist_update_entry(const char *name, const mbentry_t *mbentry, struct txn **txn)
{
    mbentry_t *old = NULL;
    int r = 0;

    mboxlist_mylookup(name, &old, txn, 0); // ignore errors, it will be NULL

    if (!cyrusdb_fetch(mbdb, "$RACL", 5, NULL, NULL, txn)) {
        r = mboxlist_update_racl(name, old, mbentry, txn);
        /* XXX return value here is discarded? */
    }

    if (mbentry) {
        char *mboxent = mboxlist_entry_cstring(mbentry);
        r = cyrusdb_store(mbdb, name, strlen(name), mboxent, strlen(mboxent), txn);
        free(mboxent);

        if (!r && config_auditlog) {
            /* XXX is there a difference between "" and NULL? */
            if (old && strcmpsafe(old->acl, mbentry->acl)) {
                syslog(LOG_NOTICE, "auditlog: acl sessionid=<%s> "
                                   "mailbox=<%s> uniqueid=<%s> "
                                   "oldacl=<%s> acl=<%s>",
                       session_id(),
                       name, mbentry->uniqueid,
                       old->acl, mbentry->acl);
            }
        }
    }
    else {
        r = cyrusdb_delete(mbdb, name, strlen(name), txn, /*force*/1);
    }

    mboxlist_entry_free(&old);
    return r;
}