diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-smtp/test-smtp-submit.c | |
parent | Initial commit. (diff) | |
download | dovecot-upstream.tar.xz dovecot-upstream.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-smtp/test-smtp-submit.c')
-rw-r--r-- | src/lib-smtp/test-smtp-submit.c | 2199 |
1 files changed, 2199 insertions, 0 deletions
diff --git a/src/lib-smtp/test-smtp-submit.c b/src/lib-smtp/test-smtp-submit.c new file mode 100644 index 0000000..f82dc4d --- /dev/null +++ b/src/lib-smtp/test-smtp-submit.c @@ -0,0 +1,2199 @@ +/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "hostpid.h" +#include "ioloop.h" +#include "istream.h" +#include "istream-chain.h" +#include "ostream.h" +#include "time-util.h" +#include "sleep.h" +#include "unlink-directory.h" +#include "write-full.h" +#include "connection.h" +#include "master-service.h" +#include "istream-dot.h" +#include "test-common.h" +#include "test-subprocess.h" + +#include "smtp-address.h" +#include "smtp-submit.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define SERVER_KILL_TIMEOUT_SECS 20 + +static void main_deinit(void); + +static const char *test_message1 = + "Subject: Test message\r\n" + "To: rcpt@example.com\r\n" + "From: sender@example.com\r\n" + "\r\n" + "Test message\r\n"; +static const char *test_message2 = + "Subject: Test message\r\n" + "To: rcpt@example.com\r\n" + "From: sender@example.com\r\n" + "\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n" + "Test message Test message Test message Test message Test message\r\n"; + +/* + * Types + */ + +struct server_connection { + struct connection conn; + + void *context; + + pool_t pool; +}; + +typedef void (*test_server_init_t)(unsigned int index); +typedef bool +(*test_client_init_t)(const struct smtp_submit_settings *submit_set); + +/* + * State + */ + +/* common */ +static struct ip_addr bind_ip; +static in_port_t *bind_ports = NULL; +static struct ioloop *ioloop; +static bool debug = FALSE; +static char *tmp_dir = NULL; + +/* server */ +static struct io *io_listen; +static int fd_listen = -1; +static in_port_t server_port = 0; +static struct connection_list *server_conn_list; +static unsigned int server_index; +static void (*test_server_input)(struct server_connection *conn); +static void (*test_server_init)(struct server_connection *conn); +static void (*test_server_deinit)(struct server_connection *conn); + +/* client */ + +/* + * Forward declarations + */ + +/* server */ +static void test_server_run(unsigned int index); +static void server_connection_deinit(struct server_connection **_conn); + +/* client */ +static void +test_client_defaults(struct smtp_submit_settings *smtp_set); +static void test_client_deinit(void); + +static int +test_client_smtp_send_simple(const struct smtp_submit_settings *smtp_set, + const char *message, const char *host, + const char **error_r); +static int +test_client_smtp_send_simple_port(const struct smtp_submit_settings *smtp_set, + const char *message, unsigned int port, + const char **error_r); + +/* test*/ +static const char *test_tmp_dir_get(void); + +static void test_message_delivery(const char *message, const char *file); + +static void +test_run_client_server(const struct smtp_submit_settings *submit_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count) ATTR_NULL(3); + +/* + * Host lookup failed + */ + +/* client */ + +static bool +test_client_host_lookup_failed(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple(submit_set, test_message1, + "host.invalid", &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_host_lookup_failed(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("host lookup failed"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_host_lookup_failed, NULL, 0); + test_end(); +} + +/* + * Connection refused + */ + +/* server */ + +static void test_server_connection_refused(unsigned int index ATTR_UNUSED) +{ + i_close_fd(&fd_listen); +} + +/* client */ + +static bool +test_client_connection_refused(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_connection_refused(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("connection refused"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_connection_refused, + test_server_connection_refused, 1); + test_end(); +} + +/* + * Connection timed out + */ + +/* server */ + +static void test_connection_timed_out_input(struct server_connection *conn) +{ + i_sleep_intr_secs(10); + server_connection_deinit(&conn); +} + +static void test_server_connection_timed_out(unsigned int index) +{ + test_server_input = test_connection_timed_out_input; + test_server_run(index); +} +/* client */ + +static bool +test_client_connection_timed_out(const struct smtp_submit_settings *submit_set) +{ + time_t time; + const char *error = NULL; + int ret; + + io_loop_time_refresh(); + time = ioloop_time; + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + io_loop_time_refresh(); + test_out("timeout", (ioloop_time - time) < 5); + + return FALSE; +} + +/* test */ + +static void test_connection_timed_out(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 1; + + test_begin("connection timed out"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_connection_timed_out, + test_server_connection_timed_out, 1); + test_end(); +} + +/* + * Bad greeting + */ + +/* server */ + +static void test_bad_greeting_input(struct server_connection *conn) +{ + server_connection_deinit(&conn); +} + +static void test_bad_greeting_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "554 No SMTP service here.\r\n"); +} + +static void test_server_bad_greeting(unsigned int index) +{ + test_server_init = test_bad_greeting_init; + test_server_input = test_bad_greeting_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_bad_greeting(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_bad_greeting(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("bad greeting"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_bad_greeting, + test_server_bad_greeting, 1); + test_end(); +} + +/* + * Denied HELO + */ + +/* server */ + +static void test_denied_helo_input(struct server_connection *conn) +{ + const char *line; + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + o_stream_nsend_str(conn->conn.output, + "550 Command rejected for testing reasons\r\n"); + server_connection_deinit(&conn); +} + +static void test_denied_helo_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_helo(unsigned int index) +{ + test_server_init = test_denied_helo_init; + test_server_input = test_denied_helo_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_helo(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, + test_message1, bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_helo(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied helo"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_helo, + test_server_denied_helo, 1); + test_end(); +} + +/* + * Disconnect HELO + */ + +/* server */ + +static void test_disconnect_helo_input(struct server_connection *conn) +{ + const char *line; + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + server_connection_deinit(&conn); +} + +static void test_disconnect_helo_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_disconnect_helo(unsigned int index) +{ + test_server_init = test_disconnect_helo_init; + test_server_input = test_disconnect_helo_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_disconnect_helo(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_disconnect_helo(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("disconnect helo"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_disconnect_helo, + test_server_disconnect_helo, 1); + test_end(); +} + +/* + * Denied MAIL + */ + +/* server */ + +enum _denied_mail_state { + DENIED_MAIL_STATE_EHLO = 0, + DENIED_MAIL_STATE_MAIL_FROM +}; + +struct _denied_mail_server { + enum _denied_mail_state state; +}; + +static void test_denied_mail_input(struct server_connection *conn) +{ + struct _denied_mail_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_mail_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_mail_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_MAIL_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_MAIL_STATE_MAIL_FROM; + return; + case DENIED_MAIL_STATE_MAIL_FROM: + o_stream_nsend_str( + conn->conn.output,"453 4.3.2 " + "Incapable of accepting messages at this time.\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_mail_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_mail(unsigned int index) +{ + test_server_init = test_denied_mail_init; + test_server_input = test_denied_mail_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_mail(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_mail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied mail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_mail, + test_server_denied_mail, 1); + test_end(); +} + +/* + * Denied RCPT + */ + +/* server */ + +enum _denied_rcpt_state { + DENIED_RCPT_STATE_EHLO = 0, + DENIED_RCPT_STATE_MAIL_FROM, + DENIED_RCPT_STATE_RCPT_TO +}; + +struct _denied_rcpt_server { + enum _denied_rcpt_state state; +}; + +static void test_denied_rcpt_input(struct server_connection *conn) +{ + struct _denied_rcpt_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_rcpt_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_rcpt_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_RCPT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_RCPT_STATE_MAIL_FROM; + return; + case DENIED_RCPT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DENIED_RCPT_STATE_RCPT_TO; + continue; + case DENIED_RCPT_STATE_RCPT_TO: + o_stream_nsend_str( + conn->conn.output, "550 5.4.3 " + "Directory server failure\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_rcpt_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_rcpt(unsigned int index) +{ + test_server_init = test_denied_rcpt_init; + test_server_input = test_denied_rcpt_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_rcpt(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_rcpt(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied rcpt"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_rcpt, + test_server_denied_rcpt, 1); + test_end(); +} + +/* + * Denied second RCPT + */ + +/* server */ + +enum _denied_second_rcpt_state { + DENIED_SECOND_RCPT_STATE_EHLO = 0, + DENIED_SECOND_RCPT_STATE_MAIL_FROM, + DENIED_SECOND_RCPT_STATE_RCPT_TO, + DENIED_SECOND_RCPT_STATE_RCPT_TO2 +}; + +struct _denied_second_rcpt_server { + enum _denied_second_rcpt_state state; +}; + +static void test_denied_second_rcpt_input(struct server_connection *conn) +{ + struct _denied_second_rcpt_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_second_rcpt_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_second_rcpt_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_SECOND_RCPT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_SECOND_RCPT_STATE_MAIL_FROM; + return; + case DENIED_SECOND_RCPT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DENIED_SECOND_RCPT_STATE_RCPT_TO; + continue; + case DENIED_SECOND_RCPT_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DENIED_SECOND_RCPT_STATE_RCPT_TO2; + continue; + case DENIED_SECOND_RCPT_STATE_RCPT_TO2: + o_stream_nsend_str(conn->conn.output, "550 5.4.3 " + "Directory server failure\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_second_rcpt_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_second_rcpt(unsigned int index) +{ + test_server_init = test_denied_second_rcpt_init; + test_server_input = test_denied_second_rcpt_input; + test_server_run(index); +} + +/* client */ + +static void test_smtp_submit_input_init(struct smtp_submit_input *smtp_input_r) +{ + i_zero(smtp_input_r); + smtp_input_r->allow_root = TRUE; +} + +static bool +test_client_denied_second_rcpt(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit *smtp_submit; + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct ostream *output; + const char *error = NULL; + int ret; + + smtp_submit_set = *submit_set; + smtp_submit_set.submission_host = + t_strdup_printf("127.0.0.1:%u", bind_ports[0]); + smtp_submit_set.submission_timeout = 1000; + + test_smtp_submit_input_init(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt2", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, test_message1); + + ret = smtp_submit_run(smtp_submit, &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + smtp_submit_deinit(&smtp_submit); + + return FALSE; +} + +/* test */ + +static void test_denied_second_rcpt(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("denied second rcpt"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_second_rcpt, + test_server_denied_second_rcpt, 1); + test_end(); +} + +/* + * Denied DATA + */ + +/* server */ + +enum _denied_data_state { + DENIED_DATA_STATE_EHLO = 0, + DENIED_DATA_STATE_MAIL_FROM, + DENIED_DATA_STATE_RCPT_TO, + DENIED_DATA_STATE_DATA +}; + +struct _denied_data_server { + enum _denied_data_state state; +}; + +static void test_denied_data_input(struct server_connection *conn) +{ + struct _denied_data_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _denied_data_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _denied_data_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_DATA_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DENIED_DATA_STATE_MAIL_FROM; + return; + case DENIED_DATA_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DENIED_DATA_STATE_RCPT_TO; + continue; + case DENIED_DATA_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DENIED_DATA_STATE_DATA; + continue; + case DENIED_DATA_STATE_DATA: + o_stream_nsend_str(conn->conn.output, "500 5.0.0 " + "Unacceptable recipients\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_denied_data_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_denied_data(unsigned int index) +{ + test_server_init = test_denied_data_init; + test_server_input = test_denied_data_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_denied_data(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_data(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("denied data"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_denied_data, + test_server_denied_data, 1); + test_end(); +} + +/* + * Data failure + */ + +/* server */ + +enum _data_failure_state { + DATA_FAILURE_STATE_EHLO = 0, + DATA_FAILURE_STATE_MAIL_FROM, + DATA_FAILURE_STATE_RCPT_TO, + DATA_FAILURE_STATE_DATA, + DATA_FAILURE_STATE_FINISH +}; + +struct _data_failure_server { + enum _data_failure_state state; +}; + +static void test_data_failure_input(struct server_connection *conn) +{ + struct _data_failure_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _data_failure_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _data_failure_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_FAILURE_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DATA_FAILURE_STATE_MAIL_FROM; + return; + case DATA_FAILURE_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DATA_FAILURE_STATE_RCPT_TO; + continue; + case DATA_FAILURE_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DATA_FAILURE_STATE_DATA; + continue; + case DATA_FAILURE_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with <CR><LF>.<CR><LF>\r\n"); + ctx->state = DATA_FAILURE_STATE_FINISH; + continue; + case DATA_FAILURE_STATE_FINISH: + if (strcmp(line, ".") == 0) { + o_stream_nsend_str( + conn->conn.output, "552 5.2.3 " + "Message length exceeds administrative limit\r\n"); + server_connection_deinit(&conn); + return; + } + continue; + } + i_unreached(); + } +} + +static void test_data_failure_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_data_failure(unsigned int index) +{ + test_server_init = test_data_failure_init; + test_server_input = test_data_failure_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_data_failure(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_data_failure(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("data failure"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_data_failure, + test_server_data_failure, 1); + test_end(); +} + +/* + * Data disconnect + */ + +/* server */ + +enum _data_disconnect_state { + DATA_DISCONNECT_STATE_EHLO = 0, + DATA_DISCONNECT_STATE_MAIL_FROM, + DATA_DISCONNECT_STATE_RCPT_TO, + DATA_DISCONNECT_STATE_DATA, + DATA_DISCONNECT_STATE_FINISH +}; + +struct _data_disconnect_server { + enum _data_disconnect_state state; +}; + +static void test_data_disconnect_input(struct server_connection *conn) +{ + struct _data_disconnect_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _data_disconnect_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _data_disconnect_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_DISCONNECT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DATA_DISCONNECT_STATE_MAIL_FROM; + return; + case DATA_DISCONNECT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DATA_DISCONNECT_STATE_RCPT_TO; + continue; + case DATA_DISCONNECT_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DATA_DISCONNECT_STATE_DATA; + continue; + case DATA_DISCONNECT_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with <CR><LF>.<CR><LF>\r\n"); + ctx->state = DATA_DISCONNECT_STATE_FINISH; + continue; + case DATA_DISCONNECT_STATE_FINISH: + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_data_disconnect_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_data_disconnect(unsigned int index) +{ + test_server_init = test_data_disconnect_init; + test_server_input = test_data_disconnect_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_data_disconnect(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_data_disconnect(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 5; + + test_begin("data disconnect"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_data_disconnect, + test_server_data_disconnect, 1); + test_end(); +} + +/* + * Data timout + */ + +/* server */ + +enum _data_timout_state { + DATA_TIMEOUT_STATE_EHLO = 0, + DATA_TIMEOUT_STATE_MAIL_FROM, + DATA_TIMEOUT_STATE_RCPT_TO, + DATA_TIMEOUT_STATE_DATA, + DATA_TIMEOUT_STATE_FINISH +}; + +struct _data_timout_server { + enum _data_timout_state state; +}; + +static void test_data_timout_input(struct server_connection *conn) +{ + struct _data_timout_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _data_timout_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _data_timout_server *)conn->context; + } + + for (;;) { + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_TIMEOUT_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = DATA_TIMEOUT_STATE_MAIL_FROM; + return; + case DATA_TIMEOUT_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = DATA_TIMEOUT_STATE_RCPT_TO; + continue; + case DATA_TIMEOUT_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = DATA_TIMEOUT_STATE_DATA; + continue; + case DATA_TIMEOUT_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with <CR><LF>.<CR><LF>\r\n"); + ctx->state = DATA_TIMEOUT_STATE_FINISH; + continue; + case DATA_TIMEOUT_STATE_FINISH: + i_sleep_intr_secs(10); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void test_data_timout_init(struct server_connection *conn) +{ + o_stream_nsend_str( + conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_server_data_timout(unsigned int index) +{ + test_server_init = test_data_timout_init; + test_server_input = test_data_timout_input; + test_server_run(index); +} + +/* client */ + +static bool +test_client_data_timout(const struct smtp_submit_settings *submit_set) +{ + time_t time; + const char *error = NULL; + int ret; + + io_loop_time_refresh(); + time = ioloop_time; + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + io_loop_time_refresh(); + test_out("timeout", (ioloop_time - time) < 5); + + return FALSE; +} + +/* test */ + +static void test_data_timeout(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + smtp_submit_set.submission_timeout = 2; + + test_begin("data timeout"); + test_expect_errors(1); + test_run_client_server(&smtp_submit_set, + test_client_data_timout, + test_server_data_timout, 1); + test_end(); +} + +/* + * Successful delivery + */ + +/* server */ + +enum _successful_delivery_state { + SUCCESSFUL_DELIVERY_STATE_EHLO = 0, + SUCCESSFUL_DELIVERY_STATE_MAIL_FROM, + SUCCESSFUL_DELIVERY_STATE_RCPT_TO, + SUCCESSFUL_DELIVERY_STATE_DATA, + SUCCESSFUL_DELIVERY_STATE_FINISH +}; + +struct _successful_delivery_server { + enum _successful_delivery_state state; + + char *file_path; + struct istream *dot_input; + struct ostream *file; +}; + +static void test_successful_delivery_input(struct server_connection *conn) +{ + struct _successful_delivery_server *ctx; + const char *line; + + if (conn->context == NULL) { + ctx = p_new(conn->pool, struct _successful_delivery_server, 1); + conn->context = (void*)ctx; + } else { + ctx = (struct _successful_delivery_server *)conn->context; + } + + // FIXME: take structure from test-smtp-client-errors + + for (;;) { + if (ctx->state == SUCCESSFUL_DELIVERY_STATE_FINISH) { + enum ostream_send_istream_result res; + + if (ctx->dot_input == NULL) { + int fd; + + ctx->dot_input = + i_stream_create_dot(conn->conn.input, TRUE); + ctx->file_path = p_strdup_printf( + conn->pool, "%s/message-%u.eml", + test_tmp_dir_get(), server_port); + + if ((fd = open(ctx->file_path, O_WRONLY | O_CREAT, + 0600)) < 0) { + i_fatal("failed create tmp file for message: " + "open(%s) failed: %m", + ctx->file_path); + } + ctx->file = o_stream_create_fd_autoclose( + &fd, IO_BLOCK_SIZE); + } + + res = o_stream_send_istream(ctx->file, ctx->dot_input); + switch (res) { + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + break; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + return; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + i_error("test server: " + "Failed to read all message payload [%s]", + ctx->file_path); + server_connection_deinit(&conn); + return; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + i_error("test server: " + "Failed to write all message payload [%s]", + ctx->file_path); + server_connection_deinit(&conn); + return; + } + + o_stream_nsend_str( + conn->conn.output, + "250 2.0.0 Ok: queued as 73BDE342129\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_MAIL_FROM; + continue; + } + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case SUCCESSFUL_DELIVERY_STATE_EHLO: + o_stream_nsend_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-8BITMIME\r\n" + "250 DSN\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_MAIL_FROM; + return; + case SUCCESSFUL_DELIVERY_STATE_MAIL_FROM: + o_stream_nsend_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_RCPT_TO; + continue; + case SUCCESSFUL_DELIVERY_STATE_RCPT_TO: + o_stream_nsend_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_DATA; + continue; + case SUCCESSFUL_DELIVERY_STATE_DATA: + o_stream_nsend_str( + conn->conn.output, + "354 End data with <CR><LF>.<CR><LF>\r\n"); + ctx->state = SUCCESSFUL_DELIVERY_STATE_FINISH; + continue; + case SUCCESSFUL_DELIVERY_STATE_FINISH: + break; + } + i_unreached(); + } +} + +static void test_successful_delivery_init(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); +} + +static void test_successful_delivery_deinit(struct server_connection *conn) +{ + struct _successful_delivery_server *ctx = + (struct _successful_delivery_server *)conn->context; + + i_stream_unref(&ctx->dot_input); + o_stream_unref(&ctx->file); +} + +static void test_server_successful_delivery(unsigned int index) +{ + test_server_init = test_successful_delivery_init; + test_server_input = test_successful_delivery_input; + test_server_deinit = test_successful_delivery_deinit; + test_server_run(index); +} + +/* client */ + +static bool +test_client_successful_delivery(const struct smtp_submit_settings *submit_set) +{ + const char *error = NULL; + int ret; + + /* send the message */ + ret = test_client_smtp_send_simple_port(submit_set, test_message1, + bind_ports[0], &error); + test_out_reason("run (ret > 0)", ret > 0, error); + + /* verify delivery */ + test_message_delivery(test_message1, + t_strdup_printf("%s/message-%u.eml", + test_tmp_dir_get(), + bind_ports[0])); + + return FALSE; +} + +struct _parallel_delivery_client { + unsigned int count; +}; + +static void +test_client_parallel_delivery_callback(const struct smtp_submit_result *result, + struct _parallel_delivery_client *ctx) +{ + if (result->status <= 0) + i_error("Submit failed: %s", result->error); + + if (--ctx->count == 0) + io_loop_stop(current_ioloop); +} + +static bool +test_client_parallel_delivery(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct _parallel_delivery_client *ctx; + struct smtp_submit *smtp_submit1, *smtp_submit2; + struct ostream *output; + struct ioloop *ioloop; + + ioloop = io_loop_create(); + + ctx = i_new(struct _parallel_delivery_client, 1); + ctx->count = 2; + + smtp_submit_set = *submit_set; + smtp_submit_set.submission_timeout = 5; + + /* submit 1 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.submission_host = + t_strdup_printf("127.0.0.1:%u", bind_ports[0]); + smtp_submit1 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit1, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit1); + o_stream_nsend_str(output, test_message1); + + smtp_submit_run_async( + smtp_submit1, test_client_parallel_delivery_callback, ctx); + + /* submit 2 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.submission_host = + t_strdup_printf("127.0.0.1:%u", bind_ports[1]); + smtp_submit2 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit2, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit2); + o_stream_nsend_str(output, test_message2); + + smtp_submit_run_async( + smtp_submit2, test_client_parallel_delivery_callback, ctx); + + io_loop_run(ioloop); + + smtp_submit_deinit(&smtp_submit1); + smtp_submit_deinit(&smtp_submit2); + io_loop_destroy(&ioloop); + + /* verify delivery */ + test_message_delivery(test_message1, + t_strdup_printf("%s/message-%u.eml", + test_tmp_dir_get(), + bind_ports[0])); + test_message_delivery(test_message2, + t_strdup_printf("%s/message-%u.eml", + test_tmp_dir_get(), + bind_ports[1])); + i_free(ctx); + + return FALSE; +} + +/* test */ + +static void test_successful_delivery(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("successful delivery"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_successful_delivery, + test_server_successful_delivery, 1); + test_end(); + + test_begin("parallel delivery"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_parallel_delivery, + test_server_successful_delivery, 2); + test_end(); +} + +/* + * Failed sendmail + */ + +/* client */ + +static bool +test_client_failed_sendmail(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_settings smtp_submit_set; + struct smtp_submit_input smtp_input; + struct smtp_submit *smtp_submit; + struct ostream *output; + const char *sendmail_path, *error = NULL; + int ret; + + sendmail_path = TEST_BIN_DIR"/sendmail-exit-1.sh"; + + smtp_submit_set = *submit_set; + smtp_submit_set.sendmail_path = sendmail_path; + smtp_submit_set.submission_timeout = 5; + + test_smtp_submit_input_init(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, test_message1); + + ret = smtp_submit_run(smtp_submit, &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + smtp_submit_deinit(&smtp_submit); + + return FALSE; +} + +/* test */ + +static void test_failed_sendmail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("failed sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_failed_sendmail, NULL, 0); + test_end(); +} + +/* + * Successful sendmail + */ + +/* client */ + +static bool +test_client_successful_sendmail(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct smtp_submit *smtp_submit; + struct ostream *output; + const char *sendmail_path, *msg_path, *error = NULL; + int ret; + + msg_path = t_strdup_printf("%s/message.eml", test_tmp_dir_get()); + + sendmail_path = t_strdup_printf( + TEST_BIN_DIR"/sendmail-success.sh %s", msg_path); + + smtp_submit_set = *submit_set; + smtp_submit_set.sendmail_path = sendmail_path; + smtp_submit_set.submission_timeout = 5; + + test_smtp_submit_input_init(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt(smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, test_message1); + + ret = smtp_submit_run(smtp_submit, &error); + test_out_reason("run (ret > 0)", ret > 0, error); + + smtp_submit_deinit(&smtp_submit); + + /* verify delivery */ + test_message_delivery(test_message1, msg_path); + + return FALSE; +} + +/* test */ + +static void test_successful_sendmail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("successful sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_successful_sendmail, NULL, 0); + test_end(); +} + +/* + * Parallel sendmail + */ + +/* client */ + +struct _parallel_sendmail_client { + unsigned int count; +}; + +static void +test_client_parallel_sendmail_callback(const struct smtp_submit_result *result, + struct _parallel_sendmail_client *ctx) +{ + if (result->status <= 0) + i_error("Submit failed: %s", result->error); + + if (--ctx->count == 0) + io_loop_stop(current_ioloop); +} + +static bool +test_client_parallel_sendmail(const struct smtp_submit_settings *submit_set) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct _parallel_sendmail_client *ctx; + struct smtp_submit *smtp_submit1, *smtp_submit2; + struct ostream *output; + const char *sendmail_path1, *sendmail_path2; + const char *msg_path1, *msg_path2; + struct ioloop *ioloop; + + ctx = i_new(struct _parallel_sendmail_client, 1); + ctx->count = 2; + + ioloop = io_loop_create(); + + msg_path1 = t_strdup_printf("%s/message1.eml", test_tmp_dir_get()); + msg_path2 = t_strdup_printf("%s/message2.eml", test_tmp_dir_get()); + + sendmail_path1 = t_strdup_printf( + TEST_BIN_DIR"/sendmail-success.sh %s", msg_path1); + sendmail_path2 = t_strdup_printf( + TEST_BIN_DIR"/sendmail-success.sh %s", msg_path2); + + smtp_submit_set = *submit_set; + smtp_submit_set.submission_timeout = 5; + + /* submit 1 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.sendmail_path = sendmail_path1; + smtp_submit1 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit1, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit1); + o_stream_nsend_str(output, test_message1); + + smtp_submit_run_async( + smtp_submit1, test_client_parallel_sendmail_callback, ctx); + + /* submit 2 */ + test_smtp_submit_input_init(&smtp_input); + smtp_submit_set.sendmail_path = sendmail_path2; + smtp_submit2 = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit2, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit2); + o_stream_nsend_str(output, test_message2); + + smtp_submit_run_async( + smtp_submit2, test_client_parallel_sendmail_callback, ctx); + + io_loop_run(ioloop); + + smtp_submit_deinit(&smtp_submit1); + smtp_submit_deinit(&smtp_submit2); + io_loop_destroy(&ioloop); + + /* verify delivery */ + test_message_delivery(test_message1, msg_path1); + test_message_delivery(test_message2, msg_path2); + + i_free(ctx); + + return FALSE; +} + +/* test */ + +static void test_parallel_sendmail(void) +{ + struct smtp_submit_settings smtp_submit_set; + + test_client_defaults(&smtp_submit_set); + + test_begin("parallel sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_submit_set, + test_client_parallel_sendmail, NULL, 0); + test_end(); +} + +/* + * All tests + */ + +static void (*const test_functions[])(void) = { + test_host_lookup_failed, + test_connection_refused, + test_connection_timed_out, + test_bad_greeting, + test_denied_helo, + test_disconnect_helo, + test_denied_mail, + test_denied_rcpt, + test_denied_second_rcpt, + test_denied_data, + test_data_failure, + test_data_disconnect, + test_data_timeout, + test_successful_delivery, + test_failed_sendmail, + test_successful_sendmail, + test_parallel_sendmail, + NULL +}; + +/* + * Test client + */ + +static void test_client_defaults(struct smtp_submit_settings *smtp_set) +{ + i_zero(smtp_set); + smtp_set->hostname = "test"; + smtp_set->submission_host = ""; + smtp_set->sendmail_path = "/bin/false"; + smtp_set->mail_debug = debug; +} + +static void test_client_deinit(void) +{ +} + +static int +test_client_smtp_send_simple(const struct smtp_submit_settings *smtp_set, + const char *message, const char *host, + const char **error_r) +{ + struct smtp_submit_input smtp_input; + struct smtp_submit_settings smtp_submit_set; + struct smtp_submit *smtp_submit; + struct ostream *output; + int ret; + + /* send the message */ + smtp_submit_set = *smtp_set; + smtp_submit_set.submission_host = host, + + i_zero(&smtp_input); + smtp_submit = smtp_submit_init_simple( + &smtp_input, &smtp_submit_set, &((struct smtp_address){ + .localpart = "sender", + .domain = "example.com"})); + + smtp_submit_add_rcpt( + smtp_submit, &((struct smtp_address){ + .localpart = "rcpt", + .domain = "example.com"})); + output = smtp_submit_send(smtp_submit); + o_stream_nsend_str(output, message); + + ret = smtp_submit_run(smtp_submit, error_r); + + smtp_submit_deinit(&smtp_submit); + + return ret; +} + +static int +test_client_smtp_send_simple_port(const struct smtp_submit_settings *smtp_set, + const char *message, unsigned int port, + const char **error_r) +{ + const char *host = t_strdup_printf("127.0.0.1:%u", port); + + return test_client_smtp_send_simple(smtp_set, message, host, error_r); +} + +/* + * Test server + */ + +/* client connection */ + +static void server_connection_input(struct connection *_conn) +{ + struct server_connection *conn = (struct server_connection *)_conn; + + test_server_input(conn); +} + +static void server_connection_init(int fd) +{ + struct server_connection *conn; + pool_t pool; + + net_set_nonblock(fd, TRUE); + + pool = pool_alloconly_create("server connection", 256); + conn = p_new(pool, struct server_connection, 1); + conn->pool = pool; + + connection_init_server(server_conn_list, &conn->conn, + "server connection", fd, fd); + + if (test_server_init != NULL) + test_server_init(conn); +} + +static void server_connection_deinit(struct server_connection **_conn) +{ + struct server_connection *conn = *_conn; + + *_conn = NULL; + + if (test_server_deinit != NULL) + test_server_deinit(conn); + + connection_deinit(&conn->conn); + pool_unref(&conn->pool); +} + +static void server_connection_destroy(struct connection *_conn) +{ + struct server_connection *conn = + (struct server_connection *)_conn; + + server_connection_deinit(&conn); +} + +static void server_connection_accept(void *context ATTR_UNUSED) +{ + int fd; + + /* accept new client */ + fd = net_accept(fd_listen, NULL, NULL); + if (fd == -1) + return; + if (fd == -2) { + i_fatal("test server: accept() failed: %m"); + } + + server_connection_init(fd); +} + +/* */ + +static struct connection_settings server_connection_set = { + .input_max_size = SIZE_MAX, + .output_max_size = SIZE_MAX, + .client = FALSE +}; + +static const struct connection_vfuncs server_connection_vfuncs = { + .destroy = server_connection_destroy, + .input = server_connection_input +}; + +static void test_server_run(unsigned int index) +{ + server_index = index; + + /* open server socket */ + io_listen = io_add(fd_listen, + IO_READ, server_connection_accept, NULL); + + server_conn_list = connection_list_init(&server_connection_set, + &server_connection_vfuncs); + + io_loop_run(ioloop); + + /* close server socket */ + io_remove(&io_listen); + + connection_list_deinit(&server_conn_list); +} + +/* + * Tests + */ + +struct test_server_data { + unsigned int index; + test_server_init_t server_test; +}; + +static int test_open_server_fd(in_port_t *bind_port) +{ + int fd = net_listen(&bind_ip, bind_port, 128); + if (debug) + i_debug("server listening on %u", *bind_port); + if (fd == -1) { + i_fatal("listen(%s:%u) failed: %m", + net_ip2addr(&bind_ip), *bind_port); + } + return fd; +} + +static void test_tmp_dir_init(void) +{ + tmp_dir = i_strdup_printf("/tmp/dovecot-test-smtp-client.%s.%s", + dec2str(time(NULL)), dec2str(getpid())); +} + +static const char *test_tmp_dir_get(void) +{ + if (mkdir(tmp_dir, 0700) < 0 && errno != EEXIST) { + i_fatal("failed to create temporary directory `%s': %m", + tmp_dir); + } + return tmp_dir; +} + +static void test_tmp_dir_deinit(void) +{ + const char *error; + + if (unlink_directory(tmp_dir, UNLINK_DIRECTORY_FLAG_RMDIR, + &error) < 0) { + i_warning("failed to remove temporary directory `%s': %s.", + tmp_dir, error); + } + + i_free(tmp_dir); +} + +static void test_message_delivery(const char *message, const char *file) +{ + struct istream *input; + const unsigned char *data; + size_t size, msize; + int ret; + + msize = strlen(message); + + input = i_stream_create_file(file, SIZE_MAX); + while ((ret = i_stream_read_more(input, &data, &size)) > 0) { + const unsigned char *mdata; + + test_assert(input->v_offset < (uoff_t)msize && + (input->v_offset + (uoff_t)size) <= (uoff_t)msize); + if (test_has_failed()) + break; + mdata = (const unsigned char *)message + input->v_offset; + test_assert(memcmp(data, mdata, size) == 0); + if (test_has_failed()) + break; + i_stream_skip(input, size); + } + + test_out_reason("delivery", ret < 0 && + input->stream_errno == 0 && + input->eof && + input->v_offset == (uoff_t)msize, + (input->stream_errno == 0 ? + NULL : i_stream_get_error(input))); + i_stream_unref(&input); +} + +static int test_run_server(struct test_server_data *data) +{ + server_port = bind_ports[data->index]; + + main_deinit(); + master_service_deinit_forked(&master_service); + + i_set_failure_prefix("SERVER[%u]: ", data->index + 1); + + if (debug) + i_debug("PID=%s", my_pid); + + ioloop = io_loop_create(); + data->server_test(data->index); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); + + i_close_fd(&fd_listen); + i_free(bind_ports); + test_tmp_dir_deinit(); + return 0; +} + +static void +test_run_client(const struct smtp_submit_settings *submit_set, + test_client_init_t client_test) +{ + i_set_failure_prefix("CLIENT: "); + + if (debug) + i_debug("PID=%s", my_pid); + + server_port = 0; + i_sleep_msecs(100); /* wait a little for server setup */ + + ioloop = io_loop_create(); + if (client_test(submit_set)) + io_loop_run(ioloop); + test_client_deinit(); + io_loop_destroy(&ioloop); + + if (debug) + i_debug("Terminated"); +} + +static void +test_run_client_server(const struct smtp_submit_settings *submit_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count) +{ + unsigned int i; + + test_tmp_dir_init(); + + if (server_tests_count > 0) { + int fds[server_tests_count]; + + bind_ports = i_new(in_port_t, server_tests_count); + for (i = 0; i < server_tests_count; i++) + fds[i] = test_open_server_fd(&bind_ports[i]); + + for (i = 0; i < server_tests_count; i++) { + struct test_server_data data; + + i_zero(&data); + data.index = i; + data.server_test = server_test; + + /* Fork server */ + fd_listen = fds[i]; + test_subprocess_fork(test_run_server, &data, FALSE); + i_close_fd(&fd_listen); + } + } + + /* Run client */ + test_run_client(submit_set, client_test); + + i_unset_failure_prefix(); + test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); + i_free(bind_ports); + test_tmp_dir_deinit(); +} + +/* + * Main + */ + +static void main_init(void) +{ + /* nothing yet */ +} + +static void main_deinit(void) +{ + /* nothing yet; also called from sub-processes */ +} + +int main(int argc, char *argv[]) +{ + const enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_DONT_SEND_STATS; + int c; + int ret; + + master_service = master_service_init("test-smtp-submit", service_flags, + &argc, &argv, "D"); + main_init(); + + while ((c = master_getopt(master_service)) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + master_service_init_finish(master_service); + test_subprocesses_init(debug); + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + ret = test_run(test_functions); + + test_subprocesses_deinit(); + main_deinit(); + master_service_deinit(&master_service); + + return ret; +} |