From: Stephan Bosch Date: Sun, 29 Oct 2017 17:36:25 +0000 (+0100) Subject: lib-smtp: Implemented test suite for client errors. X-Git-Tag: 2.3.0.rc1~351 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9051b704ca26ef833544cf006134f25cddde2525;p=thirdparty%2Fdovecot%2Fcore.git lib-smtp: Implemented test suite for client errors. --- diff --git a/src/lib-smtp/Makefile.am b/src/lib-smtp/Makefile.am index 79f958b381..345550a208 100644 --- a/src/lib-smtp/Makefile.am +++ b/src/lib-smtp/Makefile.am @@ -52,7 +52,8 @@ test_programs = \ test-smtp-reply-parser test_nocheck_programs = \ - test-smtp-submit + test-smtp-submit \ + test-smtp-client-errors noinst_PROGRAMS = $(test_programs) $(test_nocheck_programs) @@ -103,6 +104,11 @@ test_smtp_submit_LDFLAGS = -export-dynamic test_smtp_submit_LDADD = $(test_libs) test_smtp_submit_DEPENDENCIES = $(test_deps) +test_smtp_client_errors_SOURCES = test-smtp-client-errors.c +test_smtp_client_errors_LDFLAGS = -export-dynamic +test_smtp_client_errors_LDADD = $(test_libs) +test_smtp_client_errors_DEPENDENCIES = $(test_deps) + check-local: for bin in $(test_programs); do \ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ diff --git a/src/lib-smtp/test-smtp-client-errors.c b/src/lib-smtp/test-smtp-client-errors.c new file mode 100644 index 0000000000..28e59d67dc --- /dev/null +++ b/src/lib-smtp/test-smtp-client-errors.c @@ -0,0 +1,2969 @@ +/* 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-dot.h" +#include "istream-chain.h" +#include "istream-failure-at.h" +#include "ostream.h" +#include "time-util.h" +#include "connection.h" +#include "test-common.h" +#include "smtp-client.h" +#include "smtp-client-connection.h" +#include "smtp-client-transaction.h" + +#include +#include +#include +#include + +/* + * Types + */ + +enum server_connection_state { + SERVER_CONNECTION_STATE_EHLO = 0, + SERVER_CONNECTION_STATE_MAIL_FROM, + SERVER_CONNECTION_STATE_RCPT_TO, + SERVER_CONNECTION_STATE_DATA, + SERVER_CONNECTION_STATE_FINISH +}; + +struct server_connection { + struct connection conn; + void *context; + + enum server_connection_state state; + char *file_path; + struct istream *dot_input; + + pool_t pool; +}; + +typedef void (*test_server_init_t)(unsigned int index); +typedef bool (*test_client_init_t) + (const struct smtp_client_settings *client_set); +typedef void (*test_dns_init_t)(void); + +/* + * State + */ + +/* common */ +static struct ip_addr bind_ip; +static in_port_t *bind_ports = 0; +static struct ioloop *ioloop; +static bool debug = FALSE; + +/* dns */ +static pid_t dns_pid = (pid_t)-1; + +/* server */ +static struct io *io_listen; +static int fd_listen = -1; +static pid_t *server_pids = NULL; +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 int (*test_server_input_line)(struct server_connection *conn, + const char *line); +static int (*test_server_input_data)(struct server_connection *conn, + const unsigned char *data, size_t size); +static int (*test_server_init)(struct server_connection *conn); +static void (*test_server_deinit)(struct server_connection *conn); + +/* client */ +static struct smtp_client *smtp_client = NULL; + +/* + * 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_client_settings *smtp_set); +static void test_client_deinit(void); + +/* test*/ +static void test_run_client_server( + const struct smtp_client_settings *client_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count, + test_dns_init_t dns_test) + ATTR_NULL(3); + +/* + * Unconfigured SSL + */ + +/* server */ + +static void +test_server_unconfigured_ssl_input( + struct server_connection *conn ATTR_UNUSED) +{ + /* nothing */ +} + +static void +test_server_unconfigured_ssl(unsigned int index) +{ + sleep(100); + test_server_input = test_server_unconfigured_ssl_input; + test_server_run(index); +} + +/* client */ + +struct _unconfigured_ssl { + unsigned int count; +}; + +static void +test_client_unconfigured_ssl_reply(const struct smtp_reply *reply, + void *context) +{ + struct _unconfigured_ssl *ctx = (struct _unconfigured_ssl *)context; + + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_unconfigured_ssl(const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct _unconfigured_ssl *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _unconfigured_ssl, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, + test_client_unconfigured_ssl_reply, ctx); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, + test_client_unconfigured_ssl_reply, ctx); + + return TRUE; +} + +/* test */ + +static void test_unconfigured_ssl(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("unconfigured ssl"); + test_run_client_server(&smtp_client_set, + test_client_unconfigured_ssl, + test_server_unconfigured_ssl, 1, NULL); + test_end(); +} + +/* + * Unconfigured SSL abort + */ + +/* server */ + +static void +test_server_unconfigured_ssl_abort_input( + struct server_connection *conn ATTR_UNUSED) +{ + /* nothing */ +} + +static void +test_server_unconfigured_ssl_abort(unsigned int index) +{ + sleep(100); + test_server_input = test_server_unconfigured_ssl_abort_input; + test_server_run(index); +} + +/* client */ + +struct _unconfigured_ssl_abort { + unsigned int count; +}; + +static void +test_client_unconfigured_ssl_abort_reply1(const struct smtp_reply *reply, + struct _unconfigured_ssl_abort *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_out_quiet("inappropriate callback", FALSE); +} + +static void +test_client_unconfigured_ssl_abort_reply2(const struct smtp_reply *reply, + struct _unconfigured_ssl_abort *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + i_free(ctx); + io_loop_stop(ioloop); +} + +static bool +test_client_unconfigured_ssl_abort( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _unconfigured_ssl_abort *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _unconfigured_ssl_abort, 1); + ctx->count = 1; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_unconfigured_ssl_abort_reply1, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + smtp_client_command_abort(&scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "127.0.0.1", bind_ports[0], + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_unconfigured_ssl_abort_reply2, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_unconfigured_ssl_abort(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("unconfigured ssl abort"); + test_run_client_server(&smtp_client_set, + test_client_unconfigured_ssl_abort, + test_server_unconfigured_ssl_abort, 1, NULL); + test_end(); +} + +/* + * Host lookup failed + */ + +/* client */ + +struct _host_lookup_failed { + unsigned int count; +}; + +static void +test_client_host_lookup_failed_reply(const struct smtp_reply *reply, + struct _host_lookup_failed *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_host_lookup_failed( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _host_lookup_failed *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _host_lookup_failed, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_host_lookup_failed_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_host_lookup_failed_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_host_lookup_failed(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("host lookup failed"); + test_run_client_server(&smtp_client_set, + test_client_host_lookup_failed, + NULL, 0, NULL); + test_end(); +} + +/* + * Connection refused + */ + +/* server */ + +static void +test_server_connection_refused(unsigned int index ATTR_UNUSED) +{ + i_close_fd(&fd_listen); +} + +/* client */ + +struct _connection_refused { + unsigned int count; +}; + +static void +test_client_connection_refused_reply(const struct smtp_reply *reply, + struct _connection_refused *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_refused( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _connection_refused *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _connection_refused, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_connection_refused_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_connection_refused_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_connection_refused(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection refused"); + test_run_client_server(&smtp_client_set, + test_client_connection_refused, + test_server_connection_refused, 1, NULL); + test_end(); +} + +/* + * Connection lost prematurely + */ + +/* server */ + +static void +test_connection_lost_prematurely_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 || + conn->conn.input->stream_errno != 0) { + server_connection_deinit(&conn); + } + return; + } + server_connection_deinit(&conn); +} + +static int +test_connection_lost_prematurely_init(struct server_connection *conn) +{ + o_stream_send_str(conn->conn.output, + "220 testserver ESMTP Testfix (Frop/GNU)\r\n"); + return 1; +} + +static void test_server_connection_lost_prematurely(unsigned int index) +{ + test_server_init = test_connection_lost_prematurely_init; + test_server_input = test_connection_lost_prematurely_input; + test_server_run(index); +} + +/* client */ + +struct _connection_lost_prematurely { + unsigned int count; +}; + +static void +test_client_connection_lost_prematurely_reply(const struct smtp_reply *reply, + struct _connection_lost_prematurely *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_lost_prematurely( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _connection_lost_prematurely *ctx; + + ctx = i_new(struct _connection_lost_prematurely, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_connection_lost_prematurely_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_connection_lost_prematurely_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_connection_lost_prematurely(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection lost prematurely"); + test_run_client_server(&smtp_client_set, + test_client_connection_lost_prematurely, + test_server_connection_lost_prematurely, 1, NULL); + test_end(); +} + +/* + * Connection timed out + */ + +/* server */ + +static void test_server_connection_timed_out(unsigned int index ATTR_UNUSED) +{ + sleep(10); +} + +/* client */ + +struct _connection_timed_out { + unsigned int count; +}; + +static void +test_client_connection_timed_out_reply(const struct smtp_reply *reply, + struct _connection_timed_out *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECT_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_connection_timed_out( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _connection_timed_out *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _connection_timed_out, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_connection_timed_out_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_connection_timed_out_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_connection_timed_out(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.connect_timeout_msecs = 1000; + + test_begin("connection timed out"); + test_run_client_server(&smtp_client_set, + test_client_connection_timed_out, + test_server_connection_timed_out, 1, NULL); + test_end(); +} + +/* + * Broken payload + */ + +/* server */ + +static int +test_broken_payload_input_line(struct server_connection *conn ATTR_UNUSED, + const char *line ATTR_UNUSED) +{ + return 0; +} + +static void test_server_broken_payload(unsigned int index) +{ + test_server_input_line = test_broken_payload_input_line; + test_server_run(index); +} + +static int +test_broken_payload_chunking_input_line( + struct server_connection *conn, const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) { + o_stream_send_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-CHUNKING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n"); + return 1; + } + return 0; +} + +static void test_server_broken_payload_chunking(unsigned int index) +{ + test_server_input_line = test_broken_payload_chunking_input_line; + test_server_run(index); +} + +/* client */ + +static void +test_client_broken_payload_rcpt_to_cb(const struct smtp_reply *reply, + void *context ATTR_UNUSED) +{ + test_assert(smtp_reply_is_success(reply)); +} + +static void +test_client_broken_payload_rcpt_data_cb(const struct smtp_reply *reply, + void *context ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD); +} + +static void +test_client_broken_payload_data_cb(const struct smtp_reply *reply, + void *context ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_BROKEN_PAYLOAD); +} + +static void +test_client_broken_payload_finished(void *context ATTR_UNUSED) +{ + io_loop_stop(ioloop); +} + +static bool +test_client_broken_payload( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_transaction *strans; + struct istream *input; + + test_expect_errors(2); + + input = i_stream_create_error_str(EIO, "Moehahahaha!!"); + i_stream_set_name(input, "PURE EVIL"); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + strans = smtp_client_transaction_create(sconn, + SMTP_ADDRESS_LITERAL("sender", "example.com"), NULL, + test_client_broken_payload_finished, NULL); + smtp_client_connection_unref(&sconn); + + smtp_client_transaction_add_rcpt(strans, + SMTP_ADDRESS_LITERAL("rcpt", "example.com"), NULL, + test_client_broken_payload_rcpt_to_cb, + test_client_broken_payload_rcpt_data_cb, NULL); + smtp_client_transaction_send(strans, input, + test_client_broken_payload_data_cb, NULL); + i_stream_unref(&input); + + return TRUE; +} + +static bool +test_client_broken_payload_later( + const struct smtp_client_settings *client_set) +{ + static const char *message = + "From: lucifer@example.com\r\n" + "To: lostsoul@example.com\r\n" + "Subject: Moehahaha!\r\n" + "\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n" + "Moehahahahahahahahahahahahahahahahahahahahahaha!!\r\n"; + struct smtp_client_connection *sconn; + struct smtp_client_transaction *strans; + struct istream *input, *msg_input; + + test_expect_errors(1); + + msg_input = i_stream_create_from_data(message, strlen(message)); + input = i_stream_create_failure_at(msg_input, 666, "Moehahahaha!!"); + i_stream_unref(&msg_input); + i_stream_set_name(input, "PURE EVIL"); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + strans = smtp_client_transaction_create(sconn, + SMTP_ADDRESS_LITERAL("sender", "example.com"), NULL, + test_client_broken_payload_finished, NULL); + smtp_client_connection_unref(&sconn); + + smtp_client_transaction_add_rcpt(strans, + SMTP_ADDRESS_LITERAL("rcpt", "example.com"), NULL, + test_client_broken_payload_rcpt_to_cb, + test_client_broken_payload_rcpt_data_cb, NULL); + smtp_client_transaction_send + (strans, input, test_client_broken_payload_data_cb, NULL); + i_stream_unref(&input); + + return TRUE; +} + +/* test */ + +static void test_broken_payload(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.connect_timeout_msecs = 1000; + + test_begin("broken payload"); + test_run_client_server(&smtp_client_set, + test_client_broken_payload, + test_server_broken_payload, 1, NULL); + test_end(); + + test_begin("broken payload (later)"); + test_run_client_server(&smtp_client_set, + test_client_broken_payload_later, + test_server_broken_payload, 1, NULL); + test_end(); + + test_begin("broken payload (later, chunking)"); + test_run_client_server(&smtp_client_set, + test_client_broken_payload_later, + test_server_broken_payload_chunking, 1, NULL); + test_end(); +} + +/* + * Connection lost + */ + +/* server */ + +static int +test_connection_lost_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + if (server_index == 0) { + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + sleep(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (server_index == 1) { + conn->state = SERVER_CONNECTION_STATE_RCPT_TO; + sleep(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_RCPT_TO: + if (server_index == 2) { + conn->state = SERVER_CONNECTION_STATE_DATA; + sleep(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_DATA: + if (server_index == 3) { + conn->state = SERVER_CONNECTION_STATE_FINISH; + sleep(1); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + return 0; +} + +static int +test_connection_lost_input_data(struct server_connection *conn, + const unsigned char *data ATTR_UNUSED, size_t size ATTR_UNUSED) +{ + sleep(1); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_connection_lost(unsigned int index) +{ + test_server_input_line = test_connection_lost_input_line; + test_server_input_data = test_connection_lost_input_data; + test_server_run(index); +} + +/* client */ + +struct _connection_lost { + unsigned int count; +}; + +struct _connection_lost_peer { + struct _connection_lost *context; + unsigned int index; +}; + +static void +test_client_connection_lost_rcpt_to_cb(const struct smtp_reply *reply, + struct _connection_lost_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + case 1: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + case 2: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + case 3: + test_assert(smtp_reply_is_success(reply)); + break; + } +} + +static void +test_client_connection_lost_rcpt_data_cb(const struct smtp_reply *reply, + struct _connection_lost_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(FALSE); + break; + case 1: + test_assert(FALSE); + break; + case 2: + test_assert(FALSE); + break; + case 3: + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + break; + } +} + +static void +test_client_connection_lost_data_cb(const struct smtp_reply *reply, + struct _connection_lost_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); +} + +static void +test_client_connection_lost_finished(struct _connection_lost_peer *pctx) +{ + struct _connection_lost *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + i_free(pctx); +} + +static void +test_client_connection_lost_submit(struct _connection_lost *ctx, + unsigned int index) +{ + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct _connection_lost_peer *pctx; + struct smtp_client_connection *sconn; + struct smtp_client_transaction *strans; + struct istream *input; + + pctx = i_new(struct _connection_lost_peer, 1); + pctx->context = ctx; + pctx->index = index; + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + strans = smtp_client_transaction_create(sconn, + SMTP_ADDRESS_LITERAL("sender", "example.com"), NULL, + test_client_connection_lost_finished, pctx); + smtp_client_connection_unref(&sconn); + + smtp_client_transaction_add_rcpt(strans, + SMTP_ADDRESS_LITERAL("rcpt", "example.com"), NULL, + test_client_connection_lost_rcpt_to_cb, + test_client_connection_lost_rcpt_data_cb, pctx); + smtp_client_transaction_send + (strans, input, test_client_connection_lost_data_cb, pctx); + i_stream_unref(&input); +} + +static bool +test_client_connection_lost( + const struct smtp_client_settings *client_set) +{ + struct _connection_lost *ctx; + unsigned int i; + + ctx = i_new(struct _connection_lost, 1); + ctx->count = 5; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_connection_lost_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_connection_lost(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("connection lost"); + test_run_client_server(&smtp_client_set, + test_client_connection_lost, + test_server_connection_lost, 5, NULL); + test_end(); +} + +/* + * Unexpected reply + */ + +/* server */ + +static int +test_unexpected_reply_init(struct server_connection *conn) +{ + if (server_index == 5) { + o_stream_send_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); + o_stream_send_str(conn->conn.output, + "421 testserver Server shutting down for maintenance\r\n"); + sleep(4); + server_connection_deinit(&conn); + return 1; + } + return 0; +} + +static int +test_unexpected_reply_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + if (server_index == 4) { + o_stream_send_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n"); + o_stream_send_str(conn->conn.output, + "421 testserver Server shutting down for maintenance\r\n"); + sleep(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (server_index == 3) { + o_stream_send_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + o_stream_send_str(conn->conn.output, + "421 testserver Server shutting down for maintenance\r\n"); + sleep(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_RCPT_TO: + if (server_index == 2) { + o_stream_send_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + o_stream_send_str(conn->conn.output, + "421 testserver Server shutting down for maintenance\r\n"); + sleep(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_DATA: + if (server_index == 1) { + o_stream_send_str(conn->conn.output, + "354 End data with .\r\n"); + o_stream_send_str(conn->conn.output, + "421 testserver Server shutting down for maintenance\r\n"); + sleep(4); + server_connection_deinit(&conn); + return -1; + } + break; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + return 0; +} + +static void test_server_unexpected_reply(unsigned int index) +{ + test_server_init = test_unexpected_reply_init; + test_server_input_line = test_unexpected_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _unexpected_reply { + unsigned int count; +}; + +struct _unexpected_reply_peer { + struct _unexpected_reply *context; + unsigned int index; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; + struct timeout *to; + + bool login_callback:1; + bool mail_from_callback:1; + bool rcpt_to_callback:1; + bool rcpt_data_callback:1; + bool data_callback:1; +}; + +static void +test_client_unexpected_reply_login_cb(const struct smtp_reply *reply, + void *context) +{ + struct _unexpected_reply_peer *pctx = + (struct _unexpected_reply_peer *)context; + + pctx->login_callback = TRUE; + + if (debug) + i_debug("LOGIN REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); + + switch (pctx->index) { + case 0: case 1: case 2: case 3: case 4: + test_assert(smtp_reply_is_success(reply)); + break; + case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_mail_from_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) + i_debug("MAIL FROM REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); + + pctx->mail_from_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: case 2: case 3: + test_assert(smtp_reply_is_success(reply)); + break; + case 4: case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_rcpt_to_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) + i_debug("RCPT TO REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); + + pctx->rcpt_to_callback = TRUE; + + switch (pctx->index) { + case 0: case 1: case 2: + test_assert(smtp_reply_is_success(reply)); + break; + case 3: case 4: case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_rcpt_data_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) + i_debug("RCPT DATA REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); + + pctx->rcpt_data_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(smtp_reply_is_success(reply)); + break; + case 1: case 2: + test_assert(reply->status == 421); + break; + case 3: case 4: case 5: + i_unreached(); + } +} + +static void +test_client_unexpected_reply_data_cb(const struct smtp_reply *reply, + struct _unexpected_reply_peer *pctx) +{ + if (debug) + i_debug("DATA REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); + + pctx->data_callback = TRUE; + + switch (pctx->index) { + case 0: + test_assert(smtp_reply_is_success(reply)); + break; + case 1: case 2: case 3: case 4: case 5: + test_assert(reply->status == 421); + break; + } +} + +static void +test_client_unexpected_reply_finished(struct _unexpected_reply_peer *pctx) +{ + struct _unexpected_reply *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + + switch (pctx->index) { + case 0: case 1: case 2: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + case 3: case 4: case 5: + test_assert(pctx->mail_from_callback); + test_assert(pctx->rcpt_to_callback); + test_assert(!pctx->rcpt_data_callback); + test_assert(pctx->data_callback); + break; + } + + pctx->trans = NULL; + timeout_remove(&pctx->to); + i_free(pctx); +} + +static void +test_client_unexpected_reply_submit2(struct _unexpected_reply_peer *pctx) +{ + struct smtp_client_transaction *strans = pctx->trans; + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct istream *input; + + timeout_remove(&pctx->to); + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + smtp_client_transaction_send + (strans, input, test_client_unexpected_reply_data_cb, pctx); + i_stream_unref(&input); +} + +static void +test_client_unexpected_reply_submit1(struct _unexpected_reply_peer *pctx) +{ + timeout_remove(&pctx->to); + + smtp_client_transaction_add_rcpt(pctx->trans, + SMTP_ADDRESS_LITERAL("rcpt", "example.com"), NULL, + test_client_unexpected_reply_rcpt_to_cb, + test_client_unexpected_reply_rcpt_data_cb, pctx); + + pctx->to = timeout_add_short(500, + test_client_unexpected_reply_submit2, pctx); +} + +static void +test_client_unexpected_reply_submit(struct _unexpected_reply *ctx, + unsigned int index) +{ + struct _unexpected_reply_peer *pctx; + + pctx = i_new(struct _unexpected_reply_peer, 1); + pctx->context = ctx; + pctx->index = index; + + pctx->conn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + pctx->trans = smtp_client_transaction_create(pctx->conn, + SMTP_ADDRESS_LITERAL("sender", "example.com"), NULL, + test_client_unexpected_reply_finished, pctx); + smtp_client_connection_connect(pctx->conn, + test_client_unexpected_reply_login_cb, (void *)pctx); + smtp_client_transaction_start(pctx->trans, + test_client_unexpected_reply_mail_from_cb, pctx); + smtp_client_connection_unref(&pctx->conn); + + pctx->to = timeout_add_short(500, + test_client_unexpected_reply_submit1, pctx); +} + +static bool +test_client_unexpected_reply( + const struct smtp_client_settings *client_set) +{ + struct _unexpected_reply *ctx; + unsigned int i; + + ctx = i_new(struct _unexpected_reply, 1); + ctx->count = 6; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_unexpected_reply_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_unexpected_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("unexpected reply"); + test_run_client_server(&smtp_client_set, + test_client_unexpected_reply, + test_server_unexpected_reply, 6, NULL); + test_end(); +} + +/* + * Partial reply + */ + +/* server */ + +static int +test_partial_reply_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + o_stream_send_str(conn->conn.output, + "500 Command not"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_partial_reply(unsigned int index) +{ + test_server_input_line = test_partial_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _partial_reply { + unsigned int count; +}; + +static void +test_client_partial_reply_reply(const struct smtp_reply *reply, + struct _partial_reply *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_CONNECTION_LOST); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_partial_reply( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _partial_reply *ctx; + + ctx = i_new(struct _partial_reply, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_partial_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_partial_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_partial_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("partial reply"); + test_run_client_server(&smtp_client_set, + test_client_partial_reply, + test_server_partial_reply, 1, NULL); + test_end(); +} + +/* + * Bad reply + */ + +/* server */ + +static int +test_bad_reply_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + o_stream_send_str(conn->conn.output, + "666 Really bad reply\r\n"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_bad_reply(unsigned int index) +{ + test_server_input_line = test_bad_reply_input_line; + test_server_run(index); +} + +/* client */ + +struct _bad_reply { + unsigned int count; +}; + +static void +test_client_bad_reply_reply(const struct smtp_reply *reply, + struct _bad_reply *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_bad_reply( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _bad_reply *ctx; + + ctx = i_new(struct _bad_reply, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_bad_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_bad_reply_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_bad_reply(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("bad reply"); + test_run_client_server(&smtp_client_set, + test_client_bad_reply, + test_server_bad_reply, 1, NULL); + test_end(); +} + +/* + * Bad greeting + */ + +/* server */ + +static int +test_bad_greeting_init(struct server_connection *conn) +{ + switch (server_index) { + case 0: + o_stream_send_str(conn->conn.output, + "666 Mouhahahaha!!\r\n"); + break; + case 1: + o_stream_send_str(conn->conn.output, + "446 Not right now, sorry.\r\n"); + break; + case 2: + o_stream_send_str(conn->conn.output, + "233 Gimme all your mail, NOW!!\r\n"); + break; + } + server_connection_deinit(&conn); + return -1; +} + +static void test_server_bad_greeting(unsigned int index) +{ + test_server_init = test_bad_greeting_init; + test_server_run(index); +} + +/* client */ + +struct _bad_greeting { + unsigned int count; +}; + +struct _bad_greeting_peer { + struct _bad_greeting *context; + unsigned int index; +}; + +static void +test_client_bad_greeting_reply(const struct smtp_reply *reply, + struct _bad_greeting_peer *pctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + case 1: + test_assert(reply->status == 446); + break; + case 2: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); + break; + } + + if (--pctx->context->count == 0) { + i_free(pctx->context); + io_loop_stop(ioloop); + } + i_free(pctx); +} + +static void +test_client_bad_greeting_submit(struct _bad_greeting *ctx, + unsigned int index) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _bad_greeting_peer *pctx; + + pctx = i_new(struct _bad_greeting_peer, 1); + pctx->context = ctx; + pctx->index = index; + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_bad_greeting_reply, pctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); +} + +static bool +test_client_bad_greeting( + const struct smtp_client_settings *client_set) +{ + struct _bad_greeting *ctx; + + ctx = i_new(struct _bad_greeting, 1); + ctx->count = 3; + + smtp_client = smtp_client_init(client_set); + + test_client_bad_greeting_submit(ctx, 0); + test_client_bad_greeting_submit(ctx, 1); + test_client_bad_greeting_submit(ctx, 2); + return TRUE; +} + +/* test */ + +static void test_bad_greeting(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("bad greeting"); + test_run_client_server(&smtp_client_set, + test_client_bad_greeting, + test_server_bad_greeting, 3, NULL); + test_end(); +} + +/* + * Command timeout + */ + +/* server */ + +static int +test_command_timed_out_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + sleep(10); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_command_timed_out(unsigned int index) +{ + test_server_input_line = test_command_timed_out_input_line; + test_server_run(index); +} + +/* client */ + +struct _command_timed_out { + unsigned int count; +}; + +static void +test_client_command_timed_out_reply(const struct smtp_reply *reply, + struct _command_timed_out *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_TIMED_OUT); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_command_timed_out( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _command_timed_out *ctx; + + test_expect_errors(1); + + ctx = i_new(struct _command_timed_out, 1); + ctx->count = 1; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_command_timed_out_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_command_timed_out(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.command_timeout_msecs = 1000; + + test_begin("command timed out"); + test_run_client_server(&smtp_client_set, + test_client_command_timed_out, + test_server_command_timed_out, 1, NULL); + test_end(); +} + +/* + * Command aborted early + */ + +/* server */ + +static int +test_command_aborted_early_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + + sleep(1); + o_stream_nsend_str(conn->conn.output, "200 OK\r\n"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_command_aborted_early(unsigned int index) +{ + test_server_input_line = test_command_aborted_early_input_line; + test_server_run(index); +} + +/* client */ + +struct _command_aborted_early { + struct smtp_client_command *cmd; + struct timeout *to; +}; + +static void +test_client_command_aborted_early_reply(const struct smtp_reply *reply, + struct _command_aborted_early *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + /* abort does not trigger callback */ + test_assert(FALSE); +} + +static void +test_client_command_aborted_early_timeout( + struct _command_aborted_early *ctx) +{ + timeout_remove(&ctx->to); + + if (ctx->cmd != NULL) { + if (debug) + i_debug("ABORT"); + + /* abort early */ + smtp_client_command_abort(&ctx->cmd); + + /* wait a little for server to actually respond to an + already aborted request */ + ctx->to = timeout_add_short(1000, + test_client_command_aborted_early_timeout, ctx); + } else { + if (debug) + i_debug("FINISHED"); + + /* all done */ + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_command_aborted_early( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct _command_aborted_early *ctx; + + ctx = i_new(struct _command_aborted_early, 1); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + ctx->cmd = smtp_client_command_new(sconn, 0, + test_client_command_aborted_early_reply, ctx); + smtp_client_command_write(ctx->cmd, "FROP"); + smtp_client_command_submit(ctx->cmd); + + ctx->to = timeout_add_short(500, + test_client_command_aborted_early_timeout, ctx); + + return TRUE; +} + +/* test */ + +static void test_command_aborted_early(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("command aborted early"); + test_run_client_server(&smtp_client_set, + test_client_command_aborted_early, + test_server_command_aborted_early, 1, NULL); + test_end(); +} + +/* + * Client deinit early + */ + +/* server */ + +static int +test_client_deinit_early_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + if (conn->state == SERVER_CONNECTION_STATE_EHLO) + return 0; + + sleep(1); + o_stream_nsend_str(conn->conn.output, "200 OK\r\n"); + server_connection_deinit(&conn); + return -1; +} + +static void test_server_client_deinit_early(unsigned int index) +{ + test_server_input_line = test_client_deinit_early_input_line; + test_server_run(index); +} + +/* client */ + +struct _client_deinit_early { + struct smtp_client_command *cmd; + struct timeout *to; +}; + +static void +test_client_client_deinit_early_reply(const struct smtp_reply *reply, + struct _client_deinit_early *ctx ATTR_UNUSED) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + /* abort does not trigger callback */ + test_assert(FALSE); +} + +static void +test_client_client_deinit_early_timeout( + struct _client_deinit_early *ctx) +{ + timeout_remove(&ctx->to); + + /* deinit early */ + smtp_client_deinit(&smtp_client); + + /* all done */ + i_free(ctx); + io_loop_stop(ioloop); +} + +static bool +test_client_client_deinit_early( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct _client_deinit_early *ctx; + + ctx = i_new(struct _client_deinit_early, 1); + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[0], + SMTP_CLIENT_SSL_MODE_NONE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + ctx->cmd = smtp_client_command_new(sconn, 0, + test_client_client_deinit_early_reply, ctx); + smtp_client_command_write(ctx->cmd, "FROP"); + smtp_client_command_submit(ctx->cmd); + + ctx->to = timeout_add_short(500, + test_client_client_deinit_early_timeout, ctx); + + return TRUE; +} + +/* test */ + +static void test_client_deinit_early(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + + test_begin("client deinit early"); + test_run_client_server(&smtp_client_set, + test_client_client_deinit_early, + test_server_client_deinit_early, 1, NULL); + test_end(); +} + +/* + * DNS service failure + */ + +/* client */ + +struct _dns_service_failure { + unsigned int count; +}; + +static void +test_client_dns_service_failure_reply(const struct smtp_reply *reply, + struct _dns_service_failure *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_service_failure( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _dns_service_failure *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _dns_service_failure, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_dns_service_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "host.in-addr.arpa", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_dns_service_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_dns_service_failure(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.dns_client_socket_path = "./frop"; + + test_begin("dns service failure"); + test_run_client_server(&smtp_client_set, + test_client_dns_service_failure, + NULL, 0, NULL); + test_end(); +} + +/* + * DNS timeout + */ + +/* dns */ + +static void +test_dns_timeout_input(struct server_connection *conn ATTR_UNUSED) +{ + /* hang */ + sleep(100); + server_connection_deinit(&conn); +} + +static void test_dns_dns_timeout(void) +{ + test_server_input = test_dns_timeout_input; + test_server_run(0); +} + +/* client */ + +struct _dns_timeout { + unsigned int count; +}; + +static void +test_client_dns_timeout_reply(const struct smtp_reply *reply, + struct _dns_timeout *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_timeout( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _dns_timeout *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _dns_timeout, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_dns_timeout_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_dns_timeout_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_dns_timeout(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.connect_timeout_msecs = 2000; + smtp_client_set.dns_client_socket_path = "./dns-test"; + + test_begin("dns timeout"); + test_run_client_server(&smtp_client_set, + test_client_dns_timeout, NULL, 0, + test_dns_dns_timeout); + test_end(); +} + +/* + * DNS lookup failure + */ + +/* dns */ + +static void +test_dns_lookup_failure_input(struct server_connection *conn) +{ + o_stream_nsend_str(conn->conn.output, + t_strdup_printf("%d\n", EAI_FAIL)); + server_connection_deinit(&conn); +} + +static void test_dns_dns_lookup_failure(void) +{ + test_server_input = test_dns_lookup_failure_input; + test_server_run(0); +} + +/* client */ + +struct _dns_lookup_failure { + unsigned int count; +}; + +static void +test_client_dns_lookup_failure_reply(const struct smtp_reply *reply, + struct _dns_lookup_failure *ctx) +{ + if (debug) + i_debug("REPLY: %s", smtp_reply_log(reply)); + + test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_HOST_LOOKUP_FAILED); + + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } +} + +static bool +test_client_dns_lookup_failure( + const struct smtp_client_settings *client_set) +{ + struct smtp_client_connection *sconn; + struct smtp_client_command *scmd; + struct _dns_lookup_failure *ctx; + + test_expect_errors(2); + + ctx = i_new(struct _dns_lookup_failure, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_dns_lookup_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + sconn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, "example.com", 465, + SMTP_CLIENT_SSL_MODE_IMMEDIATE, NULL); + smtp_client_connection_connect(sconn, NULL, NULL); + scmd = smtp_client_command_new(sconn, 0, + test_client_dns_lookup_failure_reply, ctx); + smtp_client_command_write(scmd, "FROP"); + smtp_client_command_submit(scmd); + + return TRUE; +} + +/* test */ + +static void test_dns_lookup_failure(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + smtp_client_set.dns_client_socket_path = "./dns-test"; + + test_begin("dns lookup failure"); + test_run_client_server(&smtp_client_set, + test_client_dns_lookup_failure, NULL, 0, + test_dns_dns_lookup_failure); + test_end(); +} + +/* + * Authentication failed + */ + +/* server */ + +static int +test_authentication_failed_input_line(struct server_connection *conn, + const char *line ATTR_UNUSED) +{ + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + if (server_index == 1) { + o_stream_send_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250-AUTH PLAIN\r\n" + "250 DSN\r\n"); + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + return 1; + } + break; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (server_index == 1) { + o_stream_send_str(conn->conn.output, + "535 5.7.8 " + "Authentication credentials invalid\r\n"); + sleep(10); + server_connection_deinit(&conn); + return -1; + } + break; + default: + break; + } + return 0; +} + +static void test_server_authentication_failed(unsigned int index) +{ + test_server_input_line = test_authentication_failed_input_line; + test_server_run(index); +} + +/* client */ + +struct _authentication_failed { + unsigned int count; +}; + +struct _authentication_failed_peer { + struct _authentication_failed *context; + unsigned int index; + + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; +}; + +static void +test_client_authentication_failed_login_cb(const struct smtp_reply *reply, + void *context) +{ + struct _authentication_failed_peer *pctx = + (struct _authentication_failed_peer *)context; + + if (debug) { + i_debug("LOGIN REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + } +} + +static void +test_client_authentication_failed_mail_from_cb(const struct smtp_reply *reply, + struct _authentication_failed_peer *pctx) +{ + if (debug) { + i_debug("MAIL FROM REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + } +} + +static void +test_client_authentication_failed_rcpt_to_cb(const struct smtp_reply *reply, + struct _authentication_failed_peer *pctx) +{ + if (debug) { + i_debug("RCPT TO REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + } +} + +static void +test_client_authentication_failed_rcpt_data_cb(const struct smtp_reply *reply, + struct _authentication_failed_peer *pctx) +{ + if (debug) { + i_debug("RCPT DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + test_assert(FALSE); +} + +static void +test_client_authentication_failed_data_cb(const struct smtp_reply *reply, + struct _authentication_failed_peer *pctx) +{ + if (debug) { + i_debug("DATA REPLY[%u]: %s", + pctx->index, smtp_reply_log(reply)); + } + + switch (pctx->index) { + case 0: + test_assert(reply->status == + SMTP_CLIENT_COMMAND_ERROR_AUTH_FAILED); + break; + case 1: + test_assert(reply->status == 535); + break; + } +} + +static void +test_client_authentication_failed_finished(struct _authentication_failed_peer *pctx) +{ + struct _authentication_failed *ctx = pctx->context; + + if (debug) + i_debug("FINISHED[%u]", pctx->index); + if (--ctx->count == 0) { + i_free(ctx); + io_loop_stop(ioloop); + } + + pctx->trans = NULL; + i_free(pctx); +} + +static void +test_client_authentication_failed_submit(struct _authentication_failed *ctx, + unsigned int index) +{ + struct _authentication_failed_peer *pctx; + struct smtp_client_settings smtp_set; + static const char *message = + "From: stephan@example.com\r\n" + "To: timo@example.com\r\n" + "Subject: Frop!\r\n" + "\r\n" + "Frop!\r\n"; + struct istream *input; + + pctx = i_new(struct _authentication_failed_peer, 1); + pctx->context = ctx; + pctx->index = index; + + i_zero(&smtp_set); + smtp_set.username = "peter.wolfsen"; + smtp_set.password = "crybaby"; + + pctx->conn = smtp_client_connection_create(smtp_client, + SMTP_PROTOCOL_SMTP, net_ip2addr(&bind_ip), bind_ports[index], + SMTP_CLIENT_SSL_MODE_NONE, &smtp_set); + pctx->trans = smtp_client_transaction_create(pctx->conn, + SMTP_ADDRESS_LITERAL("sender", "example.com"), NULL, + test_client_authentication_failed_finished, pctx); + smtp_client_connection_connect(pctx->conn, + test_client_authentication_failed_login_cb, (void *)pctx); + smtp_client_transaction_start(pctx->trans, + test_client_authentication_failed_mail_from_cb, pctx); + smtp_client_connection_unref(&pctx->conn); + + smtp_client_transaction_add_rcpt(pctx->trans, + SMTP_ADDRESS_LITERAL("rcpt", "example.com"), NULL, + test_client_authentication_failed_rcpt_to_cb, + test_client_authentication_failed_rcpt_data_cb, pctx); + + input = i_stream_create_from_data(message, strlen(message)); + i_stream_set_name(input, "message"); + + smtp_client_transaction_send(pctx->trans, + input, test_client_authentication_failed_data_cb, pctx); + i_stream_unref(&input); +} + +static bool +test_client_authentication_failed( + const struct smtp_client_settings *client_set) +{ + struct _authentication_failed *ctx; + unsigned int i; + + ctx = i_new(struct _authentication_failed, 1); + ctx->count = 2; + + smtp_client = smtp_client_init(client_set); + + for (i = 0; i < ctx->count; i++) + test_client_authentication_failed_submit(ctx, i); + + return TRUE; +} + +/* test */ + +static void test_authentication_failed(void) +{ + struct smtp_client_settings smtp_client_set; + + test_client_defaults(&smtp_client_set); + + test_begin("authentication failed"); + test_expect_errors(1); + test_run_client_server(&smtp_client_set, + test_client_authentication_failed, + test_server_authentication_failed, 2, NULL); + test_end(); +} + +/* + * All tests + */ + +static void (*const test_functions[])(void) = { + test_unconfigured_ssl, + test_unconfigured_ssl_abort, + test_host_lookup_failed, + test_connection_refused, + test_connection_lost_prematurely, + test_connection_timed_out, + test_broken_payload, + test_connection_lost, + test_unexpected_reply, + test_partial_reply, + test_bad_reply, + test_bad_greeting, + test_command_timed_out, + test_command_aborted_early, + test_client_deinit_early, + test_dns_service_failure, + test_dns_timeout, + test_dns_lookup_failure, + test_authentication_failed, + NULL +}; + +/* + * Test client + */ + +static void +test_client_defaults(struct smtp_client_settings *smtp_set) +{ + /* client settings */ + i_zero(smtp_set); + smtp_set->my_hostname = "frop.example.com"; + smtp_set->debug = debug; +} + +static void test_client_deinit(void) +{ + if (smtp_client != NULL) + smtp_client_deinit(&smtp_client); + smtp_client = NULL; +} + +/* + * Test server + */ + +/* client connection */ + +static void +server_connection_input(struct connection *_conn) +{ + struct server_connection *conn = (struct server_connection *)_conn; + const char *line; + + if (test_server_input != NULL) { + test_server_input(conn); + return; + } + + for (;;) { + if (conn->state == SERVER_CONNECTION_STATE_FINISH) { + const unsigned char *data; + size_t size; + int ret; + + if (conn->dot_input == NULL) + conn->dot_input = i_stream_create_dot(conn->conn.input, TRUE); + while ((ret=i_stream_read_more(conn->dot_input, + &data, &size)) > 0) { + if (test_server_input_data != NULL) { + if (test_server_input_data(conn, data, size) < 0) + return; + } + i_stream_skip(conn->dot_input, size); + } + + if (ret == 0) + return; + if (conn->dot_input->stream_errno != 0) { + i_error("Failed to read message payload: %s", + i_stream_get_error(conn->dot_input)); + server_connection_deinit(&conn); + return; + } + + o_stream_send_str(conn->conn.output, + "250 2.0.0 Ok: queued as 73BDE342129\r\n"); + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + continue; + } + + line = i_stream_read_next_line(conn->conn.input); + if (line == NULL) { + if (conn->conn.input->eof || + conn->conn.input->stream_errno != 0) + server_connection_deinit(&conn); + return; + } + + if (test_server_input_line != NULL) { + if (test_server_input_line(conn, line) != 0) + return; + } + + switch (conn->state) { + case SERVER_CONNECTION_STATE_EHLO: + o_stream_send_str(conn->conn.output, + "250-testserver\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + "250 DSN\r\n"); + conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; + return; + case SERVER_CONNECTION_STATE_MAIL_FROM: + if (strncmp(line, "AUTH ", 5) == 0) { + o_stream_send_str(conn->conn.output, + "235 2.7.0 " + "Authentication successful\r\n"); + continue; + } + o_stream_send_str(conn->conn.output, + "250 2.1.0 Ok\r\n"); + conn->state = SERVER_CONNECTION_STATE_RCPT_TO; + continue; + case SERVER_CONNECTION_STATE_RCPT_TO: + o_stream_send_str(conn->conn.output, + "250 2.1.5 Ok\r\n"); + conn->state = SERVER_CONNECTION_STATE_DATA; + continue; + case SERVER_CONNECTION_STATE_DATA: + o_stream_send_str(conn->conn.output, + "354 End data with .\r\n"); + conn->state = SERVER_CONNECTION_STATE_FINISH; + continue; + case SERVER_CONNECTION_STATE_FINISH: + break; + } + i_unreached(); + } +} + +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) { + if (test_server_init(conn) != 0) + return; + } + + if (test_server_input == NULL) { + o_stream_send_str(conn->conn.output, + "220 testserver ESMTP Testfix (Debian/GNU)\r\n"); + } +} + +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); + + i_stream_unref(&conn->dot_input); + + 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; + + if (dns_pid != (pid_t)-1) { + (void)kill(dns_pid, SIGKILL); + (void)waitpid(dns_pid, NULL, 0); + dns_pid = (pid_t)-1; + } +} + +static void test_run_client_server( + const struct smtp_client_settings *client_set, + test_client_init_t client_test, + test_server_init_t server_test, + unsigned int server_tests_count, + test_dns_init_t dns_test) +{ + unsigned int i; + + server_pids = NULL; + server_pids_count = 0; + + if (server_tests_count > 0) { + int fds[server_tests_count]; + + bind_ports = i_new(in_port_t, server_tests_count); + + 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]; + if ((server_pids[i] = fork()) == (pid_t)-1) + i_fatal("fork() failed: %m"); + if (server_pids[i] == 0) { + lib_signals_ignore(SIGPIPE, TRUE); + server_pids[i] = (pid_t)-1; + server_pids_count = 0; + hostpid_init(); + 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); + i_close_fd(&fd_listen); + i_free(bind_ports); + i_free(server_pids); + /* wait for it to be killed; this way, valgrind will not + object to this process going away inelegantly. */ + sleep(60); + exit(1); + } + i_close_fd(&fd_listen); + } + if (debug) + i_debug("client: PID=%s", my_pid); + } + + if (dns_test != NULL) { + int fd; + + i_unlink_if_exists("./dns-test"); + fd = net_listen_unix("./dns-test", 128); + if (fd == -1) { + i_fatal("listen(./dns-test) failed: %m"); + } + + fd_listen = fd; + if ((dns_pid = fork()) == (pid_t)-1) + i_fatal("fork() failed: %m"); + if (dns_pid == 0) { + lib_signals_ignore(SIGPIPE, TRUE); + dns_pid = (pid_t)-1; + hostpid_init(); + if (debug) + i_debug("dns server: PID=%s", my_pid); + /* child: server */ + ioloop = io_loop_create(); + dns_test(); + io_loop_destroy(&ioloop); + i_close_fd(&fd_listen); + /* wait for it to be killed; this way, valgrind will not + object to this process going away inelegantly. */ + sleep(60); + exit(1); + } + i_close_fd(&fd_listen); + } + + /* parent: client */ + + usleep(100000); /* wait a little for server setup */ + + lib_signals_ignore(SIGPIPE, TRUE); + 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); + + i_unlink_if_exists("./dns-test"); +} + +/* + * 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); + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + /* 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); +}