diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-smtp/smtp-submit.c | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/src/lib-smtp/smtp-submit.c b/src/lib-smtp/smtp-submit.c new file mode 100644 index 0000000..0328b95 --- /dev/null +++ b/src/lib-smtp/smtp-submit.c @@ -0,0 +1,505 @@ +/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "iostream-temp.h" +#include "iostream-ssl.h" +#include "master-service.h" +#include "program-client.h" +#include "smtp-client.h" +#include "smtp-client-connection.h" +#include "smtp-client-transaction.h" +#include "smtp-submit.h" + +#include <unistd.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <signal.h> + +#define DEFAULT_SUBMISSION_PORT 25 + +static struct event_category event_category_smtp_submit = { + .name = "smtp-submit" +}; + +struct smtp_submit_session { + pool_t pool; + struct smtp_submit_settings set; + struct ssl_iostream_settings ssl_set; + struct event *event; + bool allow_root:1; +}; + +struct smtp_submit { + pool_t pool; + + struct smtp_submit_session *session; + struct event *event; + + struct ostream *output; + struct istream *input; + + struct smtp_address *mail_from; + ARRAY_TYPE(smtp_address) rcpt_to; + + struct timeout *to_error; + int status; + const char *error; + + struct program_client *prg_client; + struct smtp_client *smtp_client; + struct smtp_client_transaction *smtp_trans; + + smtp_submit_callback_t *callback; + void *context; + + bool simple:1; +}; + +struct smtp_submit_session * +smtp_submit_session_init(const struct smtp_submit_input *input, + const struct smtp_submit_settings *set) +{ + struct smtp_submit_session *session; + pool_t pool; + + pool = pool_alloconly_create("smtp submit session", 128); + session = p_new(pool, struct smtp_submit_session, 1); + session->pool = pool; + + session->set = *set; + session->set.hostname = + p_strdup_empty(pool, set->hostname); + session->set.submission_host = + p_strdup_empty(pool, set->submission_host); + session->set.sendmail_path = + p_strdup_empty(pool, set->sendmail_path); + session->set.submission_ssl = + p_strdup_empty(pool, set->submission_ssl); + + if (input->ssl != NULL) { + ssl_iostream_settings_init_from(pool, &session->ssl_set, + input->ssl); + } + session->allow_root = input->allow_root; + + session->event = event_create(input->event_parent); + event_add_category(session->event, &event_category_smtp_submit); + + return session; +} + +void smtp_submit_session_deinit(struct smtp_submit_session **_session) +{ + struct smtp_submit_session *session = *_session; + + *_session = NULL; + + event_unref(&session->event); + pool_unref(&session->pool); +} + +struct smtp_submit * +smtp_submit_init(struct smtp_submit_session *session, + const struct smtp_address *mail_from) +{ + struct smtp_submit *subm; + pool_t pool; + + pool = pool_alloconly_create("smtp submit", 256); + subm = p_new(pool, struct smtp_submit, 1); + subm->session = session; + subm->pool = pool; + + subm->mail_from = smtp_address_clone(pool, mail_from);; + p_array_init(&subm->rcpt_to, pool, 2); + + subm->event = event_create(session->event); + event_add_str(subm->event, "mail_from", + smtp_address_encode(subm->mail_from)); + + return subm; +} + +struct smtp_submit * +smtp_submit_init_simple(const struct smtp_submit_input *input, + const struct smtp_submit_settings *set, + const struct smtp_address *mail_from) +{ + struct smtp_submit_session *session; + struct smtp_submit *subm; + + session = smtp_submit_session_init(input, set); + subm = smtp_submit_init(session, mail_from); + subm->simple = TRUE; + return subm; +} + +void smtp_submit_deinit(struct smtp_submit **_subm) +{ + struct smtp_submit *subm = *_subm; + + *_subm = NULL; + + if (subm->output != NULL) + o_stream_destroy(&subm->output); + if (subm->input != NULL) + i_stream_destroy(&subm->input); + + if (subm->prg_client != NULL) + program_client_destroy(&subm->prg_client); + if (subm->smtp_trans != NULL) + smtp_client_transaction_destroy(&subm->smtp_trans); + if (subm->smtp_client != NULL) + smtp_client_deinit(&subm->smtp_client); + + timeout_remove(&subm->to_error); + + if (subm->simple) + smtp_submit_session_deinit(&subm->session); + event_unref(&subm->event); + pool_unref(&subm->pool); +} + +void smtp_submit_add_rcpt(struct smtp_submit *subm, + const struct smtp_address *rcpt_to) +{ + struct smtp_address *rcpt; + + i_assert(subm->output == NULL); + i_assert(!smtp_address_isnull(rcpt_to)); + + rcpt = smtp_address_clone(subm->pool, rcpt_to); + array_push_back(&subm->rcpt_to, &rcpt); +} + +struct ostream *smtp_submit_send(struct smtp_submit *subm) +{ + i_assert(subm->output == NULL); + i_assert(array_count(&subm->rcpt_to) > 0); + + event_add_int(subm->event, "recipients", array_count(&subm->rcpt_to)); + + subm->output = iostream_temp_create + (t_strconcat("/tmp/dovecot.", + master_service_get_name(master_service), NULL), 0); + o_stream_set_no_error_handling(subm->output, TRUE); + return subm->output; +} + +static void +smtp_submit_callback(struct smtp_submit *subm, int status, + const char *error) +{ + struct smtp_submit_result result; + smtp_submit_callback_t *callback; + + timeout_remove(&subm->to_error); + + struct event_passthrough *e = + event_create_passthrough(subm->event)-> + set_name("smtp_submit_finished"); + if (status > 0) + e_debug(e->event(), "Sent message successfully"); + else { + e->add_str("error", error); + e_debug(e->event(), "Failed to send message: %s", error); + } + + i_zero(&result); + result.status = status; + result.error = error; + + callback = subm->callback; + subm->callback = NULL; + callback(&result, subm->context); +} + +static void +smtp_submit_delayed_error_callback(struct smtp_submit *subm) +{ + smtp_submit_callback(subm, -1, subm->error); +} + +static void +smtp_submit_delayed_error(struct smtp_submit *subm, + const char *error) +{ + subm->status = -1; + subm->error = p_strdup(subm->pool, error); + subm->to_error = timeout_add_short(0, + smtp_submit_delayed_error_callback, subm); +} + +static void +smtp_submit_error(struct smtp_submit *subm, + int status, const char *error) +{ + const struct smtp_submit_settings *set = &subm->session->set; + i_assert(status <= 0); + if (subm->error != NULL) + return; + + subm->status = status; + subm->error = p_strdup_printf(subm->pool, + "smtp(%s): %s", + set->submission_host, error); +} + +static void +smtp_submit_success(struct smtp_submit *subm) +{ + if (subm->error != NULL) + return; + subm->status = 1; +} + +static void +smtp_submit_send_host_finished(struct smtp_submit *subm) +{ + i_assert(subm->status > 0 || subm->error != NULL); + smtp_submit_callback(subm, subm->status, subm->error); + subm->smtp_trans = NULL; +} + +static bool +reply_is_temp_fail(const struct smtp_reply *reply) +{ + return (smtp_reply_is_temp_fail(reply) || + !smtp_reply_is_remote(reply)); +} + +static void +rcpt_to_callback(const struct smtp_reply *reply, + struct smtp_submit *subm) +{ + if (!smtp_reply_is_success(reply)) { + smtp_submit_error(subm, + (reply_is_temp_fail(reply) ? -1 : 0), + t_strdup_printf("RCPT TO failed: %s", + smtp_reply_log(reply))); + } +} + +static void +data_callback(const struct smtp_reply *reply, + struct smtp_submit *subm) +{ + if (!smtp_reply_is_success(reply)) { + smtp_submit_error(subm, + (reply_is_temp_fail(reply) ? -1 : 0), + t_strdup_printf("DATA failed: %s", + smtp_reply_log(reply))); + return; + } + + smtp_submit_success(subm); +} + +static void +data_dummy_callback(const struct smtp_reply *reply ATTR_UNUSED, + struct smtp_submit *subm ATTR_UNUSED) +{ + /* nothing */ +} + +static void +smtp_submit_send_host(struct smtp_submit *subm) +{ + const struct smtp_submit_settings *set = &subm->session->set; + struct smtp_client_settings smtp_set; + struct smtp_client *smtp_client; + struct smtp_client_connection *smtp_conn; + struct smtp_client_transaction *smtp_trans; + enum smtp_client_connection_ssl_mode ssl_mode; + struct smtp_address *rcpt; + const char *host; + in_port_t port; + + if (net_str2hostport(set->submission_host, + DEFAULT_SUBMISSION_PORT, &host, &port) < 0) { + smtp_submit_delayed_error(subm, t_strdup_printf( + "Invalid submission_host: %s", host)); + return; + } + + i_zero(&smtp_set); + smtp_set.my_hostname = set->hostname; + smtp_set.connect_timeout_msecs = set->submission_timeout*1000; + smtp_set.command_timeout_msecs = set->submission_timeout*1000; + smtp_set.debug = set->mail_debug; + smtp_set.ssl = &subm->session->ssl_set; + smtp_set.event_parent = subm->event; + + ssl_mode = SMTP_CLIENT_SSL_MODE_NONE; + if (set->submission_ssl != NULL) { + if (strcasecmp(set->submission_ssl, "smtps") == 0 || + strcasecmp(set->submission_ssl, "submissions") == 0) + ssl_mode = SMTP_CLIENT_SSL_MODE_IMMEDIATE; + else if (strcasecmp(set->submission_ssl, "starttls") == 0) + ssl_mode = SMTP_CLIENT_SSL_MODE_STARTTLS; + } + + smtp_client = smtp_client_init(&smtp_set); + smtp_conn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, host, port, ssl_mode, NULL); + + smtp_trans = smtp_client_transaction_create(smtp_conn, + subm->mail_from, NULL, 0, smtp_submit_send_host_finished, subm); + smtp_client_connection_unref(&smtp_conn); + + array_foreach_elem(&subm->rcpt_to, rcpt) { + smtp_client_transaction_add_rcpt(smtp_trans, + rcpt, NULL, rcpt_to_callback, data_dummy_callback, subm); + } + + subm->smtp_client = smtp_client; + subm->smtp_trans = smtp_trans; + + smtp_client_transaction_send + (smtp_trans, subm->input, data_callback, subm); + i_stream_unref(&subm->input); +} + +static void +smtp_submit_sendmail_callback(enum program_client_exit_status status, + struct smtp_submit *subm) +{ + if (status == PROGRAM_CLIENT_EXIT_STATUS_INTERNAL_FAILURE) { + smtp_submit_callback(subm, -1, + "Failed to execute sendmail"); + return; + } + if (status == PROGRAM_CLIENT_EXIT_STATUS_FAILURE) { + smtp_submit_callback(subm, -1, + "Sendmail program returned error"); + return; + } + + smtp_submit_callback(subm, 1, NULL); +} + +static void +smtp_submit_send_sendmail(struct smtp_submit *subm) +{ + const struct smtp_submit_settings *set = &subm->session->set; + const char *const *sendmail_args, *sendmail_bin, *str; + ARRAY_TYPE(const_string) args; + struct smtp_address *rcpt; + unsigned int i; + struct program_client_settings pc_set; + struct program_client *pc; + + sendmail_args = t_strsplit(set->sendmail_path, " "); + t_array_init(&args, 16); + i_assert(sendmail_args[0] != NULL); + sendmail_bin = sendmail_args[0]; + for (i = 1; sendmail_args[i] != NULL; i++) + array_push_back(&args, &sendmail_args[i]); + + str = "-i"; array_push_back(&args, &str); /* ignore dots */ + str = "-f"; array_push_back(&args, &str); + str = !smtp_address_isnull(subm->mail_from) ? + smtp_address_encode(subm->mail_from) : "<>"; + array_push_back(&args, &str); + + str = "--"; array_push_back(&args, &str); + array_foreach_elem(&subm->rcpt_to, rcpt) { + const char *rcpt_encoded = smtp_address_encode(rcpt); + array_push_back(&args, &rcpt_encoded); + } + array_append_zero(&args); + + i_zero(&pc_set); + pc_set.client_connect_timeout_msecs = set->submission_timeout * 1000; + pc_set.input_idle_timeout_msecs = set->submission_timeout * 1000; + pc_set.debug = set->mail_debug; + pc_set.event = subm->event; + pc_set.allow_root = subm->session->allow_root; + restrict_access_init(&pc_set.restrict_set); + + pc = program_client_local_create + (sendmail_bin, array_front(&args), &pc_set); + + program_client_set_input(pc, subm->input); + i_stream_unref(&subm->input); + + subm->prg_client = pc; + + program_client_run_async(pc, smtp_submit_sendmail_callback, subm); +} + +struct smtp_submit_run_context { + int status; + char *error; +}; + +static void +smtp_submit_run_callback(const struct smtp_submit_result *result, + struct smtp_submit_run_context *rctx) +{ + rctx->error = i_strdup(result->error); + rctx->status = result->status; + io_loop_stop(current_ioloop); +} + +int smtp_submit_run(struct smtp_submit *subm, + const char **error_r) +{ + struct smtp_submit_run_context rctx; + struct ioloop *ioloop; + + ioloop = io_loop_create(); + io_loop_set_running(ioloop); + + i_zero(&rctx); + smtp_submit_run_async(subm, + smtp_submit_run_callback, &rctx); + + if (io_loop_is_running(ioloop)) + io_loop_run(ioloop); + + io_loop_destroy(&ioloop); + + if (rctx.error == NULL) + *error_r = NULL; + else { + *error_r = t_strdup(rctx.error); + i_free(rctx.error); + } + + return rctx.status; +} + +#undef smtp_submit_run_async +void smtp_submit_run_async(struct smtp_submit *subm, + smtp_submit_callback_t *callback, void *context) +{ + const struct smtp_submit_settings *set = &subm->session->set; + uoff_t data_size; + + subm->callback = callback; + subm->context = context; + + /* the mail has been written to a file. now actually send it. */ + subm->input = iostream_temp_finish + (&subm->output, IO_BLOCK_SIZE); + + if (i_stream_get_size(subm->input, TRUE, &data_size) > 0) + event_add_int(subm->event, "data_size", data_size); + + struct event_passthrough *e = + event_create_passthrough(subm->event)-> + set_name("smtp_submit_started"); + e_debug(e->event(), "Started sending message"); + + if (set->submission_host != NULL) { + smtp_submit_send_host(subm); + } else { + smtp_submit_send_sendmail(subm); + } +} |