Exemplo n.º 1
0
void test_unichar(void)
{
	static const char overlong_utf8[] = "\xf8\x80\x95\x81\xa1";
	static const char collate_in[] = "\xc3\xbc \xc2\xb3";
	static const char collate_exp[] = "U\xcc\x88 3";
	buffer_t *collate_out;
	unichar_t chr, chr2;
	string_t *str = t_str_new(16);

	test_begin("unichars");
	for (chr = 0; chr <= 0x10ffff; chr++) {
		str_truncate(str, 0);
		uni_ucs4_to_utf8_c(chr, str);
		test_assert(uni_utf8_str_is_valid(str_c(str)));
		test_assert(uni_utf8_get_char(str_c(str), &chr2) > 0);
		test_assert(chr2 == chr);
	}

	collate_out = buffer_create_dynamic(default_pool, 32);
	uni_utf8_to_decomposed_titlecase(collate_in, sizeof(collate_in),
					 collate_out);
	test_assert(!strcmp(collate_out->data, collate_exp));
	buffer_free(&collate_out);

	test_assert(!uni_utf8_str_is_valid(overlong_utf8));
	test_assert(uni_utf8_get_char(overlong_utf8, &chr2) < 0);
	test_end();

	test_unichar_uni_utf8_strlen();
	test_unichar_uni_utf8_partial_strlen_n();
}
Exemplo n.º 2
0
void test_unichar(void)
{
	static const char overlong_utf8[] = "\xf8\x80\x95\x81\xa1";
	static const char collate_in[] = "\xc3\xbc \xc2\xb3";
	static const char collate_exp[] = "U\xcc\x88 3";
	buffer_t *collate_out;
	unichar_t chr, chr2;
	string_t *str = t_str_new(16);

	test_begin("unichars encode/decode");
	for (chr = 0; chr <= 0x10ffff; chr++) {
		/* The bottom 6 bits should be irrelevant to code coverage,
		   only test 000000, 111111, and something in between. */
		if ((chr & 63) == 1)
			chr += rand() % 62; /* After 0, somewhere between 1 and 62 */
		else if ((chr & 63) > 0 && (chr & 63) < 63)
			chr |= 63; /* After random, straight to 63 */

		str_truncate(str, 0);
		uni_ucs4_to_utf8_c(chr, str);
		test_assert(uni_utf8_str_is_valid(str_c(str)));
		test_assert(uni_utf8_get_char(str_c(str), &chr2) == (int)uni_utf8_char_bytes(*str_data(str)));
		test_assert(chr2 == chr);

		if ((chr & 0x63) == 0) {
			unsigned int utf8len = uni_utf8_char_bytes(*str_c(str));

			/* virtually truncate the byte string */
			while (--utf8len > 0)
				test_assert(uni_utf8_get_char_n(str_c(str), utf8len, &chr2) == 0);

			utf8len = uni_utf8_char_bytes(*str_c(str));

			/* actually truncate the byte stream */
			while (--utf8len > 0) {
				str_truncate(str, utf8len);
				test_assert(!uni_utf8_str_is_valid(str_c(str)));
				test_assert(uni_utf8_get_char(str_c(str), &chr2) == 0);
			}
		}
	}
	test_end();

	test_begin("unichar collation");
	collate_out = buffer_create_dynamic(default_pool, 32);
	uni_utf8_to_decomposed_titlecase(collate_in, sizeof(collate_in),
					 collate_out);
	test_assert(!strcmp(collate_out->data, collate_exp));
	buffer_free(&collate_out);

	test_assert(!uni_utf8_str_is_valid(overlong_utf8));
	test_assert(uni_utf8_get_char(overlong_utf8, &chr2) < 0);
	test_end();

	test_unichar_uni_utf8_strlen();
	test_unichar_uni_utf8_partial_strlen_n();
}
Exemplo n.º 3
0
static void
dsync_fix_mailbox_name(struct mail_namespace *ns, string_t *vname,
		       char alt_char)
{
	const char *old_vname;
	char *p, list_sep = mailbox_list_get_hierarchy_sep(ns->list);
	guid_128_t guid;

	/* replace control chars */
	for (p = str_c_modifiable(vname); *p != '\0'; p++) {
		if ((unsigned char)*p < ' ')
			*p = alt_char;
	}
	/* make it valid UTF8 */
	if (!uni_utf8_str_is_valid(str_c(vname))) {
		old_vname = t_strdup(str_c(vname));
		str_truncate(vname, 0);
		if (uni_utf8_get_valid_data((const void *)old_vname,
					    strlen(old_vname), vname))
			i_unreached();
	}
	if (dsync_is_valid_name(ns, str_c(vname)))
		return;

	/* 1) change any real separators to alt separators (this wouldn't
	   be necessary with listescape, but don't bother detecting it) */
	if (list_sep != mail_namespace_get_sep(ns)) {
		for (p = str_c_modifiable(vname); *p != '\0'; p++) {
			if (*p == list_sep)
				*p = alt_char;
		}
		if (dsync_is_valid_name(ns, str_c(vname)))
			return;
	}
	/* 2) '/' characters aren't valid without listescape */
	if (mail_namespace_get_sep(ns) != '/' && list_sep != '/') {
		for (p = str_c_modifiable(vname); *p != '\0'; p++) {
			if (*p == '/')
				*p = alt_char;
		}
		if (dsync_is_valid_name(ns, str_c(vname)))
			return;
	}
	/* 3) probably some reserved name (e.g. dbox-Mails) */
	str_insert(vname, ns->prefix_len, "_");
	if (dsync_is_valid_name(ns, str_c(vname)))
		return;

	/* 4) name is too long? just give up and generate a unique name */
	guid_128_generate(guid);
	str_truncate(vname, 0);
	str_append(vname, ns->prefix);
	str_append(vname, guid_128_to_string(guid));
	i_assert(dsync_is_valid_name(ns, str_c(vname)));
}
Exemplo n.º 4
0
static int ext_fileinto_operation_execute
(const struct sieve_runtime_env *renv, sieve_size_t *address)
{
	struct sieve_side_effects_list *slist = NULL;
	string_t *folder;
	bool folder_literal;
	bool trace = sieve_runtime_trace_active(renv, SIEVE_TRLVL_ACTIONS);
	int ret = 0;

	/*
	 * Read operands
	 */

	/* Optional operands (side effects only) */
	if ( sieve_action_opr_optional_read(renv, address, NULL, &ret, &slist) != 0 )
		return ret;

	/* Folder operand */
	if ( (ret=sieve_opr_string_read_ex
		(renv, address, "folder", FALSE, &folder, &folder_literal)) <= 0 )
		return ret;

	/*
	 * Perform operation
	 */

	if ( trace ) {
		sieve_runtime_trace(renv, 0, "fileinto action");
		sieve_runtime_trace_descend(renv);
	}

	if ( !folder_literal && !uni_utf8_str_is_valid(str_c(folder)) ) {
		sieve_runtime_error(renv, NULL,
			"folder name specified for fileinto command is not utf-8: %s",
			str_c(folder));
		return SIEVE_EXEC_FAILURE;
	}


	if ( trace ) {
		sieve_runtime_trace(renv, 0, "store message in mailbox `%s'",
			str_sanitize(str_c(folder), 80));
	}

	/* Add action to result */
	if ( sieve_act_store_add_to_result
		(renv, slist, str_c(folder)) < 0 )
		return SIEVE_EXEC_FAILURE;

	sieve_message_snapshot(renv->msgctx);

	return SIEVE_EXEC_OK;
}
Exemplo n.º 5
0
static bool namespace_settings_check(void *_set, pool_t pool ATTR_UNUSED,
				     const char **error_r)
{
	struct mail_namespace_settings *ns = _set;
	struct mail_namespace_settings *const *namespaces;
	const char *name;
	unsigned int i, count;

	name = ns->prefix != NULL ? ns->prefix : "";

	if (ns->separator[0] != '\0' && ns->separator[1] != '\0') {
		*error_r = t_strdup_printf("Namespace '%s': "
			"Hierarchy separator must be only one character long",
			name);
		return FALSE;
	}
	if (!uni_utf8_str_is_valid(name)) {
		*error_r = t_strdup_printf("Namespace prefix not valid UTF8: %s",
					   name);
		return FALSE;
	}

	if (ns->alias_for != NULL && !ns->disabled) {
		if (array_is_created(&ns->user_set->namespaces)) {
			namespaces = array_get(&ns->user_set->namespaces,
					       &count);
		} else {
			namespaces = NULL;
			count = 0;
		}
		for (i = 0; i < count; i++) {
			if (strcmp(namespaces[i]->prefix, ns->alias_for) == 0)
				break;
		}
		if (i == count) {
			*error_r = t_strdup_printf(
				"Namespace '%s': alias_for points to "
				"unknown namespace: %s", name, ns->alias_for);
			return FALSE;
		}
		if (namespaces[i]->alias_for != NULL) {
			*error_r = t_strdup_printf(
				"Namespace '%s': alias_for chaining isn't "
				"allowed: %s -> %s", name, ns->alias_for,
				namespaces[i]->alias_for);
			return FALSE;
		}
	}
	return TRUE;
}
void doveadm_sieve_cmd_scriptnames_check(const char *const args[])
{
	unsigned int i;

	for (i = 0; args[i] != NULL; i++) {
		if (!uni_utf8_str_is_valid(args[i])) {
			i_fatal_status(EX_DATAERR,
				"Sieve script name not valid UTF-8: %s", args[i]);
		}
		if ( !sieve_script_name_is_valid(args[i]) ) {
			i_fatal_status(EX_DATAERR,
				"Sieve script name not valid: %s", args[i]);
		}
	}
}
Exemplo n.º 7
0
static bool mailbox_settings_check(void *_set, pool_t pool,
				   const char **error_r)
{
	struct mailbox_settings *set = _set;

	if (!uni_utf8_str_is_valid(set->name)) {
		*error_r = t_strdup_printf("mailbox %s: name isn't valid UTF-8",
					   set->name);
		return FALSE;
	}
	if (*set->special_use != '\0') {
		if (!mailbox_special_use_check(set, pool, error_r))
			return FALSE;
	}
	return TRUE;
}
Exemplo n.º 8
0
static bool
snarf_box_find(struct mail_user *user, struct mailbox_list **list_r,
	       const char **name_r)
{
	struct mail_namespace *snarf_ns;
	const char *snarf_name;

	snarf_name = mail_user_plugin_getenv(user, "snarf");
	if (snarf_name == NULL)
		return FALSE;
	if (!uni_utf8_str_is_valid(snarf_name)) {
		i_error("snarf: Mailbox name not UTF-8: %s", snarf_name);
		return FALSE;
	}

	snarf_ns = mail_namespace_find(user->namespaces, snarf_name);
	*list_r = snarf_ns->list;
	*name_r = snarf_name;
	return TRUE;
}
Exemplo n.º 9
0
bool rfc2822_header_field_body_verify
(const char *field_body, unsigned int len, bool allow_crlf, bool allow_utf8)
{
	const unsigned char *p = (const unsigned char *)field_body;
	const unsigned char *pend = p + len;
	bool is8bit = FALSE;

	/* RFC5322:
	 *
	 * unstructured    =  (*([FWS] VCHAR) *WSP)
	 * VCHAR           =  %x21-7E
	 * FWS             =  ([*WSP CRLF] 1*WSP) /   ; Folding white space
	 * WSP             =  SP / HTAB               ; White space
	 */

	while ( p < pend ) {
		if ( *p < 0x20 ) {
			if ( (*p == '\r' || *p == '\n') ) {
				if ( !allow_crlf )
					return FALSE;
			} else if ( *p != '\t' ) {
				return FALSE;
			}
		}

		if ( !is8bit && *p > 127 ) {
			if ( !allow_utf8 )
				return FALSE;

			is8bit = TRUE;
		}

		p++;
	}

	if ( is8bit && !uni_utf8_str_is_valid(field_body) ) {
		return FALSE;
	}

	return TRUE;
}
Exemplo n.º 10
0
static struct mail_search_arg *
human_search_mailbox(struct mail_search_build_context *ctx)
{
	struct mail_search_arg *sarg;

	sarg = mail_search_build_str(ctx, SEARCH_MAILBOX);
	if (sarg == NULL)
		return NULL;

	if (strchr(sarg->value.str, '*') != NULL ||
	    strchr(sarg->value.str, '%') != NULL)
		sarg->type = SEARCH_MAILBOX_GLOB;

	if (!uni_utf8_str_is_valid(sarg->value.str)) {
		ctx->_error = p_strconcat(ctx->pool,
			"Mailbox name not valid UTF-8: ",
			sarg->value.str, NULL);
		return NULL;
	}
	return sarg;
}
Exemplo n.º 11
0
static int
mailbox_list_subscription_fill_one(struct mailbox_list *list,
				   struct mailbox_list *src_list,
				   const char *name)
{
	struct mail_namespace *ns, *default_ns = list->ns;
	struct mail_namespace *namespaces = default_ns->user->namespaces;
	struct mailbox_node *node;
	const char *vname, *ns_name, *error;
	unsigned int len;
	bool created;

	/* default_ns is whatever namespace we're currently listing.
	   if we have e.g. prefix="" and prefix=pub/ namespaces with
	   pub/ namespace having subscriptions=no, we want to:

	   1) when listing "" namespace we want to skip over any names
	   that begin with pub/. */
	if (src_list->ns->prefix_len == 0)
		ns_name = name;
	else {
		/* we could have two-level namespace: ns/ns2/ */
		ns_name = t_strconcat(src_list->ns->prefix, name, NULL);
	}
	ns = mail_namespace_find_unsubscribable(namespaces, ns_name);
	if (ns != NULL && ns != default_ns) {
		if (ns->prefix_len > 0)
			return 0;
		/* prefix="" namespace=no : catching this is basically the
		   same as not finding any namespace. */
		ns = NULL;
	}

	/* 2) when listing pub/ namespace, skip over entries that don't
	   begin with pub/. */
	if (ns == NULL &&
	    (default_ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0)
		return 0;

	/* When listing shared namespace's subscriptions, we need to
	   autocreate all the visible child namespaces. their subscriptions
	   are listed later. */
	if (ns != NULL && mail_namespace_is_shared_user_root(ns)) {
		/* we'll need to get the namespace autocreated.
		   one easy way is to just ask to join a reference and
		   pattern */
		(void)mailbox_list_join_refpattern(ns->list, ns_name, "");
	}

	/* When listing pub/ namespace, skip over the namespace
	   prefix in the name. the rest of the name is storage_name. */
	if (ns == NULL)
		ns = default_ns;
	else if (strncmp(ns_name, ns->prefix, ns->prefix_len) == 0) {
		ns_name += ns->prefix_len;
		name = ns_name;
	} else {
		/* "pub" entry - this shouldn't be possible normally, because
		   it should be saved as "pub/", but handle it anyway */
		i_assert(strncmp(ns_name, ns->prefix, ns->prefix_len-1) == 0 &&
			 ns_name[ns->prefix_len-1] == '\0');
		name = ns_name = "";
	}

	len = strlen(name);
	if (len > 0 && name[len-1] == mail_namespace_get_sep(ns)) {
		/* entry ends with hierarchy separator, remove it.
		   this exists mainly for backwards compatibility with old
		   Dovecot versions and non-Dovecot software that added them */
		name = t_strndup(name, len-1);
	}

	if (!mailbox_list_is_valid_name(list, name, &error)) {
		/* we'll only get into trouble if we show this */
		return -1;
	} else {
		vname = mailbox_list_get_vname(list, name);
		if (!uni_utf8_str_is_valid(vname))
			return -1;
		node = mailbox_tree_get(list->subscriptions, vname, &created);
		node->flags = MAILBOX_SUBSCRIBED;
	}
	return 0;
}
Exemplo n.º 12
0
static int
virtual_config_parse_line(struct virtual_parse_context *ctx, const char *line,
                          const char **error_r)
{
    struct mail_user *user = ctx->mbox->storage->storage.user;
    struct virtual_backend_box *bbox;
    const char *p;
    bool no_wildcards = FALSE;

    if (*line == ' ' || *line == '\t') {
        /* continues the previous search rule */
        if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) {
            *error_r = "Search rule without a mailbox";
            return -1;
        }
        while (*line == ' ' || *line == '\t') line++;
        str_append_c(ctx->rule, ' ');
        str_append(ctx->rule, line);
        return 0;
    }
    /* if there is no rule yet, it means we want the previous mailboxes
       to use the rule that comes later */
    if (str_len(ctx->rule) > 0) {
        if (virtual_config_add_rule(ctx, error_r) < 0)
            return -1;
    }
    if (!uni_utf8_str_is_valid(line)) {
        *error_r = t_strdup_printf("Mailbox name not UTF-8: %s",
                                   line);
        return -1;
    }

    /* new mailbox. the search args are added to it later. */
    bbox = p_new(ctx->pool, struct virtual_backend_box, 1);
    bbox->virtual_mbox = ctx->mbox;
    if (strcasecmp(line, "INBOX") == 0)
        line = "INBOX";
    bbox->name = p_strdup(ctx->pool, line);
    switch (bbox->name[0]) {
    case '+':
        bbox->name++;
        bbox->clear_recent = TRUE;
        break;
    case '-':
        bbox->name++;
        bbox->negative_match = TRUE;
        break;
    case '!':
        /* save messages here */
        if (ctx->mbox->save_bbox != NULL) {
            *error_r = "Multiple save mailboxes defined";
            return -1;
        }
        bbox->name++;
        ctx->mbox->save_bbox = bbox;
        no_wildcards = TRUE;
        break;
    }
    if (bbox->name[0] == '/') {
        /* [+-!]/metadata entry:value */
        if ((p = strchr(bbox->name, ':')) == NULL) {
            *error_r = "':' separator missing between metadata entry name and value";
            return -1;
        }
        bbox->metadata_entry = p_strdup_until(ctx->pool, bbox->name, p++);
        bbox->metadata_value = p;
        if (!imap_metadata_verify_entry_name(bbox->metadata_entry, error_r))
            return -1;
        no_wildcards = TRUE;
    }

    if (!no_wildcards &&
            (strchr(bbox->name, '*') != NULL ||
             strchr(bbox->name, '%') != NULL)) {
        bbox->glob = imap_match_init(ctx->pool, bbox->name, TRUE, ctx->sep);
        ctx->have_wildcards = TRUE;
    }
    if (bbox->metadata_entry == NULL) {
        /* now that the prefix characters have been processed,
           find the namespace */
        bbox->ns = strcasecmp(bbox->name, "INBOX") == 0 ?
                   mail_namespace_find_inbox(user->namespaces) :
                   mail_namespace_find(user->namespaces, bbox->name);
        if (bbox->ns == NULL) {
            *error_r = t_strdup_printf("Namespace not found for %s",
                                       bbox->name);
            return -1;
        }
        if (strcmp(bbox->name, ctx->mbox->box.vname) == 0) {
            *error_r = "Virtual mailbox can't point to itself";
            return -1;
        }
        ctx->have_mailbox_defines = TRUE;
    }

    array_append(&ctx->mbox->backend_boxes, &bbox, 1);
    return 0;
}
Exemplo n.º 13
0
static int
maildir_fill_readdir_entry(struct maildir_list_iterate_context *ctx,
			   struct imap_match_glob *glob, const struct dirent *d,
			   bool update_only)
{
	struct mailbox_list *list = ctx->ctx.list;
	const char *fname, *storage_name, *vname;
	enum mailbox_info_flags flags;
	enum imap_match_result match;
	struct mailbox_node *node;
	bool created;
	int ret;

	fname = d->d_name;
	if (fname[0] == ctx->prefix_char)
		storage_name = fname + 1;
	else {
		if (ctx->prefix_char != '\0' || fname[0] == '.')
			return 0;
		storage_name = fname;
	}

	/* skip . and .. */
	if (fname[0] == '.' &&
	    (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0')))
		return 0;

	vname = mailbox_list_get_vname(list, storage_name);
	if (!uni_utf8_str_is_valid(vname)) {
		/* the storage_name is completely invalid, rename it to
		   something more sensible. we could do this for all names that
		   aren't valid mUTF-7, but that might lead to accidents in
		   future when UTF-8 storage names are used */
		const char *src = t_strdup_printf("%s/%s", ctx->dir, fname);
		string_t *destvname = t_str_new(128);
		string_t *dest = t_str_new(128);

		(void)uni_utf8_get_valid_data((const void *)fname,
					      strlen(fname), destvname);

		str_append(dest, ctx->dir);
		str_append_c(dest, '/');
		(void)imap_utf8_to_utf7(str_c(destvname), dest);

		if (rename(src, str_c(dest)) < 0 && errno != ENOENT)
			i_error("rename(%s, %s) failed: %m", src, str_c(dest));
		/* just skip this in this iteration, we'll see it on the
		   next list */
		return 0;
	}

	/* make sure the pattern matches */
	match = imap_match(glob, vname);
	if ((match & (IMAP_MATCH_YES | IMAP_MATCH_PARENT)) == 0)
		return 0;

	/* check if this is an actual mailbox */
	if (maildir_delete_trash_dir(ctx, fname))
		return 0;

	if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
		ret = mailbox_list_dirent_is_alias_symlink(list, ctx->dir, d);
		if (ret != 0)
			return ret < 0 ? -1 : 0;
	}
	T_BEGIN {
		ret = list->v.get_mailbox_flags(list, ctx->dir, fname,
				mailbox_list_get_file_type(d), &flags);
	} T_END;
	if (ret <= 0)
		return ret;

	/* we know the children flags ourself, so ignore if any of
	   them were set. */
	flags &= ~(MAILBOX_NOINFERIORS | MAILBOX_CHILDREN | MAILBOX_NOCHILDREN);

	if ((match & IMAP_MATCH_PARENT) != 0)
		maildir_fill_parents(ctx, glob, update_only, vname);
	else {
		created = FALSE;
		node = update_only ?
			mailbox_tree_lookup(ctx->tree_ctx, vname) :
			mailbox_tree_get(ctx->tree_ctx, vname, &created);

		if (node != NULL) {
			if (created)
				node->flags = MAILBOX_NOCHILDREN;
			else
				node->flags &= ~MAILBOX_NONEXISTENT;
			if (!update_only)
				node->flags |= MAILBOX_MATCHED;
			node->flags |= flags;
			node_fix_parents(node);
		} else {
			i_assert(update_only);
			maildir_set_children(ctx, vname);
		}
	}
	return 0;
}
Exemplo n.º 14
0
static int
virtual_config_parse_line(struct virtual_parse_context *ctx, const char *line,
			  const char **error_r)
{
	struct mail_user *user = ctx->mbox->storage->storage.user;
	struct virtual_backend_box *bbox;
	const char *name;

	if (*line == ' ' || *line == '\t') {
		/* continues the previous search rule */
		if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) {
			*error_r = "Search rule without a mailbox";
			return -1;
		}
		while (*line == ' ' || *line == '\t') line++;
		str_append_c(ctx->rule, ' ');
		str_append(ctx->rule, line);
		return 0;
	}
	/* if there is no rule yet, it means we want the previous mailboxes
	   to use the rule that comes later */
	if (str_len(ctx->rule) > 0) {
		if (virtual_config_add_rule(ctx, error_r) < 0)
			return -1;
	}

	/* new mailbox. the search args are added to it later. */
	bbox = p_new(ctx->pool, struct virtual_backend_box, 1);
	if (strcasecmp(line, "INBOX") == 0)
		line = "INBOX";
	bbox->name = p_strdup(ctx->pool, line);
	if (*line == '-' || *line == '+' || *line == '!') line++;
	bbox->ns = strcasecmp(line, "INBOX") == 0 ?
		mail_namespace_find_inbox(user->namespaces) :
		mail_namespace_find(user->namespaces, line);
	if (!uni_utf8_str_is_valid(bbox->name)) {
		*error_r = t_strdup_printf("Mailbox name not UTF-8: %s",
					   bbox->name);
		return -1;
	}
	if (bbox->ns == NULL) {
		*error_r = t_strdup_printf("Namespace not found for %s",
					   bbox->name);
		return -1;
	}
	if (bbox->name[0] == '+') {
		bbox->name++;
		bbox->clear_recent = TRUE;
	}

	if (strchr(bbox->name, '*') != NULL ||
	    strchr(bbox->name, '%') != NULL) {
		name = bbox->name[0] == '-' ? bbox->name + 1 : bbox->name;
		bbox->glob = imap_match_init(ctx->pool, name, TRUE, ctx->sep);
		ctx->have_wildcards = TRUE;
	} else if (bbox->name[0] == '!') {
		/* save messages here */
		if (ctx->mbox->save_bbox != NULL) {
			*error_r = "Multiple save mailboxes defined";
			return -1;
		}
		bbox->name++;
		ctx->mbox->save_bbox = bbox;
	}
	ctx->have_mailbox_defines = TRUE;
	array_append(&ctx->mbox->backend_boxes, &bbox, 1);
	return 0;
}