/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "llist.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "istream-crlf.h" #include "ostream.h" #include "str.h" #include "str-sanitize.h" #include "dns-lookup.h" #include "smtp-common.h" #include "smtp-address.h" #include "smtp-params.h" #include "smtp-client-private.h" #include "smtp-client-command.h" #include "smtp-client-transaction.h" #include const char *const smtp_client_transaction_state_names[] = { "new", "pending", "mail_from", "rcpt_to", "data", "reset", "finished", "aborted" }; static void smtp_client_transaction_submit_more(struct smtp_client_transaction *trans); static void smtp_client_transaction_submit(struct smtp_client_transaction *trans, bool start); static void 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); /* * Sender */ static struct smtp_client_transaction_mail * smtp_client_transaction_mail_new(struct smtp_client_transaction *trans, const struct smtp_address *mail_from, const struct smtp_params_mail *mail_params) { struct smtp_client_transaction_mail *mail; pool_t pool; pool = pool_alloconly_create("smtp transaction mail", 512); mail = p_new(pool, struct smtp_client_transaction_mail, 1); mail->pool = pool; mail->trans = trans; mail->mail_from = smtp_address_clone(pool, mail_from); smtp_params_mail_copy(pool, &mail->mail_params, mail_params); DLLIST2_APPEND(&trans->mail_head, &trans->mail_tail, mail); if (trans->mail_send == NULL) trans->mail_send = mail; return mail; } static void smtp_client_transaction_mail_free(struct smtp_client_transaction_mail **_mail) { struct smtp_client_transaction_mail *mail = *_mail; if (mail == NULL) return; *_mail = NULL; struct smtp_client_transaction *trans = mail->trans; if (mail->cmd_mail_from != NULL) smtp_client_command_abort(&mail->cmd_mail_from); DLLIST2_REMOVE(&trans->mail_head, &trans->mail_tail, mail); pool_unref(&mail->pool); } static void smtp_client_transaction_mail_replied( struct smtp_client_transaction_mail **_mail, const struct smtp_reply *reply) { struct smtp_client_transaction_mail *mail = *_mail; if (mail == NULL) return; *_mail = NULL; smtp_client_command_callback_t *mail_callback = mail->mail_callback; void *context = mail->context; mail->mail_callback = NULL; smtp_client_transaction_mail_free(&mail); /* Call the callback */ if (mail_callback != NULL) mail_callback(reply, context); } void smtp_client_transaction_mail_abort( struct smtp_client_transaction_mail **_mail) { struct smtp_client_transaction_mail *mail = *_mail; if (mail == NULL) return; *_mail = NULL; struct smtp_client_transaction *trans = mail->trans; i_assert(trans->state <= SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM || trans->state == SMTP_CLIENT_TRANSACTION_STATE_ABORTED); smtp_client_transaction_mail_free(&mail); } static void smtp_client_transaction_mail_fail_reply( struct smtp_client_transaction_mail **_mail, const struct smtp_reply *reply) { struct smtp_client_transaction_mail *mail = *_mail; if (mail == NULL) return; *_mail = NULL; smtp_client_command_callback_t *callback = mail->mail_callback; void *context = mail->context; mail->mail_callback = NULL; smtp_client_transaction_mail_free(&mail); if (callback != NULL) callback(reply, context); } /* * Recipient */ static void smtp_client_transaction_rcpt_update_event( struct smtp_client_transaction_rcpt *rcpt) { const char *to = smtp_address_encode(rcpt->rcpt_to); event_set_append_log_prefix(rcpt->event, t_strdup_printf("rcpt <%s>: ", str_sanitize(to, 128))); event_add_str(rcpt->event, "rcpt_to", to); smtp_params_rcpt_add_to_event(&rcpt->rcpt_params, rcpt->event); } static struct smtp_client_transaction_rcpt * smtp_client_transaction_rcpt_new(struct smtp_client_transaction *trans, pool_t pool, const struct smtp_address *rcpt_to, const struct smtp_params_rcpt *rcpt_params) { struct smtp_client_transaction_rcpt *rcpt; pool_ref(pool); rcpt = p_new(pool, struct smtp_client_transaction_rcpt, 1); rcpt->pool = pool; rcpt->trans = trans; rcpt->rcpt_to = smtp_address_clone(pool, rcpt_to); smtp_params_rcpt_copy(pool, &rcpt->rcpt_params, rcpt_params); DLLIST2_APPEND(&trans->rcpts_queue_head, &trans->rcpts_queue_tail, rcpt); trans->rcpts_queue_count++; rcpt->queued = TRUE; if (trans->rcpts_send == NULL) trans->rcpts_send = rcpt; rcpt->event = event_create(trans->event); smtp_client_transaction_rcpt_update_event(rcpt); trans->rcpts_total++; return rcpt; } static void smtp_client_transaction_rcpt_free( struct smtp_client_transaction_rcpt **_rcpt) { struct smtp_client_transaction_rcpt *rcpt = *_rcpt; if (rcpt == NULL) return; *_rcpt = NULL; struct smtp_client_transaction *trans = rcpt->trans; smtp_client_command_abort(&rcpt->cmd_rcpt_to); if (trans->rcpts_send == rcpt) trans->rcpts_send = rcpt->next; if (trans->rcpts_data == rcpt) trans->rcpts_data = rcpt->next; if (rcpt->queued) { DLLIST2_REMOVE(&trans->rcpts_queue_head, &trans->rcpts_queue_tail, rcpt); trans->rcpts_queue_count--; } else { DLLIST2_REMOVE(&trans->rcpts_head, &trans->rcpts_tail, rcpt); trans->rcpts_count--; } if (!rcpt->finished) { struct smtp_reply failure; trans->rcpts_aborted++; smtp_reply_init(&failure, SMTP_CLIENT_COMMAND_ERROR_ABORTED, "Aborted"); failure.enhanced_code = SMTP_REPLY_ENH_CODE(9, 0, 0); struct event_passthrough *e = event_create_passthrough(rcpt->event)-> set_name("smtp_client_transaction_rcpt_finished"); smtp_reply_add_to_event(&failure, e); e_debug(e->event(), "Aborted"); } event_unref(&rcpt->event); if (rcpt->queued || rcpt->external_pool) { i_assert(rcpt->pool != NULL); pool_unref(&rcpt->pool); } } static void smtp_client_transaction_rcpt_approved( struct smtp_client_transaction_rcpt **_rcpt) { struct smtp_client_transaction_rcpt *prcpt = *_rcpt; i_assert(prcpt != NULL); struct smtp_client_transaction *trans = prcpt->trans; struct smtp_client_transaction_rcpt *rcpt; pool_t pool; i_assert(prcpt->queued); if (prcpt->external_pool) { /* Allocated externally; just remove it from the queue */ prcpt->queued = FALSE; if (trans->rcpts_send == prcpt) trans->rcpts_send = prcpt->next; DLLIST2_REMOVE(&trans->rcpts_queue_head, &trans->rcpts_queue_tail, prcpt); trans->rcpts_queue_count--; rcpt = prcpt; } else { /* Move to transaction pool */ pool = trans->pool; rcpt = p_new(pool, struct smtp_client_transaction_rcpt, 1); rcpt->trans = trans; rcpt->rcpt_to = smtp_address_clone(pool, prcpt->rcpt_to); smtp_params_rcpt_copy(pool, &rcpt->rcpt_params, &prcpt->rcpt_params); rcpt->data_callback = prcpt->data_callback; rcpt->data_context = prcpt->data_context; rcpt->event = prcpt->event; event_ref(rcpt->event); /* Free the old object, thereby removing it from the queue */ smtp_client_transaction_rcpt_free(&prcpt); } /* Recipient is approved */ DLLIST2_APPEND(&trans->rcpts_head, &trans->rcpts_tail, rcpt); trans->rcpts_count++; if (trans->rcpts_data == NULL) trans->rcpts_data = trans->rcpts_head; *_rcpt = rcpt; } static void smtp_client_transaction_rcpt_denied( struct smtp_client_transaction_rcpt **_rcpt, const struct smtp_reply *reply) { struct smtp_client_transaction_rcpt *prcpt = *_rcpt; *_rcpt = NULL; i_assert(prcpt != NULL); struct smtp_client_transaction *trans = prcpt->trans; trans->rcpts_denied++; trans->rcpts_failed++; struct event_passthrough *e = event_create_passthrough(prcpt->event)-> set_name("smtp_client_transaction_rcpt_finished"); smtp_reply_add_to_event(reply, e); e_debug(e->event(), "Denied"); /* Not pending anymore */ smtp_client_transaction_rcpt_free(&prcpt); } static void smtp_client_transaction_rcpt_replied( struct smtp_client_transaction_rcpt **_rcpt, const struct smtp_reply *reply) { struct smtp_client_transaction_rcpt *rcpt = *_rcpt; *_rcpt = NULL; if (rcpt == NULL) return; bool success = smtp_reply_is_success(reply); smtp_client_command_callback_t *rcpt_callback = rcpt->rcpt_callback; void *context = rcpt->context; rcpt->rcpt_callback = NULL; if (rcpt->finished) return; rcpt->finished = !success; if (success) smtp_client_transaction_rcpt_approved(&rcpt); else smtp_client_transaction_rcpt_denied(&rcpt, reply); /* Call the callback */ if (rcpt_callback != NULL) rcpt_callback(reply, context); } void smtp_client_transaction_rcpt_abort( struct smtp_client_transaction_rcpt **_rcpt) { struct smtp_client_transaction_rcpt *rcpt = *_rcpt; if (rcpt == NULL) return; *_rcpt = NULL; struct smtp_client_transaction *trans = rcpt->trans; i_assert(rcpt->queued || rcpt->external_pool); i_assert(trans->state <= SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO || trans->state == SMTP_CLIENT_TRANSACTION_STATE_ABORTED); smtp_client_transaction_rcpt_free(&rcpt); } static void smtp_client_transaction_rcpt_fail_reply( struct smtp_client_transaction_rcpt **_rcpt, const struct smtp_reply *reply) { struct smtp_client_transaction_rcpt *rcpt = *_rcpt; if (rcpt == NULL) return; *_rcpt = NULL; struct smtp_client_transaction *trans = rcpt->trans; smtp_client_command_callback_t *callback; void *context; if (rcpt->finished) return; rcpt->finished = TRUE; trans->rcpts_failed++; if (rcpt->queued) { callback = rcpt->rcpt_callback; context = rcpt->context; } else { callback = rcpt->data_callback; context = rcpt->data_context; } rcpt->rcpt_callback = NULL; rcpt->data_callback = NULL; struct event_passthrough *e = event_create_passthrough(rcpt->event)-> set_name("smtp_client_transaction_rcpt_finished"); smtp_reply_add_to_event(reply, e); e_debug(e->event(), "Failed"); smtp_client_transaction_rcpt_free(&rcpt); if (callback != NULL) callback(reply, context); } static void smtp_client_transaction_rcpt_finished(struct smtp_client_transaction_rcpt *rcpt, const struct smtp_reply *reply) { struct smtp_client_transaction *trans = rcpt->trans; i_assert(!rcpt->finished); rcpt->finished = TRUE; if (smtp_reply_is_success(reply)) trans->rcpts_succeeded++; else trans->rcpts_failed++; struct event_passthrough *e = event_create_passthrough(rcpt->event)-> set_name("smtp_client_transaction_rcpt_finished"); smtp_reply_add_to_event(reply, e); e_debug(e->event(), "Finished"); if (rcpt->data_callback != NULL) rcpt->data_callback(reply, rcpt->data_context); rcpt->data_callback = NULL; } #undef smtp_client_transaction_rcpt_set_data_callback void smtp_client_transaction_rcpt_set_data_callback( struct smtp_client_transaction_rcpt *rcpt, smtp_client_command_callback_t *callback, void *context) { i_assert(!rcpt->finished); rcpt->data_callback = callback; rcpt->data_context = context; } /* * Transaction */ static void smtp_client_transaction_update_event(struct smtp_client_transaction *trans) { event_set_append_log_prefix(trans->event, "transaction: "); } static struct event_passthrough * smtp_client_transaction_result_event(struct smtp_client_transaction *trans, const struct smtp_reply *reply) { struct event_passthrough *e; unsigned int rcpts_aborted = (trans->rcpts_aborted + trans->rcpts_queue_count); e = event_create_passthrough(trans->event)-> set_name("smtp_client_transaction_finished")-> add_int("recipients", trans->rcpts_total)-> add_int("recipients_aborted", rcpts_aborted)-> add_int("recipients_denied", trans->rcpts_denied)-> add_int("recipients_failed", trans->rcpts_failed)-> add_int("recipients_succeeded", trans->rcpts_succeeded); smtp_reply_add_to_event(reply, e); if (trans->reset) e->add_str("is_reset", "yes"); return e; } #undef smtp_client_transaction_create_empty struct smtp_client_transaction * smtp_client_transaction_create_empty( struct smtp_client_connection *conn, enum smtp_client_transaction_flags flags, smtp_client_transaction_callback_t *callback, void *context) { struct smtp_client_transaction *trans; pool_t pool; if (conn->protocol == SMTP_PROTOCOL_LMTP) flags |= SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT; pool = pool_alloconly_create("smtp transaction", 4096); trans = p_new(pool, struct smtp_client_transaction, 1); trans->refcount = 1; trans->pool = pool; trans->flags = flags; trans->callback = callback; trans->context = context; trans->event = event_create(conn->event); smtp_client_transaction_update_event(trans); trans->conn = conn; smtp_client_connection_ref(conn); e_debug(trans->event, "Created"); return trans; } #undef smtp_client_transaction_create struct smtp_client_transaction * smtp_client_transaction_create(struct smtp_client_connection *conn, const struct smtp_address *mail_from, const struct smtp_params_mail *mail_params, enum smtp_client_transaction_flags flags, smtp_client_transaction_callback_t *callback, void *context) { struct smtp_client_transaction *trans; trans = smtp_client_transaction_create_empty(conn, flags, callback, context); (void)smtp_client_transaction_mail_new(trans, mail_from, mail_params); return trans; } static void smtp_client_transaction_finish(struct smtp_client_transaction *trans, const struct smtp_reply *final_reply) { struct smtp_client_connection *conn = trans->conn; if (trans->state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) return; timeout_remove(&trans->to_finish); struct event_passthrough *e = smtp_client_transaction_result_event(trans, final_reply); e_debug(e->event(), "Finished"); io_loop_time_refresh(); trans->times.finished = ioloop_timeval; i_assert(trans->to_send == NULL); trans->state = SMTP_CLIENT_TRANSACTION_STATE_FINISHED; i_assert(trans->callback != NULL); trans->callback(trans->context); if (!trans->submitted_data) smtp_client_connection_abort_transaction(conn, trans); smtp_client_transaction_unref(&trans); } void smtp_client_transaction_abort(struct smtp_client_transaction *trans) { struct smtp_client_connection *conn = trans->conn; if (trans->failing) { e_debug(trans->event, "Abort (already failing)"); return; } e_debug(trans->event, "Abort"); /* Clean up */ i_stream_unref(&trans->data_input); timeout_remove(&trans->to_send); timeout_remove(&trans->to_finish); trans->cmd_last = NULL; /* Abort any pending commands */ while (trans->mail_head != NULL) { struct smtp_client_transaction_mail *mail = trans->mail_head; smtp_client_transaction_mail_free(&mail); } while (trans->rcpts_queue_count > 0) { struct smtp_client_transaction_rcpt *rcpt = trans->rcpts_queue_head; smtp_client_transaction_rcpt_free(&rcpt); } 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); /* Abort if not finished */ if (trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED) { struct event_passthrough *e; if (trans->failure != NULL) { e = smtp_client_transaction_result_event( trans, trans->failure); e_debug(e->event(), "Failed"); } else { struct smtp_reply failure; smtp_reply_init(&failure, SMTP_CLIENT_COMMAND_ERROR_ABORTED, "Aborted"); failure.enhanced_code = SMTP_REPLY_ENH_CODE(9, 0, 0); e = smtp_client_transaction_result_event( trans, &failure); e_debug(e->event(), "Aborted"); } trans->state = SMTP_CLIENT_TRANSACTION_STATE_ABORTED; i_assert(trans->callback != NULL); trans->callback(trans->context); smtp_client_transaction_unref(&trans); } } void smtp_client_transaction_ref(struct smtp_client_transaction *trans) { trans->refcount++; } void smtp_client_transaction_unref(struct smtp_client_transaction **_trans) { struct smtp_client_transaction *trans = *_trans; struct smtp_client_connection *conn; *_trans = NULL; if (trans == NULL) return; conn = trans->conn; i_assert(trans->refcount > 0); if (--trans->refcount > 0) return; e_debug(trans->event, "Destroy"); i_stream_unref(&trans->data_input); smtp_client_transaction_abort(trans); while (trans->rcpts_count > 0) { struct smtp_client_transaction_rcpt *rcpt = trans->rcpts_head; smtp_client_transaction_rcpt_free(&rcpt); } i_assert(trans->state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED); event_unref(&trans->event); pool_unref(&trans->pool); smtp_client_connection_unref(&conn); } void smtp_client_transaction_destroy(struct smtp_client_transaction **_trans) { struct smtp_client_transaction *trans = *_trans; struct smtp_client_transaction_mail *mail; struct smtp_client_transaction_rcpt *rcpt; *_trans = NULL; if (trans == NULL) return; smtp_client_transaction_ref(trans); smtp_client_transaction_abort(trans); /* Make sure this transaction doesn't produce any more callbacks. We cannot fully abort (destroy) these commands, as this may be called from a callback. */ for (mail = trans->mail_head; mail != NULL; mail = mail->next) { if (mail->cmd_mail_from != NULL) smtp_client_command_drop_callback(mail->cmd_mail_from); } for (rcpt = trans->rcpts_queue_head; rcpt != NULL; rcpt = rcpt->next) { if (rcpt->cmd_rcpt_to != NULL) smtp_client_command_drop_callback(rcpt->cmd_rcpt_to); } 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); /* Free any approved recipients early */ while (trans->rcpts_count > 0) { struct smtp_client_transaction_rcpt *rcpt = trans->rcpts_head; smtp_client_transaction_rcpt_free(&rcpt); } if (trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED) { struct smtp_client_transaction *trans_tmp = trans; trans->state = SMTP_CLIENT_TRANSACTION_STATE_ABORTED; smtp_client_transaction_unref(&trans_tmp); } smtp_client_transaction_unref(&trans); } void smtp_client_transaction_fail_reply(struct smtp_client_transaction *trans, const struct smtp_reply *reply) { struct smtp_client_transaction_rcpt *rcpt, *rcpt_next; if (reply == NULL) reply = trans->failure; i_assert(reply != NULL); if (trans->failing) { e_debug(trans->event, "Already failing: %s", smtp_reply_log(reply)); return; } trans->failing = TRUE; e_debug(trans->event, "Returning failure: %s", smtp_reply_log(reply)); /* Hold a reference to prevent early destruction in a callback */ smtp_client_transaction_ref(trans); trans->cmd_last = NULL; timeout_remove(&trans->to_send); /* MAIL */ while (trans->mail_head != NULL) { struct smtp_client_transaction_mail *mail = trans->mail_head; smtp_client_transaction_mail_fail_reply(&mail, reply); } /* RCPT */ rcpt = trans->rcpts_queue_head; while (rcpt != NULL) { struct smtp_client_command *cmd = rcpt->cmd_rcpt_to; rcpt_next = rcpt->next; rcpt->cmd_rcpt_to = NULL; if (cmd != NULL) smtp_client_command_fail_reply(&cmd, reply); else smtp_client_transaction_rcpt_fail_reply(&rcpt, reply); rcpt = rcpt_next; } /* 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. */ rcpt = trans->rcpts_data; while (rcpt != NULL) { rcpt_next = rcpt->next; smtp_client_transaction_rcpt_fail_reply(&rcpt, reply); rcpt = rcpt_next; } if (trans->data_callback != NULL) trans->data_callback(reply, trans->data_context); trans->data_callback = NULL; } /* Plug */ if (trans->failure == NULL) trans->failure = smtp_reply_clone(trans->pool, reply); if (trans->cmd_plug != NULL) smtp_client_command_abort(&trans->cmd_plug); trans->cmd_plug = NULL; trans->failing = FALSE; if (trans->data_provided || trans->reset) { /* Abort the transaction only if smtp_client_transaction_send() or smtp_client_transaction_reset() was called (and if it is not aborted already) */ smtp_client_transaction_abort(trans); } /* Drop reference held earlier in this function */ smtp_client_transaction_unref(&trans); } void smtp_client_transaction_fail(struct smtp_client_transaction *trans, unsigned int status, const char *error) { struct smtp_reply reply; smtp_reply_init(&reply, status, error); smtp_client_transaction_fail_reply(trans, &reply); } void smtp_client_transaction_set_event(struct smtp_client_transaction *trans, struct event *event) { i_assert(trans->conn != NULL); event_unref(&trans->event); trans->event = event_create(event); event_set_forced_debug(trans->event, trans->conn->set.debug); smtp_client_transaction_update_event(trans); } static void smtp_client_transaction_timeout(struct smtp_client_transaction *trans) { struct smtp_reply reply; smtp_reply_printf( &reply, 451, "Remote server not answering " "(transaction timed out while %s)", smtp_client_transaction_get_state_destription(trans)); reply.enhanced_code = SMTP_REPLY_ENH_CODE(4, 4, 0); smtp_client_transaction_fail_reply(trans, &reply); } void smtp_client_transaction_set_timeout(struct smtp_client_transaction *trans, unsigned int timeout_msecs) { i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); trans->finish_timeout_msecs = timeout_msecs; if (trans->data_input != NULL && timeout_msecs > 0) { /* Adjust timeout if it is already started */ timeout_remove(&trans->to_finish); trans->to_finish = timeout_add(trans->finish_timeout_msecs, smtp_client_transaction_timeout, trans); } } static void smtp_client_transaction_mail_cb(const struct smtp_reply *reply, struct smtp_client_transaction *trans) { struct smtp_client_transaction_mail *mail = trans->mail_head; bool success = smtp_reply_is_success(reply); e_debug(trans->event, "Got MAIL reply: %s", smtp_reply_log(reply)); i_assert(mail != NULL); i_assert(trans->conn != NULL); if (success) { if (trans->sender_accepted) { smtp_client_transaction_fail( trans, SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY, "Server accepted more than a single MAIL command."); return; } trans->mail_failure = NULL; trans->sender_accepted = TRUE; } /* Plug command line pipeline if no RCPT commands are yet issued */ if (!trans->immediate && mail->next == NULL && mail->cmd_mail_from == trans->cmd_last) { trans->cmd_plug = trans->cmd_last = smtp_client_command_plug(trans->conn, trans->cmd_last); } mail->cmd_mail_from = NULL; if (trans->rcpts_queue_count > 0) trans->state = SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO; else if (trans->reset) trans->state = SMTP_CLIENT_TRANSACTION_STATE_RESET; { enum smtp_client_transaction_state state; struct smtp_client_transaction *tmp_trans = trans; smtp_client_transaction_ref(tmp_trans); smtp_client_transaction_mail_replied(&mail, reply); state = trans->state; smtp_client_transaction_unref(&tmp_trans); if (state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) return; } if (!trans->sender_accepted && trans->mail_head != NULL) { /* Update transaction with next MAIL command candidate */ mail = trans->mail_head; event_add_str(trans->event, "mail_from", smtp_address_encode(mail->mail_from)); smtp_params_mail_add_to_event(&mail->mail_params, trans->event); } if (!success && !trans->sender_accepted) { if (trans->state > SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) smtp_client_transaction_fail_reply(trans, reply); else if (trans->mail_failure == NULL) { trans->mail_failure = smtp_reply_clone(trans->pool, reply); } } } #undef smtp_client_transaction_add_mail struct smtp_client_transaction_mail * smtp_client_transaction_add_mail(struct smtp_client_transaction *trans, const struct smtp_address *mail_from, const struct smtp_params_mail *mail_params, smtp_client_command_callback_t *mail_callback, void *context) { struct smtp_client_transaction_mail *mail; e_debug(trans->event, "Add MAIL command"); i_assert(!trans->data_provided); i_assert(!trans->reset); i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO); mail = smtp_client_transaction_mail_new(trans, mail_from, mail_params); mail->mail_callback = mail_callback; mail->context = context; smtp_client_transaction_submit(trans, FALSE); return mail; } static void smtp_client_transaction_connection_ready(struct smtp_client_transaction *trans) { if (trans->state != SMTP_CLIENT_TRANSACTION_STATE_PENDING) return; e_debug(trans->event, "Connecton is ready for transaction"); trans->state = SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM; smtp_client_transaction_submit_more(trans); } #undef smtp_client_transaction_start void smtp_client_transaction_start( struct smtp_client_transaction *trans, smtp_client_command_callback_t *mail_callback, void *context) { struct smtp_client_connection *conn = trans->conn; struct smtp_client_transaction_mail *mail = trans->mail_head; i_assert(trans->state == SMTP_CLIENT_TRANSACTION_STATE_NEW); i_assert(trans->conn != NULL); i_assert(mail != NULL); event_add_str(trans->event, "mail_from", smtp_address_encode(mail->mail_from)); event_add_str(trans->event, "mail_from_raw", smtp_address_encode_raw(mail->mail_from)); smtp_params_mail_add_to_event(&mail->mail_params, trans->event); struct event_passthrough *e = event_create_passthrough(trans->event)-> set_name("smtp_client_transaction_started"); e_debug(e->event(), "Start"); io_loop_time_refresh(); trans->times.started = ioloop_timeval; i_assert(mail->mail_callback == NULL); mail->mail_callback = mail_callback; mail->context = context; trans->state = SMTP_CLIENT_TRANSACTION_STATE_PENDING; smtp_client_connection_add_transaction(conn, trans); if (trans->immediate && conn->state == SMTP_CLIENT_CONNECTION_STATE_READY) { trans->state = SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM; if (!trans->submitting) smtp_client_transaction_submit_more(trans); } else if (trans->cmd_last == NULL) { trans->cmd_plug = trans->cmd_last = smtp_client_command_plug(trans->conn, NULL); } } #undef smtp_client_transaction_start_empty void smtp_client_transaction_start_empty( struct smtp_client_transaction *trans, const struct smtp_address *mail_from, const struct smtp_params_mail *mail_params, smtp_client_command_callback_t *mail_callback, void *context) { i_assert(trans->mail_head == NULL); (void)smtp_client_transaction_mail_new(trans, mail_from, mail_params); smtp_client_transaction_start(trans, mail_callback, context); } static void smtp_client_transaction_rcpt_cb(const struct smtp_reply *reply, struct smtp_client_transaction_rcpt *rcpt) { struct smtp_client_transaction *trans = rcpt->trans; i_assert(trans->conn != NULL); e_debug(trans->event, "Got RCPT reply: %s", smtp_reply_log(reply)); /* Plug command line pipeline if DATA command is not yet issued */ if (!trans->immediate && !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); } rcpt->cmd_rcpt_to = NULL; { enum smtp_client_transaction_state state; struct smtp_client_transaction *tmp_trans = trans; smtp_client_transaction_ref(tmp_trans); smtp_client_transaction_rcpt_replied(&rcpt, reply); state = trans->state; smtp_client_transaction_unref(&tmp_trans); if (state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) return; } smtp_client_transaction_try_complete(trans); } #undef smtp_client_transaction_add_rcpt struct smtp_client_transaction_rcpt * smtp_client_transaction_add_rcpt(struct smtp_client_transaction *trans, const struct smtp_address *rcpt_to, const struct smtp_params_rcpt *rcpt_params, smtp_client_command_callback_t *rcpt_callback, smtp_client_command_callback_t *data_callback, void *context) { struct smtp_client_transaction_rcpt *rcpt; pool_t pool; e_debug(trans->event, "Add recipient"); i_assert(!trans->data_provided); i_assert(!trans->reset); i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); if (trans->mail_head == NULL && trans->state == SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) trans->state = SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO; pool = pool_alloconly_create("smtp transaction rcpt", 512); rcpt = smtp_client_transaction_rcpt_new(trans, pool, rcpt_to, rcpt_params); pool_unref(&pool); rcpt->rcpt_callback = rcpt_callback; rcpt->context = context; rcpt->data_callback = data_callback; rcpt->data_context = context; smtp_client_transaction_submit(trans, FALSE); return rcpt; } #undef smtp_client_transaction_add_pool_rcpt struct smtp_client_transaction_rcpt * smtp_client_transaction_add_pool_rcpt( struct smtp_client_transaction *trans, pool_t pool, const struct smtp_address *rcpt_to, const struct smtp_params_rcpt *rcpt_params, smtp_client_command_callback_t *rcpt_callback, void *context) { struct smtp_client_transaction_rcpt *rcpt; e_debug(trans->event, "Add recipient (external pool)"); i_assert(!trans->data_provided); i_assert(!trans->reset); i_assert(trans->state < SMTP_CLIENT_TRANSACTION_STATE_FINISHED); if (trans->mail_head == NULL && trans->state == SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) trans->state = SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO; rcpt = smtp_client_transaction_rcpt_new(trans, pool, rcpt_to, rcpt_params); rcpt->rcpt_callback = rcpt_callback; rcpt->context = context; rcpt->external_pool = TRUE; smtp_client_transaction_submit(trans, FALSE); return rcpt; } static void smtp_client_transaction_data_cb(const struct smtp_reply *reply, struct smtp_client_transaction *trans) { bool reply_per_rcpt = HAS_ALL_BITS( trans->flags, SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT); i_assert(!trans->reset); smtp_client_transaction_ref(trans); if (trans->data_input != NULL) { event_add_int(trans->event, "data_sent", trans->data_input->v_offset); i_stream_unref(&trans->data_input); } if (reply_per_rcpt && trans->cmd_data != NULL && /* NULL when failed early */ trans->rcpts_data == NULL && trans->rcpts_count > 0) { smtp_client_command_set_replies(trans->cmd_data, trans->rcpts_count); } while (trans->rcpts_data != NULL) { struct smtp_client_transaction_rcpt *rcpt = trans->rcpts_data; trans->rcpts_data = trans->rcpts_data->next; smtp_client_transaction_rcpt_finished(rcpt, reply); if (HAS_ALL_BITS(trans->flags, SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT)) break; } if (reply_per_rcpt && trans->rcpts_count > 1 && !smtp_reply_is_success(reply) && trans->data_failure == NULL) trans->data_failure = smtp_reply_clone(trans->pool, reply); if (trans->rcpts_data != NULL) { smtp_client_transaction_unref(&trans); return; } trans->cmd_data = NULL; if (trans->data_callback != NULL) trans->data_callback(reply, trans->data_context); trans->data_callback = NULL; /* finished */ smtp_client_transaction_finish( trans, (trans->data_failure == NULL ? reply : trans->data_failure)); smtp_client_transaction_unref(&trans); } static void smtp_client_transaction_send_data(struct smtp_client_transaction *trans) { struct smtp_reply failure; i_assert(!trans->reset); i_assert(trans->data_input != NULL); e_debug(trans->event, "Sending data"); timeout_remove(&trans->to_send); i_zero(&failure); if (trans->failure != NULL) { smtp_client_transaction_fail_reply(trans, trans->failure); failure = *trans->failure; i_assert(failure.status != 0); } else if ((trans->rcpts_count + trans->rcpts_queue_count) == 0) { e_debug(trans->event, "No valid recipients"); if (trans->failure != NULL) failure = *trans->failure; else { smtp_reply_init(&failure, 554, "No valid recipients"); failure.enhanced_code = SMTP_REPLY_ENH_CODE(5, 5, 0); } i_assert(failure.status != 0); } else { i_assert(trans->conn != NULL); trans->cmd_data = smtp_client_command_data_submit_after( trans->conn, 0, trans->cmd_last, trans->data_input, smtp_client_transaction_data_cb, trans); trans->submitted_data = TRUE; if (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; if (failure.status != 0) smtp_client_transaction_finish(trans, &failure); } #undef smtp_client_transaction_send void smtp_client_transaction_send( struct smtp_client_transaction *trans, struct istream *data_input, 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 (trans->rcpts_queue_count == 0) e_debug(trans->event, "Got all RCPT replies"); e_debug(trans->event, "Send"); trans->data_provided = TRUE; i_assert(trans->data_input == NULL); trans->data_input = i_stream_create_crlf(data_input); trans->data_callback = data_callback; trans->data_context = data_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_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, reply); smtp_client_transaction_unref(&trans); } static void smtp_client_transaction_send_reset(struct smtp_client_transaction *trans) { struct smtp_reply failure; i_assert(trans->reset); e_debug(trans->event, "Sending reset"); timeout_remove(&trans->to_send); i_zero(&failure); if (trans->failure != NULL) { smtp_client_transaction_fail_reply(trans, trans->failure); failure = *trans->failure; i_assert(failure.status != 0); } else { i_assert(trans->conn != NULL); trans->cmd_rset = smtp_client_command_rset_submit_after( trans->conn, 0, trans->cmd_last, smtp_client_transaction_rset_cb, trans); if (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; if (failure.status != 0) smtp_client_transaction_finish(trans, &failure); } #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); e_debug(trans->event, "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_do_submit_more(struct smtp_client_transaction *trans) { timeout_remove(&trans->to_send); /* Check whether we already failed */ if (trans->failure == NULL && trans->state > SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) trans->failure = trans->mail_failure; if (trans->failure != NULL) { smtp_client_transaction_fail_reply(trans, trans->failure); return; } i_assert(trans->conn != NULL); /* Make sure transaction is started */ if (trans->state == SMTP_CLIENT_TRANSACTION_STATE_NEW) { enum smtp_client_transaction_state state; struct smtp_client_transaction *tmp_trans = trans; smtp_client_transaction_ref(tmp_trans); smtp_client_transaction_start(tmp_trans, NULL, NULL); state = trans->state; smtp_client_transaction_unref(&tmp_trans); if (state >= SMTP_CLIENT_TRANSACTION_STATE_FINISHED) return; } if (trans->state <= SMTP_CLIENT_TRANSACTION_STATE_PENDING) { if (trans->cmd_last == NULL) { trans->cmd_plug = trans->cmd_last = smtp_client_command_plug(trans->conn, NULL); } return; } /* MAIL */ if (trans->mail_send != NULL) { e_debug(trans->event, "Sending MAIL command"); i_assert(trans->state == SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM); if (trans->cmd_last != NULL) smtp_client_command_unlock(trans->cmd_last); while (trans->mail_send != NULL) { struct smtp_client_transaction_mail *mail = trans->mail_send; trans->mail_send = trans->mail_send->next; mail->cmd_mail_from = trans->cmd_last = smtp_client_command_mail_submit_after( trans->conn, 0, trans->cmd_last, mail->mail_from, &mail->mail_params, smtp_client_transaction_mail_cb, trans); } } else if (trans->immediate) trans->cmd_last = NULL; /* RCPT */ if (trans->rcpts_send != NULL) { e_debug(trans->event, "Sending recipients"); if (trans->cmd_last != NULL) smtp_client_command_unlock(trans->cmd_last); while (trans->rcpts_send != NULL) { struct smtp_client_transaction_rcpt *rcpt = trans->rcpts_send; trans->rcpts_send = trans->rcpts_send->next; rcpt->cmd_rcpt_to = trans->cmd_last = smtp_client_command_rcpt_submit_after( trans->conn, 0, trans->cmd_last, rcpt->rcpt_to, &rcpt->rcpt_params, smtp_client_transaction_rcpt_cb, rcpt); } } if (trans->cmd_plug != NULL && (trans->immediate || trans->cmd_last != trans->cmd_plug)) smtp_client_command_abort(&trans->cmd_plug); if (trans->cmd_last != NULL && !trans->immediate) smtp_client_command_lock(trans->cmd_last); /* 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 smtp_client_transaction_submit_more(struct smtp_client_transaction *trans) { smtp_client_transaction_ref(trans); trans->submitting = TRUE; smtp_client_transaction_do_submit_more(trans); trans->submitting = FALSE; smtp_client_transaction_unref(&trans); } static void smtp_client_transaction_submit(struct smtp_client_transaction *trans, bool start) { if (trans->failure == NULL && !start && trans->state <= SMTP_CLIENT_TRANSACTION_STATE_PENDING) { /* Cannot submit commands at this time */ return; } if (trans->immediate) { /* Submit immediately if not failed already: avoid calling failure callbacks directly (which is the first thing smtp_client_transaction_submit_more() would do). */ if (trans->failure == NULL && trans->state > SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM) trans->failure = trans->mail_failure; if (trans->failure == NULL) { smtp_client_transaction_submit_more(trans); return; } } if (trans->to_send != NULL) { /* Already scheduled command submission */ return; } trans->to_send = timeout_add_short(0, smtp_client_transaction_submit_more, trans); } static void smtp_client_transaction_try_complete(struct smtp_client_transaction *trans) { i_assert(trans->conn != NULL); if (trans->rcpts_queue_count > 0) { /* Not all RCPT replies have come in yet */ e_debug(trans->event, "RCPT replies are still pending (%u/%u)", trans->rcpts_queue_count, (trans->rcpts_queue_count + trans->rcpts_count)); return; } if (!trans->data_provided && !trans->reset) { /* Still waiting for application to issue either smtp_client_transaction_send() or smtp_client_transaction_reset() */ e_debug(trans->event, "Transaction is not yet complete"); return; } if (trans->state == SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO) { /* Completed at this instance */ e_debug(trans->event, "Got all RCPT replies and transaction is complete"); } if (trans->reset) { /* Entering reset state */ trans->state = SMTP_CLIENT_TRANSACTION_STATE_RESET; if (trans->cmd_rset == NULL) return; } else { /* Entering data state */ trans->state = SMTP_CLIENT_TRANSACTION_STATE_DATA; if (trans->rcpts_count == 0) { /* abort transaction if all recipients failed */ smtp_client_transaction_abort(trans); return; } if (trans->cmd_data == NULL) return; if (HAS_ALL_BITS(trans->flags, SMTP_CLIENT_TRANSACTION_FLAG_REPLY_PER_RCPT)) { smtp_client_command_set_replies(trans->cmd_data, trans->rcpts_count); } } /* Got replies for all recipients and submitted our last command; the next transaction can submit its commands now. */ smtp_client_connection_next_transaction(trans->conn, trans); } void smtp_client_transaction_set_immediate( struct smtp_client_transaction *trans, bool immediate) { trans->immediate = immediate; } void smtp_client_transaction_connection_result( struct smtp_client_transaction *trans, const struct smtp_reply *reply) { if (!smtp_reply_is_success(reply)) { if (trans->state <= SMTP_CLIENT_TRANSACTION_STATE_PENDING) { e_debug(trans->event, "Failed to connect: %s", smtp_reply_log(reply)); } else { e_debug(trans->event, "Connection lost: %s", smtp_reply_log(reply)); } smtp_client_transaction_fail_reply(trans, reply); return; } smtp_client_transaction_connection_ready(trans); } void smtp_client_transaction_connection_destroyed( struct smtp_client_transaction *trans) { i_assert(trans->failure != NULL); smtp_client_connection_unref(&trans->conn); } const struct smtp_client_transaction_times * smtp_client_transaction_get_times(struct smtp_client_transaction *trans) { return &trans->times; } enum smtp_client_transaction_state smtp_client_transaction_get_state(struct smtp_client_transaction *trans) { return trans->state; } const char * smtp_client_transaction_get_state_name(struct smtp_client_transaction *trans) { i_assert(trans->state >= SMTP_CLIENT_TRANSACTION_STATE_NEW && trans->state <= SMTP_CLIENT_TRANSACTION_STATE_ABORTED); return smtp_client_transaction_state_names[trans->state]; } const char * smtp_client_transaction_get_state_destription( struct smtp_client_transaction *trans) { enum smtp_client_connection_state conn_state; switch (trans->state) { case SMTP_CLIENT_TRANSACTION_STATE_NEW: break; case SMTP_CLIENT_TRANSACTION_STATE_PENDING: i_assert(trans->conn != NULL); conn_state = smtp_client_connection_get_state(trans->conn); switch (conn_state) { case SMTP_CLIENT_CONNECTION_STATE_CONNECTING: case SMTP_CLIENT_CONNECTION_STATE_HANDSHAKING: case SMTP_CLIENT_CONNECTION_STATE_AUTHENTICATING: return smtp_client_connection_state_names[conn_state]; case SMTP_CLIENT_CONNECTION_STATE_TRANSACTION: return "waiting for connection"; case SMTP_CLIENT_CONNECTION_STATE_DISCONNECTED: case SMTP_CLIENT_CONNECTION_STATE_READY: default: break; } break; case SMTP_CLIENT_TRANSACTION_STATE_MAIL_FROM: return "waiting for reply to MAIL FROM"; case SMTP_CLIENT_TRANSACTION_STATE_RCPT_TO: 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: return "aborted"; } i_unreached(); } void smtp_client_transaction_switch_ioloop( struct smtp_client_transaction *trans) { if (trans->to_send != NULL) trans->to_send = io_loop_move_timeout(&trans->to_send); if (trans->to_finish != NULL) trans->to_finish = io_loop_move_timeout(&trans->to_finish); }