/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "hostpid.h" #include "ioloop.h" #include "istream.h" #include "istream-dot.h" #include "istream-chain.h" #include "istream-failure-at.h" #include "ostream.h" #include "iostream-ssl.h" #include "iostream-ssl-test.h" #ifdef HAVE_OPENSSL # include "iostream-openssl.h" #endif #include "time-util.h" #include "sleep.h" #include "connection.h" #include "test-common.h" #include "test-subprocess.h" #include "smtp-client.h" #include "smtp-client-connection.h" #include "smtp-client-transaction.h" #include #define CLIENT_PROGRESS_TIMEOUT 10 #define SERVER_KILL_TIMEOUT_SECS 20 static void main_deinit(void); /* * 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; struct ssl_iostream *ssl_iostream; enum server_connection_state state; char *file_path; struct istream *dot_input; pool_t pool; bool version_sent:1; }; 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; /* server */ static struct io *io_listen; static int fd_listen = -1; static struct connection_list *server_conn_list; static unsigned int server_index; struct ssl_iostream_context *server_ssl_ctx = NULL; bool test_server_ssl = FALSE; 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 timeout *to_client_progress = NULL; 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) { i_sleep_intr_secs(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) { i_sleep_intr_secs(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_nsend_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) { i_sleep_intr_secs(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_nsend_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, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, test_client_broken_payload_finished, NULL); smtp_client_connection_unref(&sconn); smtp_client_transaction_add_rcpt( strans, &((struct smtp_address){ .localpart = "rcpt", .domain = "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, EIO, "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, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, test_client_broken_payload_finished, NULL); smtp_client_connection_unref(&sconn); smtp_client_transaction_add_rcpt( strans, &((struct smtp_address){ .localpart = "rcpt", .domain = "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; i_sleep_intr_secs(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; i_sleep_intr_secs(1); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_RCPT_TO: if (server_index == 2) { conn->state = SERVER_CONNECTION_STATE_DATA; i_sleep_intr_secs(1); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_DATA: if (server_index == 3) { conn->state = SERVER_CONNECTION_STATE_FINISH; i_sleep_intr_secs(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) { i_sleep_intr_secs(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, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, test_client_connection_lost_finished, pctx); smtp_client_connection_unref(&sconn); smtp_client_transaction_add_rcpt( strans, &((struct smtp_address){ .localpart = "rcpt", .domain = "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_nsend_str(conn->conn.output, "220 testserver " "ESMTP Testfix (Debian/GNU)\r\n"); o_stream_nsend_str(conn->conn.output, "421 testserver " "Server shutting down for maintenance\r\n"); i_sleep_intr_secs(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_nsend_str(conn->conn.output, "250-testserver\r\n" "250-PIPELINING\r\n" "250-ENHANCEDSTATUSCODES\r\n" "250 DSN\r\n"); o_stream_nsend_str( conn->conn.output, "421 testserver " "Server shutting down for maintenance\r\n"); i_sleep_intr_secs(4); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_MAIL_FROM: if (server_index == 3) { o_stream_nsend_str(conn->conn.output, "250 2.1.0 Ok\r\n"); o_stream_nsend_str( conn->conn.output, "421 testserver " "Server shutting down for maintenance\r\n"); i_sleep_intr_secs(4); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_RCPT_TO: if (server_index == 2) { o_stream_nsend_str(conn->conn.output, "250 2.1.5 Ok\r\n"); o_stream_nsend_str( conn->conn.output, "421 testserver " "Server shutting down for maintenance\r\n"); i_sleep_intr_secs(4); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_DATA: if (server_index == 1) { o_stream_nsend_str( conn->conn.output, "354 End data with .\r\n"); o_stream_nsend_str( conn->conn.output, "421 testserver " "Server shutting down for maintenance\r\n"); i_sleep_intr_secs(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, &((struct smtp_address){ .localpart = "rcpt", .domain = "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, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, 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_nsend_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(); } /* * Premature reply */ /* server */ static int test_premature_reply_init(struct server_connection *conn) { if (server_index == 5) { o_stream_nsend_str( conn->conn.output, "220 testserver ESMTP Testfix (Debian/GNU)\r\n" "250-testserver\r\n" "250-PIPELINING\r\n" "250-ENHANCEDSTATUSCODES\r\n" "250 DSN\r\n"); i_sleep_intr_secs(4); server_connection_deinit(&conn); return 1; } return 0; } static int test_premature_reply_input_line(struct server_connection *conn, const char *line) { if (debug) i_debug("[%u] GOT LINE: %s", server_index, line); switch (conn->state) { case SERVER_CONNECTION_STATE_EHLO: if (debug) i_debug("[%u] EHLO", server_index); if (server_index == 4) { o_stream_nsend_str(conn->conn.output, "250-testserver\r\n" "250-PIPELINING\r\n" "250-ENHANCEDSTATUSCODES\r\n" "250 DSN\r\n" "250 2.1.0 Ok\r\n"); conn->state = SERVER_CONNECTION_STATE_MAIL_FROM; return 1; } break; case SERVER_CONNECTION_STATE_MAIL_FROM: if (server_index == 4) { conn->state = SERVER_CONNECTION_STATE_RCPT_TO; return 1; } if (server_index == 3) { o_stream_nsend_str(conn->conn.output, "250 2.1.0 Ok\r\n" "250 2.1.5 Ok\r\n"); i_sleep_intr_secs(4); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_RCPT_TO: if (server_index == 2) { o_stream_nsend_str( conn->conn.output, "250 2.1.5 Ok\r\n" "354 End data with .\r\n"); i_sleep_intr_secs(4); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_DATA: if (server_index == 1) { o_stream_nsend_str( conn->conn.output, "354 End data with .\r\n" "250 2.0.0 Ok: queued as 35424ed4af24\r\n"); i_sleep_intr_secs(4); server_connection_deinit(&conn); return -1; } break; case SERVER_CONNECTION_STATE_FINISH: break; } return 0; } static void test_server_premature_reply(unsigned int index) { test_server_init = test_premature_reply_init; test_server_input_line = test_premature_reply_input_line; test_server_run(index); } /* client */ struct _premature_reply { unsigned int count; }; struct _premature_reply_peer { struct _premature_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_premature_reply_login_cb(const struct smtp_reply *reply, void *context) { struct _premature_reply_peer *pctx = (struct _premature_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 == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); /* Don't bother continueing with this test. Second try after smtp_client_transaction_start() will have the same result. */ smtp_client_transaction_abort(pctx->trans); break; } } static void test_client_premature_reply_mail_from_cb(const struct smtp_reply *reply, struct _premature_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 == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); break; } } static void test_client_premature_reply_rcpt_to_cb(const struct smtp_reply *reply, struct _premature_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 == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); break; } } static void test_client_premature_reply_rcpt_data_cb(const struct smtp_reply *reply, struct _premature_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 == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); break; case 3: case 4: case 5: i_unreached(); } } static void test_client_premature_reply_data_cb(const struct smtp_reply *reply, struct _premature_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 == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); break; } } static void test_client_premature_reply_finished(struct _premature_reply_peer *pctx) { struct _premature_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: 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 5: test_assert(!pctx->mail_from_callback); test_assert(!pctx->rcpt_to_callback); test_assert(!pctx->rcpt_data_callback); test_assert(!pctx->data_callback); } pctx->trans = NULL; timeout_remove(&pctx->to); i_free(pctx); } static void test_client_premature_reply_submit3(struct _premature_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); if (debug) i_debug("SUBMIT3[%u]", pctx->index); input = i_stream_create_from_data(message, strlen(message)); i_stream_set_name(input, "message"); smtp_client_transaction_send( strans, input, test_client_premature_reply_data_cb, pctx); i_stream_unref(&input); } static void test_client_premature_reply_submit2(struct _premature_reply_peer *pctx) { timeout_remove(&pctx->to); if (debug) i_debug("SUBMIT2[%u]", pctx->index); smtp_client_transaction_add_rcpt( pctx->trans, &((struct smtp_address){ .localpart = "rcpt", .domain = "example.com"}), NULL, test_client_premature_reply_rcpt_to_cb, test_client_premature_reply_rcpt_data_cb, pctx); pctx->to = timeout_add_short( 500, test_client_premature_reply_submit3, pctx); } static void test_client_premature_reply_submit1(struct _premature_reply_peer *pctx) { timeout_remove(&pctx->to); if (debug) i_debug("SUBMIT1[%u]", pctx->index); smtp_client_transaction_start( pctx->trans, test_client_premature_reply_mail_from_cb, pctx); pctx->to = timeout_add_short( 500, test_client_premature_reply_submit2, pctx); } static void test_client_premature_reply_submit(struct _premature_reply *ctx, unsigned int index) { struct _premature_reply_peer *pctx; struct smtp_client_connection *conn; pctx = i_new(struct _premature_reply_peer, 1); pctx->context = ctx; pctx->index = index; pctx->conn = 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( conn, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, test_client_premature_reply_finished, pctx); smtp_client_connection_connect( conn, test_client_premature_reply_login_cb, (void *)pctx); smtp_client_connection_unref(&conn); pctx->to = timeout_add_short( 500, test_client_premature_reply_submit1, pctx); } static bool test_client_premature_reply(const struct smtp_client_settings *client_set) { struct _premature_reply *ctx; unsigned int i; test_expect_errors(6); ctx = i_new(struct _premature_reply, 1); ctx->count = 6; smtp_client = smtp_client_init(client_set); for (i = 0; i < ctx->count; i++) test_client_premature_reply_submit(ctx, i); return TRUE; } /* test */ static void test_premature_reply(void) { struct smtp_client_settings smtp_client_set; test_client_defaults(&smtp_client_set); test_begin("premature reply"); test_run_client_server(&smtp_client_set, test_client_premature_reply, test_server_premature_reply, 6, NULL); test_end(); } /* * Early data reply */ /* server */ static int test_early_data_reply_input_line(struct server_connection *conn ATTR_UNUSED, const char *line) { if (debug) i_debug("[%u] GOT LINE: %s", server_index, line); switch (conn->state) { case SERVER_CONNECTION_STATE_DATA: break; default: return 0; } if ((uintptr_t)conn->context == 0) { if (debug) i_debug("[%u] REPLIED 354", server_index); o_stream_nsend_str(conn->conn.output, "354 End data with .\r\n"); conn->context = (void*)1; return 1; } if (server_index == 2 && strcmp(line, ".") == 0) { if (debug) i_debug("[%u] FINISHED TRANSACTION", server_index); o_stream_nsend_str(conn->conn.output, "250 2.0.0 Ok: queued as 73BDE342129\r\n"); return 1; } if ((uintptr_t)conn->context == 5 && server_index < 2) { if (debug) i_debug("[%u] FINISHED TRANSACTION EARLY", server_index); if (server_index == 0) { o_stream_nsend_str( conn->conn.output, "250 2.0.0 Ok: queued as 73BDE342129\r\n"); } else { o_stream_nsend_str( conn->conn.output, "452 4.3.1 Mail system full\r\n"); } } if ((uintptr_t)conn->context > 5) { o_stream_nsend_str(conn->conn.output, "250 2.0.0 OK\r\n"); return 1; } conn->context = (void*)(((uintptr_t)conn->context) + 1); return 1; } static void test_server_early_data_reply(unsigned int index) { test_server_input_line = test_early_data_reply_input_line; test_server_run(index); } /* client */ struct _early_data_reply { unsigned int count; }; struct _early_data_reply_peer { struct _early_data_reply *context; unsigned int index; struct ostream *output; struct smtp_client_connection *conn; struct smtp_client_transaction *trans; struct timeout *to; bool data_callback:1; }; static void test_client_early_data_reply_submit1(struct _early_data_reply_peer *pctx); static void test_client_early_data_reply_login_cb(const struct smtp_reply *reply, void *context) { struct _early_data_reply_peer *pctx = context; if (debug) { i_debug("LOGIN REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); } test_assert(smtp_reply_is_success(reply)); } static void test_client_early_data_reply_mail_from_cb(const struct smtp_reply *reply, struct _early_data_reply_peer *pctx) { if (debug) { i_debug("MAIL FROM REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); } test_assert(smtp_reply_is_success(reply)); } static void test_client_early_data_reply_rcpt_to_cb(const struct smtp_reply *reply, struct _early_data_reply_peer *pctx) { if (debug) { i_debug("RCPT TO REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); } test_assert(smtp_reply_is_success(reply)); pctx->to = timeout_add_short( 1000, test_client_early_data_reply_submit1, pctx); } static void test_client_early_data_reply_rcpt_data_cb(const struct smtp_reply *reply, struct _early_data_reply_peer *pctx) { if (debug) { i_debug("RCPT DATA REPLY[%u]: %s", pctx->index, 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 == 452); break; case 2: test_assert(smtp_reply_is_success(reply)); break; } } static void test_client_early_data_reply_data_cb(const struct smtp_reply *reply, struct _early_data_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(reply->status == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); break; case 1: test_assert(reply->status == 452); break; case 2: test_assert(smtp_reply_is_success(reply)); break; } } static void test_client_early_data_reply_noop_cb(const struct smtp_reply *reply, struct _early_data_reply_peer *pctx) { struct _early_data_reply *ctx = pctx->context; if (debug) { i_debug("NOOP REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); } switch (pctx->index) { case 0: test_assert(reply->status == SMTP_CLIENT_COMMAND_ERROR_BAD_REPLY); break; case 1: case 2: test_assert(smtp_reply_is_success(reply)); break; } if (--ctx->count == 0) { i_free(ctx); io_loop_stop(ioloop); } test_assert(pctx->data_callback); pctx->trans = NULL; timeout_remove(&pctx->to); o_stream_destroy(&pctx->output); smtp_client_connection_unref(&pctx->conn); i_free(pctx); } static void test_client_early_data_reply_finished(struct _early_data_reply_peer *pctx) { if (debug) i_debug("FINISHED[%u]", pctx->index); /* Send NOOP command to check that connection is still viable. */ smtp_client_command_noop_submit( pctx->conn, 0, test_client_early_data_reply_noop_cb, pctx); } static void test_client_early_data_reply_submit1(struct _early_data_reply_peer *pctx) { if (debug) i_debug("FINISH DATA[%u]", pctx->index); timeout_remove(&pctx->to); if (o_stream_finish(pctx->output) < 0) { i_error("Failed to finish output: %s", o_stream_get_error(pctx->output)); } o_stream_destroy(&pctx->output); } static void test_client_early_data_reply_submit(struct _early_data_reply *ctx, unsigned int index) { struct _early_data_reply_peer *pctx; struct smtp_client_connection *conn; 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"; int pipefd[2]; struct istream *input; pctx = i_new(struct _early_data_reply_peer, 1); pctx->context = ctx; pctx->index = index; if (pipe(pipefd) < 0) i_fatal("Failed to create pipe: %m"); fd_set_nonblock(pipefd[0], TRUE); fd_set_nonblock(pipefd[1], TRUE); input = i_stream_create_fd_autoclose(&pipefd[0], 1024); pctx->output = o_stream_create_fd_autoclose(&pipefd[1], 1024); pctx->conn = conn = 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(conn, test_client_early_data_reply_login_cb, (void *)pctx); pctx->trans = smtp_client_transaction_create( conn, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, test_client_early_data_reply_finished, pctx); smtp_client_transaction_add_rcpt( pctx->trans, &((struct smtp_address){ .localpart = "rcpt", .domain = "example.com"}), NULL, test_client_early_data_reply_rcpt_to_cb, test_client_early_data_reply_rcpt_data_cb, pctx); smtp_client_transaction_start(pctx->trans, test_client_early_data_reply_mail_from_cb, pctx); smtp_client_transaction_send( pctx->trans, input, test_client_early_data_reply_data_cb, pctx); i_stream_unref(&input); o_stream_nsend(pctx->output, message, strlen(message)); } static bool test_client_early_data_reply(const struct smtp_client_settings *client_set) { struct _early_data_reply *ctx; unsigned int i; test_expect_errors(2); ctx = i_new(struct _early_data_reply, 1); ctx->count = 3; smtp_client = smtp_client_init(client_set); for (i = 0; i < ctx->count; i++) test_client_early_data_reply_submit(ctx, i); return TRUE; } /* test */ static void test_early_data_reply(void) { struct smtp_client_settings smtp_client_set; test_client_defaults(&smtp_client_set); test_begin("early data reply"); test_run_client_server(&smtp_client_set, test_client_early_data_reply, test_server_early_data_reply, 3, 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_nsend_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; test_expect_errors(2); 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_nsend_str(conn->conn.output, "666 Mouhahahaha!!\r\n"); break; case 1: o_stream_nsend_str(conn->conn.output, "446 Not right now, sorry.\r\n"); break; case 2: o_stream_nsend_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; test_expect_errors(2); 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; i_sleep_intr_secs(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; i_sleep_intr_secs(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; i_sleep_intr_secs(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 */ i_sleep_intr_secs(100); io_loop_stop(current_ioloop); io_remove(&io_listen); i_close_fd(&fd_listen); 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("VERSION\tdns\t1\t0\n%d\tFAIL\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_input_line(struct server_connection *conn, const char *line ATTR_UNUSED) { switch (conn->state) { case SERVER_CONNECTION_STATE_EHLO: if (server_index > 0) { o_stream_nsend_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: switch (server_index ) { case 1: o_stream_nsend_str( conn->conn.output, "535 5.7.8 " "Authentication credentials invalid\r\n"); i_sleep_intr_secs(10); server_connection_deinit(&conn); return -1; case 3: case 5: if (str_begins(line, "AUTH ")) { o_stream_nsend_str(conn->conn.output, "334 \r\n"); return 1; } if (str_begins(line, "EHLO ")) { o_stream_nsend_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"); return 1; } if (!str_begins(line, "MAIL ")) { o_stream_nsend_str( conn->conn.output, "235 2.7.0 " "Authentication successful\r\n"); return 1; } } break; default: break; } return 0; } static void test_server_authentication(unsigned int index) { test_server_input_line = test_authentication_input_line; test_server_run(index); } /* client */ struct _authentication { unsigned int count; }; struct _authentication_peer { struct _authentication *context; unsigned int index; struct smtp_client_connection *conn; struct smtp_client_transaction *trans; }; static void test_client_authentication_login_cb(const struct smtp_reply *reply, void *context) { struct _authentication_peer *pctx = (struct _authentication_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; case 2: case 3: case 4: case 5: test_assert(reply->status == 250); break; } } static void test_client_authentication_mail_from_cb( const struct smtp_reply *reply, struct _authentication_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; case 2: case 3: case 4: case 5: test_assert(reply->status == 250); break; } } static void test_client_authentication_rcpt_to_cb( const struct smtp_reply *reply, struct _authentication_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; case 2: case 3: case 4: case 5: test_assert(reply->status == 250); break; } } static void test_client_authentication_rcpt_data_cb( const struct smtp_reply *reply, struct _authentication_peer *pctx) { if (debug) { i_debug("RCPT DATA REPLY[%u]: %s", pctx->index, smtp_reply_log(reply)); } switch (pctx->index) { case 0: case 1: test_assert(FALSE); break; case 2: case 3: case 4: case 5: test_assert(TRUE); break; } } static void test_client_authentication_data_cb( const struct smtp_reply *reply, struct _authentication_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; case 2: case 3: case 4: case 5: test_assert(reply->status == 250); break; } } static void test_client_authentication_finished( struct _authentication_peer *pctx) { struct _authentication *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_submit(struct _authentication *ctx, unsigned int index) { struct _authentication_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_peer, 1); pctx->context = ctx; pctx->index = index; i_zero(&smtp_set); smtp_set.username = "peter.wolfsen"; switch (index) { case 3: /* Much too large for initial response */ smtp_set.password = "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef"; break; case 4: /* Just small enough for initial response */ smtp_set.password = "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "01234"; break; case 5: /* Just too large for initial response */ smtp_set.password = "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "0123456789abcdef0123456789abcdef" "012345"; break; default: smtp_set.password = "crybaby"; break; } 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, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, test_client_authentication_finished, pctx); smtp_client_connection_connect( pctx->conn, test_client_authentication_login_cb, (void *)pctx); smtp_client_transaction_start( pctx->trans, test_client_authentication_mail_from_cb, pctx); smtp_client_connection_unref(&pctx->conn); smtp_client_transaction_add_rcpt( pctx->trans, &((struct smtp_address){ .localpart = "rcpt", .domain = "example.com"}), NULL, test_client_authentication_rcpt_to_cb, test_client_authentication_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_data_cb, pctx); i_stream_unref(&input); } static bool test_client_authentication(const struct smtp_client_settings *client_set) { struct _authentication *ctx; unsigned int i; test_expect_errors(2); ctx = i_new(struct _authentication, 1); ctx->count = 6; smtp_client = smtp_client_init(client_set); for (i = 0; i < ctx->count; i++) test_client_authentication_submit(ctx, i); return TRUE; } /* test */ static void test_authentication(void) { struct smtp_client_settings smtp_client_set; test_client_defaults(&smtp_client_set); test_begin("authentication"); test_run_client_server(&smtp_client_set, test_client_authentication, test_server_authentication, 6, NULL); test_end(); } /* * Transaction timeout */ /* server */ static int test_transaction_timeout_input_line(struct server_connection *conn, const char *line ATTR_UNUSED) { switch (conn->state) { case SERVER_CONNECTION_STATE_EHLO: break; case SERVER_CONNECTION_STATE_MAIL_FROM: if (server_index == 0) i_sleep_intr_secs(20); break; case SERVER_CONNECTION_STATE_RCPT_TO: if (server_index == 1) i_sleep_intr_secs(20); break; case SERVER_CONNECTION_STATE_DATA: if (server_index == 2) i_sleep_intr_secs(20); break; case SERVER_CONNECTION_STATE_FINISH: break; } return 0; } static void test_server_transaction_timeout(unsigned int index) { test_expect_errors(1); test_server_input_line = test_transaction_timeout_input_line; test_server_run(index); } /* client */ struct _transaction_timeout { unsigned int count; }; struct _transaction_timeout_peer { struct _transaction_timeout *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_transaction_timeout_mail_from_cb( const struct smtp_reply *reply, struct _transaction_timeout_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: test_assert(reply->status == 451); break; case 1: case 2: case 3: test_assert(smtp_reply_is_success(reply)); break; } } static void test_client_transaction_timeout_rcpt_to_cb( const struct smtp_reply *reply, struct _transaction_timeout_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: test_assert(reply->status == 451); break; case 2: case 3: test_assert(smtp_reply_is_success(reply)); break; } } static void test_client_transaction_timeout_rcpt_data_cb( const struct smtp_reply *reply, struct _transaction_timeout_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: case 1: i_unreached(); case 2: test_assert(reply->status == 451); break; case 3: test_assert(smtp_reply_is_success(reply)); break; } } static void test_client_transaction_timeout_data_cb(const struct smtp_reply *reply, struct _transaction_timeout_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: case 1: case 2: test_assert(reply->status == 451); break; case 3: test_assert(smtp_reply_is_success(reply)); break; } } static void test_client_transaction_timeout_finished(struct _transaction_timeout_peer *pctx) { struct _transaction_timeout *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: 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 2: case 3: 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_transaction_timeout_submit2(struct _transaction_timeout_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_transaction_timeout_data_cb, pctx); i_stream_unref(&input); } static void test_client_transaction_timeout_submit1(struct _transaction_timeout_peer *pctx) { timeout_remove(&pctx->to); smtp_client_transaction_add_rcpt( pctx->trans, &((struct smtp_address){ .localpart = "rcpt", .domain = "example.com"}), NULL, test_client_transaction_timeout_rcpt_to_cb, test_client_transaction_timeout_rcpt_data_cb, pctx); pctx->to = timeout_add_short( 500, test_client_transaction_timeout_submit2, pctx); } static void test_client_transaction_timeout_submit(struct _transaction_timeout *ctx, unsigned int index) { struct _transaction_timeout_peer *pctx; pctx = i_new(struct _transaction_timeout_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, &((struct smtp_address){ .localpart = "sender", .domain = "example.com"}), NULL, 0, test_client_transaction_timeout_finished, pctx); smtp_client_transaction_set_timeout(pctx->trans, 1000); smtp_client_transaction_start( pctx->trans, test_client_transaction_timeout_mail_from_cb, pctx); smtp_client_connection_unref(&pctx->conn); pctx->to = timeout_add_short( 500, test_client_transaction_timeout_submit1, pctx); } static bool test_client_transaction_timeout(const struct smtp_client_settings *client_set) { struct _transaction_timeout *ctx; unsigned int i; ctx = i_new(struct _transaction_timeout, 1); ctx->count = 4; smtp_client = smtp_client_init(client_set); for (i = 0; i < ctx->count; i++) test_client_transaction_timeout_submit(ctx, i); return TRUE; } /* test */ static void test_transaction_timeout(void) { struct smtp_client_settings smtp_client_set; test_client_defaults(&smtp_client_set); test_begin("transaction timeout"); test_run_client_server(&smtp_client_set, test_client_transaction_timeout, test_server_transaction_timeout, 6, NULL); test_end(); } /* * Invalid SSL certificate */ #ifdef HAVE_OPENSSL /* dns */ static void test_dns_invalid_ssl_certificate_input(struct server_connection *conn) { const char *line; if (!conn->version_sent) { conn->version_sent = TRUE; o_stream_nsend_str(conn->conn.output, "VERSION\tdns\t1\t0\n"); } while ((line = i_stream_read_next_line(conn->conn.input)) != NULL) { if (debug) i_debug("DNS REQUEST: %s", line); o_stream_nsend_str(conn->conn.output, t_strdup_printf("0\t%s\n", net_ip2addr(&bind_ip))); } } static void test_dns_invalid_ssl_certificate(void) { test_server_input = test_dns_invalid_ssl_certificate_input; test_server_run(0); } /* server */ static void test_invalid_ssl_certificate_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_invalid_ssl_certificate_init(struct server_connection *conn) { sleep(1); o_stream_nsend_str(conn->conn.output, "220 testserver ESMTP Testfix (Frop/GNU)\r\n"); return 1; } static void test_server_invalid_ssl_certificate(unsigned int index) { test_server_ssl = TRUE; test_server_init = test_invalid_ssl_certificate_init; test_server_input = test_invalid_ssl_certificate_input; test_server_run(index); } /* client */ struct _invalid_ssl_certificate { unsigned int count; }; static void test_client_invalid_ssl_certificate_reply(const struct smtp_reply *reply, struct _invalid_ssl_certificate *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_invalid_ssl_certificate( const struct smtp_client_settings *client_set) { struct smtp_client_connection *sconn; struct smtp_client_command *scmd; struct _invalid_ssl_certificate *ctx; test_expect_errors(2); ctx = i_new(struct _invalid_ssl_certificate, 1); ctx->count = 2; smtp_client = smtp_client_init(client_set); sconn = smtp_client_connection_create( smtp_client, SMTP_PROTOCOL_SMTP, "example.com", 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_invalid_ssl_certificate_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", 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_invalid_ssl_certificate_reply, ctx); smtp_client_command_write(scmd, "FROP"); smtp_client_command_submit(scmd); return TRUE; } /* test */ static void test_invalid_ssl_certificate(void) { struct smtp_client_settings smtp_client_set; struct ssl_iostream_settings ssl_set; /* ssl settings */ ssl_iostream_test_settings_client(&ssl_set); ssl_set.verbose = debug; test_client_defaults(&smtp_client_set); smtp_client_set.dns_client_socket_path = "./dns-test"; smtp_client_set.ssl = &ssl_set; test_begin("invalid ssl certificate"); test_run_client_server(&smtp_client_set, test_client_invalid_ssl_certificate, test_server_invalid_ssl_certificate, 1, test_dns_invalid_ssl_certificate); test_end(); } #endif /* * 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_premature_reply, test_early_data_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, test_transaction_timeout, #ifdef HAVE_OPENSSL test_invalid_ssl_certificate, #endif 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_progress_timeout(void *context ATTR_UNUSED) { /* Terminate test due to lack of progress */ test_assert(FALSE); timeout_remove(&to_client_progress); io_loop_stop(current_ioloop); } static bool test_client_init(test_client_init_t client_test, const struct smtp_client_settings *client_set) { i_assert(client_test != NULL); if (!client_test(client_set)) return FALSE; to_client_progress = timeout_add(CLIENT_PROGRESS_TIMEOUT*1000, test_client_progress_timeout, NULL); return TRUE; } static void test_client_deinit(void) { timeout_remove(&to_client_progress); if (smtp_client != NULL) smtp_client_deinit(&smtp_client); } static void test_client_run(test_client_init_t client_test, const struct smtp_client_settings *client_set) { if (test_client_init(client_test, client_set)) io_loop_run(ioloop); test_client_deinit(); } /* * Test server */ /* client connection */ static int server_connection_init_ssl(struct server_connection *conn) { struct ssl_iostream_settings ssl_set; const char *error; if (!test_server_ssl) return 0; connection_input_halt(&conn->conn); ssl_iostream_test_settings_server(&ssl_set); ssl_set.verbose = debug; if (server_ssl_ctx == NULL && ssl_iostream_context_init_server(&ssl_set, &server_ssl_ctx, &error) < 0) { i_error("SSL context initialization failed: %s", error); return -1; } if (io_stream_create_ssl_server(server_ssl_ctx, &ssl_set, &conn->conn.input, &conn->conn.output, &conn->ssl_iostream, &error) < 0) { i_error("SSL init failed: %s", error); return -1; } if (ssl_iostream_handshake(conn->ssl_iostream) < 0) { i_error("SSL handshake failed: %s", ssl_iostream_get_last_error(conn->ssl_iostream)); return -1; } connection_input_resume(&conn->conn); return 0; } static void server_connection_input(struct connection *_conn) { struct server_connection *conn = (struct server_connection *)_conn; const char *line; int ret; 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) { if (debug) { i_debug("Failed to read message payload: %s", i_stream_get_error(conn->dot_input)); } server_connection_deinit(&conn); return; } o_stream_nsend_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 ((ret = test_server_input_line(conn, line)) < 0) return; if (ret > 0) continue; } switch (conn->state) { case SERVER_CONNECTION_STATE_EHLO: o_stream_nsend_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 (str_begins(line, "AUTH ")) { o_stream_nsend_str( conn->conn.output, "235 2.7.0 " "Authentication successful\r\n"); continue; } if (str_begins(line, "EHLO ")) { o_stream_nsend_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"); continue; } o_stream_nsend_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_nsend_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_nsend_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 (server_connection_init_ssl(conn) < 0) { server_connection_deinit(&conn); return; } if (test_server_init != NULL) { if (test_server_init(conn) != 0) return; } if (test_server_input == NULL) { o_stream_nsend_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); ssl_iostream_destroy(&conn->ssl_iostream); connection_deinit(&conn->conn); pool_unref(&conn->pool); } static void server_connection_destroy(struct connection *_conn) { struct server_connection *conn = (struct server_connection *)_conn; server_connection_deinit(&conn); } static void server_connection_accept(void *context ATTR_UNUSED) { int fd; /* accept new client */ fd = net_accept(fd_listen, NULL, NULL); if (fd == -1) return; if (fd == -2) i_fatal("test server: accept() failed: %m"); server_connection_init(fd); } /* */ static struct connection_settings server_connection_set = { .input_max_size = SIZE_MAX, .output_max_size = SIZE_MAX, .client = FALSE, }; static const struct connection_vfuncs server_connection_vfuncs = { .destroy = server_connection_destroy, .input = server_connection_input, }; static void test_server_run(unsigned int index) { server_index = index; /* open server socket */ io_listen = io_add(fd_listen, IO_READ, server_connection_accept, NULL); server_conn_list = connection_list_init(&server_connection_set, &server_connection_vfuncs); io_loop_run(ioloop); /* close server socket */ io_remove(&io_listen); connection_list_deinit(&server_conn_list); if (server_ssl_ctx != NULL) ssl_iostream_context_unref(&server_ssl_ctx); } /* * Tests */ struct test_server_data { unsigned int index; test_server_init_t server_test; }; static int test_open_server_fd(in_port_t *bind_port) { int fd = net_listen(&bind_ip, bind_port, 128); if (debug) i_debug("server listening on %u", *bind_port); if (fd == -1) { i_fatal("listen(%s:%u) failed: %m", net_ip2addr(&bind_ip), *bind_port); } return fd; } static int test_run_server(struct test_server_data *data) { i_set_failure_prefix("SERVER[%u]: ", data->index + 1); if (debug) i_debug("PID=%s", my_pid); server_ssl_ctx = NULL; ioloop = io_loop_create(); data->server_test(data->index); io_loop_destroy(&ioloop); if (debug) i_debug("Terminated"); i_close_fd(&fd_listen); i_free(bind_ports); main_deinit(); return 0; } static int test_run_dns(test_dns_init_t dns_test) { i_set_failure_prefix("DNS: "); if (debug) i_debug("PID=%s", my_pid); ioloop = io_loop_create(); dns_test(); io_loop_destroy(&ioloop); if (debug) i_debug("Terminated"); i_close_fd(&fd_listen); i_free(bind_ports); main_deinit(); return 0; } static void test_run_client(const struct smtp_client_settings *client_set, test_client_init_t client_test) { i_set_failure_prefix("CLIENT: "); if (debug) i_debug("PID=%s", my_pid); i_sleep_msecs(100); /* wait a little for server setup */ ioloop = io_loop_create(); test_client_run(client_test, client_set); io_loop_destroy(&ioloop); if (debug) i_debug("Terminated"); } 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; if (server_tests_count > 0) { int fds[server_tests_count]; bind_ports = i_new(in_port_t, server_tests_count); for (i = 0; i < server_tests_count; i++) fds[i] = test_open_server_fd(&bind_ports[i]); for (i = 0; i < server_tests_count; i++) { struct test_server_data data; i_zero(&data); data.index = i; data.server_test = server_test; /* Fork server */ fd_listen = fds[i]; test_subprocess_fork(test_run_server, &data, FALSE); i_close_fd(&fd_listen); } } 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"); } /* Fork DNS service */ fd_listen = fd; test_subprocess_fork(test_run_dns, dns_test, FALSE); i_close_fd(&fd_listen); } /* Run client */ test_run_client(client_set, client_test); i_unset_failure_prefix(); test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS); i_free(bind_ports); i_unlink_if_exists("./dns-test"); } /* * Main */ static void main_init(void) { #ifdef HAVE_OPENSSL ssl_iostream_openssl_init(); #endif } static void main_deinit(void) { ssl_iostream_context_cache_free(); #ifdef HAVE_OPENSSL ssl_iostream_openssl_deinit(); #endif } int main(int argc, char *argv[]) { int c; int ret; lib_init(); main_init(); while ((c = getopt(argc, argv, "D")) > 0) { switch (c) { case 'D': debug = TRUE; break; default: i_fatal("Usage: %s [-D]", argv[0]); } } test_subprocesses_init(debug); /* listen on localhost */ i_zero(&bind_ip); bind_ip.family = AF_INET; bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); ret = test_run(test_functions); test_subprocesses_deinit(); main_deinit(); lib_deinit(); return ret; }