diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-smtp/smtp-server-command.c | 901 |
1 files changed, 901 insertions, 0 deletions
diff --git a/src/lib-smtp/smtp-server-command.c b/src/lib-smtp/smtp-server-command.c new file mode 100644 index 0000000..992a345 --- /dev/null +++ b/src/lib-smtp/smtp-server-command.c @@ -0,0 +1,901 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "llist.h" +#include "array.h" + +#include "smtp-reply.h" +#include "smtp-server-private.h" + +/* + * Command registry + */ + +void smtp_server_command_register(struct smtp_server *server, const char *name, + smtp_server_cmd_start_func_t *func, + enum smtp_server_command_flags flags) +{ + struct smtp_server_command_reg cmd; + + i_zero(&cmd); + cmd.name = name; + cmd.func = func; + cmd.flags = flags; + array_push_back(&server->commands_reg, &cmd); + + server->commands_unsorted = TRUE; +} + +static bool ATTR_NOWARN_UNUSED_RESULT +smtp_server_command_do_unregister(struct smtp_server *server, + const char *name) +{ + const struct smtp_server_command_reg *cmd; + unsigned int i, count; + + cmd = array_get(&server->commands_reg, &count); + for (i = 0; i < count; i++) { + if (strcasecmp(cmd[i].name, name) == 0) { + array_delete(&server->commands_reg, i, 1); + return TRUE; + } + } + + return FALSE; +} + +void smtp_server_command_unregister(struct smtp_server *server, + const char *name) +{ + if (smtp_server_command_do_unregister(server, name)) + return; + i_panic("smtp-server: Trying to unregister unknown command '%s'", name); +} + +void smtp_server_command_override(struct smtp_server *server, const char *name, + smtp_server_cmd_start_func_t *func, + enum smtp_server_command_flags flags) +{ + smtp_server_command_do_unregister(server, name); + smtp_server_command_register(server, name, func, flags); +} + +static int +smtp_server_command_cmp(const struct smtp_server_command_reg *c1, + const struct smtp_server_command_reg *c2) +{ + return strcasecmp(c1->name, c2->name); +} + +static int +smtp_server_command_bsearch(const char *name, + const struct smtp_server_command_reg *cmd) +{ + return strcasecmp(name, cmd->name); +} + +static struct smtp_server_command_reg * +smtp_server_command_find(struct smtp_server *server, const char *name) +{ + if (server->commands_unsorted) { + array_sort(&server->commands_reg, smtp_server_command_cmp); + server->commands_unsorted = FALSE; + } + + return array_bsearch(&server->commands_reg, + name, smtp_server_command_bsearch); +} + +void smtp_server_commands_init(struct smtp_server *server) +{ + p_array_init(&server->commands_reg, server->pool, 16); + + switch (server->set.protocol) { + case SMTP_PROTOCOL_SMTP: + smtp_server_command_register( + server, "EHLO", smtp_server_cmd_ehlo, + SMTP_SERVER_CMD_FLAG_PRETLS | + SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "HELO", smtp_server_cmd_helo, + SMTP_SERVER_CMD_FLAG_PREAUTH); + break; + case SMTP_PROTOCOL_LMTP: + smtp_server_command_register( + server, "LHLO", smtp_server_cmd_ehlo, + SMTP_SERVER_CMD_FLAG_PRETLS | + SMTP_SERVER_CMD_FLAG_PREAUTH); + break; + } + + smtp_server_command_register( + server, "AUTH", smtp_server_cmd_auth, + SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "STARTTLS", smtp_server_cmd_starttls, + SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "MAIL", smtp_server_cmd_mail, 0); + smtp_server_command_register(server, "RCPT", smtp_server_cmd_rcpt, 0); + smtp_server_command_register(server, "DATA", smtp_server_cmd_data, 0); + smtp_server_command_register(server, "BDAT", smtp_server_cmd_bdat, 0); + smtp_server_command_register( + server, "RSET", smtp_server_cmd_rset, + SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register(server, "VRFY", smtp_server_cmd_vrfy, 0); + smtp_server_command_register( + server, "NOOP", smtp_server_cmd_noop, + SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH); + smtp_server_command_register( + server, "QUIT", smtp_server_cmd_quit, + SMTP_SERVER_CMD_FLAG_PRETLS | SMTP_SERVER_CMD_FLAG_PREAUTH); + + smtp_server_command_register( + server, "XCLIENT", smtp_server_cmd_xclient, + SMTP_SERVER_CMD_FLAG_PREAUTH); +} + +/* + * + */ + +static void smtp_server_command_update_event(struct smtp_server_command *cmd) +{ + struct event *event = cmd->context.event; + const char *label = (cmd->context.name == NULL ? + "[unknown]" : + t_str_ucase(cmd->context.name)); + + if (cmd->reg != NULL) + event_add_str(event, "cmd_name", cmd->reg->name); + else + event_add_str(event, "cmd_name", "unknown"); + event_add_str(event, "cmd_input_name", cmd->context.name); + event_set_append_log_prefix(event, + t_strdup_printf("command %s: ", label)); +} + +static struct smtp_server_command * +smtp_server_command_alloc(struct smtp_server_connection *conn) +{ + struct smtp_server_command *cmd; + pool_t pool; + + pool = pool_alloconly_create("smtp_server_command", 1024); + cmd = p_new(pool, struct smtp_server_command, 1); + cmd->context.pool = pool; + cmd->context.cmd = cmd; + cmd->context.event = event_create(conn->event); + cmd->refcount = 1; + cmd->context.conn = conn; + cmd->context.server = conn->server; + cmd->replies_expected = 1; + + DLLIST2_APPEND(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count++; + + return cmd; +} + +struct smtp_server_command * +smtp_server_command_new_invalid(struct smtp_server_connection *conn) +{ + struct smtp_server_command *cmd; + + cmd = smtp_server_command_alloc(conn); + smtp_server_command_update_event(cmd); + + e_debug(cmd->context.event, "Invalid command"); + + return cmd; +} + +struct smtp_server_command * +smtp_server_command_new(struct smtp_server_connection *conn, + const char *name) +{ + struct smtp_server *server = conn->server; + struct smtp_server_command *cmd; + + cmd = smtp_server_command_alloc(conn); + cmd->context.name = p_strdup(cmd->context.pool, name); + cmd->reg = smtp_server_command_find(server, name); + + smtp_server_command_update_event(cmd); + + e_debug(cmd->context.event, "New command"); + + return cmd; +} + +void smtp_server_command_execute(struct smtp_server_command *cmd, + const char *params) +{ + struct smtp_server_connection *conn = cmd->context.conn; + + event_add_str(cmd->context.event, "cmd_args", params); + event_add_str(cmd->context.event, "cmd_human_args", params); + + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_started"); + e_debug(e->event(), "Execute command"); + + if (cmd->reg == NULL) { + /* RFC 5321, Section 4.2.4: Reply Code 502 + + Questions have been raised as to when reply code 502 (Command + not implemented) SHOULD be returned in preference to other + codes. 502 SHOULD be used when the command is actually + recognized by the SMTP server, but not implemented. If the + command is not recognized, code 500 SHOULD be returned. + */ + smtp_server_command_fail(cmd, + 500, "5.5.1", "Unknown command"); + + } else if (!conn->ssl_secured && conn->set.tls_required && + (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PRETLS) == 0) { + /* RFC 3207, Section 4: + + A SMTP server that is not publicly referenced may choose to + require that the client perform a TLS negotiation before + accepting any commands. In this case, the server SHOULD + return the reply code: + + 530 Must issue a STARTTLS command first + + to every command other than NOOP, EHLO, STARTTLS, or QUIT. If + the client and server are using the ENHANCEDSTATUSCODES ESMTP + extension [RFC2034], the status code to be returned SHOULD be + 5.7.0. + */ + smtp_server_command_fail(cmd, + 530, "5.7.0", "TLS required."); + + } else if (!conn->authenticated && !conn->set.auth_optional && + (cmd->reg->flags & SMTP_SERVER_CMD_FLAG_PREAUTH) == 0) { + /* RFC 4954, Section 6: Status Codes + + 530 5.7.0 Authentication required + + This response SHOULD be returned by any command other than + AUTH, EHLO, HELO, NOOP, RSET, or QUIT when server policy + requires authentication in order to perform the requested + action and authentication is not currently in force. + */ + smtp_server_command_fail(cmd, + 530, "5.7.0", "Authentication required."); + + } else { + struct smtp_server_command *tmp_cmd = cmd; + + i_assert(cmd->reg->func != NULL); + smtp_server_command_ref(tmp_cmd); + cmd->reg->func(&tmp_cmd->context, params); + if (tmp_cmd->state == SMTP_SERVER_COMMAND_STATE_NEW) + tmp_cmd->state = SMTP_SERVER_COMMAND_STATE_PROCESSING; + if (!smtp_server_command_unref(&tmp_cmd)) + cmd = NULL; + } +} + +void smtp_server_command_ref(struct smtp_server_command *cmd) +{ + if (cmd->destroying) + return; + cmd->refcount++; +} + +bool smtp_server_command_unref(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + struct smtp_server_connection *conn = cmd->context.conn; + + *_cmd = NULL; + + if (cmd->destroying) + return FALSE; + + i_assert(cmd->refcount > 0); + if (--cmd->refcount > 0) + return TRUE; + cmd->destroying = TRUE; + + if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) { + e_debug(cmd->context.event, "Destroy"); + } else { + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_finished"); + e->add_int("status_code", 9000); + e->add_str("enhanced_code", "9.0.0"); + e->add_str("error", "Aborted"); + e_debug(e->event(), "Destroy"); + + cmd->state = SMTP_SERVER_COMMAND_STATE_ABORTED; + DLLIST2_REMOVE(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count--; + } + + /* Execute hooks */ + if (!smtp_server_command_call_hooks( + &cmd, SMTP_SERVER_COMMAND_HOOK_DESTROY, TRUE)) + i_unreached(); + + smtp_server_command_pipeline_unblock(&cmd->context); + + smtp_server_reply_free(cmd); + event_unref(&cmd->context.event); + pool_unref(&cmd->context.pool); + return FALSE; +} + +void smtp_server_command_abort(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + struct smtp_server_connection *conn = cmd->context.conn; + + /* Preemptively remove command from queue (references may still exist) + */ + if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) { + e_debug(cmd->context.event, "Abort"); + } else { + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_finished"); + e->add_int("status_code", 9000); + e->add_str("enhanced_code", "9.0.0"); + e->add_str("error", "Aborted"); + e_debug(e->event(), "Abort"); + + cmd->state = SMTP_SERVER_COMMAND_STATE_ABORTED; + DLLIST2_REMOVE(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count--; + } + smtp_server_reply_free(cmd); + + smtp_server_command_pipeline_unblock(&cmd->context); + smtp_server_command_unref(_cmd); +} + +#undef smtp_server_command_add_hook +void smtp_server_command_add_hook(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type, + smtp_server_cmd_func_t func, + void *context) +{ + struct smtp_server_command_hook *hook; + + i_assert(func != NULL); + + hook = cmd->hooks_head; + while (hook != NULL) { + /* No double registrations */ + i_assert(hook->type != type || hook->func != func); + + hook = hook->next; + } + + hook = p_new(cmd->context.pool, struct smtp_server_command_hook, 1); + hook->type = type; + hook->func = func; + hook->context = context; + + DLLIST2_APPEND(&cmd->hooks_head, &cmd->hooks_tail, hook); +} + +#undef smtp_server_command_remove_hook +void smtp_server_command_remove_hook(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type, + smtp_server_cmd_func_t *func) +{ + struct smtp_server_command_hook *hook; + bool found = FALSE; + + hook = cmd->hooks_head; + while (hook != NULL) { + struct smtp_server_command_hook *hook_next = hook->next; + + if (hook->type == type && hook->func == func) { + DLLIST2_REMOVE(&cmd->hooks_head, &cmd->hooks_tail, + hook); + found = TRUE; + break; + } + + hook = hook_next; + } + i_assert(found); +} + +bool smtp_server_command_call_hooks(struct smtp_server_command **_cmd, + enum smtp_server_command_hook_type type, + bool remove) +{ + struct smtp_server_command *cmd = *_cmd; + struct smtp_server_command_hook *hook; + + if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) { + if (cmd->state >= SMTP_SERVER_COMMAND_STATE_FINISHED) + return FALSE; + smtp_server_command_ref(cmd); + } + + hook = cmd->hooks_head; + while (hook != NULL) { + struct smtp_server_command_hook *hook_next = hook->next; + + if (hook->type == type) { + if (remove) { + DLLIST2_REMOVE(&cmd->hooks_head, + &cmd->hooks_tail, hook); + } + hook->func(&cmd->context, hook->context); + } + + hook = hook_next; + } + + if (type != SMTP_SERVER_COMMAND_HOOK_DESTROY) { + if (!smtp_server_command_unref(&cmd)) { + *_cmd = NULL; + return FALSE; + } + } + return TRUE; +} + +void smtp_server_command_remove_hooks(struct smtp_server_command *cmd, + enum smtp_server_command_hook_type type) +{ + struct smtp_server_command_hook *hook; + + hook = cmd->hooks_head; + while (hook != NULL) { + struct smtp_server_command_hook *hook_next = hook->next; + + if (hook->type == type) { + DLLIST2_REMOVE(&cmd->hooks_head, &cmd->hooks_tail, + hook); + } + + hook = hook_next; + } +} + +void smtp_server_command_set_reply_count(struct smtp_server_command *cmd, + unsigned int count) +{ + i_assert(count > 0); + i_assert(!array_is_created(&cmd->replies)); + cmd->replies_expected = count; +} + +unsigned int +smtp_server_command_get_reply_count(struct smtp_server_command *cmd) +{ + i_assert(cmd->replies_expected > 0); + return cmd->replies_expected; +} + +bool smtp_server_command_next_to_reply(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + + e_debug(cmd->context.event, "Next to reply"); + + if (!smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_NEXT, TRUE)) + return FALSE; + + smtp_server_command_remove_hooks(cmd, SMTP_SERVER_COMMAND_HOOK_NEXT); + return TRUE; +} + +void smtp_server_command_ready_to_reply(struct smtp_server_command *cmd) +{ + cmd->state = SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY; + e_debug(cmd->context.event, "Ready to reply"); + smtp_server_connection_trigger_output(cmd->context.conn); +} + +static bool +smtp_server_command_replied(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + + if (cmd->replies_submitted < cmd->replies_expected) { + e_debug(cmd->context.event, "Replied (one)"); + + return smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, FALSE); + } + + e_debug(cmd->context.event, "Replied"); + + return (smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED_ONE, TRUE) && + smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_REPLIED, TRUE)); +} + +bool smtp_server_command_completed(struct smtp_server_command **_cmd) +{ + struct smtp_server_command *cmd = *_cmd; + + if (cmd->replies_submitted < cmd->replies_expected) + return TRUE; + + e_debug(cmd->context.event, "Completed"); + + if (cmd->pipeline_blocked) + smtp_server_command_pipeline_unblock(&cmd->context); + + return smtp_server_command_call_hooks( + _cmd, SMTP_SERVER_COMMAND_HOOK_COMPLETED, TRUE); +} + +static bool +smtp_server_command_handle_reply(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + + smtp_server_connection_ref(conn); + + if (!smtp_server_command_replied(&cmd)) + return smtp_server_connection_unref(&conn); + + if (cmd->input_locked) + smtp_server_command_input_unlock(&cmd->context); + + /* Submit reply */ + switch (cmd->state) { + case SMTP_SERVER_COMMAND_STATE_NEW: + case SMTP_SERVER_COMMAND_STATE_PROCESSING: + if (!smtp_server_command_is_complete(cmd)) { + e_debug(cmd->context.event, "Not ready to reply"); + cmd->state = SMTP_SERVER_COMMAND_STATE_SUBMITTED_REPLY; + break; + } + smtp_server_command_ready_to_reply(cmd); + break; + case SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY: + case SMTP_SERVER_COMMAND_STATE_ABORTED: + break; + default: + i_unreached(); + } + + return smtp_server_connection_unref(&conn); +} + +void smtp_server_command_submit_reply(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + unsigned int i, submitted; + bool is_bad = FALSE; + + i_assert(conn != NULL && array_is_created(&cmd->replies)); + + submitted = 0; + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + continue; + submitted++; + + i_assert(reply->content != NULL); + switch (reply->content->status) { + case 500: + case 501: + case 503: + is_bad = TRUE; + break; + } + } + + i_assert(submitted == cmd->replies_submitted); + + /* Limit number of consecutive bad commands */ + if (is_bad) + conn->bad_counter++; + else if (cmd->replies_submitted == cmd->replies_expected) + conn->bad_counter = 0; + + if (!smtp_server_command_handle_reply(cmd)) + return; + + if (conn != NULL && conn->bad_counter > conn->set.max_bad_commands) { + smtp_server_connection_terminate(&conn, + "4.7.0", "Too many invalid commands."); + return; + } +} + +bool smtp_server_command_is_replied(struct smtp_server_command *cmd) +{ + unsigned int i; + + if (!array_is_created(&cmd->replies)) + return FALSE; + + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + return FALSE; + } + + return TRUE; +} + +bool smtp_server_command_reply_is_forwarded(struct smtp_server_command *cmd) +{ + unsigned int i; + + if (!array_is_created(&cmd->replies)) + return FALSE; + + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + return FALSE; + if (reply->forwarded) + return TRUE; + } + + return FALSE; +} + +struct smtp_server_reply * +smtp_server_command_get_reply(struct smtp_server_command *cmd, + unsigned int idx) +{ + struct smtp_server_reply *reply; + + i_assert(idx < cmd->replies_expected); + + if (!array_is_created(&cmd->replies)) + return NULL; + + reply = array_idx_get_space(&cmd->replies, idx); + if (!reply->submitted) + return NULL; + return reply; +} + +bool smtp_server_command_reply_status_equals(struct smtp_server_command *cmd, + unsigned int status) +{ + struct smtp_server_reply *reply; + + i_assert(cmd->replies_expected == 1); + reply = smtp_server_command_get_reply(cmd, 0); + + return (reply->content != NULL && reply->content->status == status); +} + +bool smtp_server_command_replied_success(struct smtp_server_command *cmd) +{ + bool success = FALSE; + unsigned int i; + + if (!array_is_created(&cmd->replies)) + return FALSE; + + for (i = 0; i < cmd->replies_expected; i++) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + if (!reply->submitted) + return FALSE; + if (smtp_server_reply_is_success(reply)) + success = TRUE; + } + + return success; +} + +static int +smtp_server_command_send_more_replies(struct smtp_server_command *cmd) +{ + unsigned int i; + int ret = 1; + + smtp_server_command_ref(cmd); + + // FIXME: handle LMTP DATA command with enormous number of recipients; + // i.e. don't keep filling output stream with replies indefinitely. + for (i = 0; i < cmd->replies_expected; i++) { + struct smtp_server_reply *reply; + + reply = array_idx_modifiable(&cmd->replies, i); + + if (!reply->submitted) { + i_assert(!reply->sent); + ret = 0; + break; + } + if (smtp_server_reply_send(reply) < 0) { + ret = -1; + break; + } + } + + if (!smtp_server_command_unref(&cmd)) + return -1; + return ret; +} + +bool smtp_server_command_send_replies(struct smtp_server_command *cmd) +{ + int ret; + + if (!smtp_server_command_next_to_reply(&cmd)) + return FALSE; + if (cmd->state < SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY) + return FALSE; + + i_assert(cmd->state == SMTP_SERVER_COMMAND_STATE_READY_TO_REPLY && + array_is_created(&cmd->replies)); + + if (!smtp_server_command_completed(&cmd)) + return TRUE; + + /* Send command replies */ + ret = smtp_server_command_send_more_replies(cmd); + if (ret < 0) + return FALSE; + if (ret == 0) { + cmd->state = SMTP_SERVER_COMMAND_STATE_PROCESSING; + return FALSE; + } + + smtp_server_command_finished(cmd); + return TRUE; +} + +void smtp_server_command_finished(struct smtp_server_command *cmd) +{ + struct smtp_server_connection *conn = cmd->context.conn; + struct smtp_server_reply *reply; + + i_assert(cmd->state < SMTP_SERVER_COMMAND_STATE_FINISHED); + cmd->state = SMTP_SERVER_COMMAND_STATE_FINISHED; + + DLLIST2_REMOVE(&conn->command_queue_head, + &conn->command_queue_tail, cmd); + conn->command_queue_count--; + conn->stats.reply_count++; + + i_assert(array_is_created(&cmd->replies)); + reply = array_front_modifiable(&cmd->replies); + i_assert(reply->content != NULL); + + struct event_passthrough *e = + event_create_passthrough(cmd->context.event)-> + set_name("smtp_server_command_finished"); + smtp_server_reply_add_to_event(reply, e); + e_debug(e->event(), "Finished"); + + if (reply->content->status == 221 || reply->content->status == 421) { + i_assert(cmd->replies_expected == 1); + if (reply->content->status == 421) { + smtp_server_connection_close(&conn, t_strdup_printf( + "Server closed the connection: %s", + smtp_server_reply_get_one_line(reply))); + + } else if (conn->set.auth_optional || conn->authenticated) { + smtp_server_connection_close(&conn, "Logged out"); + } else { + smtp_server_connection_close(&conn, + "Aborted login by logging out"); + } + smtp_server_command_unref(&cmd); + return; + } + if (cmd->input_locked) + smtp_server_command_input_unlock(&cmd->context); + if (cmd->pipeline_blocked) + smtp_server_command_pipeline_unblock(&cmd->context); + + smtp_server_command_unref(&cmd); + smtp_server_connection_trigger_output(conn); +} + +void smtp_server_command_fail(struct smtp_server_command *cmd, + unsigned int status, const char *enh_code, + const char *fmt, ...) +{ + unsigned int i; + va_list args; + + i_assert(status / 100 > 2); + + va_start(args, fmt); + if (cmd->replies_expected == 1) { + smtp_server_reply_indexv(&cmd->context, 0, + status, enh_code, fmt, args); + } else for (i = 0; i < cmd->replies_expected; i++) { + bool sent = FALSE; + + if (array_is_created(&cmd->replies)) { + const struct smtp_server_reply *reply = + array_idx(&cmd->replies, i); + sent = reply->sent; + } + + /* Send the same reply for all */ + if (!sent) { + va_list args_copy; + VA_COPY(args_copy, args); + smtp_server_reply_indexv(&cmd->context, i, + status, enh_code, fmt, args_copy); + va_end(args_copy); + } + } + va_end(args); +} + +void smtp_server_command_input_lock(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + command->input_locked = TRUE; + smtp_server_connection_input_halt(conn); +} + +void smtp_server_command_input_unlock(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + command->input_locked = FALSE; + if (command->input_captured) { + command->input_captured = FALSE; + smtp_server_connection_input_halt(conn); + } + smtp_server_connection_input_resume(conn); +} + +void smtp_server_command_input_capture( + struct smtp_server_cmd_ctx *cmd, + smtp_server_cmd_input_callback_t *callback) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + smtp_server_connection_input_capture(conn, *callback, cmd); + command->input_locked = TRUE; + command->input_captured = TRUE; +} + +void smtp_server_command_pipeline_block(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + e_debug(cmd->event, "Pipeline blocked"); + + command->pipeline_blocked = TRUE; + smtp_server_connection_input_lock(conn); +} + +void smtp_server_command_pipeline_unblock(struct smtp_server_cmd_ctx *cmd) +{ + struct smtp_server_command *command = cmd->cmd; + struct smtp_server_connection *conn = cmd->conn; + + if (!command->pipeline_blocked) + return; + + command->pipeline_blocked = FALSE; + smtp_server_connection_input_unlock(conn); + + e_debug(cmd->event, "Pipeline unblocked"); +} |