From: Stephan Bosch Date: Sat, 11 Nov 2017 09:30:14 +0000 (+0100) Subject: lib-smtp: Created test-smtp-payload, which tests client<->server payload exchange. X-Git-Tag: 2.3.0.rc1~179 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8620dc793885749c37000f11dd83b902b6844e20;p=thirdparty%2Fdovecot%2Fcore.git lib-smtp: Created test-smtp-payload, which tests client<->server payload exchange. --- diff --git a/src/lib-smtp/Makefile.am b/src/lib-smtp/Makefile.am index 801f9b175e..ffc2e801ec 100644 --- a/src/lib-smtp/Makefile.am +++ b/src/lib-smtp/Makefile.am @@ -77,6 +77,7 @@ test_programs = \ test-smtp-command-parser test_nocheck_programs = \ + test-smtp-payload \ test-smtp-submit \ test-smtp-client-errors @@ -129,6 +130,11 @@ test_smtp_command_parser_LDFLAGS = -export-dynamic test_smtp_command_parser_LDADD = $(test_libs) test_smtp_command_parser_DEPENDENCIES = $(test_deps) +test_smtp_payload_SOURCES = test-smtp-payload.c +test_smtp_payload_LDFLAGS = -export-dynamic +test_smtp_payload_LDADD = $(test_libs) +test_smtp_payload_DEPENDENCIES = $(test_deps) + test_smtp_submit_SOURCES = test-smtp-submit.c test_smtp_submit_LDFLAGS = -export-dynamic test_smtp_submit_LDADD = $(test_libs) diff --git a/src/lib-smtp/test-smtp-payload.c b/src/lib-smtp/test-smtp-payload.c new file mode 100644 index 0000000000..1843fd39e4 --- /dev/null +++ b/src/lib-smtp/test-smtp-payload.c @@ -0,0 +1,972 @@ +/* Copyright (c) 2013-2017 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 "connection.h" +#include "test-common.h" +#include "smtp-server.h" +#include "smtp-client.h" +#include "smtp-client-connection.h" +#include "smtp-client-transaction.h" + +#include +#include +#include +#include +#include +#include +#include + +static bool debug = FALSE; + +static unsigned int test_max_pending = 1; +static bool test_unknown_size = FALSE; + +static struct ip_addr bind_ip; +static in_port_t bind_port = 0; +static int fd_listen = -1; +static pid_t server_pid = (pid_t)-1; + +/* + * 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_append(&files, &file, 1); + } else { + 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 %"PRIuSIZE_T" 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_cmd_rcpt *data) +{ + if (debug) { + i_debug("test server: RCPT TO:%s", + smtp_address_encode(data->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_destroy(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_destroy = test_server_connection_destroy +}; + +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, NULL, &server_callbacks, client); + smtp_server_connection_start(client->smtp_conn, FALSE); + 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_destroy(void *context) +{ + struct client *client = context; + + client->smtp_conn = NULL; + client_deinit(&client); +} + +static void client_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"); + } + + 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, (void *)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_transaction { + struct test_client_transaction *prev, *next; + struct smtp_client_connection *conn; + struct smtp_client_transaction *trans; + + struct io *io; + struct istream *file; + unsigned int files_idx; +}; + +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; + +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) +{ + if (tctrans->trans != NULL) + smtp_client_transaction_destroy(&tctrans->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->trans = NULL; + 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; + + 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; + + 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 (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)); + } + + test_client_finished(tctrans->files_idx); +} + +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; + + if (debug) + i_debug("test client: continue"); + + timeout_remove(&client_to); + + 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++); + + if (debug) { + i_debug("test client: " + "received until [%u/%u]", client_files_first-1, 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 && + (client_files_last - client_files_first) < test_max_pending; + client_files_last++) { + 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 = smtp_client_connection_create(smtp_client, + client_protocol, net_ip2addr(&bind_ip), bind_port, + SMTP_CLIENT_SSL_MODE_NONE, NULL); + + i_zero(&mail_params); + mail_params.envid = path; + + tctrans->trans = smtp_client_transaction_create(tctrans->conn, + SMTP_ADDRESS_LITERAL("user", "example.com"), + &mail_params, test_client_transaction_finish, tctrans); + smtp_client_connection_unref(&tctrans->conn); + + rcpts = tctrans->files_idx % 10 + 1; + for (r = 1; r <= rcpts; r++) { + smtp_client_transaction_add_rcpt(tctrans->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) { + size_t raw_size = (size_t)-1, b64_size = (size_t)-1; + + (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->trans, payload, + test_client_transaction_data, tctrans); + + i_stream_unref(&payload); + i_stream_unref(&fstream); + } +} + +static void +test_client(enum smtp_protocol protocol, + const struct smtp_client_settings *client_set) +{ + client_protocol = protocol; + + /* create client */ + smtp_client = smtp_client_init(client_set); + + /* start querying server */ + client_files_first = client_files_last = 0; + test_client_continue(NULL); +} + +/* cleanup */ + +static void test_client_deinit(void) +{ + timeout_remove(&client_to); + smtp_client_deinit(&smtp_client); +} + +/* + * Tests + */ + +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); + } +} + +static void test_server_kill(void) +{ + if (server_pid != (pid_t)-1) { + (void)kill(server_pid, SIGKILL); + (void)waitpid(server_pid, NULL, 0); + } + server_pid = (pid_t)-1; +} + +static void test_run_client_server( + enum smtp_protocol protocol, + const struct smtp_client_settings *client_set, + const struct smtp_server_settings *server_set, + void (*client_init)(enum smtp_protocol protocol, + const struct smtp_client_settings *client_set)) +{ + struct ioloop *ioloop; + + test_open_server_fd(); + + if ((server_pid = fork()) == (pid_t)-1) + i_fatal("fork() failed: %m"); + if (server_pid == 0) { + server_pid = (pid_t)-1; + hostpid_init(); + if (debug) + i_debug("server: PID=%s", my_pid); + i_set_failure_prefix("SERVER: "); + /* child: server */ + ioloop = io_loop_create(); + test_server_init(server_set); + io_loop_run(ioloop); + test_server_deinit(); + io_loop_destroy(&ioloop); + i_close_fd(&fd_listen); + } else { + if (debug) + i_debug("client: PID=%s", my_pid); + i_set_failure_prefix("CLIENT: "); + i_close_fd(&fd_listen); + /* parent: client */ + ioloop = io_loop_create(); + client_init(protocol, client_set); + io_loop_run(ioloop); + test_client_deinit(); + io_loop_destroy(&ioloop); + bind_port = 0; + test_server_kill(); + } +} + +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; + + /* 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 = 10*000; + smtp_server_set.max_pipelined_commands = 1; + smtp_server_set.auth_optional = TRUE; + 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.debug = debug; + + test_max_pending = 1; + test_unknown_size = FALSE; + test_files_init(); + test_run_client_server(protocol, + &smtp_client_set, &smtp_server_set, + client_init); + test_files_deinit(); + + test_out("sequential", TRUE); + + test_max_pending = 200; + test_unknown_size = FALSE; + test_files_init(); + test_run_client_server(protocol, + &smtp_client_set, &smtp_server_set, + client_init); + test_files_deinit(); + + test_out("parallel", TRUE); + + smtp_server_set.max_pipelined_commands = 5; + smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING; + test_max_pending = 200; + test_unknown_size = FALSE; + test_files_init(); + test_run_client_server(protocol, + &smtp_client_set, &smtp_server_set, + client_init); + test_files_deinit(); + + test_out("parallel pipelining", TRUE); + + smtp_server_set.max_pipelined_commands = 5; + smtp_server_set.capabilities |= SMTP_CAPABILITY_PIPELINING; + test_max_pending = 200; + test_unknown_size = TRUE; + test_files_init(); + test_run_client_server(protocol, + &smtp_client_set, &smtp_server_set, + client_init); + test_files_deinit(); + + test_out("unknown payload size", TRUE); +} + +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 + */ + +volatile sig_atomic_t terminating = 0; + +static void +test_signal_handler(int signo) +{ + if (terminating != 0) + raise(signo); + terminating = 1; + + /* make sure we don't leave any pesky children alive */ + test_server_kill(); + + (void)signal(signo, SIG_DFL); + raise(signo); +} + +static void test_atexit(void) +{ + test_server_kill(); +} + +int main(int argc, char *argv[]) +{ + int c; + + atexit(test_atexit); + (void)signal(SIGCHLD, SIG_IGN); + (void)signal(SIGTERM, test_signal_handler); + (void)signal(SIGQUIT, test_signal_handler); + (void)signal(SIGINT, test_signal_handler); + (void)signal(SIGSEGV, test_signal_handler); + (void)signal(SIGABRT, test_signal_handler); + + while ((c = getopt(argc, argv, "D")) > 0) { + switch (c) { + case 'D': + debug = TRUE; + break; + default: + i_fatal("Usage: %s [-D]", argv[0]); + } + } + + /* listen on localhost */ + i_zero(&bind_ip); + bind_ip.family = AF_INET; + bind_ip.u.ip4.s_addr = htonl(INADDR_LOOPBACK); + + test_run(test_functions); +}