summaryrefslogtreecommitdiffstats
path: root/src/lib-smtp/test-smtp-client-errors.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-smtp/test-smtp-client-errors.c')
-rw-r--r--src/lib-smtp/test-smtp-client-errors.c4374
1 files changed, 4374 insertions, 0 deletions
diff --git a/src/lib-smtp/test-smtp-client-errors.c b/src/lib-smtp/test-smtp-client-errors.c
new file mode 100644
index 0000000..673ed04
--- /dev/null
+++ b/src/lib-smtp/test-smtp-client-errors.c
@@ -0,0 +1,4374 @@
+/* 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 <unistd.h>
+
+#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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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 <CR><LF>.<CR><LF>\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;
+}