summaryrefslogtreecommitdiffstats
path: root/src/lib-smtp/test-smtp-payload.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:51:24 +0000
commitf7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch)
treea3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-smtp/test-smtp-payload.c
parentInitial commit. (diff)
downloaddovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz
dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-smtp/test-smtp-payload.c')
-rw-r--r--src/lib-smtp/test-smtp-payload.c1148
1 files changed, 1148 insertions, 0 deletions
diff --git a/src/lib-smtp/test-smtp-payload.c b/src/lib-smtp/test-smtp-payload.c
new file mode 100644
index 0000000..7363736
--- /dev/null
+++ b/src/lib-smtp/test-smtp-payload.c
@@ -0,0 +1,1148 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "llist.h"
+#include "array.h"
+#include "path-util.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "istream-base64.h"
+#include "istream-crlf.h"
+#include "iostream-temp.h"
+#include "iostream-ssl.h"
+#include "iostream-ssl-test.h"
+#ifdef HAVE_OPENSSL
+#include "iostream-openssl.h"
+#endif
+#include "connection.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+#include "smtp-server.h"
+#include "smtp-client.h"
+#include "smtp-client-connection.h"
+#include "smtp-client-transaction.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#define CLIENT_PROGRESS_TIMEOUT 60
+#define SERVER_KILL_TIMEOUT_SECS 20
+#define MAX_PARALLEL_PENDING 200
+
+static bool debug = FALSE;
+static bool small_socket_buffers = FALSE;
+static const char *failure = NULL;
+
+enum test_ssl_mode {
+ TEST_SSL_MODE_NONE = 0,
+ TEST_SSL_MODE_IMMEDIATE,
+ TEST_SSL_MODE_STARTTLS
+};
+
+static unsigned int test_max_pending = 1;
+static bool test_unknown_size = FALSE;
+static enum test_ssl_mode test_ssl_mode = TEST_SSL_MODE_NONE;
+
+static struct ip_addr bind_ip;
+static in_port_t bind_port = 0;
+static int fd_listen = -1;
+
+static void main_deinit(void);
+
+/*
+ * Test files
+ */
+
+static ARRAY_TYPE(const_string) files;
+static pool_t files_pool;
+
+static void test_files_read_dir(const char *path)
+{
+ DIR *dirp;
+
+ /* open the directory */
+ if ((dirp = opendir(path)) == NULL) {
+ if (errno == ENOENT || errno == EACCES)
+ return;
+ i_fatal("test files: "
+ "failed to open directory %s: %m", path);
+ }
+
+ /* read entries */
+ for (;;) {
+ const char *file;
+ struct dirent *dp;
+ struct stat st;
+#if 0
+ if (array_count(&files) > 10)
+ break;
+#endif
+ errno = 0;
+ if ((dp = readdir(dirp)) == NULL)
+ break;
+ if (*dp->d_name == '.')
+ continue;
+
+ file = t_abspath_to(dp->d_name, path);
+ if (stat(file, &st) == 0) {
+ if (S_ISREG(st.st_mode)) {
+ file += 2; /* skip "./" */
+ file = p_strdup(files_pool, file);
+ array_push_back(&files, &file);
+ } else if (S_ISDIR(st.st_mode)) {
+ test_files_read_dir(file);
+ }
+ }
+ }
+
+ if (errno != 0)
+ i_fatal("test files: "
+ "failed to read directory %s: %m", path);
+
+ /* Close the directory */
+ if (closedir(dirp) < 0)
+ i_error("test files: "
+ "failed to close directory %s: %m", path);
+}
+
+static void test_files_init(void)
+{
+ /* initialize file array */
+ files_pool = pool_alloconly_create(
+ MEMPOOL_GROWING"smtp_server_request", 4096);
+ p_array_init(&files, files_pool, 512);
+
+ /* obtain all filenames */
+ test_files_read_dir(".");
+}
+
+static void test_files_deinit(void)
+{
+ pool_unref(&files_pool);
+}
+
+static struct istream *test_file_open(const char *path)
+{
+ struct istream *file;
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ if (errno != ENOENT && errno != EACCES) {
+ i_fatal("test files: "
+ "open(%s) failed: %m", path);
+ }
+ if (debug) {
+ i_debug("test files: "
+ "open(%s) failed: %m", path);
+ }
+ return NULL;
+ }
+
+ file = i_stream_create_fd_autoclose(&fd, 40960);
+ i_stream_set_name(file, path);
+ return file;
+}
+
+/*
+ * Test server
+ */
+
+struct client {
+ pool_t pool;
+ struct client *prev, *next;
+
+ struct smtp_server_connection *smtp_conn;
+};
+
+struct client_transaction {
+ struct client *client;
+ struct smtp_server_cmd_ctx *data_cmd;
+ struct smtp_server_transaction *trans;
+
+ const char *path;
+
+ struct istream *payload, *file;
+};
+
+static struct smtp_server *smtp_server;
+
+static struct io *io_listen;
+static struct client *clients;
+
+static int
+client_transaction_read_more(struct client_transaction *ctrans)
+{
+ struct istream *payload = ctrans->payload;
+ const unsigned char *pdata, *fdata;
+ size_t psize, fsize, pleft;
+ off_t ret;
+
+ if (debug) {
+ i_debug("test server: read more payload for [%s]",
+ ctrans->path);
+ }
+
+ /* read payload */
+ while ((ret = i_stream_read_more(payload, &pdata, &psize)) > 0) {
+ if (debug) {
+ i_debug("test server: "
+ "got data for [%s] (size=%d)",
+ ctrans->path, (int)psize);
+ }
+ /* compare with file on disk */
+ pleft = psize;
+ while ((ret = i_stream_read_more(ctrans->file,
+ &fdata, &fsize)) > 0 &&
+ pleft > 0) {
+ fsize = (fsize > pleft ? pleft : fsize);
+ if (memcmp(pdata, fdata, fsize) != 0) {
+ i_fatal("test server: "
+ "received data does not match file [%s] "
+ "(%"PRIuUOFF_T":%"PRIuUOFF_T")",
+ ctrans->path, payload->v_offset,
+ ctrans->file->v_offset);
+ }
+ i_stream_skip(ctrans->file, fsize);
+ pleft -= fsize;
+ pdata += fsize;
+ }
+ if (ret < 0 && ctrans->file->stream_errno != 0) {
+ i_fatal("test server: "
+ "failed to read file: %s",
+ i_stream_get_error(ctrans->file));
+ }
+ i_stream_skip(payload, psize);
+ }
+
+ if (ret == 0) {
+ if (debug) {
+ i_debug("test server: "
+ "need more data for [%s]",
+ ctrans->path);
+ }
+ /* we will be called again for this request */
+ return 0;
+ }
+
+ (void)i_stream_read(ctrans->file);
+ if (payload->stream_errno != 0) {
+ i_fatal("test server: "
+ "failed to read transaction payload: %s",
+ i_stream_get_error(payload));
+ }
+ if (i_stream_have_bytes_left(ctrans->file)) {
+ if (i_stream_read_more(ctrans->file, &fdata, &fsize) <= 0)
+ fsize = 0;
+ i_fatal("test server: "
+ "payload ended prematurely "
+ "(at least %zu bytes left)", fsize);
+ }
+
+ if (debug) {
+ i_debug("test server: "
+ "finished transaction for [%s]",
+ ctrans->path);
+ }
+
+ /* dereference payload stream; finishes the request */
+ i_stream_unref(&payload);
+ ctrans->payload = NULL;
+ i_stream_unref(&ctrans->file);
+
+ /* finished */
+ smtp_server_reply_all(ctrans->data_cmd, 250, "2.0.0", "OK");
+ return 1;
+}
+
+static void
+client_transaction_handle_payload(struct client_transaction *ctrans,
+ const char *path, struct istream *data_input)
+{
+ struct smtp_server_transaction *trans = ctrans->trans;
+ struct istream *fstream;
+
+ ctrans->path = p_strdup(trans->pool, path);
+
+ if (debug) {
+ i_debug("test server: got transaction for: %s",
+ path);
+ }
+
+ fstream = test_file_open(path);
+ if (fstream == NULL)
+ i_fatal("test server: failed to open: %s", path);
+
+ i_stream_ref(data_input);
+ ctrans->payload = data_input;
+ i_assert(ctrans->payload != NULL);
+
+ ctrans->file = i_stream_create_base64_encoder(fstream, 80, TRUE),
+ i_stream_unref(&fstream);
+
+ (void)client_transaction_read_more(ctrans);
+}
+
+/* transaction */
+
+static struct client_transaction *
+client_transaction_init(struct client *client,
+ struct smtp_server_cmd_ctx *data_cmd,
+ struct smtp_server_transaction *trans)
+{
+ struct client_transaction *ctrans;
+ pool_t pool = trans->pool;
+
+ ctrans = p_new(pool, struct client_transaction, 1);
+ ctrans->client = client;
+ ctrans->trans = trans;
+ ctrans->data_cmd = data_cmd;
+
+ return ctrans;
+}
+
+static void client_transaction_deinit(struct client_transaction **_ctrans)
+{
+ struct client_transaction *ctrans = *_ctrans;
+
+ *_ctrans = NULL;
+
+ i_stream_unref(&ctrans->payload);
+ i_stream_unref(&ctrans->file);
+}
+
+static void
+test_server_conn_trans_free(void *context ATTR_UNUSED,
+ struct smtp_server_transaction *trans)
+{
+ struct client_transaction *ctrans =
+ (struct client_transaction *)trans->context;
+ client_transaction_deinit(&ctrans);
+}
+
+static int
+test_server_conn_cmd_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt)
+{
+ if (debug) {
+ i_debug("test server: RCPT TO:%s",
+ smtp_address_encode(rcpt->path));
+ }
+
+ return 1;
+}
+
+static int
+test_server_conn_cmd_data_begin(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans,
+ struct istream *data_input)
+{
+ struct client *client = (struct client *)conn_ctx;
+ const char *fpath = trans->params.envid;
+ struct client_transaction *ctrans;
+
+ i_assert(fpath != NULL);
+
+ if (debug)
+ i_debug("test server: DATA (file path = %s)", fpath);
+
+ ctrans = client_transaction_init(client, cmd, trans);
+ client_transaction_handle_payload(ctrans, fpath, data_input);
+ trans->context = ctrans;
+ return 0;
+}
+
+static int
+test_server_conn_cmd_data_continue(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans)
+{
+ struct client_transaction *ctrans =
+ (struct client_transaction *)trans->context;
+
+ if (debug)
+ i_debug("test server: DATA continue");
+
+ ctrans->data_cmd = cmd;
+
+ return client_transaction_read_more(ctrans);
+}
+
+/* client connection */
+
+static void test_server_connection_free(void *context);
+
+static const struct smtp_server_callbacks server_callbacks =
+{
+ .conn_cmd_rcpt = test_server_conn_cmd_rcpt,
+ .conn_cmd_data_begin = test_server_conn_cmd_data_begin,
+ .conn_cmd_data_continue = test_server_conn_cmd_data_continue,
+
+ .conn_trans_free = test_server_conn_trans_free,
+
+ .conn_free = test_server_connection_free,
+};
+
+static void client_init(int fd)
+{
+ struct client *client;
+ pool_t pool;
+
+ net_set_nonblock(fd, TRUE);
+
+ pool = pool_alloconly_create("client", 256);
+ client = p_new(pool, struct client, 1);
+ client->pool = pool;
+
+ client->smtp_conn = smtp_server_connection_create(
+ smtp_server, fd, fd, NULL, 0,
+ (test_ssl_mode == TEST_SSL_MODE_IMMEDIATE),
+ NULL, &server_callbacks, client);
+ smtp_server_connection_start(client->smtp_conn);
+ DLLIST_PREPEND(&clients, client);
+}
+
+static void client_deinit(struct client **_client)
+{
+ struct client *client = *_client;
+
+ *_client = NULL;
+
+ DLLIST_REMOVE(&clients, client);
+
+ if (client->smtp_conn != NULL) {
+ smtp_server_connection_terminate(&client->smtp_conn,
+ NULL, "deinit");
+ }
+ pool_unref(&client->pool);
+}
+
+static void test_server_connection_free(void *context)
+{
+ struct client *client = context;
+
+ client->smtp_conn = NULL;
+ client_deinit(&client);
+}
+
+static void client_accept(void *context ATTR_UNUSED)
+{
+ int fd;
+
+ for (;;) {
+ /* accept new client */
+ if ((fd = net_accept(fd_listen, NULL, NULL)) < 0) {
+ if (errno == EAGAIN)
+ break;
+ if (errno == ECONNABORTED)
+ continue;
+ i_fatal("test server: accept() failed: %m");
+ }
+
+ client_init(fd);
+ }
+}
+
+/* */
+
+static void test_server_init(const struct smtp_server_settings *server_set)
+{
+ /* open server socket */
+ io_listen = io_add(fd_listen, IO_READ, client_accept, NULL);
+
+ smtp_server = smtp_server_init(server_set);
+}
+
+static void test_server_deinit(void)
+{
+ /* close server socket */
+ io_remove(&io_listen);
+
+ /* deinitialize */
+ smtp_server_deinit(&smtp_server);
+}
+
+/*
+ * Test client
+ */
+
+struct test_client_connection {
+ struct smtp_client_connection *conn;
+ struct smtp_client_transaction *trans;
+};
+
+struct test_client_transaction {
+ struct test_client_transaction *prev, *next;
+ struct test_client_connection *conn;
+
+ struct io *io;
+ struct istream *file;
+ unsigned int files_idx;
+};
+
+static struct test_client_connection test_conns[MAX_PARALLEL_PENDING];
+static struct smtp_client *smtp_client;
+static enum smtp_protocol client_protocol;
+static struct test_client_transaction *client_requests;
+static unsigned int client_files_first, client_files_last;
+static struct timeout *client_to = NULL;
+struct timeout *to_client_progress = NULL;
+
+static struct test_client_connection *test_client_connection_get(void)
+{
+ unsigned int i;
+ enum smtp_client_connection_ssl_mode ssl_mode;
+
+ for (i = 0; i < MAX_PARALLEL_PENDING; i++) {
+ if (test_conns[i].trans == NULL)
+ break;
+ }
+
+ i_assert(i < MAX_PARALLEL_PENDING);
+
+ switch (test_ssl_mode) {
+ case TEST_SSL_MODE_NONE:
+ default:
+ ssl_mode = SMTP_CLIENT_SSL_MODE_NONE;
+ break;
+ case TEST_SSL_MODE_IMMEDIATE:
+ ssl_mode = SMTP_CLIENT_SSL_MODE_IMMEDIATE;
+ break;
+ case TEST_SSL_MODE_STARTTLS:
+ ssl_mode = SMTP_CLIENT_SSL_MODE_STARTTLS;
+ break;
+ }
+
+ if (test_conns[i].conn == NULL) {
+ test_conns[i].conn = smtp_client_connection_create(
+ smtp_client, client_protocol,
+ net_ip2addr(&bind_ip), bind_port,
+ ssl_mode, NULL);
+ }
+ return &test_conns[i];
+}
+
+static struct test_client_transaction *test_client_transaction_new(void)
+{
+ struct test_client_transaction *tctrans;
+
+ tctrans = i_new(struct test_client_transaction, 1);
+ DLLIST_PREPEND(&client_requests, tctrans);
+
+ return tctrans;
+}
+
+static void
+test_client_transaction_destroy(struct test_client_transaction *tctrans)
+{
+ smtp_client_transaction_destroy(&tctrans->conn->trans);
+ io_remove(&tctrans->io);
+ i_stream_unref(&tctrans->file);
+
+ DLLIST_REMOVE(&client_requests, tctrans);
+ i_free(tctrans);
+}
+
+static void test_client_continue(void *dummy);
+
+static void test_client_finished(unsigned int files_idx)
+{
+ const char **paths;
+ unsigned int count;
+
+ if (debug) {
+ i_debug("test client: "
+ "finished [%u]", files_idx);
+ }
+
+ paths = array_get_modifiable(&files, &count);
+ i_assert(files_idx < count);
+ i_assert(client_files_first < count);
+ i_assert(paths[files_idx] != NULL);
+
+ paths[files_idx] = NULL;
+ if (client_to == NULL)
+ client_to = timeout_add_short(0, test_client_continue, NULL);
+}
+
+static void
+test_client_transaction_finish(struct test_client_transaction *tctrans)
+{
+ tctrans->conn->trans = NULL;
+ if (io_loop_is_running(current_ioloop))
+ test_client_finished(tctrans->files_idx);
+ test_client_transaction_destroy(tctrans);
+}
+
+static void
+test_client_transaction_rcpt(const struct smtp_reply *reply,
+ struct test_client_transaction *tctrans)
+{
+ const char **paths;
+ const char *path;
+ unsigned int count;
+
+ if (to_client_progress != NULL)
+ timeout_reset(to_client_progress);
+
+ paths = array_get_modifiable(&files, &count);
+ i_assert(tctrans->files_idx < count);
+ i_assert(client_files_first < count);
+ path = paths[tctrans->files_idx];
+ i_assert(path != NULL);
+
+ if (reply->status / 100 != 2) {
+ i_fatal("test client: "
+ "SMTP RCPT for %s failed: %s",
+ path, smtp_reply_log(reply));
+ }
+}
+
+static void
+test_client_transaction_rcpt_data(const struct smtp_reply *reply ATTR_UNUSED,
+ struct test_client_transaction *tctrans)
+{
+ const char **paths;
+ const char *path;
+ unsigned int count;
+
+ if (to_client_progress != NULL)
+ timeout_reset(to_client_progress);
+
+ paths = array_get_modifiable(&files, &count);
+ i_assert(tctrans->files_idx < count);
+ i_assert(client_files_first < count);
+ path = paths[tctrans->files_idx];
+ i_assert(path != NULL);
+
+ if (reply->status / 100 != 2) {
+ i_fatal("test client: "
+ "SMTP DATA for %s failed: %s",
+ path, smtp_reply_log(reply));
+ }
+}
+
+static void
+test_client_transaction_data(const struct smtp_reply *reply,
+ struct test_client_transaction *tctrans)
+{
+ const char **paths;
+ const char *path;
+ unsigned int count;
+
+ if (to_client_progress != NULL)
+ timeout_reset(to_client_progress);
+
+ if (debug) {
+ i_debug("test client: "
+ "got response for DATA [%u]",
+ tctrans->files_idx);
+ }
+
+ paths = array_get_modifiable(&files, &count);
+ i_assert(tctrans->files_idx < count);
+ i_assert(client_files_first < count);
+ path = paths[tctrans->files_idx];
+ i_assert(path != NULL);
+
+ if (debug) {
+ i_debug("test client: "
+ "path for [%u]: %s",
+ tctrans->files_idx, path);
+ }
+
+ if (reply->status / 100 != 2) {
+ i_fatal("test client: "
+ "SMTP transaction for %s failed: %s",
+ path, smtp_reply_log(reply));
+ }
+}
+
+static void test_client_continue(void *dummy ATTR_UNUSED)
+{
+ struct test_client_transaction *tctrans;
+ struct smtp_params_mail mail_params;
+ const char **paths;
+ unsigned int count, pending_count, i;
+
+ if (debug)
+ i_debug("test client: continue");
+
+ timeout_remove(&client_to);
+ if (to_client_progress != NULL)
+ timeout_reset(to_client_progress);
+
+ paths = array_get_modifiable(&files, &count);
+
+ i_assert(client_files_first <= count);
+ i_assert(client_files_last <= count);
+
+ i_assert(client_files_first <= client_files_last);
+ for (; (client_files_first < client_files_last &&
+ paths[client_files_first] == NULL); client_files_first++);
+
+ pending_count = 0;
+ for (i = client_files_first; i < client_files_last; i++) {
+ if (paths[i] != NULL)
+ pending_count++;
+ }
+
+ if (debug) {
+ i_debug("test client: finished until [%u/%u]; "
+ "sending until [%u/%u] (%u pending)",
+ client_files_first-1, count,
+ client_files_last, count, pending_count);
+ }
+
+ if (debug && client_files_first < count) {
+ const char *path = paths[client_files_first];
+ i_debug("test client: "
+ "next blocking: %s [%d]",
+ (path == NULL ? "none" : path),
+ client_files_first);
+ }
+
+ if (client_files_first >= count) {
+ io_loop_stop(current_ioloop);
+ return;
+ }
+
+ for (; client_files_last < count && pending_count < test_max_pending;
+ client_files_last++, pending_count++) {
+ struct istream *fstream, *payload;
+ const char *path = paths[client_files_last];
+ unsigned int r, rcpts;
+
+ fstream = test_file_open(path);
+ if (fstream == NULL) {
+ paths[client_files_last] = NULL;
+ if (debug) {
+ i_debug("test client: "
+ "skipping %s [%u]",
+ path, client_files_last);
+ }
+ if (client_to == NULL) {
+ client_to = timeout_add_short(
+ 0, test_client_continue, NULL);
+ }
+ continue;
+ }
+
+ if (debug) {
+ i_debug("test client: "
+ "retrieving %s [%u]",
+ path, client_files_last);
+ }
+
+ tctrans = test_client_transaction_new();
+ tctrans->files_idx = client_files_last;
+ tctrans->conn = test_client_connection_get();
+
+ i_zero(&mail_params);
+ mail_params.envid = path;
+
+ tctrans->conn->trans = smtp_client_transaction_create(
+ tctrans->conn->conn,
+ &((struct smtp_address){.localpart = "user",
+ .domain = "example.com"}),
+ &mail_params, 0,
+ test_client_transaction_finish, tctrans);
+
+ rcpts = tctrans->files_idx % 10 + 1;
+ for (r = 1; r <= rcpts; r++) {
+ smtp_client_transaction_add_rcpt(
+ tctrans->conn->trans,
+ smtp_address_create_temp(
+ t_strdup_printf("rcpt%u", r),
+ "example.com"), NULL,
+ test_client_transaction_rcpt,
+ test_client_transaction_rcpt_data, tctrans);
+ }
+
+ if (!test_unknown_size) {
+ payload = i_stream_create_base64_encoder(
+ fstream, 80, TRUE);
+ } else {
+ struct istream *b64_stream =
+ i_stream_create_base64_encoder(
+ fstream, 80, FALSE);
+ payload = i_stream_create_crlf(b64_stream);
+ i_stream_unref(&b64_stream);
+ }
+
+ if (debug) {
+ uoff_t raw_size = UOFF_T_MAX, b64_size = UOFF_T_MAX;
+
+ (void)i_stream_get_size(fstream, TRUE, &raw_size);
+ (void)i_stream_get_size(payload, TRUE, &b64_size);
+ i_debug("test client: "
+ "sending %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes payload %s [%u]",
+ raw_size, b64_size, path, client_files_last);
+ }
+
+ smtp_client_transaction_send(tctrans->conn->trans, payload,
+ test_client_transaction_data,
+ tctrans);
+
+ i_stream_unref(&payload);
+ i_stream_unref(&fstream);
+ }
+}
+
+static void test_client_progress_timeout(void *context ATTR_UNUSED)
+{
+ /* Terminate test due to lack of progress */
+ failure = "Test is hanging";
+ timeout_remove(&to_client_progress);
+ io_loop_stop(current_ioloop);
+}
+
+static void
+test_client(enum smtp_protocol protocol,
+ const struct smtp_client_settings *client_set)
+{
+ client_protocol = protocol;
+
+ if (!small_socket_buffers) {
+ to_client_progress = timeout_add(
+ CLIENT_PROGRESS_TIMEOUT*1000,
+ test_client_progress_timeout, NULL);
+ }
+
+ /* create client */
+ smtp_client = smtp_client_init(client_set);
+
+ /* start querying server */
+ client_files_first = client_files_last = 0;
+ test_client_continue(NULL);
+}
+
+static void test_client_init(void)
+{
+ i_zero(&test_conns);
+}
+
+static void test_client_deinit(void)
+{
+ timeout_remove(&client_to);
+ timeout_remove(&to_client_progress);
+ smtp_client_deinit(&smtp_client);
+
+ i_zero(&test_conns);
+}
+
+/*
+ * Tests
+ */
+
+struct test_server_data {
+ const struct smtp_server_settings *set;
+};
+
+static void test_open_server_fd(void)
+{
+ if (fd_listen != -1)
+ i_close_fd(&fd_listen);
+ fd_listen = net_listen(&bind_ip, &bind_port, 128);
+ if (fd_listen == -1) {
+ i_fatal("listen(%s:%u) failed: %m",
+ net_ip2addr(&bind_ip), bind_port);
+ }
+ net_set_nonblock(fd_listen, TRUE);
+}
+
+static int test_run_server(struct test_server_data *data)
+{
+ const struct smtp_server_settings *server_set = data->set;
+ struct ioloop *ioloop;
+
+ i_set_failure_prefix("SERVER: ");
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ ioloop = io_loop_create();
+ test_server_init(server_set);
+ io_loop_run(ioloop);
+ test_server_deinit();
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+
+ i_close_fd(&fd_listen);
+ test_files_deinit();
+ main_deinit();
+ return 0;
+}
+
+static void
+test_run_client(
+ enum smtp_protocol protocol, struct smtp_client_settings *client_set,
+ void (*client_init)(enum smtp_protocol protocol,
+ const struct smtp_client_settings *client_set))
+{
+ struct ioloop *ioloop;
+
+ i_set_failure_prefix("CLIENT: ");
+
+ if (debug)
+ i_debug("client: PID=%s", my_pid);
+
+ ioloop = io_loop_create();
+ test_client_init();
+ client_init(protocol, client_set);
+ io_loop_run(ioloop);
+ test_client_deinit();
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+}
+
+static void
+test_run_client_server(
+ enum smtp_protocol protocol,
+ struct smtp_client_settings *client_set,
+ struct smtp_server_settings *server_set,
+ void (*client_init)(enum smtp_protocol protocol,
+ const struct smtp_client_settings *client_set))
+{
+ struct test_server_data data;
+
+ if (test_ssl_mode == TEST_SSL_MODE_STARTTLS)
+ server_set->capabilities |= SMTP_CAPABILITY_STARTTLS;
+
+ failure = NULL;
+
+ i_zero(&data);
+ data.set = server_set;
+
+ test_files_init();
+
+ /* Fork server */
+ test_open_server_fd();
+ test_subprocess_fork(test_run_server, &data, FALSE);
+ i_close_fd(&fd_listen);
+
+ /* Run client */
+ test_run_client(protocol, client_set, client_init);
+
+ i_unset_failure_prefix();
+ bind_port = 0;
+ test_subprocess_kill_all(SERVER_KILL_TIMEOUT_SECS);
+ test_files_deinit();
+}
+
+static void
+test_run_scenarios(
+ enum smtp_protocol protocol,
+ enum smtp_capability capabilities,
+ void (*client_init)(enum smtp_protocol protocol,
+ const struct smtp_client_settings *client_set))
+{
+ struct smtp_server_settings smtp_server_set;
+ struct smtp_client_settings smtp_client_set;
+ struct ssl_iostream_settings ssl_server_set, ssl_client_set;
+
+ /* ssl settings */
+ ssl_iostream_test_settings_server(&ssl_server_set);
+ ssl_server_set.verbose = debug;
+ ssl_iostream_test_settings_client(&ssl_client_set);
+ ssl_client_set.verbose = debug;
+
+ /* server settings */
+ i_zero(&smtp_server_set);
+ smtp_server_set.protocol = protocol;
+ smtp_server_set.capabilities = capabilities;
+ smtp_server_set.hostname = "localhost";
+ smtp_server_set.max_client_idle_time_msecs =
+ CLIENT_PROGRESS_TIMEOUT*1000;
+ smtp_server_set.max_pipelined_commands = 1;
+ smtp_server_set.auth_optional = TRUE;
+ smtp_server_set.ssl = &ssl_server_set;
+ smtp_server_set.debug = debug;
+
+ /* client settings */
+ i_zero(&smtp_client_set);
+ smtp_client_set.my_hostname = "localhost";
+ smtp_client_set.temp_path_prefix = "/tmp";
+ smtp_client_set.command_timeout_msecs = CLIENT_PROGRESS_TIMEOUT*1000;
+ smtp_client_set.connect_timeout_msecs = CLIENT_PROGRESS_TIMEOUT*1000;
+ smtp_client_set.ssl = &ssl_client_set;
+ smtp_client_set.debug = debug;
+
+ if (small_socket_buffers) {
+ smtp_client_set.socket_send_buffer_size = 4096;
+ smtp_client_set.socket_recv_buffer_size = 4096;
+ smtp_client_set.command_timeout_msecs = 20*60*1000;
+ smtp_client_set.connect_timeout_msecs = 20*60*1000;
+ smtp_server_set.socket_send_buffer_size = 4096;
+ smtp_server_set.socket_recv_buffer_size = 4096;
+ }
+
+ test_max_pending = 1;
+ test_unknown_size = FALSE;
+ test_ssl_mode = TEST_SSL_MODE_NONE;
+ test_run_client_server(protocol, &smtp_client_set, &smtp_server_set,
+ client_init);
+
+ test_out_reason("sequential", (failure == NULL), failure);
+
+ test_max_pending = MAX_PARALLEL_PENDING;
+ test_unknown_size = FALSE;
+ test_ssl_mode = TEST_SSL_MODE_NONE;
+ test_run_client_server(protocol, &smtp_client_set, &smtp_server_set,
+ client_init);
+
+ test_out_reason("parallel", (failure == NULL), failure);
+
+ smtp_server_set.max_pipelined_commands = 5;
+ smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING;
+ test_max_pending = MAX_PARALLEL_PENDING;
+ test_unknown_size = FALSE;
+ test_ssl_mode = TEST_SSL_MODE_NONE;
+ test_run_client_server(protocol, &smtp_client_set, &smtp_server_set,
+ client_init);
+
+ test_out_reason("parallel pipelining", (failure == NULL), failure);
+
+ smtp_server_set.max_pipelined_commands = 5;
+ smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING;
+ test_max_pending = MAX_PARALLEL_PENDING;
+ test_unknown_size = TRUE;
+ test_ssl_mode = TEST_SSL_MODE_NONE;
+ test_run_client_server(protocol, &smtp_client_set, &smtp_server_set,
+ client_init);
+
+ test_out_reason("unknown payload size", (failure == NULL), failure);
+
+#ifdef HAVE_OPENSSL
+ smtp_server_set.max_pipelined_commands = 5;
+ smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING;
+ test_max_pending = MAX_PARALLEL_PENDING;
+ test_unknown_size = FALSE;
+ test_ssl_mode = TEST_SSL_MODE_IMMEDIATE;
+ test_run_client_server(protocol, &smtp_client_set, &smtp_server_set,
+ client_init);
+
+ test_out_reason("parallel pipelining ssl",
+ (failure == NULL), failure);
+
+ smtp_server_set.max_pipelined_commands = 5;
+ smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING;
+ test_max_pending = MAX_PARALLEL_PENDING;
+ test_unknown_size = FALSE;
+ test_ssl_mode = TEST_SSL_MODE_STARTTLS;
+ test_run_client_server(protocol, &smtp_client_set, &smtp_server_set,
+ client_init);
+
+ test_out_reason("parallel pipelining startls",
+ (failure == NULL), failure);
+#endif
+}
+
+static void test_smtp_normal(void)
+{
+ test_begin("smtp payload - normal");
+ test_run_scenarios(SMTP_PROTOCOL_SMTP,
+ SMTP_CAPABILITY_DSN, test_client);
+ test_end();
+}
+
+static void test_smtp_chunking(void)
+{
+ test_begin("smtp payload - chunking");
+ test_run_scenarios(SMTP_PROTOCOL_SMTP,
+ SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_CHUNKING,
+ test_client);
+ test_end();
+}
+
+static void test_lmtp_normal(void)
+{
+ test_begin("lmtp payload - normal");
+ test_run_scenarios(SMTP_PROTOCOL_LMTP,
+ SMTP_CAPABILITY_DSN, test_client);
+ test_end();
+}
+
+static void test_lmtp_chunking(void)
+{
+ test_begin("lmtp payload - chunking");
+ test_run_scenarios(SMTP_PROTOCOL_LMTP,
+ SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_CHUNKING,
+ test_client);
+ test_end();
+}
+
+static void (*const test_functions[])(void) = {
+ test_smtp_normal,
+ test_smtp_chunking,
+ test_lmtp_normal,
+ test_lmtp_chunking,
+ NULL
+};
+
+/*
+ * 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, "DS")) > 0) {
+ switch (c) {
+ case 'D':
+ debug = TRUE;
+ break;
+ case 'S':
+ small_socket_buffers = TRUE;
+ break;
+ default:
+ i_fatal("Usage: %s [-D][-S]", 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;
+}