Example #1
0
static bool cmd_append_finish_parsing(struct client_command_context *cmd)
{
	struct cmd_append_context *ctx = cmd->context;
	enum mailbox_sync_flags sync_flags;
	enum imap_sync_flags imap_flags;
	struct mail_transaction_commit_changes changes;
	unsigned int save_count;
	string_t *msg;
	int ret;

	/* eat away the trailing CRLF */
	cmd->client->input_skip_line = TRUE;

	if (ctx->failed) {
		/* we failed earlier, error message is sent */
		cmd_append_finish(ctx);
		return TRUE;
	}
	if (ctx->count == 0) {
		client_send_command_error(cmd, "Missing message size.");
		cmd_append_finish(ctx);
		return TRUE;
	}

	ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes);
	if (ret < 0) {
		client_send_box_error(cmd, ctx->box);
		cmd_append_finish(ctx);
		return TRUE;
	}

	msg = t_str_new(256);
	save_count = seq_range_count(&changes.saved_uids);
	if (save_count == 0 || changes.no_read_perm) {
		/* not supported by backend (virtual) */
		str_append(msg, "OK Append completed.");
	} else {
		i_assert(ctx->count == save_count);
		str_printfa(msg, "OK [APPENDUID %u ",
			    changes.uid_validity);
		imap_write_seq_range(msg, &changes.saved_uids);
		str_append(msg, "] Append completed.");
	}
	pool_unref(&changes.pool);

	if (ctx->box == cmd->client->mailbox) {
		sync_flags = 0;
		imap_flags = IMAP_SYNC_FLAG_SAFE;
	} else {
		sync_flags = MAILBOX_SYNC_FLAG_FAST;
		imap_flags = 0;
	}

	cmd_append_finish(ctx);
	return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
}
Example #2
0
static void imapc_sync_expunge_finish(struct imapc_sync_context *ctx)
{
	string_t *str;
	enum imapc_capability caps;

	if (array_count(&ctx->expunged_uids) == 0)
		return;

	caps = imapc_client_get_capabilities(ctx->mbox->storage->client);
	if ((caps & IMAPC_CAPABILITY_UIDPLUS) == 0) {
		/* just expunge everything */
		imapc_sync_cmd(ctx, "EXPUNGE");
		return;
	}

	/* build a list of UIDs to expunge */
	str = t_str_new(128);
	str_append(str, "UID EXPUNGE ");
	imap_write_seq_range(str, &ctx->expunged_uids);
	imapc_sync_cmd(ctx, str_c(str));
}
bool mail_search_arg_to_imap(string_t *dest, const struct mail_search_arg *arg,
			     const char **error_r)
{
	unsigned int start_pos;

	if (arg->match_not)
		str_append(dest, "NOT ");
	start_pos = str_len(dest);
	switch (arg->type) {
	case SEARCH_OR:
		if (!mail_search_subargs_to_imap(dest, arg->value.subargs,
						 "OR ", error_r))
			return FALSE;
		break;
	case SEARCH_SUB:
		if (!mail_search_subargs_to_imap(dest, arg->value.subargs,
						 "", error_r))
			return FALSE;
		break;
	case SEARCH_ALL:
		str_append(dest, "ALL");
		break;
	case SEARCH_SEQSET:
		imap_write_seq_range(dest, &arg->value.seqset);
		break;
	case SEARCH_UIDSET:
		str_append(dest, "UID ");
		imap_write_seq_range(dest, &arg->value.seqset);
		break;
	case SEARCH_FLAGS:
		i_assert((arg->value.flags & MAIL_FLAGS_MASK) != 0);
		str_append_c(dest, '(');
		if ((arg->value.flags & MAIL_ANSWERED) != 0)
			str_append(dest, "ANSWERED ");
		if ((arg->value.flags & MAIL_FLAGGED) != 0)
			str_append(dest, "FLAGGED ");
		if ((arg->value.flags & MAIL_DELETED) != 0)
			str_append(dest, "DELETED ");
		if ((arg->value.flags & MAIL_SEEN) != 0)
			str_append(dest, "SEEN ");
		if ((arg->value.flags & MAIL_DRAFT) != 0)
			str_append(dest, "DRAFT ");
		if ((arg->value.flags & MAIL_RECENT) != 0)
			str_append(dest, "RECENT ");
		str_truncate(dest, str_len(dest)-1);
		str_append_c(dest, ')');
		break;
	case SEARCH_KEYWORDS: {
		const struct mail_keywords *kw = arg->value.keywords;
		const ARRAY_TYPE(keywords) *names_arr;
		const char *const *namep;
		unsigned int i;

		if (kw == NULL) {
			/* uninitialized */
			str_printfa(dest, "KEYWORD %s", arg->value.str);
			break;
		}

		names_arr = mail_index_get_keywords(kw->index);

		str_append_c(dest, '(');
		for (i = 0; i < kw->count; i++) {
			namep = array_idx(names_arr, kw->idx[i]);
			if (i > 0)
				str_append_c(dest, ' ');
			str_printfa(dest, "KEYWORD %s", *namep);
		}
		str_append_c(dest, ')');
		break;
	}

	case SEARCH_BEFORE:
		switch (arg->value.date_type) {
		case MAIL_SEARCH_DATE_TYPE_SENT:
			str_append(dest, "SENTBEFORE");
			break;
		case MAIL_SEARCH_DATE_TYPE_RECEIVED:
			str_append(dest, "BEFORE");
			break;
		case MAIL_SEARCH_DATE_TYPE_SAVED:
			str_append(dest, "X-SAVEDBEFORE");
			break;
		}
		if (mail_search_arg_to_imap_date(dest, arg))
			;
		else if (arg->value.date_type != MAIL_SEARCH_DATE_TYPE_RECEIVED ||
			 arg->value.time > ioloop_time) {
			*error_r = t_strdup_printf(
				"SEARCH_BEFORE can't be written as IMAP for timestamp %ld (type=%d, use_tz=%d)",
				(long)arg->value.time, arg->value.date_type,
				(arg->value.search_flags & MAIL_SEARCH_ARG_FLAG_USE_TZ) != 0);
			return FALSE;
		} else {
			str_truncate(dest, start_pos);
			str_printfa(dest, "OLDER %u",
				    (unsigned int)(ioloop_time - arg->value.time + 1));
		}
		break;
	case SEARCH_ON:
		switch (arg->value.date_type) {
		case MAIL_SEARCH_DATE_TYPE_SENT:
			str_append(dest, "SENTON");
			break;
		case MAIL_SEARCH_DATE_TYPE_RECEIVED:
			str_append(dest, "ON");
			break;
		case MAIL_SEARCH_DATE_TYPE_SAVED:
			str_append(dest, "X-SAVEDON");
			break;
		}
		if (!mail_search_arg_to_imap_date(dest, arg)) {
			*error_r = t_strdup_printf(
				"SEARCH_ON can't be written as IMAP for timestamp %ld (type=%d, use_tz=%d)",
				(long)arg->value.time, arg->value.date_type,
				(arg->value.search_flags & MAIL_SEARCH_ARG_FLAG_USE_TZ) != 0);
			return FALSE;
		}
		break;
	case SEARCH_SINCE:
		switch (arg->value.date_type) {
		case MAIL_SEARCH_DATE_TYPE_SENT:
			str_append(dest, "SENTSINCE");
			break;
		case MAIL_SEARCH_DATE_TYPE_RECEIVED:
			str_append(dest, "SINCE");
			break;
		case MAIL_SEARCH_DATE_TYPE_SAVED:
			str_append(dest, "X-SAVEDSINCE");
			break;
		}
		if (mail_search_arg_to_imap_date(dest, arg))
			;
		else if (arg->value.date_type != MAIL_SEARCH_DATE_TYPE_RECEIVED ||
			 arg->value.time >= ioloop_time) {
			*error_r = t_strdup_printf(
				"SEARCH_SINCE can't be written as IMAP for timestamp %ld (type=%d, use_tz=%d)",
				(long)arg->value.time, arg->value.date_type,
				(arg->value.search_flags & MAIL_SEARCH_ARG_FLAG_USE_TZ) != 0);
			return FALSE;
		} else {
			str_truncate(dest, start_pos);
			str_printfa(dest, "YOUNGER %u",
				    (unsigned int)(ioloop_time - arg->value.time));
		}
		break;
	case SEARCH_SMALLER:
		str_printfa(dest, "SMALLER %llu", (unsigned long long)arg->value.size);
		break;
	case SEARCH_LARGER:
		str_printfa(dest, "LARGER %llu", (unsigned long long)arg->value.size);
		break;
	case SEARCH_HEADER:
	case SEARCH_HEADER_ADDRESS:
	case SEARCH_HEADER_COMPRESS_LWSP:
		if (strcasecmp(arg->hdr_field_name, "From") == 0 ||
		    strcasecmp(arg->hdr_field_name, "To") == 0 ||
		    strcasecmp(arg->hdr_field_name, "Cc") == 0 ||
		    strcasecmp(arg->hdr_field_name, "Bcc") == 0 ||
		    strcasecmp(arg->hdr_field_name, "Subject") == 0)
			str_append(dest, t_str_ucase(arg->hdr_field_name));
		else {
			str_append(dest, "HEADER ");
			imap_append_astring(dest, arg->hdr_field_name);
		}
		str_append_c(dest, ' ');
		imap_append_astring(dest, arg->value.str);
		break;

	case SEARCH_BODY:
		str_append(dest, "BODY ");
		imap_append_astring(dest, arg->value.str);
		break;
	case SEARCH_TEXT:
		str_append(dest, "TEXT ");
		imap_append_astring(dest, arg->value.str);
		break;

	/* extensions */
	case SEARCH_MODSEQ: {
		bool extended_output = FALSE;

		str_append(dest, "MODSEQ ");
		if (arg->value.str != NULL) {
			str_printfa(dest, "/flags/%s", arg->value.str);
			extended_output = TRUE;
		} else if (arg->value.flags != 0) {
			str_append(dest, "/flags/");
			imap_write_flags(dest, arg->value.flags, NULL);
			extended_output = TRUE;
		}
		if (extended_output) {
			str_append_c(dest, ' ');
			switch (arg->value.modseq->type) {
			case MAIL_SEARCH_MODSEQ_TYPE_ANY:
				str_append(dest, "all");
				break;
			case MAIL_SEARCH_MODSEQ_TYPE_PRIVATE:
				str_append(dest, "priv");
				break;
			case MAIL_SEARCH_MODSEQ_TYPE_SHARED:
				str_append(dest, "shared");
				break;
			}
			str_append_c(dest, ' ');
		}
		str_printfa(dest, "%llu", (unsigned long long)arg->value.modseq->modseq);
		break;
	}
	case SEARCH_INTHREAD:
		str_append(dest, "INTHREAD ");
		imap_append_astring(dest, mail_thread_type_to_str(arg->value.thread_type));
		str_append_c(dest, ' ');
		if (!mail_search_subargs_to_imap(dest, arg->value.subargs,
						 "", error_r))
			return FALSE;
		break;
	case SEARCH_GUID:
		str_append(dest, "X-GUID ");
		imap_append_astring(dest, arg->value.str);
		break;
	case SEARCH_MAILBOX:
		*error_r = "SEARCH_MAILBOX can't be written as IMAP";
		return FALSE;
	case SEARCH_MAILBOX_GUID:
		*error_r = "SEARCH_MAILBOX_GUID can't be written as IMAP";
		return FALSE;
	case SEARCH_MAILBOX_GLOB:
		str_append(dest, "X-MAILBOX ");
		imap_append_astring(dest, arg->value.str);
		break;
	case SEARCH_REAL_UID:
		str_append(dest, "X-REAL-UID ");
		imap_write_seq_range(dest, &arg->value.seqset);
		break;
	}
	return TRUE;
}
bool cmd_store(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	const struct imap_arg *args;
	struct mail_search_args *search_args;
	struct mail_search_context *search_ctx;
        struct mailbox_transaction_context *t;
	struct mail *mail;
	struct imap_store_context ctx;
	ARRAY_TYPE(seq_range) modified_set, uids;
	enum mailbox_transaction_flags flags = 0;
	enum imap_sync_flags imap_sync_flags = 0;
	const char *set, *reply, *tagged_reply;
	string_t *str;
	int ret;

	if (!client_read_args(cmd, 0, 0, &args))
		return FALSE;

	if (!client_verify_open_mailbox(cmd))
		return TRUE;

	if (!imap_arg_get_atom(args, &set)) {
		client_send_command_error(cmd, "Invalid arguments.");
		return TRUE;
	}
	ret = imap_search_get_seqset(cmd, set, cmd->uid, &search_args);
	if (ret <= 0)
		return ret < 0;

	memset(&ctx, 0, sizeof(ctx));
	ctx.cmd = cmd;
	if (!store_parse_args(&ctx, ++args)) {
		mail_search_args_unref(&search_args);
		return TRUE;
	}

	if (client->mailbox_examined) {
		mail_search_args_unref(&search_args);
		if (ctx.max_modseq < (uint64_t)-1)
			reply = "NO CONDSTORE failed: Mailbox is read-only.";
		else
			reply = "OK Store ignored with read-only mailbox.";
		return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
				(cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
				0, reply);
	}

	if (ctx.silent)
		flags |= MAILBOX_TRANSACTION_FLAG_HIDE;
	if (ctx.max_modseq < (uint64_t)-1) {
		/* update modseqs so we can check them early */
		flags |= MAILBOX_TRANSACTION_FLAG_REFRESH;
	}

	t = mailbox_transaction_begin(client->mailbox, flags);
	search_ctx = mailbox_search_init(t, search_args, NULL,
					 MAIL_FETCH_FLAGS, NULL);
	mail_search_args_unref(&search_args);

	i_array_init(&modified_set, 64);
	if (ctx.max_modseq < (uint32_t)-1) {
		/* STORE UNCHANGEDSINCE is being used */
		mailbox_transaction_set_max_modseq(t, ctx.max_modseq,
						   &modified_set);
	}

	while (mailbox_search_next(search_ctx, &mail)) {
		if (ctx.max_modseq < (uint64_t)-1) {
			/* check early so there's less work for transaction
			   commit if something has to be cancelled */
			if (mail_get_modseq(mail) > ctx.max_modseq) {
				seq_range_array_add(&modified_set, mail->seq);
				continue;
			}
		}
		if (ctx.modify_type == MODIFY_REPLACE || ctx.flags != 0)
			mail_update_flags(mail, ctx.modify_type, ctx.flags);
		if (ctx.modify_type == MODIFY_REPLACE || ctx.keywords != NULL) {
			mail_update_keywords(mail, ctx.modify_type,
					     ctx.keywords);
		}
	}

	if (ctx.keywords != NULL)
		mailbox_keywords_unref(&ctx.keywords);

	ret = mailbox_search_deinit(&search_ctx);
	if (ret < 0)
		mailbox_transaction_rollback(&t);
	 else
		ret = mailbox_transaction_commit(&t);
	if (ret < 0) {
		array_free(&modified_set);
		client_send_box_error(cmd, client->mailbox);
		return TRUE;
	}

	if (array_count(&modified_set) == 0)
		tagged_reply = "OK Store completed.";
	else {
		if (cmd->uid) {
			i_array_init(&uids, array_count(&modified_set)*2);
			mailbox_get_uid_range(client->mailbox, &modified_set,
					      &uids);
			array_free(&modified_set);
			modified_set = uids;
		}
		str = str_new(cmd->pool, 256);
		str_append(str, "OK [MODIFIED ");
		imap_write_seq_range(str, &modified_set);
		str_append(str, "] Conditional store failed.");
		tagged_reply = str_c(str);
	}
	array_free(&modified_set);

	/* With UID STORE we have to return UID for the flags as well.
	   Unfortunately we don't have the ability to separate those
	   flag changes that were caused by UID STORE and those that
	   came externally, so we'll just send the UID for all flag
	   changes that we see. */
	if (cmd->uid && (!ctx.silent || (client->enabled_features &
					 MAILBOX_FEATURE_CONDSTORE) != 0))
		imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;

	return cmd_sync(cmd, (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
			imap_sync_flags, tagged_reply);
}
Example #5
0
static bool cmd_copy_full(struct client_command_context *cmd, bool move)
{
	struct client *client = cmd->client;
	struct mail_storage *dest_storage;
	struct mailbox *destbox;
	struct mailbox_transaction_context *t, *src_trans;
        struct mail_search_args *search_args;
	const char *messageset, *mailbox, *src_uidset;
	enum mailbox_sync_flags sync_flags = 0;
	enum imap_sync_flags imap_flags = 0;
	struct mail_transaction_commit_changes changes;
	unsigned int copy_count;
	string_t *msg;
	int ret;

	/* <message set> <mailbox> */
	if (!client_read_string_args(cmd, 2, &messageset, &mailbox))
		return FALSE;

	if (!client_verify_open_mailbox(cmd))
		return TRUE;

	ret = imap_search_get_seqset(cmd, messageset, cmd->uid, &search_args);
	if (ret <= 0)
		return ret < 0;

	if (client_open_save_dest_box(cmd, mailbox, &destbox) < 0) {
		mail_search_args_unref(&search_args);
		return TRUE;
	}

	t = mailbox_transaction_begin(destbox,
				      MAILBOX_TRANSACTION_FLAG_EXTERNAL |
				      MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS);
	ret = fetch_and_copy(client, move, t, &src_trans, search_args,
			     &src_uidset, &copy_count);
	mail_search_args_unref(&search_args);

	msg = t_str_new(256);
	if (ret <= 0)
		mailbox_transaction_rollback(&t);
	else if (mailbox_transaction_commit_get_changes(&t, &changes) < 0)
		ret = -1;
	else if (copy_count == 0) {
		str_append(msg, "OK No messages found.");
		pool_unref(&changes.pool);
	} else if (seq_range_count(&changes.saved_uids) == 0 ||
		   changes.no_read_perm) {
		/* not supported by backend (virtual) or no read permissions
		   for mailbox */
		str_append(msg, move ? "OK Move completed." :
			   "OK Copy completed.");
		pool_unref(&changes.pool);
	} else if (move) {
		i_assert(copy_count == seq_range_count(&changes.saved_uids));

		str_printfa(msg, "* OK [COPYUID %u %s ",
			    changes.uid_validity, src_uidset);
		imap_write_seq_range(msg, &changes.saved_uids);
		str_append(msg, "] Moved UIDs.");
		client_send_line(client, str_c(msg));

		str_truncate(msg, 0);
		str_append(msg, "OK Move completed.");
		pool_unref(&changes.pool);
	} else {
		i_assert(copy_count == seq_range_count(&changes.saved_uids));

		str_printfa(msg, "OK [COPYUID %u %s ", changes.uid_validity,
			    src_uidset);
		imap_write_seq_range(msg, &changes.saved_uids);
		str_append(msg, "] Copy completed.");
		pool_unref(&changes.pool);
	}

	if (ret <= 0 && move) {
		/* move failed, don't expunge anything */
		mailbox_transaction_rollback(&src_trans);
	} else {
		if (mailbox_transaction_commit(&src_trans) < 0)
			ret = -1;
	}

 	dest_storage = mailbox_get_storage(destbox);
	if (destbox != client->mailbox) {
		if (move)
			sync_flags |= MAILBOX_SYNC_FLAG_EXPUNGE;
		else
			sync_flags |= MAILBOX_SYNC_FLAG_FAST;
		imap_flags |= IMAP_SYNC_FLAG_SAFE;
		mailbox_free(&destbox);
	} else if (move) {
		sync_flags |= MAILBOX_SYNC_FLAG_EXPUNGE;
		imap_flags |= IMAP_SYNC_FLAG_SAFE;
	}

	if (ret > 0)
		return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
	else if (ret == 0) {
		/* some messages were expunged, sync them */
		return cmd_sync(cmd, 0, 0,
			"NO ["IMAP_RESP_CODE_EXPUNGEISSUED"] "
			"Some of the requested messages no longer exist.");
	} else {
		client_send_storage_error(cmd, dest_storage);
		return TRUE;
	}
}
Example #6
0
bool cmd_copy(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	struct mail_namespace *dest_ns;
	struct mail_storage *dest_storage;
	struct mailbox *destbox;
	struct mailbox_transaction_context *t;
        struct mail_search_args *search_args;
	const char *messageset, *mailbox, *storage_name, *src_uidset;
	enum mailbox_name_status status;
	enum mailbox_sync_flags sync_flags = 0;
	enum imap_sync_flags imap_flags = 0;
	struct mail_transaction_commit_changes changes;
	unsigned int copy_count;
	string_t *msg;
	int ret;

	/* <message set> <mailbox> */
	if (!client_read_string_args(cmd, 2, &messageset, &mailbox))
		return FALSE;

	if (!client_verify_open_mailbox(cmd))
		return TRUE;

	ret = imap_search_get_seqset(cmd, messageset, cmd->uid, &search_args);
	if (ret <= 0)
		return ret < 0;

	/* open the destination mailbox */
	dest_ns = client_find_namespace(cmd, mailbox, &storage_name, &status);
	if (dest_ns == NULL) {
		mail_search_args_unref(&search_args);
		return TRUE;
	}

	switch (status) {
	case MAILBOX_NAME_EXISTS_MAILBOX:
		break;
	case MAILBOX_NAME_EXISTS_DIR:
		status = MAILBOX_NAME_VALID;
		/* fall through */
	case MAILBOX_NAME_VALID:
	case MAILBOX_NAME_INVALID:
	case MAILBOX_NAME_NOINFERIORS:
		client_fail_mailbox_name_status(cmd, mailbox,
						"TRYCREATE", status);
		mail_search_args_unref(&search_args);
		return TRUE;
	}

	if (mailbox_equals(client->mailbox, dest_ns, storage_name))
		destbox = client->mailbox;
	else {
		destbox = mailbox_alloc(dest_ns->list, storage_name,
					MAILBOX_FLAG_SAVEONLY |
					MAILBOX_FLAG_KEEP_RECENT);
		if (mailbox_open(destbox) < 0) {
			client_send_storage_error(cmd,
				mailbox_get_storage(destbox));
			mailbox_free(&destbox);
			mail_search_args_unref(&search_args);
			return TRUE;
		}
		if (client->enabled_features != 0)
			mailbox_enable(destbox, client->enabled_features);
	}

	t = mailbox_transaction_begin(destbox,
				      MAILBOX_TRANSACTION_FLAG_EXTERNAL |
				      MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS);
	ret = fetch_and_copy(client, t, search_args, &src_uidset, &copy_count);
	mail_search_args_unref(&search_args);

	msg = t_str_new(256);
	if (ret <= 0)
		mailbox_transaction_rollback(&t);
	else if (mailbox_transaction_commit_get_changes(&t, &changes) < 0)
		ret = -1;
	else if (copy_count == 0) {
		str_append(msg, "OK No messages copied.");
		pool_unref(&changes.pool);
	} else if (seq_range_count(&changes.saved_uids) == 0) {
		/* not supported by backend (virtual) */
		str_append(msg, "OK Copy completed.");
		pool_unref(&changes.pool);
	} else {
		i_assert(copy_count == seq_range_count(&changes.saved_uids));

		str_printfa(msg, "OK [COPYUID %u %s ", changes.uid_validity,
			    src_uidset);
		imap_write_seq_range(msg, &changes.saved_uids);
		str_append(msg, "] Copy completed.");
		pool_unref(&changes.pool);
	}

	dest_storage = mailbox_get_storage(destbox);
	if (destbox != client->mailbox) {
		sync_flags |= MAILBOX_SYNC_FLAG_FAST;
		imap_flags |= IMAP_SYNC_FLAG_SAFE;
		mailbox_free(&destbox);
	}

	if (ret > 0)
		return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
	else if (ret == 0) {
		/* some messages were expunged, sync them */
		return cmd_sync(cmd, 0, 0,
			"NO ["IMAP_RESP_CODE_EXPUNGEISSUED"] "
			"Some of the requested messages no longer exist.");
	} else {
		client_send_storage_error(cmd, dest_storage);
		return TRUE;
	}
}