From: Stephan Bosch Date: Tue, 28 Feb 2017 22:56:47 +0000 (+0100) Subject: lib-lda: Created tests for SMTP message submission using the smtp-client API. X-Git-Tag: 2.3.0.rc1~1051 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=be02ac47a984f422823feb20a77dcf160f7d562e;p=thirdparty%2Fdovecot%2Fcore.git lib-lda: Created tests for SMTP message submission using the smtp-client API. --- diff --git a/src/lib-lda/Makefile.am b/src/lib-lda/Makefile.am index b02828508d..54cad69036 100644 --- a/src/lib-lda/Makefile.am +++ b/src/lib-lda/Makefile.am @@ -2,6 +2,7 @@ noinst_LTLIBRARIES = liblda.la AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/lib-dns \ @@ -10,7 +11,8 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib-imap \ -I$(top_srcdir)/src/lib-mail \ -I$(top_srcdir)/src/lib-storage \ - -I$(top_srcdir)/src/lib-program-client + -I$(top_srcdir)/src/lib-program-client \ + -DTEST_BIN_DIR=\"$(abs_srcdir)/test-bin\" liblda_la_SOURCES = \ duplicate.c \ @@ -36,3 +38,46 @@ libdovecot_lda_la_SOURCES = libdovecot_lda_la_LIBADD = liblda.la $(deps) libdovecot_lda_la_DEPENDENCIES = liblda.la $(deps) libdovecot_lda_la_LDFLAGS = -export-dynamic + +test_programs = + +test_nocheck_programs = \ + test-smtp-client + +noinst_PROGRAMS = $(test_programs) $(test_nocheck_programs) + +test_libs = \ + liblda.la \ + ../lib-smtp/libsmtp.la \ + ../lib-program-client/libprogram_client.la \ + ../lib-dns/libdns.la \ + ../lib-mail/libmail.la \ + ../lib-master/libmaster.la \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-settings/libsettings.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la \ + $(MODULE_LIBS) + +test_deps = \ + liblda.la \ + ../lib-smtp/libsmtp.la \ + ../lib-program-client/libprogram_client.la \ + ../lib-dns/libdns.la \ + ../lib-mail/libmail.la \ + ../lib-master/libmaster.la \ + ../lib-ssl-iostream/libssl_iostream.la \ + ../lib-settings/libsettings.la \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_smtp_client_SOURCES = test-smtp-client.c +test_smtp_client_LDFLAGS = -export-dynamic +test_smtp_client_LDADD = $(test_libs) +test_smtp_client_DEPENDENCIES = $(test_deps) + +check: check-am check-test +check-test: all-am + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done diff --git a/src/lib-lda/test-bin/sendmail-exit-1.sh b/src/lib-lda/test-bin/sendmail-exit-1.sh new file mode 100755 index 0000000000..195df7c2c4 --- /dev/null +++ b/src/lib-lda/test-bin/sendmail-exit-1.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 1; diff --git a/src/lib-lda/test-bin/sendmail-success.sh b/src/lib-lda/test-bin/sendmail-success.sh new file mode 100755 index 0000000000..bb09b2b602 --- /dev/null +++ b/src/lib-lda/test-bin/sendmail-success.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cat > $1 +exit 0; diff --git a/src/lib-lda/test-smtp-client.c b/src/lib-lda/test-smtp-client.c new file mode 100644 index 0000000000..bb8f176bda --- /dev/null +++ b/src/lib-lda/test-smtp-client.c @@ -0,0 +1,1961 @@ +/* Copyright (c) 2016-2017 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "lib-signals.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 "unlink-directory.h" +#include "write-full.h" +#include "connection.h" +#include "master-service.h" +#include "istream-dot.h" +#include "test-common.h" +#include "lda-settings.h" +#include "smtp-client.h" + +#include +#include +#include +#include +#include +#include + +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"; + +/* + * 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 lda_settings *client_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 pid_t *server_pids = NULL; +static in_port_t server_port = 0; +static unsigned int server_pids_count = 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 lda_settings *smtp_set); +static void test_client_deinit(void); + +static int +test_client_smtp_send_simple(const struct lda_settings *smtp_set, + const char *message, const char *host, + unsigned int timeout_secs, const char **error_r); +static int +test_client_smtp_send_simple_port(const struct lda_settings *smtp_set, + const char *message, unsigned int port, + unsigned int timeout_secs, 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 lda_settings *client_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple(client_set, + test_message1, "host.invalid", 5, &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_host_lookup_failed(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("host lookup failed"); + test_expect_errors(1); + test_run_client_server(&smtp_client_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_connection_refused(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection refused"); + test_expect_errors(1); + test_run_client_server(&smtp_client_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) +{ + sleep(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 lda_settings *client_set) +{ + time_t time; + const char *error = NULL; + int ret; + + io_loop_time_refresh(); + time = ioloop_time; + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 1, &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 lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection timed out"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + // FIXME: lmtp client handles this wrong, the greeting is not "bad" + //test_out_reason("run", ret == 0, error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_bad_greeting(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("bad greeting"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + o_stream_send_str(conn->conn.output, + "502 Command not implemented\r\n"); + server_connection_deinit(&conn); +} + +static void +test_denied_helo_init(struct server_connection *conn) +{ + o_stream_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + // FIXME: lmtp client handles this wrong, the greeting is not "bad" + //test_out_reason("run", ret == 0, error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_helo(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("denied helo"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + server_connection_deinit(&conn); +} + +static void +test_disconnect_helo_init(struct server_connection *conn) +{ + o_stream_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + // FIXME: lmtp client handles this wrong, the greeting is not "bad" + //test_out_reason("run", ret == 0, error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_disconnect_helo(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("disconnect helo"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_MAIL_STATE_EHLO: + o_stream_send_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_send_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_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + // FIXME: lmtp client handles this wrong, the greeting is not "bad" + //test_out_reason("run", ret == 0, error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_mail(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("denied mail"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_RCPT_STATE_EHLO: + o_stream_send_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_send_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_send_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_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_rcpt(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("denied rcpt"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_SECOND_RCPT_STATE_EHLO: + o_stream_send_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_send_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_send_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_send_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_send_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 bool +test_client_denied_second_rcpt(const struct lda_settings *client_set) +{ + struct smtp_client *smtp_client; + struct lda_settings smtp_client_set; + struct ostream *output; + const char *error = NULL; + int ret; + + smtp_client_set = *client_set; + smtp_client_set.submission_host = + t_strdup_printf("127.0.0.1:%u", bind_ports[0]); + + smtp_client = smtp_client_init(&smtp_client_set, "sender@example.com"); + + smtp_client_add_rcpt(smtp_client, "rcpt@example.com"); + smtp_client_add_rcpt(smtp_client, "rcpt2@example.com"); + output = smtp_client_send(smtp_client); + o_stream_send_str(output, test_message1); + + ret = smtp_client_deinit_timeout(smtp_client, 5, &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_second_rcpt(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("denied second rcpt"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DENIED_DATA_STATE_EHLO: + o_stream_send_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_send_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_send_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_send_str(conn->conn.output, + "500 5.0.0 Unacceptabe recipients\r\n"); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void +test_denied_data_init(struct server_connection *conn) +{ + o_stream_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_denied_data(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("denied data"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_FAILURE_STATE_EHLO: + o_stream_send_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_send_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_send_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_send_str(conn->conn.output, + "354 End data with .\r\n"); + ctx->state = DATA_FAILURE_STATE_FINISH; + continue; + case DATA_FAILURE_STATE_FINISH: + if (strcmp(line, ".") == 0) { + o_stream_send_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_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + test_out_reason("run (ret == 0)", ret == 0, error); + + return FALSE; +} + +/* test */ + +static void test_data_failure(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("data failure"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_DISCONNECT_STATE_EHLO: + o_stream_send_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_send_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_send_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_send_str(conn->conn.output, + "354 End data with .\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_send_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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_data_disconnect(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("data disconnect"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case DATA_TIMEOUT_STATE_EHLO: + o_stream_send_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_send_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_send_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_send_str(conn->conn.output, + "354 End data with .\r\n"); + ctx->state = DATA_TIMEOUT_STATE_FINISH; + continue; + case DATA_TIMEOUT_STATE_FINISH: + sleep(10); + server_connection_deinit(&conn); + return; + } + i_unreached(); + } +} + +static void +test_data_timout_init(struct server_connection *conn) +{ + o_stream_send_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 lda_settings *client_set) +{ + time_t time; + const char *error = NULL; + int ret; + + io_loop_time_refresh(); + time = ioloop_time; + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 2, &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 lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("data timeout"); + test_expect_errors(0); + test_run_client_server(&smtp_client_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; + } + + for (;;) { + if (ctx->state == SUCCESSFUL_DELIVERY_STATE_FINISH) { + // FIXME: put somewhere common + 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_send_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 (i_stream_is_eof(conn->conn.input) || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + switch (ctx->state) { + case SUCCESSFUL_DELIVERY_STATE_EHLO: + o_stream_send_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_send_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_send_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_send_str(conn->conn.output, + "354 End data with .\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_send_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; + if (ctx->dot_input != NULL) + i_stream_unref(&ctx->dot_input); + if (ctx->file != NULL) + 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 lda_settings *client_set) +{ + const char *error = NULL; + int ret; + + /* send the message */ + ret = test_client_smtp_send_simple_port(client_set, + test_message1, bind_ports[0], 5, &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; +} + +/* test */ + +static void test_successful_delivery(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("successful delivery"); + test_expect_errors(0); + test_run_client_server(&smtp_client_set, + test_client_successful_delivery, + test_server_successful_delivery, 1); + test_end(); +} + +/* + * Failed sendmail + */ + +/* client */ + +static bool +test_client_failed_sendmail(const struct lda_settings *client_set) +{ + struct lda_settings smtp_client_set; + struct smtp_client *smtp_client; + struct ostream *output; + const char *sendmail_path, *error = NULL; + int ret; + + sendmail_path = TEST_BIN_DIR"/sendmail-exit-1.sh"; + + smtp_client_set = *client_set; + smtp_client_set.sendmail_path = sendmail_path; + + smtp_client = smtp_client_init(&smtp_client_set, "sender@example.com"); + + smtp_client_add_rcpt(smtp_client, "rcpt@example.com"); + output = smtp_client_send(smtp_client); + o_stream_send_str(output, test_message1); + + ret = smtp_client_deinit_timeout(smtp_client, 5, &error); + test_out_reason("run (ret < 0)", ret < 0, error); + + return FALSE; +} + +/* test */ + +static void test_failed_sendmail(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("failed sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_client_set, + test_client_failed_sendmail, NULL, 0); + test_end(); +} + +/* + * Successful sendmail + */ + +/* client */ + +static bool +test_client_successful_sendmail(const struct lda_settings *client_set) +{ + struct lda_settings smtp_client_set; + struct smtp_client *smtp_client; + 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_client_set = *client_set; + smtp_client_set.sendmail_path = sendmail_path; + + smtp_client = smtp_client_init(&smtp_client_set, "sender@example.com"); + + smtp_client_add_rcpt(smtp_client, "rcpt@example.com"); + output = smtp_client_send(smtp_client); + o_stream_send_str(output, test_message1); + + ret = smtp_client_deinit_timeout(smtp_client, 5, &error); + test_out_reason("run (ret > 0)", ret > 0, error); + + /* verify delivery */ + test_message_delivery(test_message1, msg_path); + + return FALSE; +} + +/* test */ + +static void test_successful_sendmail(void) +{ + struct lda_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("successful sendmail"); + test_expect_errors(0); + test_run_client_server(&smtp_client_set, + test_client_successful_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, + NULL +}; + +/* + * Test client + */ + +static void +test_client_defaults(struct lda_settings *smtp_set) +{ + i_zero(smtp_set); + smtp_set->hostname = "test"; + smtp_set->submission_host = ""; + smtp_set->sendmail_path = "/bin/false"; +} + +static void test_client_deinit(void) +{ +} + +static int +test_client_smtp_send_simple(const struct lda_settings *smtp_set, + const char *message, const char *host, + unsigned int timeout_secs, const char **error_r) +{ + struct lda_settings smtp_client_set; + struct smtp_client *smtp_client; + struct ostream *output; + + /* send the message */ + smtp_client_set = *smtp_set; + smtp_client_set.submission_host = host, + + smtp_client = smtp_client_init(&smtp_client_set, "sender@example.com"); + + smtp_client_add_rcpt(smtp_client, "rcpt@example.com"); + output = smtp_client_send(smtp_client); + o_stream_send_str(output, message); + + return smtp_client_deinit_timeout + (smtp_client, timeout_secs, error_r); +} + +static int +test_client_smtp_send_simple_port(const struct lda_settings *smtp_set, + const char *message, unsigned int port, + unsigned int timeout_secs, 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, timeout_secs, 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_t)-1, + .output_max_size = (size_t)-1, + .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, (void *)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 + */ + +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_servers_kill_all(void) +{ + unsigned int i; + + if (server_pids_count > 0) { + for (i = 0; i < server_pids_count; i++) { + if (server_pids[i] != (pid_t)-1) { + (void)kill(server_pids[i], SIGKILL); + (void)waitpid(server_pids[i], NULL, 0); + server_pids[i] = -1; + } + } + } + server_pids_count = 0; +} + +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 && errno != ENOENT) { + 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_t)-1); + while ((ret=i_stream_read_more(input, &data, &size)) > 0) { + const unsigned char *mdata; + if (input->v_offset >= (uoff_t)msize || + (input->v_offset + (uoff_t)size) > (uoff_t)msize) { + ret = -1; + break; + } + mdata = (const unsigned char *)message + input->v_offset; + if (memcmp(data, mdata, size) != 0) { + ret = -1; + break; + } + i_stream_skip(input, size); + } + + test_out_reason("delivery", + input->stream_errno == 0 && + i_stream_is_eof(input) && + input->v_offset == (uoff_t)msize, + (input->stream_errno == 0 ? NULL : i_stream_get_error(input))); + i_stream_unref(&input); +} + +static void test_run_client_server( + const struct lda_settings *client_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count) +{ + unsigned int i; + + server_pids = NULL; + server_pids_count = 0; + + test_tmp_dir_init(); + + if (server_tests_count > 0) { + int fds[server_tests_count]; + + bind_ports = i_new(in_port_t, server_tests_count); + + lib_signals_ioloop_detach(); + + server_pids = i_new(pid_t, server_tests_count); + for (i = 0; i < server_tests_count; i++) + server_pids[i] = (pid_t)-1; + server_pids_count = 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++) { + fd_listen = fds[i]; + server_port = bind_ports[i]; + if ((server_pids[i] = fork()) == (pid_t)-1) + i_fatal("fork() failed: %m"); + if (server_pids[i] == 0) { + server_pids[i] = (pid_t)-1; + server_pids_count = 0; + hostpid_init(); + while (current_ioloop != NULL) { + ioloop = current_ioloop; + io_loop_destroy(&ioloop); + } + lib_signals_deinit(); + if (debug) + i_debug("server[%d]: PID=%s", i+1, my_pid); + /* child: server */ + ioloop = io_loop_create(); + server_test(i); + io_loop_destroy(&ioloop); + if (fd_listen != -1) + i_close_fd(&fd_listen); + i_free(bind_ports); + i_free(server_pids); + test_tmp_dir_deinit(); + /* wait for it to be killed; this way, valgrind will not + object to this process going away inelegantly. */ + sleep(60); + exit(1); + } + if (fd_listen != -1) + i_close_fd(&fd_listen); + } + if (debug) + i_debug("client: PID=%s", my_pid); + + lib_signals_ioloop_attach(); + } + + /* parent: client */ + + usleep(100000); /* wait a little for server setup */ + server_port = 0; + + ioloop = io_loop_create(); + if (client_test(client_set)) + io_loop_run(ioloop); + test_client_deinit(); + io_loop_destroy(&ioloop); + + test_servers_kill_all(); + i_free(server_pids); + i_free(bind_ports); + test_tmp_dir_deinit(); +} + +/* + * Main + */ + +volatile sig_atomic_t terminating = 0; + +static void +test_signal_handler(int signo) +{ + if (terminating != 0) + raise(signo); + terminating = 1; + + /* make sure we don't leave any pesky children alive */ + test_servers_kill_all(); + + (void)signal(signo, SIG_DFL); + raise(signo); +} + +static void test_atexit(void) +{ + test_servers_kill_all(); +} + +int main(int argc, char *argv[]) +{ + int c; + + atexit(test_atexit); + (void)signal(SIGCHLD, SIG_IGN); + (void)signal(SIGTERM, test_signal_handler); + (void)signal(SIGQUIT, test_signal_handler); + (void)signal(SIGINT, test_signal_handler); + (void)signal(SIGSEGV, test_signal_handler); + (void)signal(SIGABRT, test_signal_handler); + + master_service = master_service_init("test-smtp-client", + MASTER_SERVICE_FLAG_STANDALONE, &argc, &argv, "D"); + + 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); + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + test_run(test_functions); + + master_service_deinit(&master_service); +}