Mercurial > dovecot > core-2.2
changeset 21372:73f614573fb6
lib-lda: Moved LMTP client to lib-smtp.
This makes the LMTP client available without dependency on lib-storage.
For Dovecot v2.3, the newly created lib-smtp will evolve into a full client/server SMTP implementation.
That will then remove the remaining SMTP code from lib-lda.
author | Stephan Bosch <stephan.bosch@dovecot.fi> |
---|---|
date | Fri, 30 Dec 2016 16:20:12 +0100 |
parents | bf03a476152b |
children | 9d3889a5fbb3 |
files | configure.ac dovecot-config.in.in src/Makefile.am src/lib-lda/Makefile.am src/lib-lda/lmtp-client.c src/lib-lda/lmtp-client.h src/lib-smtp/Makefile.am src/lib-smtp/lmtp-client.c src/lib-smtp/lmtp-client.h src/lmtp/Makefile.am |
diffstat | 10 files changed, 1025 insertions(+), 1009 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.ac Fri Oct 28 21:34:41 2016 +0300 +++ b/configure.ac Fri Dec 30 16:20:12 2016 +0100 @@ -2590,7 +2590,7 @@ dnl ** Shared libraries usage dnl ** -LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-program-client/libprogram_client.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la' +LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-program-client/libprogram_client.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-smtp/libsmtp.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la' if test "$want_shared_libs" = "yes"; then LIBDOVECOT_DEPS='$(top_builddir)/src/lib-dovecot/libdovecot.la' LIBDOVECOT="$LIBDOVECOT_DEPS \$(MODULE_LIBS)" @@ -2954,6 +2954,7 @@ src/lib-dovecot/Makefile src/lib-sasl/Makefile src/lib-settings/Makefile +src/lib-smtp/Makefile src/lib-ssl-iostream/Makefile src/lib-stats/Makefile src/lib-test/Makefile
--- a/dovecot-config.in.in Fri Oct 28 21:34:41 2016 +0300 +++ b/dovecot-config.in.in Fri Dec 30 16:20:12 2016 +0100 @@ -22,7 +22,7 @@ LIBDOVECOT_DSYNC_DEPS="@LIBDOVECOT_DSYNC@" LIBDOVECOT_LIBFTS_DEPS="@LIBDOVECOT_LIBFTS@" -LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-http -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-ssl-iostream -I$(incdir)/src/lib-compression -I$(incdir)/src/lib-settings -I$(incdir)/src/lib-test -I$(incdir)/src/lib-sasl -I$(incdir)/src/lib-stats -I$(incdir)/src/lib-dcrypt -I$(incdir)/src/lib-program-client" +LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-http -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-smtp -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-ssl-iostream -I$(incdir)/src/lib-compression -I$(incdir)/src/lib-settings -I$(incdir)/src/lib-test -I$(incdir)/src/lib-sasl -I$(incdir)/src/lib-stats -I$(incdir)/src/lib-dcrypt -I$(incdir)/src/lib-program-client" LIBDOVECOT_LDA_INCLUDE="-I$(incdir)/src/lib-lda -I$(incdir)/src/lda" LIBDOVECOT_AUTH_INCLUDE="-I$(incdir)/src/auth" LIBDOVECOT_DOVEADM_INCLUDE="-I$(incdir)/src/doveadm"
--- a/src/Makefile.am Fri Oct 28 21:34:41 2016 +0300 +++ b/src/Makefile.am Fri Dec 30 16:20:12 2016 +0100 @@ -18,6 +18,7 @@ lib-http \ lib-fs \ lib-mail \ + lib-smtp \ lib-imap \ lib-imap-storage \ lib-program-client
--- a/src/lib-lda/Makefile.am Fri Oct 28 21:34:41 2016 +0300 +++ b/src/lib-lda/Makefile.am Fri Dec 30 16:20:12 2016 +0100 @@ -5,6 +5,7 @@ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-storage @@ -12,7 +13,6 @@ liblda_la_SOURCES = \ duplicate.c \ lda-settings.c \ - lmtp-client.c \ mail-deliver.c \ mail-send.c \ smtp-client.c @@ -20,7 +20,6 @@ headers = \ duplicate.h \ lda-settings.h \ - lmtp-client.h \ mail-deliver.h \ mail-send.h \ smtp-client.h
--- a/src/lib-lda/lmtp-client.c Fri Oct 28 21:34:41 2016 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,898 +0,0 @@ -/* Copyright (c) 2009-2016 Dovecot authors, see the included COPYING file */ - -#include "lib.h" -#include "array.h" -#include "ioloop.h" -#include "net.h" -#include "istream.h" -#include "ostream.h" -#include "str.h" -#include "dns-lookup.h" -#include "lmtp-client.h" - -#include <ctype.h> - -#define LMTP_MAX_LINE_LEN 1024 -#define LMTP_CLIENT_DNS_LOOKUP_TIMEOUT_MSECS (60*1000) - -enum lmtp_input_state { - LMTP_INPUT_STATE_GREET, - LMTP_INPUT_STATE_LHLO, - LMTP_INPUT_STATE_MAIL_FROM, - LMTP_INPUT_STATE_RCPT_TO, - LMTP_INPUT_STATE_DATA_CONTINUE, - LMTP_INPUT_STATE_DATA, - LMTP_INPUT_STATE_XCLIENT -}; - -struct lmtp_rcpt { - const char *address; - lmtp_callback_t *rcpt_to_callback; - lmtp_callback_t *data_callback; - void *context; - - struct lmtp_recipient_params params; - - unsigned int data_called:1; - unsigned int failed:1; -}; - -struct lmtp_client { - pool_t pool; - int refcount; - - struct lmtp_client_settings set; - const char *host; - struct ip_addr ip; - in_port_t port; - enum lmtp_client_protocol protocol; - enum lmtp_input_state input_state; - const char *global_fail_string; - string_t *input_multiline; - const char **xclient_args; - - struct dns_lookup *dns_lookup; - struct istream *input; - struct ostream *output; - struct io *io; - struct timeout *to; - int fd; - - void (*data_output_callback)(void *); - void *data_output_context; - - lmtp_finish_callback_t *finish_callback; - void *finish_context; - - const char *data_header; - ARRAY(struct lmtp_rcpt) recipients; - unsigned int rcpt_next_receive_idx; - unsigned int rcpt_next_data_idx; - unsigned int rcpt_next_send_idx; - struct istream *data_input; - unsigned char output_last; - struct lmtp_client_times times; - - unsigned int running:1; - unsigned int xclient_sent:1; - unsigned int rcpt_to_successes:1; - unsigned int output_finished:1; - unsigned int finish_called:1; - unsigned int global_remote_failure:1; -}; - -static void lmtp_client_send_rcpts(struct lmtp_client *client); - -struct lmtp_client * -lmtp_client_init(const struct lmtp_client_settings *set, - lmtp_finish_callback_t *finish_callback, void *context) -{ - struct lmtp_client *client; - pool_t pool; - - i_assert(*set->mail_from == '<'); - i_assert(*set->my_hostname != '\0'); - - pool = pool_alloconly_create("lmtp client", 512); - client = p_new(pool, struct lmtp_client, 1); - client->refcount = 1; - client->pool = pool; - client->set.mail_from = p_strdup(pool, set->mail_from); - client->set.my_hostname = p_strdup(pool, set->my_hostname); - client->set.dns_client_socket_path = - p_strdup(pool, set->dns_client_socket_path); - client->set.source_ip = set->source_ip; - client->set.source_port = set->source_port; - client->set.proxy_ttl = set->proxy_ttl; - client->set.proxy_timeout_secs = set->proxy_timeout_secs; - client->set.timeout_secs = set->timeout_secs; - client->finish_callback = finish_callback; - client->finish_context = context; - client->fd = -1; - client->input_multiline = str_new(default_pool, 128); - p_array_init(&client->recipients, pool, 16); - return client; -} - -void lmtp_client_close(struct lmtp_client *client) -{ - if (client->dns_lookup != NULL) - dns_lookup_abort(&client->dns_lookup); - if (client->to != NULL) - timeout_remove(&client->to); - if (client->io != NULL) - io_remove(&client->io); - if (client->input != NULL) - i_stream_close(client->input); - if (client->output != NULL) - o_stream_close(client->output); - if (client->fd != -1) { - net_disconnect(client->fd); - client->fd = -1; - } - if (client->data_input != NULL) - i_stream_unref(&client->data_input); - client->output_finished = TRUE; - - if (!client->finish_called) { - client->finish_called = TRUE; - client->finish_callback(client->finish_context); - } -} - -static void lmtp_client_ref(struct lmtp_client *client) -{ - client->refcount++; -} - -static void lmtp_client_unref(struct lmtp_client **_client) -{ - struct lmtp_client *client = *_client; - - *_client = NULL; - - i_assert(client->refcount > 0); - if (--client->refcount > 0) - return; - - i_assert(client->finish_called); - if (client->input != NULL) - i_stream_unref(&client->input); - if (client->output != NULL) - o_stream_unref(&client->output); - str_free(&client->input_multiline); - pool_unref(&client->pool); -} - -void lmtp_client_deinit(struct lmtp_client **_client) -{ - struct lmtp_client *client = *_client; - - *_client = NULL; - - lmtp_client_close(client); - lmtp_client_unref(&client); -} - -const char *lmtp_client_state_to_string(struct lmtp_client *client) -{ - uoff_t size; - - switch (client->input_state) { - case LMTP_INPUT_STATE_GREET: - return "greeting"; - case LMTP_INPUT_STATE_LHLO: - return "LHLO"; - case LMTP_INPUT_STATE_MAIL_FROM: - return "MAIL FROM"; - case LMTP_INPUT_STATE_RCPT_TO: - return "RCPT TO"; - case LMTP_INPUT_STATE_DATA_CONTINUE: - return "DATA init"; - case LMTP_INPUT_STATE_DATA: - if (client->output_finished) - return "DATA reply"; - else if (i_stream_get_size(client->data_input, FALSE, &size) > 0) { - return t_strdup_printf( - "DATA (%"PRIuUOFF_T"/%"PRIuUOFF_T")", - client->data_input->v_offset, size); - } else { - return t_strdup_printf("DATA (%"PRIuUOFF_T"/?)", - client->data_input->v_offset); - } - case LMTP_INPUT_STATE_XCLIENT: - return "XCLIENT"; - } - return "??"; -} - -static void -lmtp_client_fail_full(struct lmtp_client *client, const char *line, bool remote) -{ - enum lmtp_client_result result; - struct lmtp_rcpt *recipients; - unsigned int i, count; - - client->global_fail_string = p_strdup(client->pool, line); - client->global_remote_failure = remote; - result = remote ? LMTP_CLIENT_RESULT_REMOTE_ERROR : - LMTP_CLIENT_RESULT_INTERNAL_ERROR; - - lmtp_client_ref(client); - recipients = array_get_modifiable(&client->recipients, &count); - for (i = client->rcpt_next_receive_idx; i < count; i++) { - recipients[i].rcpt_to_callback(result, line, - recipients[i].context); - recipients[i].failed = TRUE; - } - client->rcpt_next_receive_idx = count; - - for (i = client->rcpt_next_data_idx; i < count; i++) { - if (!recipients[i].failed) { - recipients[i].data_callback(result, line, - recipients[i].context); - } - } - client->rcpt_next_data_idx = count; - - lmtp_client_close(client); - lmtp_client_unref(&client); -} - -static void -lmtp_client_fail_remote(struct lmtp_client *client, const char *line) -{ - lmtp_client_fail_full(client, line, TRUE); -} - -void lmtp_client_fail(struct lmtp_client *client, const char *line) -{ - lmtp_client_fail_full(client, line, FALSE); -} - -static void -lmtp_client_rcpt_next(struct lmtp_client *client, const char *line) -{ - struct lmtp_rcpt *rcpt; - enum lmtp_client_result result; - - result = line[0] == '2' ? LMTP_CLIENT_RESULT_OK : - LMTP_CLIENT_RESULT_REMOTE_ERROR; - if (result == LMTP_CLIENT_RESULT_OK) - client->rcpt_to_successes = TRUE; - - rcpt = array_idx_modifiable(&client->recipients, - client->rcpt_next_receive_idx); - client->rcpt_next_receive_idx++; - - rcpt->failed = result != LMTP_CLIENT_RESULT_OK; - rcpt->rcpt_to_callback(result, line, rcpt->context); -} - -static int lmtp_client_send_data_cmd(struct lmtp_client *client) -{ - if (client->rcpt_next_receive_idx < array_count(&client->recipients)) - return 0; - - if (client->global_fail_string != NULL) { - lmtp_client_fail_full(client, client->global_fail_string, - client->global_remote_failure); - return -1; - } else if (!client->rcpt_to_successes) { - /* This error string shouldn't become visible anywhere */ - lmtp_client_fail_full(client, "No valid recipients", FALSE); - return -1; - } else { - client->input_state++; - o_stream_nsend_str(client->output, "DATA\r\n"); - return 0; - } -} - -static int -lmtp_client_data_next(struct lmtp_client *client, const char *line) -{ - struct lmtp_rcpt *rcpt; - unsigned int i, count; - enum lmtp_client_result result; - - rcpt = array_get_modifiable(&client->recipients, &count); - for (i = client->rcpt_next_data_idx; i < count; i++) { - if (rcpt[i].failed) { - /* already called rcpt_to_callback with failure */ - continue; - } - - client->rcpt_next_data_idx = i + 1; - rcpt[i].failed = line[0] != '2'; - result = rcpt[i].failed ? LMTP_CLIENT_RESULT_REMOTE_ERROR : - LMTP_CLIENT_RESULT_OK; - rcpt[i].data_callback(result, line, rcpt[i].context); - if (client->protocol == LMTP_CLIENT_PROTOCOL_LMTP) - break; - } - if (client->rcpt_next_data_idx < count) - return 0; - - o_stream_send_str(client->output, "QUIT\r\n"); - lmtp_client_close(client); - return -1; -} - -static int lmtp_client_send_data(struct lmtp_client *client) -{ - const unsigned char *data; - unsigned char add; - size_t i, size; - bool sent_bytes = FALSE; - int ret; - - if (client->output_finished) - return 0; - - while ((ret = i_stream_read_data(client->data_input, - &data, &size, 0)) > 0) { - add = '\0'; - for (i = 0; i < size; i++) { - if (data[i] == '\n') { - if ((i == 0 && client->output_last != '\r') || - (i > 0 && data[i-1] != '\r')) { - /* missing CR */ - add = '\r'; - break; - } - } else if (data[i] == '.' && - ((i == 0 && client->output_last == '\n') || - (i > 0 && data[i-1] == '\n'))) { - /* escape the dot */ - add = '.'; - break; - } - } - - if (i > 0) { - if (o_stream_send(client->output, data, i) < 0) - break; - client->output_last = data[i-1]; - i_stream_skip(client->data_input, i); - sent_bytes = TRUE; - } - - if (o_stream_get_buffer_used_size(client->output) >= 4096) { - if ((ret = o_stream_flush(client->output)) < 0) - break; - if (ret == 0) { - /* continue later */ - o_stream_set_flush_pending(client->output, TRUE); - return 0; - } - } - - if (add != '\0') { - if (o_stream_send(client->output, &add, 1) < 0) - break; - - client->output_last = add; - } - } - if (client->data_input->stream_errno != 0) { - i_error("lmtp client: read(%s) failed: %s", - i_stream_get_name(client->data_input), - i_stream_get_error(client->data_input)); - lmtp_client_fail(client, - "451 4.3.0 Internal failure while reading DATA input"); - return -1; - } - if (sent_bytes && client->data_output_callback != NULL) - client->data_output_callback(client->data_output_context); - - if (ret == 0 || ret == -2) { - /* -2 can happen with tee istreams */ - return 0; - } - - if (client->output_last != '\n') { - /* didn't end with CRLF */ - o_stream_nsend(client->output, "\r\n", 2); - } - o_stream_nsend(client->output, ".\r\n", 3); - client->output_finished = TRUE; - io_loop_time_refresh(); - client->times.data_sent = ioloop_timeval; - return 0; -} - -static void lmtp_client_send_handshake(struct lmtp_client *client) -{ - switch (client->protocol) { - case LMTP_CLIENT_PROTOCOL_LMTP: - o_stream_nsend_str(client->output, - t_strdup_printf("LHLO %s\r\n", - client->set.my_hostname)); - break; - case LMTP_CLIENT_PROTOCOL_SMTP: - o_stream_nsend_str(client->output, - t_strdup_printf("EHLO %s\r\n", - client->set.my_hostname)); - break; - } -} - -static int lmtp_input_get_reply_code(const char *line, int *reply_code_r, - string_t *multiline) -{ - if (!i_isdigit(line[0]) || !i_isdigit(line[1]) || !i_isdigit(line[2])) - return -1; - - *reply_code_r = (line[0]-'0') * 100 + - (line[1]-'0') * 10 + - (line[2]-'0'); - - if (line[3] == ' ') { - /* final reply */ - return 1; - } else if (line[3] == '-') { - /* multiline reply. */ - str_append(multiline, line); - str_append_c(multiline, '\n'); - return 0; - } else { - /* invalid input */ - return -1; - } -} - -static void -lmtp_client_parse_capabilities(struct lmtp_client *client, const char *lines) -{ - const char *const *linep; - - for (linep = t_strsplit(lines, "\n"); *linep != NULL; linep++) { - const char *line = *linep; - - line += 4; /* already checked this is valid */ - if (strncasecmp(line, "XCLIENT ", 8) == 0) { - client->xclient_args = - (void *)p_strsplit(client->pool, line + 8, " "); - } - } -} - -static bool lmtp_client_send_xclient(struct lmtp_client *client) -{ - string_t *str; - size_t empty_len; - - if (client->xclient_args == NULL) { - /* not supported */ - return FALSE; - } - if (client->xclient_sent) - return FALSE; - - str = t_str_new(64); - str_append(str, "XCLIENT"); - empty_len = str_len(str); - if (client->set.source_ip.family != 0 && - str_array_icase_find(client->xclient_args, "ADDR")) - str_printfa(str, " ADDR=%s", net_ip2addr(&client->set.source_ip)); - if (client->set.source_port != 0 && - str_array_icase_find(client->xclient_args, "PORT")) - str_printfa(str, " PORT=%u", client->set.source_port); - if (client->set.proxy_ttl != 0 && - str_array_icase_find(client->xclient_args, "TTL")) - str_printfa(str, " TTL=%u", client->set.proxy_ttl); - if (client->set.proxy_timeout_secs != 0 && - str_array_icase_find(client->xclient_args, "TIMEOUT")) - str_printfa(str, " TIMEOUT=%u", client->set.proxy_timeout_secs); - - if (str_len(str) == empty_len) - return FALSE; - - str_append(str, "\r\n"); - o_stream_nsend(client->output, str_data(str), str_len(str)); - return TRUE; -} - -static int lmtp_client_input_line(struct lmtp_client *client, const char *line) -{ - int ret, reply_code = 0; - - if ((ret = lmtp_input_get_reply_code(line, &reply_code, - client->input_multiline)) <= 0) { - if (ret == 0) - return 0; - lmtp_client_fail(client, t_strdup_printf( - "451 4.5.0 Received invalid input: %s", line)); - return -1; - } - - switch (client->input_state) { - case LMTP_INPUT_STATE_GREET: - if (reply_code != 220) { - lmtp_client_fail(client, t_strdup_printf( - "451 4.5.0 Received invalid greeting: %s", line)); - return -1; - } - client->times.banner_received = ioloop_timeval; - lmtp_client_send_handshake(client); - client->input_state = LMTP_INPUT_STATE_LHLO; - break; - case LMTP_INPUT_STATE_XCLIENT: - if (reply_code != 220) { - lmtp_client_fail(client, t_strdup_printf( - "451 4.5.0 XCLIENT failed: %s", line)); - return -1; - } - lmtp_client_send_handshake(client); - client->input_state = LMTP_INPUT_STATE_LHLO; - break; - case LMTP_INPUT_STATE_LHLO: - if (reply_code != 250) { - lmtp_client_fail(client, t_strdup_printf( - "451 4.5.0 LHLO failed: %s", line)); - lmtp_client_fail(client, line); - return -1; - } - str_append(client->input_multiline, line); - lmtp_client_parse_capabilities(client, - str_c(client->input_multiline)); - if (lmtp_client_send_xclient(client)) { - client->input_state = LMTP_INPUT_STATE_XCLIENT; - client->xclient_sent = TRUE; - break; - } - o_stream_nsend_str(client->output, t_strdup_printf( - "MAIL FROM:%s\r\n", client->set.mail_from)); - client->input_state++; - break; - case LMTP_INPUT_STATE_MAIL_FROM: - if (reply_code != 250) { - lmtp_client_fail(client, t_strdup_printf( - "451 4.5.0 MAIL FROM failed: %s", line)); - return -1; - } - client->input_state++; - lmtp_client_send_rcpts(client); - break; - case LMTP_INPUT_STATE_RCPT_TO: - lmtp_client_rcpt_next(client, line); - if (client->data_input == NULL) - break; - if (lmtp_client_send_data_cmd(client) < 0) - return -1; - break; - case LMTP_INPUT_STATE_DATA_CONTINUE: - /* Start sending DATA */ - if (strncmp(line, "354", 3) != 0) { - lmtp_client_fail_remote(client, line); - return -1; - } - client->input_state++; - client->times.data_started = ioloop_timeval; - if (client->data_header != NULL) - o_stream_nsend_str(client->output, client->data_header); - if (lmtp_client_send_data(client) < 0) - return -1; - break; - case LMTP_INPUT_STATE_DATA: - /* DATA replies */ - if (lmtp_client_data_next(client, line) < 0) - return -1; - break; - } - return 1; -} - -static void lmtp_client_input(struct lmtp_client *client) -{ - const char *line; - int ret; - - lmtp_client_ref(client); - o_stream_cork(client->output); - while ((line = i_stream_read_next_line(client->input)) != NULL) { - T_BEGIN { - ret = lmtp_client_input_line(client, line); - } T_END; - if (ret < 0) { - o_stream_uncork(client->output); - lmtp_client_unref(&client); - return; - } - if (ret > 0) - str_truncate(client->input_multiline, 0); - } - - if (client->input->stream_errno == ENOBUFS) { - lmtp_client_fail(client, - "501 5.5.4 Command reply line too long"); - } else if (client->input->stream_errno != 0) { - i_error("lmtp client: read() failed: %s", - i_stream_get_error(client->input)); - lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE - " (read failure)"); - } else if (client->input->eof) { - lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE - " (disconnected in input)"); - } - o_stream_uncork(client->output); - if (client->to != NULL) - timeout_reset(client->to); - lmtp_client_unref(&client); -} - -static void lmtp_client_wait_connect(struct lmtp_client *client) -{ - int err; - - err = net_geterror(client->fd); - if (err != 0) { - i_error("lmtp client: connect(%s, %u) failed: %s", - client->host, client->port, strerror(err)); - lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE - " (connect)"); - return; - } - if (client->to != NULL) - timeout_remove(&client->to); - io_remove(&client->io); - client->io = io_add(client->fd, IO_READ, lmtp_client_input, client); - lmtp_client_input(client); -} - -static void lmtp_client_connect_timeout(struct lmtp_client *client) -{ - i_error("lmtp client: connect(%s, %u) failed: Timed out in %u secs", - client->host, client->port, client->set.timeout_secs); - lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE - " (connect timeout)"); -} - -static int lmtp_client_output(struct lmtp_client *client) -{ - int ret; - - lmtp_client_ref(client); - o_stream_cork(client->output); - if ((ret = o_stream_flush(client->output)) < 0) - lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE - " (disconnected in output)"); - else if (client->input_state == LMTP_INPUT_STATE_DATA) - (void)lmtp_client_send_data(client); - o_stream_uncork(client->output); - if (client->to != NULL) - timeout_reset(client->to); - lmtp_client_unref(&client); - return ret; -} - -static int lmtp_client_connect(struct lmtp_client *client) -{ - i_assert(client->fd == -1); - - client->times.connect_started = ioloop_timeval; - - client->fd = net_connect_ip(&client->ip, client->port, NULL); - if (client->fd == -1) { - i_error("lmtp client: connect(%s, %u) failed: %m", - client->host, client->port); - return -1; - } - client->input = - i_stream_create_fd(client->fd, LMTP_MAX_LINE_LEN, FALSE); - client->output = o_stream_create_fd(client->fd, (size_t)-1, FALSE); - o_stream_set_no_error_handling(client->output, TRUE); - o_stream_set_flush_callback(client->output, lmtp_client_output, client); - /* we're already sending data in ostream, so can't use IO_WRITE here */ - client->io = io_add(client->fd, IO_READ, - lmtp_client_wait_connect, client); - if (client->set.timeout_secs > 0) { - client->to = timeout_add(client->set.timeout_secs*1000, - lmtp_client_connect_timeout, client); - } - return 0; -} - -static void lmtp_client_dns_done(const struct dns_lookup_result *result, - struct lmtp_client *client) -{ - client->dns_lookup = NULL; - - if (result->ret != 0) { - i_error("lmtp client: DNS lookup of %s failed: %s", - client->host, result->error); - if (client->running) { - lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE - " (DNS lookup)"); - } - } else { - client->ip = result->ips[0]; - if (lmtp_client_connect(client) < 0 && client->running) { - lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE - " (connect)"); - } - } -} - -int lmtp_client_connect_tcp(struct lmtp_client *client, - enum lmtp_client_protocol protocol, - const char *host, in_port_t port) -{ - struct dns_lookup_settings dns_lookup_set; - struct ip_addr *ips; - unsigned int ips_count; - int ret; - - client->input_state = LMTP_INPUT_STATE_GREET; - client->host = p_strdup(client->pool, host); - client->port = port; - client->protocol = protocol; - - if (*host == '\0') { - i_error("lmtp client: host not given"); - return -1; - } - - memset(&dns_lookup_set, 0, sizeof(dns_lookup_set)); - dns_lookup_set.dns_client_socket_path = - client->set.dns_client_socket_path; - dns_lookup_set.timeout_msecs = LMTP_CLIENT_DNS_LOOKUP_TIMEOUT_MSECS; - - if (net_addr2ip(host, &client->ip) == 0) { - /* IP address */ - } else if (dns_lookup_set.dns_client_socket_path == NULL) { - /* no dns-client, use blocking lookup */ - ret = net_gethostbyname(host, &ips, &ips_count); - if (ret != 0) { - i_error("lmtp client: DNS lookup of %s failed: %s", - client->host, net_gethosterror(ret)); - return -1; - } - client->ip = ips[0]; - } else { - if (dns_lookup(host, &dns_lookup_set, - lmtp_client_dns_done, client, - &client->dns_lookup) < 0) - return -1; - client->running = TRUE; - return 0; - } - - if (lmtp_client_connect(client) < 0) - return -1; - return 0; -} - -void lmtp_client_set_data_header(struct lmtp_client *client, const char *str) -{ - client->data_header = p_strdup(client->pool, str); -} - -static void lmtp_append_xtext(string_t *dest, const char *str) -{ - unsigned int i; - - for (i = 0; str[i] != '\0'; i++) { - if (str[i] >= 33 && str[i] <= 126 && - str[i] != '+' && str[i] != '=') - str_append_c(dest, str[i]); - else - str_printfa(dest, "+%02X", str[i]); - } -} - -static void lmtp_client_send_rcpts(struct lmtp_client *client) -{ - const struct lmtp_rcpt *rcpt; - unsigned int i, count; - string_t *str = t_str_new(128); - - rcpt = array_get(&client->recipients, &count); - for (i = client->rcpt_next_send_idx; i < count; i++) { - str_truncate(str, 0); - str_printfa(str, "RCPT TO:<%s>", rcpt[i].address); - if (rcpt->params.dsn_orcpt != NULL) { - str_append(str, " ORCPT="); - lmtp_append_xtext(str, rcpt->params.dsn_orcpt); - } - str_append(str, "\r\n"); - o_stream_nsend(client->output, str_data(str), str_len(str)); - } - client->rcpt_next_send_idx = i; -} - -void lmtp_client_add_rcpt(struct lmtp_client *client, const char *address, - lmtp_callback_t *rcpt_to_callback, - lmtp_callback_t *data_callback, void *context) -{ - struct lmtp_recipient_params params; - - memset(¶ms, 0, sizeof(params)); - lmtp_client_add_rcpt_params(client, address, ¶ms, rcpt_to_callback, - data_callback, context); -} - -void lmtp_client_add_rcpt_params(struct lmtp_client *client, const char *address, - const struct lmtp_recipient_params *params, - lmtp_callback_t *rcpt_to_callback, - lmtp_callback_t *data_callback, void *context) -{ - struct lmtp_rcpt *rcpt; - enum lmtp_client_result result; - - rcpt = array_append_space(&client->recipients); - rcpt->address = p_strdup(client->pool, address); - rcpt->params.dsn_orcpt = p_strdup(client->pool, params->dsn_orcpt); - rcpt->rcpt_to_callback = rcpt_to_callback; - rcpt->data_callback = data_callback; - rcpt->context = context; - - if (client->global_fail_string != NULL) { - /* we've already failed */ - client->rcpt_next_receive_idx++; - i_assert(client->rcpt_next_receive_idx == - array_count(&client->recipients)); - - result = client->global_remote_failure ? - LMTP_CLIENT_RESULT_REMOTE_ERROR : - LMTP_CLIENT_RESULT_INTERNAL_ERROR; - rcpt->failed = TRUE; - rcpt_to_callback(result, client->global_fail_string, context); - } else if (client->input_state == LMTP_INPUT_STATE_RCPT_TO) - lmtp_client_send_rcpts(client); -} - -static void lmtp_client_timeout(struct lmtp_client *client) -{ - const char *line; - - line = t_strdup_printf(ERRSTR_TEMP_REMOTE_FAILURE - " (Timed out after %u secs while waiting for reply to %s)", - client->set.timeout_secs, lmtp_client_state_to_string(client)); - lmtp_client_fail(client, line); -} - -void lmtp_client_send(struct lmtp_client *client, struct istream *data_input) -{ - i_assert(client->data_input == NULL); - - i_stream_ref(data_input); - client->data_input = data_input; - - /* now we actually want to start doing I/O. start the timeout - handling. */ - if (client->set.timeout_secs > 0) { - if (client->to != NULL) { - /* still waiting for connect to finish */ - timeout_remove(&client->to); - } - client->to = timeout_add(client->set.timeout_secs*1000, - lmtp_client_timeout, client); - } - - (void)lmtp_client_send_data_cmd(client); -} - -void lmtp_client_send_more(struct lmtp_client *client) -{ - if (client->input_state == LMTP_INPUT_STATE_DATA) { - o_stream_cork(client->output); - (void)lmtp_client_send_data(client); - o_stream_uncork(client->output); - } -} - -void lmtp_client_set_data_output_callback(struct lmtp_client *client, - void (*callback)(void *), - void *context) -{ - client->data_output_callback = callback; - client->data_output_context = context; -} - -const struct lmtp_client_times * -lmtp_client_get_times(struct lmtp_client *client) -{ - return &client->times; -}
--- a/src/lib-lda/lmtp-client.h Fri Oct 28 21:34:41 2016 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -#ifndef LMTP_CLIENT_H -#define LMTP_CLIENT_H - -#include "net.h" - -#define ERRSTR_TEMP_REMOTE_FAILURE "451 4.4.0 Remote server not answering" - -/* LMTP/SMTP client code. */ - -enum lmtp_client_protocol { - LMTP_CLIENT_PROTOCOL_LMTP, - LMTP_CLIENT_PROTOCOL_SMTP -}; - -enum lmtp_client_result { - /* Command succeeded */ - LMTP_CLIENT_RESULT_OK = 1, - /* Command failed because remote server returned an error */ - LMTP_CLIENT_RESULT_REMOTE_ERROR = 0, - /* Command failed because of an internal error (e.g. couldn't connect - to remote) */ - LMTP_CLIENT_RESULT_INTERNAL_ERROR = -1 -}; - -struct lmtp_recipient_params { - const char *dsn_orcpt; -}; - -struct lmtp_client_times { - struct timeval connect_started; - struct timeval banner_received; - struct timeval data_started; - struct timeval data_sent; -}; - -struct lmtp_client_settings { - const char *my_hostname; - /* The whole MAIL FROM line, including parameters */ - const char *mail_from; - const char *dns_client_socket_path; - - /* if remote server supports XCLIENT capability, - send the these as ADDR/PORT/TTL/TIMEOUT */ - struct ip_addr source_ip; - in_port_t source_port; - /* send TTL as this (default 0 means "don't send it") */ - unsigned int proxy_ttl; - /* remote is notified that the connection is going to be closed after - this many seconds, so it should try to keep lock waits and such - lower than this. */ - unsigned int proxy_timeout_secs; - /* Don't wait an answer from destination server longer than this many - seconds (0 = unlimited) */ - unsigned int timeout_secs; -}; - -/* reply contains the reply coming from remote server, or NULL - if it's a connection error. */ -typedef void lmtp_callback_t(enum lmtp_client_result result, - const char *reply, void *context); -/* called when session is finished, either because all RCPT TOs failed or - because all DATA replies have been received. */ -typedef void lmtp_finish_callback_t(void *context); - -struct lmtp_client * -lmtp_client_init(const struct lmtp_client_settings *set, - lmtp_finish_callback_t *finish_callback, void *context); -void lmtp_client_deinit(struct lmtp_client **client); - -int lmtp_client_connect_tcp(struct lmtp_client *client, - enum lmtp_client_protocol protocol, - const char *host, in_port_t port); -void lmtp_client_close(struct lmtp_client *client); - -/* Add headers from given string before the rest of the data. The string must - use CRLF line feeds and end with CRLF. */ -void lmtp_client_set_data_header(struct lmtp_client *client, const char *str); -/* Add recipient to the session. rcpt_to_callback is called once LMTP server - replies with RCPT TO. If RCPT TO was a succees, data_callback is called - when DATA replies. */ -void lmtp_client_add_rcpt(struct lmtp_client *client, const char *address, - lmtp_callback_t *rcpt_to_callback, - lmtp_callback_t *data_callback, void *context); -void lmtp_client_add_rcpt_params(struct lmtp_client *client, const char *address, - const struct lmtp_recipient_params *params, - lmtp_callback_t *rcpt_to_callback, - lmtp_callback_t *data_callback, void *context); -/* Start sending input stream as DATA. */ -void lmtp_client_send(struct lmtp_client *client, struct istream *data_input); -/* Call this function whenever input stream can potentially be read forward. - This is useful with non-blocking istreams and tee-istreams. */ -void lmtp_client_send_more(struct lmtp_client *client); -/* Fail the connection with line as the reply to unfinished RCPT TO/DATA - replies. This will be treated as an internal failure. */ -void lmtp_client_fail(struct lmtp_client *client, const char *line); -/* Return the state (command reply) the client is currently waiting for. */ -const char *lmtp_client_state_to_string(struct lmtp_client *client); -/* Call the given callback whenever client manages to send some more DATA - output to client. */ -void lmtp_client_set_data_output_callback(struct lmtp_client *client, - void (*callback)(void *), - void *context); -/* Return LMTP client statistics. */ -const struct lmtp_client_times * -lmtp_client_get_times(struct lmtp_client *client); - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-smtp/Makefile.am Fri Dec 30 16:20:12 2016 +0100 @@ -0,0 +1,14 @@ +noinst_LTLIBRARIES = libsmtp.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dns + +libsmtp_la_SOURCES = \ + lmtp-client.c + +headers = \ + lmtp-client.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-smtp/lmtp-client.c Fri Dec 30 16:20:12 2016 +0100 @@ -0,0 +1,898 @@ +/* Copyright (c) 2009-2016 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "str.h" +#include "dns-lookup.h" +#include "lmtp-client.h" + +#include <ctype.h> + +#define LMTP_MAX_LINE_LEN 1024 +#define LMTP_CLIENT_DNS_LOOKUP_TIMEOUT_MSECS (60*1000) + +enum lmtp_input_state { + LMTP_INPUT_STATE_GREET, + LMTP_INPUT_STATE_LHLO, + LMTP_INPUT_STATE_MAIL_FROM, + LMTP_INPUT_STATE_RCPT_TO, + LMTP_INPUT_STATE_DATA_CONTINUE, + LMTP_INPUT_STATE_DATA, + LMTP_INPUT_STATE_XCLIENT +}; + +struct lmtp_rcpt { + const char *address; + lmtp_callback_t *rcpt_to_callback; + lmtp_callback_t *data_callback; + void *context; + + struct lmtp_recipient_params params; + + unsigned int data_called:1; + unsigned int failed:1; +}; + +struct lmtp_client { + pool_t pool; + int refcount; + + struct lmtp_client_settings set; + const char *host; + struct ip_addr ip; + in_port_t port; + enum lmtp_client_protocol protocol; + enum lmtp_input_state input_state; + const char *global_fail_string; + string_t *input_multiline; + const char **xclient_args; + + struct dns_lookup *dns_lookup; + struct istream *input; + struct ostream *output; + struct io *io; + struct timeout *to; + int fd; + + void (*data_output_callback)(void *); + void *data_output_context; + + lmtp_finish_callback_t *finish_callback; + void *finish_context; + + const char *data_header; + ARRAY(struct lmtp_rcpt) recipients; + unsigned int rcpt_next_receive_idx; + unsigned int rcpt_next_data_idx; + unsigned int rcpt_next_send_idx; + struct istream *data_input; + unsigned char output_last; + struct lmtp_client_times times; + + unsigned int running:1; + unsigned int xclient_sent:1; + unsigned int rcpt_to_successes:1; + unsigned int output_finished:1; + unsigned int finish_called:1; + unsigned int global_remote_failure:1; +}; + +static void lmtp_client_send_rcpts(struct lmtp_client *client); + +struct lmtp_client * +lmtp_client_init(const struct lmtp_client_settings *set, + lmtp_finish_callback_t *finish_callback, void *context) +{ + struct lmtp_client *client; + pool_t pool; + + i_assert(*set->mail_from == '<'); + i_assert(*set->my_hostname != '\0'); + + pool = pool_alloconly_create("lmtp client", 512); + client = p_new(pool, struct lmtp_client, 1); + client->refcount = 1; + client->pool = pool; + client->set.mail_from = p_strdup(pool, set->mail_from); + client->set.my_hostname = p_strdup(pool, set->my_hostname); + client->set.dns_client_socket_path = + p_strdup(pool, set->dns_client_socket_path); + client->set.source_ip = set->source_ip; + client->set.source_port = set->source_port; + client->set.proxy_ttl = set->proxy_ttl; + client->set.proxy_timeout_secs = set->proxy_timeout_secs; + client->set.timeout_secs = set->timeout_secs; + client->finish_callback = finish_callback; + client->finish_context = context; + client->fd = -1; + client->input_multiline = str_new(default_pool, 128); + p_array_init(&client->recipients, pool, 16); + return client; +} + +void lmtp_client_close(struct lmtp_client *client) +{ + if (client->dns_lookup != NULL) + dns_lookup_abort(&client->dns_lookup); + if (client->to != NULL) + timeout_remove(&client->to); + if (client->io != NULL) + io_remove(&client->io); + if (client->input != NULL) + i_stream_close(client->input); + if (client->output != NULL) + o_stream_close(client->output); + if (client->fd != -1) { + net_disconnect(client->fd); + client->fd = -1; + } + if (client->data_input != NULL) + i_stream_unref(&client->data_input); + client->output_finished = TRUE; + + if (!client->finish_called) { + client->finish_called = TRUE; + client->finish_callback(client->finish_context); + } +} + +static void lmtp_client_ref(struct lmtp_client *client) +{ + client->refcount++; +} + +static void lmtp_client_unref(struct lmtp_client **_client) +{ + struct lmtp_client *client = *_client; + + *_client = NULL; + + i_assert(client->refcount > 0); + if (--client->refcount > 0) + return; + + i_assert(client->finish_called); + if (client->input != NULL) + i_stream_unref(&client->input); + if (client->output != NULL) + o_stream_unref(&client->output); + str_free(&client->input_multiline); + pool_unref(&client->pool); +} + +void lmtp_client_deinit(struct lmtp_client **_client) +{ + struct lmtp_client *client = *_client; + + *_client = NULL; + + lmtp_client_close(client); + lmtp_client_unref(&client); +} + +const char *lmtp_client_state_to_string(struct lmtp_client *client) +{ + uoff_t size; + + switch (client->input_state) { + case LMTP_INPUT_STATE_GREET: + return "greeting"; + case LMTP_INPUT_STATE_LHLO: + return "LHLO"; + case LMTP_INPUT_STATE_MAIL_FROM: + return "MAIL FROM"; + case LMTP_INPUT_STATE_RCPT_TO: + return "RCPT TO"; + case LMTP_INPUT_STATE_DATA_CONTINUE: + return "DATA init"; + case LMTP_INPUT_STATE_DATA: + if (client->output_finished) + return "DATA reply"; + else if (i_stream_get_size(client->data_input, FALSE, &size) > 0) { + return t_strdup_printf( + "DATA (%"PRIuUOFF_T"/%"PRIuUOFF_T")", + client->data_input->v_offset, size); + } else { + return t_strdup_printf("DATA (%"PRIuUOFF_T"/?)", + client->data_input->v_offset); + } + case LMTP_INPUT_STATE_XCLIENT: + return "XCLIENT"; + } + return "??"; +} + +static void +lmtp_client_fail_full(struct lmtp_client *client, const char *line, bool remote) +{ + enum lmtp_client_result result; + struct lmtp_rcpt *recipients; + unsigned int i, count; + + client->global_fail_string = p_strdup(client->pool, line); + client->global_remote_failure = remote; + result = remote ? LMTP_CLIENT_RESULT_REMOTE_ERROR : + LMTP_CLIENT_RESULT_INTERNAL_ERROR; + + lmtp_client_ref(client); + recipients = array_get_modifiable(&client->recipients, &count); + for (i = client->rcpt_next_receive_idx; i < count; i++) { + recipients[i].rcpt_to_callback(result, line, + recipients[i].context); + recipients[i].failed = TRUE; + } + client->rcpt_next_receive_idx = count; + + for (i = client->rcpt_next_data_idx; i < count; i++) { + if (!recipients[i].failed) { + recipients[i].data_callback(result, line, + recipients[i].context); + } + } + client->rcpt_next_data_idx = count; + + lmtp_client_close(client); + lmtp_client_unref(&client); +} + +static void +lmtp_client_fail_remote(struct lmtp_client *client, const char *line) +{ + lmtp_client_fail_full(client, line, TRUE); +} + +void lmtp_client_fail(struct lmtp_client *client, const char *line) +{ + lmtp_client_fail_full(client, line, FALSE); +} + +static void +lmtp_client_rcpt_next(struct lmtp_client *client, const char *line) +{ + struct lmtp_rcpt *rcpt; + enum lmtp_client_result result; + + result = line[0] == '2' ? LMTP_CLIENT_RESULT_OK : + LMTP_CLIENT_RESULT_REMOTE_ERROR; + if (result == LMTP_CLIENT_RESULT_OK) + client->rcpt_to_successes = TRUE; + + rcpt = array_idx_modifiable(&client->recipients, + client->rcpt_next_receive_idx); + client->rcpt_next_receive_idx++; + + rcpt->failed = result != LMTP_CLIENT_RESULT_OK; + rcpt->rcpt_to_callback(result, line, rcpt->context); +} + +static int lmtp_client_send_data_cmd(struct lmtp_client *client) +{ + if (client->rcpt_next_receive_idx < array_count(&client->recipients)) + return 0; + + if (client->global_fail_string != NULL) { + lmtp_client_fail_full(client, client->global_fail_string, + client->global_remote_failure); + return -1; + } else if (!client->rcpt_to_successes) { + /* This error string shouldn't become visible anywhere */ + lmtp_client_fail_full(client, "No valid recipients", FALSE); + return -1; + } else { + client->input_state++; + o_stream_nsend_str(client->output, "DATA\r\n"); + return 0; + } +} + +static int +lmtp_client_data_next(struct lmtp_client *client, const char *line) +{ + struct lmtp_rcpt *rcpt; + unsigned int i, count; + enum lmtp_client_result result; + + rcpt = array_get_modifiable(&client->recipients, &count); + for (i = client->rcpt_next_data_idx; i < count; i++) { + if (rcpt[i].failed) { + /* already called rcpt_to_callback with failure */ + continue; + } + + client->rcpt_next_data_idx = i + 1; + rcpt[i].failed = line[0] != '2'; + result = rcpt[i].failed ? LMTP_CLIENT_RESULT_REMOTE_ERROR : + LMTP_CLIENT_RESULT_OK; + rcpt[i].data_callback(result, line, rcpt[i].context); + if (client->protocol == LMTP_CLIENT_PROTOCOL_LMTP) + break; + } + if (client->rcpt_next_data_idx < count) + return 0; + + o_stream_send_str(client->output, "QUIT\r\n"); + lmtp_client_close(client); + return -1; +} + +static int lmtp_client_send_data(struct lmtp_client *client) +{ + const unsigned char *data; + unsigned char add; + size_t i, size; + bool sent_bytes = FALSE; + int ret; + + if (client->output_finished) + return 0; + + while ((ret = i_stream_read_data(client->data_input, + &data, &size, 0)) > 0) { + add = '\0'; + for (i = 0; i < size; i++) { + if (data[i] == '\n') { + if ((i == 0 && client->output_last != '\r') || + (i > 0 && data[i-1] != '\r')) { + /* missing CR */ + add = '\r'; + break; + } + } else if (data[i] == '.' && + ((i == 0 && client->output_last == '\n') || + (i > 0 && data[i-1] == '\n'))) { + /* escape the dot */ + add = '.'; + break; + } + } + + if (i > 0) { + if (o_stream_send(client->output, data, i) < 0) + break; + client->output_last = data[i-1]; + i_stream_skip(client->data_input, i); + sent_bytes = TRUE; + } + + if (o_stream_get_buffer_used_size(client->output) >= 4096) { + if ((ret = o_stream_flush(client->output)) < 0) + break; + if (ret == 0) { + /* continue later */ + o_stream_set_flush_pending(client->output, TRUE); + return 0; + } + } + + if (add != '\0') { + if (o_stream_send(client->output, &add, 1) < 0) + break; + + client->output_last = add; + } + } + if (client->data_input->stream_errno != 0) { + i_error("lmtp client: read(%s) failed: %s", + i_stream_get_name(client->data_input), + i_stream_get_error(client->data_input)); + lmtp_client_fail(client, + "451 4.3.0 Internal failure while reading DATA input"); + return -1; + } + if (sent_bytes && client->data_output_callback != NULL) + client->data_output_callback(client->data_output_context); + + if (ret == 0 || ret == -2) { + /* -2 can happen with tee istreams */ + return 0; + } + + if (client->output_last != '\n') { + /* didn't end with CRLF */ + o_stream_nsend(client->output, "\r\n", 2); + } + o_stream_nsend(client->output, ".\r\n", 3); + client->output_finished = TRUE; + io_loop_time_refresh(); + client->times.data_sent = ioloop_timeval; + return 0; +} + +static void lmtp_client_send_handshake(struct lmtp_client *client) +{ + switch (client->protocol) { + case LMTP_CLIENT_PROTOCOL_LMTP: + o_stream_nsend_str(client->output, + t_strdup_printf("LHLO %s\r\n", + client->set.my_hostname)); + break; + case LMTP_CLIENT_PROTOCOL_SMTP: + o_stream_nsend_str(client->output, + t_strdup_printf("EHLO %s\r\n", + client->set.my_hostname)); + break; + } +} + +static int lmtp_input_get_reply_code(const char *line, int *reply_code_r, + string_t *multiline) +{ + if (!i_isdigit(line[0]) || !i_isdigit(line[1]) || !i_isdigit(line[2])) + return -1; + + *reply_code_r = (line[0]-'0') * 100 + + (line[1]-'0') * 10 + + (line[2]-'0'); + + if (line[3] == ' ') { + /* final reply */ + return 1; + } else if (line[3] == '-') { + /* multiline reply. */ + str_append(multiline, line); + str_append_c(multiline, '\n'); + return 0; + } else { + /* invalid input */ + return -1; + } +} + +static void +lmtp_client_parse_capabilities(struct lmtp_client *client, const char *lines) +{ + const char *const *linep; + + for (linep = t_strsplit(lines, "\n"); *linep != NULL; linep++) { + const char *line = *linep; + + line += 4; /* already checked this is valid */ + if (strncasecmp(line, "XCLIENT ", 8) == 0) { + client->xclient_args = + (void *)p_strsplit(client->pool, line + 8, " "); + } + } +} + +static bool lmtp_client_send_xclient(struct lmtp_client *client) +{ + string_t *str; + size_t empty_len; + + if (client->xclient_args == NULL) { + /* not supported */ + return FALSE; + } + if (client->xclient_sent) + return FALSE; + + str = t_str_new(64); + str_append(str, "XCLIENT"); + empty_len = str_len(str); + if (client->set.source_ip.family != 0 && + str_array_icase_find(client->xclient_args, "ADDR")) + str_printfa(str, " ADDR=%s", net_ip2addr(&client->set.source_ip)); + if (client->set.source_port != 0 && + str_array_icase_find(client->xclient_args, "PORT")) + str_printfa(str, " PORT=%u", client->set.source_port); + if (client->set.proxy_ttl != 0 && + str_array_icase_find(client->xclient_args, "TTL")) + str_printfa(str, " TTL=%u", client->set.proxy_ttl); + if (client->set.proxy_timeout_secs != 0 && + str_array_icase_find(client->xclient_args, "TIMEOUT")) + str_printfa(str, " TIMEOUT=%u", client->set.proxy_timeout_secs); + + if (str_len(str) == empty_len) + return FALSE; + + str_append(str, "\r\n"); + o_stream_nsend(client->output, str_data(str), str_len(str)); + return TRUE; +} + +static int lmtp_client_input_line(struct lmtp_client *client, const char *line) +{ + int ret, reply_code = 0; + + if ((ret = lmtp_input_get_reply_code(line, &reply_code, + client->input_multiline)) <= 0) { + if (ret == 0) + return 0; + lmtp_client_fail(client, t_strdup_printf( + "451 4.5.0 Received invalid input: %s", line)); + return -1; + } + + switch (client->input_state) { + case LMTP_INPUT_STATE_GREET: + if (reply_code != 220) { + lmtp_client_fail(client, t_strdup_printf( + "451 4.5.0 Received invalid greeting: %s", line)); + return -1; + } + client->times.banner_received = ioloop_timeval; + lmtp_client_send_handshake(client); + client->input_state = LMTP_INPUT_STATE_LHLO; + break; + case LMTP_INPUT_STATE_XCLIENT: + if (reply_code != 220) { + lmtp_client_fail(client, t_strdup_printf( + "451 4.5.0 XCLIENT failed: %s", line)); + return -1; + } + lmtp_client_send_handshake(client); + client->input_state = LMTP_INPUT_STATE_LHLO; + break; + case LMTP_INPUT_STATE_LHLO: + if (reply_code != 250) { + lmtp_client_fail(client, t_strdup_printf( + "451 4.5.0 LHLO failed: %s", line)); + lmtp_client_fail(client, line); + return -1; + } + str_append(client->input_multiline, line); + lmtp_client_parse_capabilities(client, + str_c(client->input_multiline)); + if (lmtp_client_send_xclient(client)) { + client->input_state = LMTP_INPUT_STATE_XCLIENT; + client->xclient_sent = TRUE; + break; + } + o_stream_nsend_str(client->output, t_strdup_printf( + "MAIL FROM:%s\r\n", client->set.mail_from)); + client->input_state++; + break; + case LMTP_INPUT_STATE_MAIL_FROM: + if (reply_code != 250) { + lmtp_client_fail(client, t_strdup_printf( + "451 4.5.0 MAIL FROM failed: %s", line)); + return -1; + } + client->input_state++; + lmtp_client_send_rcpts(client); + break; + case LMTP_INPUT_STATE_RCPT_TO: + lmtp_client_rcpt_next(client, line); + if (client->data_input == NULL) + break; + if (lmtp_client_send_data_cmd(client) < 0) + return -1; + break; + case LMTP_INPUT_STATE_DATA_CONTINUE: + /* Start sending DATA */ + if (strncmp(line, "354", 3) != 0) { + lmtp_client_fail_remote(client, line); + return -1; + } + client->input_state++; + client->times.data_started = ioloop_timeval; + if (client->data_header != NULL) + o_stream_nsend_str(client->output, client->data_header); + if (lmtp_client_send_data(client) < 0) + return -1; + break; + case LMTP_INPUT_STATE_DATA: + /* DATA replies */ + if (lmtp_client_data_next(client, line) < 0) + return -1; + break; + } + return 1; +} + +static void lmtp_client_input(struct lmtp_client *client) +{ + const char *line; + int ret; + + lmtp_client_ref(client); + o_stream_cork(client->output); + while ((line = i_stream_read_next_line(client->input)) != NULL) { + T_BEGIN { + ret = lmtp_client_input_line(client, line); + } T_END; + if (ret < 0) { + o_stream_uncork(client->output); + lmtp_client_unref(&client); + return; + } + if (ret > 0) + str_truncate(client->input_multiline, 0); + } + + if (client->input->stream_errno == ENOBUFS) { + lmtp_client_fail(client, + "501 5.5.4 Command reply line too long"); + } else if (client->input->stream_errno != 0) { + i_error("lmtp client: read() failed: %s", + i_stream_get_error(client->input)); + lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE + " (read failure)"); + } else if (client->input->eof) { + lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE + " (disconnected in input)"); + } + o_stream_uncork(client->output); + if (client->to != NULL) + timeout_reset(client->to); + lmtp_client_unref(&client); +} + +static void lmtp_client_wait_connect(struct lmtp_client *client) +{ + int err; + + err = net_geterror(client->fd); + if (err != 0) { + i_error("lmtp client: connect(%s, %u) failed: %s", + client->host, client->port, strerror(err)); + lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE + " (connect)"); + return; + } + if (client->to != NULL) + timeout_remove(&client->to); + io_remove(&client->io); + client->io = io_add(client->fd, IO_READ, lmtp_client_input, client); + lmtp_client_input(client); +} + +static void lmtp_client_connect_timeout(struct lmtp_client *client) +{ + i_error("lmtp client: connect(%s, %u) failed: Timed out in %u secs", + client->host, client->port, client->set.timeout_secs); + lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE + " (connect timeout)"); +} + +static int lmtp_client_output(struct lmtp_client *client) +{ + int ret; + + lmtp_client_ref(client); + o_stream_cork(client->output); + if ((ret = o_stream_flush(client->output)) < 0) + lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE + " (disconnected in output)"); + else if (client->input_state == LMTP_INPUT_STATE_DATA) + (void)lmtp_client_send_data(client); + o_stream_uncork(client->output); + if (client->to != NULL) + timeout_reset(client->to); + lmtp_client_unref(&client); + return ret; +} + +static int lmtp_client_connect(struct lmtp_client *client) +{ + i_assert(client->fd == -1); + + client->times.connect_started = ioloop_timeval; + + client->fd = net_connect_ip(&client->ip, client->port, NULL); + if (client->fd == -1) { + i_error("lmtp client: connect(%s, %u) failed: %m", + client->host, client->port); + return -1; + } + client->input = + i_stream_create_fd(client->fd, LMTP_MAX_LINE_LEN, FALSE); + client->output = o_stream_create_fd(client->fd, (size_t)-1, FALSE); + o_stream_set_no_error_handling(client->output, TRUE); + o_stream_set_flush_callback(client->output, lmtp_client_output, client); + /* we're already sending data in ostream, so can't use IO_WRITE here */ + client->io = io_add(client->fd, IO_READ, + lmtp_client_wait_connect, client); + if (client->set.timeout_secs > 0) { + client->to = timeout_add(client->set.timeout_secs*1000, + lmtp_client_connect_timeout, client); + } + return 0; +} + +static void lmtp_client_dns_done(const struct dns_lookup_result *result, + struct lmtp_client *client) +{ + client->dns_lookup = NULL; + + if (result->ret != 0) { + i_error("lmtp client: DNS lookup of %s failed: %s", + client->host, result->error); + if (client->running) { + lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE + " (DNS lookup)"); + } + } else { + client->ip = result->ips[0]; + if (lmtp_client_connect(client) < 0 && client->running) { + lmtp_client_fail(client, ERRSTR_TEMP_REMOTE_FAILURE + " (connect)"); + } + } +} + +int lmtp_client_connect_tcp(struct lmtp_client *client, + enum lmtp_client_protocol protocol, + const char *host, in_port_t port) +{ + struct dns_lookup_settings dns_lookup_set; + struct ip_addr *ips; + unsigned int ips_count; + int ret; + + client->input_state = LMTP_INPUT_STATE_GREET; + client->host = p_strdup(client->pool, host); + client->port = port; + client->protocol = protocol; + + if (*host == '\0') { + i_error("lmtp client: host not given"); + return -1; + } + + memset(&dns_lookup_set, 0, sizeof(dns_lookup_set)); + dns_lookup_set.dns_client_socket_path = + client->set.dns_client_socket_path; + dns_lookup_set.timeout_msecs = LMTP_CLIENT_DNS_LOOKUP_TIMEOUT_MSECS; + + if (net_addr2ip(host, &client->ip) == 0) { + /* IP address */ + } else if (dns_lookup_set.dns_client_socket_path == NULL) { + /* no dns-client, use blocking lookup */ + ret = net_gethostbyname(host, &ips, &ips_count); + if (ret != 0) { + i_error("lmtp client: DNS lookup of %s failed: %s", + client->host, net_gethosterror(ret)); + return -1; + } + client->ip = ips[0]; + } else { + if (dns_lookup(host, &dns_lookup_set, + lmtp_client_dns_done, client, + &client->dns_lookup) < 0) + return -1; + client->running = TRUE; + return 0; + } + + if (lmtp_client_connect(client) < 0) + return -1; + return 0; +} + +void lmtp_client_set_data_header(struct lmtp_client *client, const char *str) +{ + client->data_header = p_strdup(client->pool, str); +} + +static void lmtp_append_xtext(string_t *dest, const char *str) +{ + unsigned int i; + + for (i = 0; str[i] != '\0'; i++) { + if (str[i] >= 33 && str[i] <= 126 && + str[i] != '+' && str[i] != '=') + str_append_c(dest, str[i]); + else + str_printfa(dest, "+%02X", str[i]); + } +} + +static void lmtp_client_send_rcpts(struct lmtp_client *client) +{ + const struct lmtp_rcpt *rcpt; + unsigned int i, count; + string_t *str = t_str_new(128); + + rcpt = array_get(&client->recipients, &count); + for (i = client->rcpt_next_send_idx; i < count; i++) { + str_truncate(str, 0); + str_printfa(str, "RCPT TO:<%s>", rcpt[i].address); + if (rcpt->params.dsn_orcpt != NULL) { + str_append(str, " ORCPT="); + lmtp_append_xtext(str, rcpt->params.dsn_orcpt); + } + str_append(str, "\r\n"); + o_stream_nsend(client->output, str_data(str), str_len(str)); + } + client->rcpt_next_send_idx = i; +} + +void lmtp_client_add_rcpt(struct lmtp_client *client, const char *address, + lmtp_callback_t *rcpt_to_callback, + lmtp_callback_t *data_callback, void *context) +{ + struct lmtp_recipient_params params; + + memset(¶ms, 0, sizeof(params)); + lmtp_client_add_rcpt_params(client, address, ¶ms, rcpt_to_callback, + data_callback, context); +} + +void lmtp_client_add_rcpt_params(struct lmtp_client *client, const char *address, + const struct lmtp_recipient_params *params, + lmtp_callback_t *rcpt_to_callback, + lmtp_callback_t *data_callback, void *context) +{ + struct lmtp_rcpt *rcpt; + enum lmtp_client_result result; + + rcpt = array_append_space(&client->recipients); + rcpt->address = p_strdup(client->pool, address); + rcpt->params.dsn_orcpt = p_strdup(client->pool, params->dsn_orcpt); + rcpt->rcpt_to_callback = rcpt_to_callback; + rcpt->data_callback = data_callback; + rcpt->context = context; + + if (client->global_fail_string != NULL) { + /* we've already failed */ + client->rcpt_next_receive_idx++; + i_assert(client->rcpt_next_receive_idx == + array_count(&client->recipients)); + + result = client->global_remote_failure ? + LMTP_CLIENT_RESULT_REMOTE_ERROR : + LMTP_CLIENT_RESULT_INTERNAL_ERROR; + rcpt->failed = TRUE; + rcpt_to_callback(result, client->global_fail_string, context); + } else if (client->input_state == LMTP_INPUT_STATE_RCPT_TO) + lmtp_client_send_rcpts(client); +} + +static void lmtp_client_timeout(struct lmtp_client *client) +{ + const char *line; + + line = t_strdup_printf(ERRSTR_TEMP_REMOTE_FAILURE + " (Timed out after %u secs while waiting for reply to %s)", + client->set.timeout_secs, lmtp_client_state_to_string(client)); + lmtp_client_fail(client, line); +} + +void lmtp_client_send(struct lmtp_client *client, struct istream *data_input) +{ + i_assert(client->data_input == NULL); + + i_stream_ref(data_input); + client->data_input = data_input; + + /* now we actually want to start doing I/O. start the timeout + handling. */ + if (client->set.timeout_secs > 0) { + if (client->to != NULL) { + /* still waiting for connect to finish */ + timeout_remove(&client->to); + } + client->to = timeout_add(client->set.timeout_secs*1000, + lmtp_client_timeout, client); + } + + (void)lmtp_client_send_data_cmd(client); +} + +void lmtp_client_send_more(struct lmtp_client *client) +{ + if (client->input_state == LMTP_INPUT_STATE_DATA) { + o_stream_cork(client->output); + (void)lmtp_client_send_data(client); + o_stream_uncork(client->output); + } +} + +void lmtp_client_set_data_output_callback(struct lmtp_client *client, + void (*callback)(void *), + void *context) +{ + client->data_output_callback = callback; + client->data_output_context = context; +} + +const struct lmtp_client_times * +lmtp_client_get_times(struct lmtp_client *client) +{ + return &client->times; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-smtp/lmtp-client.h Fri Dec 30 16:20:12 2016 +0100 @@ -0,0 +1,107 @@ +#ifndef LMTP_CLIENT_H +#define LMTP_CLIENT_H + +#include "net.h" + +#define ERRSTR_TEMP_REMOTE_FAILURE "451 4.4.0 Remote server not answering" + +/* LMTP/SMTP client code. */ + +enum lmtp_client_protocol { + LMTP_CLIENT_PROTOCOL_LMTP, + LMTP_CLIENT_PROTOCOL_SMTP +}; + +enum lmtp_client_result { + /* Command succeeded */ + LMTP_CLIENT_RESULT_OK = 1, + /* Command failed because remote server returned an error */ + LMTP_CLIENT_RESULT_REMOTE_ERROR = 0, + /* Command failed because of an internal error (e.g. couldn't connect + to remote) */ + LMTP_CLIENT_RESULT_INTERNAL_ERROR = -1 +}; + +struct lmtp_recipient_params { + const char *dsn_orcpt; +}; + +struct lmtp_client_times { + struct timeval connect_started; + struct timeval banner_received; + struct timeval data_started; + struct timeval data_sent; +}; + +struct lmtp_client_settings { + const char *my_hostname; + /* The whole MAIL FROM line, including parameters */ + const char *mail_from; + const char *dns_client_socket_path; + + /* if remote server supports XCLIENT capability, + send the these as ADDR/PORT/TTL/TIMEOUT */ + struct ip_addr source_ip; + in_port_t source_port; + /* send TTL as this (default 0 means "don't send it") */ + unsigned int proxy_ttl; + /* remote is notified that the connection is going to be closed after + this many seconds, so it should try to keep lock waits and such + lower than this. */ + unsigned int proxy_timeout_secs; + /* Don't wait an answer from destination server longer than this many + seconds (0 = unlimited) */ + unsigned int timeout_secs; +}; + +/* reply contains the reply coming from remote server, or NULL + if it's a connection error. */ +typedef void lmtp_callback_t(enum lmtp_client_result result, + const char *reply, void *context); +/* called when session is finished, either because all RCPT TOs failed or + because all DATA replies have been received. */ +typedef void lmtp_finish_callback_t(void *context); + +struct lmtp_client * +lmtp_client_init(const struct lmtp_client_settings *set, + lmtp_finish_callback_t *finish_callback, void *context); +void lmtp_client_deinit(struct lmtp_client **client); + +int lmtp_client_connect_tcp(struct lmtp_client *client, + enum lmtp_client_protocol protocol, + const char *host, in_port_t port); +void lmtp_client_close(struct lmtp_client *client); + +/* Add headers from given string before the rest of the data. The string must + use CRLF line feeds and end with CRLF. */ +void lmtp_client_set_data_header(struct lmtp_client *client, const char *str); +/* Add recipient to the session. rcpt_to_callback is called once LMTP server + replies with RCPT TO. If RCPT TO was a succees, data_callback is called + when DATA replies. */ +void lmtp_client_add_rcpt(struct lmtp_client *client, const char *address, + lmtp_callback_t *rcpt_to_callback, + lmtp_callback_t *data_callback, void *context); +void lmtp_client_add_rcpt_params(struct lmtp_client *client, const char *address, + const struct lmtp_recipient_params *params, + lmtp_callback_t *rcpt_to_callback, + lmtp_callback_t *data_callback, void *context); +/* Start sending input stream as DATA. */ +void lmtp_client_send(struct lmtp_client *client, struct istream *data_input); +/* Call this function whenever input stream can potentially be read forward. + This is useful with non-blocking istreams and tee-istreams. */ +void lmtp_client_send_more(struct lmtp_client *client); +/* Fail the connection with line as the reply to unfinished RCPT TO/DATA + replies. This will be treated as an internal failure. */ +void lmtp_client_fail(struct lmtp_client *client, const char *line); +/* Return the state (command reply) the client is currently waiting for. */ +const char *lmtp_client_state_to_string(struct lmtp_client *client); +/* Call the given callback whenever client manages to send some more DATA + output to client. */ +void lmtp_client_set_data_output_callback(struct lmtp_client *client, + void (*callback)(void *), + void *context); +/* Return LMTP client statistics. */ +const struct lmtp_client_times * +lmtp_client_get_times(struct lmtp_client *client); + +#endif
--- a/src/lmtp/Makefile.am Fri Oct 28 21:34:41 2016 +0300 +++ b/src/lmtp/Makefile.am Fri Dec 30 16:20:12 2016 +0100 @@ -7,6 +7,7 @@ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-smtp \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-index \ -I$(top_srcdir)/src/lib-master \