From: Stephan Bosch Date: Thu, 20 Sep 2018 22:55:51 +0000 (+0200) Subject: lib-smtp: client: transaction: Add support for ending the transaction in a graceful... X-Git-Tag: 2.3.9~1252 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7d8abb1bff2e06fe076fdf38ba1a010fbf55a4c2;p=thirdparty%2Fdovecot%2Fcore.git lib-smtp: client: transaction: Add support for ending the transaction in a graceful RSET command. Allows running the pending transaction until RSET, without abruptly aborting it or issuing a DATA command. --- diff --git a/src/lib-smtp/smtp-client-private.h b/src/lib-smtp/smtp-client-private.h index 13f52ccb41..2ab6e365ec 100644 --- a/src/lib-smtp/smtp-client-private.h +++ b/src/lib-smtp/smtp-client-private.h @@ -78,7 +78,7 @@ struct smtp_client_transaction { struct smtp_params_mail mail_params; enum smtp_client_transaction_state state; - struct smtp_client_command *cmd_mail_from, *cmd_data; + struct smtp_client_command *cmd_mail_from, *cmd_data, *cmd_rset; struct smtp_client_command *cmd_plug, *cmd_last; struct smtp_reply *failure; @@ -93,6 +93,9 @@ struct smtp_client_transaction { smtp_client_command_callback_t *data_callback; void *data_context; + smtp_client_command_callback_t *reset_callback; + void *reset_context; + smtp_client_transaction_callback_t *callback; void *context; @@ -102,6 +105,7 @@ struct smtp_client_transaction { struct timeout *to_finish, *to_send; bool data_provided:1; + bool reset:1; bool finished:1; bool failing:1; bool submitted_data:1; diff --git a/src/lib-smtp/smtp-client-transaction.c b/src/lib-smtp/smtp-client-transaction.c index 9babcc9908..b0da38c315 100644 --- a/src/lib-smtp/smtp-client-transaction.c +++ b/src/lib-smtp/smtp-client-transaction.c @@ -23,6 +23,7 @@ const char *const smtp_client_transaction_state_names[] = { "mail_from", "rcpt_to", "data", + "reset", "finished", "aborted" }; @@ -38,6 +39,8 @@ smtp_client_transaction_try_complete(struct smtp_client_transaction *trans); static void smtp_client_transaction_send_data(struct smtp_client_transaction *trans); +static void +smtp_client_transaction_send_reset(struct smtp_client_transaction *trans); /* * Recipient @@ -252,10 +255,13 @@ void smtp_client_transaction_abort(struct smtp_client_transaction *trans) if (conn->state != SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED) { if (trans->cmd_data != NULL) smtp_client_command_abort(&trans->cmd_data); + if (trans->cmd_rset != NULL) + smtp_client_command_abort(&trans->cmd_rset); if (trans->cmd_plug != NULL) smtp_client_command_abort(&trans->cmd_plug); } trans->cmd_data = NULL; + trans->cmd_rset = NULL; trans->cmd_plug = NULL; smtp_client_connection_abort_transaction(conn, trans); @@ -322,6 +328,8 @@ void smtp_client_transaction_destroy(struct smtp_client_transaction **_trans) } if (trans->cmd_data != NULL) smtp_client_command_drop_callback(trans->cmd_data); + if (trans->cmd_rset != NULL) + smtp_client_command_drop_callback(trans->cmd_rset); if (trans->cmd_plug != NULL) smtp_client_command_abort(&trans->cmd_plug); @@ -394,15 +402,22 @@ void smtp_client_transaction_fail_reply(struct smtp_client_transaction *trans, } } - /* DATA */ - if (!trans->data_provided) { - /* smtp_client_transaction_send() was not called yet + /* DATA / RSET */ + if (!trans->data_provided && !trans->reset) { + /* none of smtp_client_transaction_send() and + smtp_client_transaction_reset() was called so far */ } else if (trans->cmd_data != NULL) { /* the DATA command is still pending; handle the failure by failing the DATA command. */ smtp_client_command_fail_reply(&trans->cmd_data, reply); + } else if (trans->cmd_rset != NULL) { + /* the RSET command is still pending; handle the failure by + failing the RSET command. */ + smtp_client_command_fail_reply(&trans->cmd_rset, reply); } else { + i_assert(!trans->reset); + /* the DATA command was not sent yet; call all DATA callbacks for the recipients that were previously accepted. */ rcpts = array_get_modifiable(&trans->rcpts, &count); @@ -428,9 +443,10 @@ void smtp_client_transaction_fail_reply(struct smtp_client_transaction *trans, trans->failing = FALSE; - if (trans->data_provided) { + if (trans->data_provided || trans->reset) { /* abort the transaction only if smtp_client_transaction_send() - was called (and if it is not aborted already) */ + or smtp_client_transaction_reset() was called (and if it is + not aborted already) */ smtp_client_transaction_abort(trans); } @@ -494,6 +510,8 @@ smtp_client_transaction_mail_cb(const struct smtp_reply *reply, if (array_count(&trans->rcpts_pending) > 0) trans->state = SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO; + else if (trans->reset) + trans->state = SMTP_CLIENT_TRANSACTION_STATE_RESET; if (trans->mail_from_callback != NULL) { enum smtp_client_transaction_state state; @@ -569,7 +587,7 @@ smtp_client_transaction_rcpt_cb(const struct smtp_reply *reply, rcpt->rcpt_callback = NULL; /* plug command line pipeline if DATA command is not yet issued */ - if (rcpt->cmd_rcpt_to == trans->cmd_last && + if (!trans->reset && rcpt->cmd_rcpt_to == trans->cmd_last && trans->cmd_data == NULL) { trans->cmd_plug = trans->cmd_last = smtp_client_command_plug(trans->conn, trans->cmd_last); @@ -599,6 +617,7 @@ void smtp_client_transaction_add_rcpt( smtp_client_transaction_debug(trans, "Add recipient"); i_assert(!trans->data_provided); + i_assert(!trans->reset); i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); @@ -622,6 +641,8 @@ smtp_client_transaction_data_cb(const struct smtp_reply *reply, struct smtp_client_transaction_rcpt *const *rcpt; unsigned int i, count; + i_assert(!trans->reset); + smtp_client_transaction_ref(trans); rcpt = array_get_modifiable(&trans->rcpts, &count); @@ -659,6 +680,7 @@ smtp_client_transaction_send_data(struct smtp_client_transaction *trans) { bool finished = FALSE; + i_assert(!trans->reset); i_assert(trans->data_input != NULL); smtp_client_transaction_debug(trans, "Sending data"); @@ -700,6 +722,8 @@ void smtp_client_transaction_send( smtp_client_command_callback_t *data_callback, void *data_context) { i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + i_assert(!trans->data_provided); + i_assert(!trans->reset); if (array_count(&trans->rcpts_pending) == 0) smtp_client_transaction_debug(trans, "Got all RCPT replies"); @@ -724,6 +748,72 @@ void smtp_client_transaction_send( smtp_client_transaction_submit(trans, TRUE); } +static void +smtp_client_transaction_rset_cb(const struct smtp_reply *reply, + struct smtp_client_transaction *trans) +{ + smtp_client_transaction_ref(trans); + + trans->cmd_rset = NULL; + + if (trans->reset_callback != NULL) + trans->reset_callback(reply, trans->reset_context); + trans->reset_callback = NULL; + + /* finished */ + smtp_client_transaction_finish(trans); + + smtp_client_transaction_unref(&trans); +} + +static void +smtp_client_transaction_send_reset(struct smtp_client_transaction *trans) +{ + i_assert(trans->reset); + + smtp_client_transaction_debug(trans, "Sending reset"); + + timeout_remove(&trans->to_send); + + trans->cmd_rset = smtp_client_command_rset_submit_after( + trans->conn, 0, trans->cmd_last, + smtp_client_transaction_rset_cb, trans); + + i_assert(trans->cmd_last != NULL); + smtp_client_command_unlock(trans->cmd_last); + + smtp_client_transaction_try_complete(trans); + + if (trans->cmd_plug != NULL) + smtp_client_command_abort(&trans->cmd_plug); + trans->cmd_last = NULL; +} + +#undef smtp_client_transaction_reset +void smtp_client_transaction_reset( + struct smtp_client_transaction *trans, + smtp_client_command_callback_t *reset_callback, void *reset_context) +{ + i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); + i_assert(!trans->data_provided); + i_assert(!trans->reset); + + smtp_client_transaction_debug(trans, "Reset"); + + trans->reset = TRUE; + + trans->reset_callback = reset_callback; + trans->reset_context = reset_context; + + if (trans->finish_timeout_msecs > 0) { + i_assert(trans->to_finish == NULL); + trans->to_finish = timeout_add(trans->finish_timeout_msecs, + smtp_client_transaction_timeout, trans); + } + + smtp_client_transaction_submit(trans, TRUE); +} + static void smtp_client_transaction_submit_more(struct smtp_client_transaction *trans) { @@ -779,9 +869,12 @@ smtp_client_transaction_submit_more(struct smtp_client_transaction *trans) if (trans->cmd_plug != NULL && trans->cmd_last != trans->cmd_plug) smtp_client_command_abort(&trans->cmd_plug); - /* DATA */ - if (trans->data_input != NULL) + /* DATA / RSET */ + if (trans->reset) { + smtp_client_transaction_send_reset(trans); + } else if (trans->data_input != NULL) { smtp_client_transaction_send_data(trans); + } } static void @@ -816,9 +909,10 @@ smtp_client_transaction_try_complete(struct smtp_client_transaction *trans) array_count(&trans->rcpts))); return; } - if (!trans->data_provided) { - /* Still waiting for application to issue - smtp_client_transaction_send() */ + if (!trans->data_provided && !trans->reset) { + /* Still waiting for application to issue either + smtp_client_transaction_send() or + smtp_client_transaction_reset() */ smtp_client_transaction_debug( trans, "Transaction is not yet complete"); return; @@ -831,21 +925,29 @@ smtp_client_transaction_try_complete(struct smtp_client_transaction *trans) "transaction is complete"); } - /* Entering DATA state */ - trans->state = SMTP_CLIENT_TRANSACTION_STATE_DATA; + if (trans->reset) { + /* Entering reset state */ + trans->state = SMTP_CLIENT_TRANSACTION_STATE_RESET; - if (array_count(&trans->rcpts) == 0) { - /* abort transaction if all recipients failed */ - smtp_client_transaction_abort(trans); - return; - } + if (trans->cmd_rset == NULL) + return; + } else { + /* Entering data state */ + trans->state = SMTP_CLIENT_TRANSACTION_STATE_DATA; - if (trans->cmd_data == NULL) - return; + if (array_count(&trans->rcpts) == 0) { + /* abort transaction if all recipients failed */ + smtp_client_transaction_abort(trans); + return; + } - if (conn->protocol == SMTP_PROTOCOL_LMTP) { - smtp_client_command_set_replies(trans->cmd_data, - array_count(&trans->rcpts)); + if (trans->cmd_data == NULL) + return; + + if (conn->protocol == SMTP_PROTOCOL_LMTP) { + smtp_client_command_set_replies(trans->cmd_data, + array_count(&trans->rcpts)); + } } /* Got replies for all recipients and submitted our last command; @@ -922,6 +1024,8 @@ smtp_client_transaction_get_state_destription( return "waiting for reply to RCPT TO"; case SMTP_CLIENT_TRANSACTION_STATE_DATA: return "waiting for reply to DATA"; + case SMTP_CLIENT_TRANSACTION_STATE_RESET: + return "waiting for reply to RESET"; case SMTP_CLIENT_TRANSACTION_STATE_FINISHED: return "finished"; case SMTP_CLIENT_TRANSACTION_STATE_ABORTED: diff --git a/src/lib-smtp/smtp-client-transaction.h b/src/lib-smtp/smtp-client-transaction.h index 2e2c17a076..87fc98abb3 100644 --- a/src/lib-smtp/smtp-client-transaction.h +++ b/src/lib-smtp/smtp-client-transaction.h @@ -13,6 +13,7 @@ enum smtp_client_transaction_state { SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM, SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO, SMTP_CLIENT_TRANSACTION_STATE_DATA, + SMTP_CLIENT_TRANSACTION_STATE_RESET, SMTP_CLIENT_TRANSACTION_STATE_FINISHED, SMTP_CLIENT_TRANSACTION_STATE_ABORTED }; @@ -108,6 +109,19 @@ void smtp_client_transaction_send( const struct smtp_reply *reply, typeof(data_context))), \ (smtp_client_command_callback_t *)data_callback, data_context) +/* Gracefully reset the transaction by sending the RSET command and waiting for + the response. This does not try to abort pending MAIL and RCPT commands, + allowing the transaction to be evaluated without proceeding with the DATA + command. */ +void smtp_client_transaction_reset( + struct smtp_client_transaction *trans, + smtp_client_command_callback_t *reset_callback, void *reset_context); +#define smtp_client_transaction_reset(trans, reset_callback, reset_context) \ + smtp_client_transaction_reset(trans, \ + (smtp_client_command_callback_t *)reset_callback, \ + reset_context + CALLBACK_TYPECHECK(reset_callback, void (*)( \ + const struct smtp_reply *reply, typeof(reset_context)))) + /* Return transaction statistics. */ const struct smtp_client_transaction_times * smtp_client_transaction_get_times(struct smtp_client_transaction *trans);