summaryrefslogtreecommitdiffstats
path: root/src/lib-smtp/test-smtp-server-errors.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-smtp/test-smtp-server-errors.c')
-rw-r--r--src/lib-smtp/test-smtp-server-errors.c3883
1 files changed, 3883 insertions, 0 deletions
diff --git a/src/lib-smtp/test-smtp-server-errors.c b/src/lib-smtp/test-smtp-server-errors.c
new file mode 100644
index 0000000..d3e528c
--- /dev/null
+++ b/src/lib-smtp/test-smtp-server-errors.c
@@ -0,0 +1,3883 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "array.h"
+#include "hostpid.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "time-util.h"
+#include "sleep.h"
+#include "connection.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+#include "smtp-address.h"
+#include "smtp-reply-parser.h"
+#include "smtp-server.h"
+
+#include <unistd.h>
+
+#define SERVER_MAX_TIMEOUT_MSECS 10*1000
+#define CLIENT_KILL_TIMEOUT_SECS 20
+
+static void main_deinit(void);
+
+/*
+ * Types
+ */
+
+struct server_connection {
+ void *context;
+};
+
+struct client_connection {
+ struct connection conn;
+ void *context;
+
+ pool_t pool;
+};
+
+typedef void
+(*test_server_init_t)(const struct smtp_server_settings *server_set);
+typedef void (*test_client_init_t)(unsigned int index);
+
+/*
+ * State
+ */
+
+/* common */
+static struct ip_addr bind_ip;
+static in_port_t bind_port = 0;
+static struct ioloop *ioloop;
+static bool debug = FALSE;
+
+/* server */
+static struct smtp_server *smtp_server = NULL;
+static struct io *io_listen;
+static int fd_listen = -1;
+static size_t server_io_buffer_size = 0;
+static struct smtp_server_callbacks server_callbacks;
+static unsigned int server_pending;
+
+/* client */
+static struct connection_list *client_conn_list;
+static unsigned int client_index;
+static void (*test_client_connected)(struct client_connection *conn);
+static void (*test_client_input)(struct client_connection *conn);
+static void (*test_client_deinit)(struct client_connection *conn);
+
+/*
+ * Forward declarations
+ */
+
+/* server */
+static void test_server_defaults(struct smtp_server_settings *smtp_set);
+static void test_server_run(const struct smtp_server_settings *smtp_set);
+
+/* client */
+static void test_client_run(unsigned int index);
+
+/* test*/
+static void
+test_run_client_server(const struct smtp_server_settings *server_set,
+ test_server_init_t server_test,
+ test_client_init_t client_test,
+ unsigned int client_tests_count) ATTR_NULL(3);
+
+/*
+ * Slow server
+ */
+
+/* client */
+
+static void test_slow_server_input(struct client_connection *conn ATTR_UNUSED)
+{
+ /* do nothing */
+ sleep(10);
+}
+
+static void test_slow_server_connected(struct client_connection *conn)
+{
+ if (debug)
+ i_debug("CONNECTED");
+
+ o_stream_nsend_str(conn->conn.output,
+ "EHLO frop\r\n");
+}
+
+static void test_client_slow_server(unsigned int index)
+{
+ test_client_input = test_slow_server_input;
+ test_client_connected = test_slow_server_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _slow_server {
+ struct smtp_server_cmd_ctx *cmd;
+ struct timeout *to_delay;
+ bool serviced:1;
+};
+
+static void
+test_server_slow_server_destroyed(struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct _slow_server *ctx)
+{
+ test_assert(ctx->serviced);
+ timeout_remove(&ctx->to_delay);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void test_server_slow_server_delayed(struct _slow_server *ctx)
+{
+ struct smtp_server_reply *reply;
+ struct smtp_server_cmd_ctx *cmd = ctx->cmd;
+
+ reply = smtp_server_reply_create_ehlo(cmd->cmd);
+ smtp_server_reply_ehlo_add(reply, "FROP");
+
+ smtp_server_reply_submit(reply);
+ ctx->serviced = TRUE;
+}
+
+static int
+test_server_slow_server_cmd_helo(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_cmd_helo *data ATTR_UNUSED)
+{
+ struct _slow_server *ctx;
+
+ if (debug)
+ i_debug("HELO");
+
+ ctx = i_new(struct _slow_server, 1);
+ ctx->cmd = cmd;
+
+ smtp_server_command_add_hook(cmd->cmd,
+ SMTP_SERVER_COMMAND_HOOK_DESTROY,
+ test_server_slow_server_destroyed, ctx);
+
+ ctx->to_delay = timeout_add(4000, test_server_slow_server_delayed, ctx);
+
+ return 0;
+}
+
+static void
+test_server_slow_server(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_cmd_helo = test_server_slow_server_cmd_helo;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_slow_server(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("slow server");
+ test_run_client_server(&smtp_server_set,
+ test_server_slow_server,
+ test_client_slow_server, 1);
+ test_end();
+}
+
+/*
+ * Slow client
+ */
+
+/* client */
+
+static void test_slow_client_input(struct client_connection *conn ATTR_UNUSED)
+{
+ /* nothing */
+}
+
+static void test_slow_client_connected(struct client_connection *conn)
+{
+ if (debug)
+ i_debug("CONNECTED");
+
+ o_stream_nsend_str(conn->conn.output, "EHLO frop\r\n");
+}
+
+static void test_client_slow_client(unsigned int index)
+{
+ test_client_input = test_slow_client_input;
+ test_client_connected = test_slow_client_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _slow_client {
+ struct smtp_server_cmd_ctx *cmd;
+ struct timeout *to_delay;
+ struct timeout *to_disconnect;
+ bool serviced:1;
+};
+
+static void test_server_slow_client_disconnect_timeout(struct _slow_client *ctx)
+{
+ test_assert(FALSE);
+
+ timeout_remove(&ctx->to_disconnect);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void
+test_server_slow_client_disconnect(void *conn_ctx, const char *reason)
+{
+ struct server_connection *conn = (struct server_connection *)conn_ctx;
+ struct _slow_client *ctx = (struct _slow_client *)conn->context;
+
+ if (debug)
+ i_debug("DISCONNECTED: %s", reason);
+
+ timeout_remove(&ctx->to_disconnect);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static void
+test_server_slow_client_cmd_destroyed(
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED, struct _slow_client *ctx)
+{
+ test_assert(ctx->serviced);
+ timeout_remove(&ctx->to_delay);
+}
+
+static void test_server_slow_client_delayed(struct _slow_client *ctx)
+{
+ struct smtp_server_reply *reply;
+ struct smtp_server_cmd_ctx *cmd = ctx->cmd;
+
+ timeout_remove(&ctx->to_delay);
+
+ reply = smtp_server_reply_create_ehlo(cmd->cmd);
+ smtp_server_reply_ehlo_add(reply, "FROP");
+
+ ctx->to_disconnect = timeout_add(
+ 2000, test_server_slow_client_disconnect_timeout, ctx);
+
+ smtp_server_reply_submit(reply);
+ ctx->serviced = TRUE;
+}
+
+static int
+test_server_slow_client_cmd_helo(void *conn_ctx,
+ struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_cmd_helo *data ATTR_UNUSED)
+{
+ struct server_connection *conn = (struct server_connection *)conn_ctx;
+ struct _slow_client *ctx;
+
+ if (debug)
+ i_debug("HELO");
+
+ ctx = i_new(struct _slow_client, 1);
+ ctx->cmd = cmd;
+
+ conn->context = ctx;
+
+ smtp_server_command_add_hook(cmd->cmd,
+ SMTP_SERVER_COMMAND_HOOK_DESTROY,
+ test_server_slow_client_cmd_destroyed,
+ ctx);
+
+ ctx->to_delay = timeout_add_short(500,
+ test_server_slow_client_delayed, ctx);
+
+ return 0;
+}
+
+static void
+test_server_slow_client(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect = test_server_slow_client_disconnect;
+ server_callbacks.conn_cmd_helo = test_server_slow_client_cmd_helo;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_slow_client(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("slow client");
+ test_run_client_server(&smtp_server_set,
+ test_server_slow_client,
+ test_client_slow_client, 1);
+ test_end();
+}
+
+/*
+ * Hanging command payload
+ */
+
+/* client */
+
+static void
+test_hanging_command_payload_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "EHLO frop\r\n"
+ "MAIL FROM:<hangman@example.com>\r\n"
+ "RCPT TO:<jerry@example.com>\r\n"
+ "DATA\r\n"
+ "To be continued... or not");
+}
+
+static void test_client_hanging_command_payload(unsigned int index)
+{
+ test_client_connected = test_hanging_command_payload_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _hanging_command_payload {
+ struct istream *payload_input;
+ struct io *io;
+
+ bool serviced:1;
+};
+
+static void
+test_server_hanging_command_payload_trans_free(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_transaction *trans)
+{
+ struct _hanging_command_payload *ctx =
+ (struct _hanging_command_payload *)trans->context;
+
+ test_assert(!ctx->serviced);
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_hanging_command_payload_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt)
+{
+ if (debug) {
+ i_debug("RCPT TO:%s",
+ smtp_address_encode(rcpt->path));
+ }
+
+ return 1;
+}
+
+static int
+test_server_hanging_command_payload_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans, struct istream *data_input)
+{
+ struct _hanging_command_payload *ctx;
+
+ if (debug)
+ i_debug("DATA");
+
+ ctx = i_new(struct _hanging_command_payload, 1);
+ trans->context = ctx;
+
+ ctx->payload_input = data_input;
+ return 0;
+}
+
+static int
+test_server_hanging_command_payload_data_continue(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans)
+{
+ struct _hanging_command_payload *ctx =
+ (struct _hanging_command_payload *)trans->context;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+
+ if (debug)
+ i_debug("DATA continue");
+
+ while ((ret = i_stream_read_data(ctx->payload_input,
+ &data, &size, 0)) > 0)
+ i_stream_skip(ctx->payload_input, size);
+
+ if (ret == 0)
+ return 0;
+ if (ctx->payload_input->stream_errno != 0) {
+ i_error("failed to read DATA payload: %s",
+ i_stream_get_error(ctx->payload_input));
+ return -1;
+ }
+
+ i_assert(ctx->payload_input->eof);
+
+ smtp_server_reply(cmd, 250, "2.0.0", "OK");
+ ctx->serviced = TRUE;
+
+ i_stream_unref(&ctx->payload_input);
+ return 1;
+}
+
+static void
+test_server_hanging_command_payload(
+ const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_hanging_command_payload_trans_free;
+
+ server_callbacks.conn_cmd_rcpt =
+ test_server_hanging_command_payload_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_hanging_command_payload_data_begin;
+ server_callbacks.conn_cmd_data_continue =
+ test_server_hanging_command_payload_data_continue;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_hanging_command_payload(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("hanging command payload");
+ test_run_client_server(&smtp_server_set,
+ test_server_hanging_command_payload,
+ test_client_hanging_command_payload, 1);
+ test_end();
+}
+
+/*
+ * Bad command
+ */
+
+/* client */
+
+static void
+test_bad_command_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output, "EHLO\tfrop\r\n");
+}
+
+static void test_client_bad_command(unsigned int index)
+{
+ test_client_connected = test_bad_command_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _bad_command {
+ struct istream *payload_input;
+ struct io *io;
+
+ bool serviced:1;
+};
+
+static void
+test_server_bad_command_disconnect(void *context ATTR_UNUSED,
+ const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_bad_command_helo(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_cmd_helo *data ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_bad_command_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ return 1;
+}
+
+static int
+test_server_bad_command_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ smtp_server_reply(cmd, 250, "2.0.0", "OK");
+ return 1;
+}
+
+static void
+test_server_bad_command(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_bad_command_disconnect;
+
+ server_callbacks.conn_cmd_helo =
+ test_server_bad_command_helo;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_bad_command_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_bad_command_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_command(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("bad command");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_command,
+ test_client_bad_command, 1);
+ test_end();
+}
+
+/*
+ * Many bad commands
+ */
+
+/* client */
+
+struct _many_bad_commands_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+ bool replied:1;
+};
+
+static void
+test_many_bad_commands_client_input(struct client_connection *conn)
+{
+ struct _many_bad_commands_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret=smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY #%u: %s", ctx->reply, smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ /* greeting */
+ case 0:
+ i_assert(reply->status == 220);
+ break;
+ /* bad command reply */
+ case 1: case 2: case 3: case 4: case 5:
+ case 6: case 7: case 8: case 9: case 10:
+ i_assert(reply->status == 500);
+ break;
+ case 11:
+ i_assert(reply->status == 421);
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void
+test_many_bad_commands_client_connected(struct client_connection *conn)
+{
+ struct _many_bad_commands_client *ctx;
+
+ ctx = p_new(conn->pool, struct _many_bad_commands_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(conn->conn.output,
+ "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
+ break;
+ case 1:
+ o_stream_nsend_str(conn->conn.output,
+ "a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\n"
+ "i\r\nj\r\nk\r\nl\r\nm\r\nn\r\no\r\np\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void
+test_many_bad_commands_client_deinit(struct client_connection *conn)
+{
+ struct _many_bad_commands_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_many_bad_commands(unsigned int index)
+{
+ test_client_input = test_many_bad_commands_client_input;
+ test_client_connected = test_many_bad_commands_client_connected;
+ test_client_deinit = test_many_bad_commands_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_many_bad_commands_disconnect(void *context ATTR_UNUSED,
+ const char *reason)
+{
+ struct server_connection *sconn = context;
+
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+
+ sconn->context = POINTER_CAST(POINTER_CAST_TO(sconn->context,
+ unsigned int) + 1);
+
+ if (POINTER_CAST_TO(sconn->context, unsigned int) == 2)
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_many_bad_commands_helo(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_cmd_helo *data ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_many_bad_commands_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_many_bad_commands_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void test_server_many_bad_commands
+(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_many_bad_commands_disconnect;
+
+ server_callbacks.conn_cmd_helo =
+ test_server_many_bad_commands_helo;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_many_bad_commands_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_many_bad_commands_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_many_bad_commands(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_bad_commands = 10;
+
+ test_begin("many bad commands");
+ test_run_client_server(&smtp_server_set,
+ test_server_many_bad_commands,
+ test_client_many_bad_commands, 2);
+ test_end();
+}
+
+/*
+ * Long command
+ */
+
+/* client */
+
+static void test_long_command_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(
+ conn->conn.output,
+ "EHLO some.very.very.very.very.very.long.domain\r\n");
+}
+
+static void test_client_long_command(unsigned int index)
+{
+ test_client_connected = test_long_command_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _long_command {
+ struct istream *payload_input;
+ struct io *io;
+
+ bool serviced:1;
+};
+
+static void
+test_server_long_command_disconnect(void *context ATTR_UNUSED,
+ const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_long_command_helo(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_cmd_helo *data ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_long_command_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ return 1;
+}
+
+static int
+test_server_long_command_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ smtp_server_reply(cmd, 250, "2.0.0", "OK");
+ return 1;
+}
+
+static void
+test_server_long_command(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_long_command_disconnect;
+
+ server_callbacks.conn_cmd_helo =
+ test_server_long_command_helo;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_long_command_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_long_command_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_long_command(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.command_limits.max_parameters_size = 32;
+
+ test_begin("long command");
+ test_run_client_server(&smtp_server_set,
+ test_server_long_command,
+ test_client_long_command, 1);
+ test_end();
+}
+
+/*
+ * Long auth line
+ */
+
+/* client */
+
+#define _LONG_AUTH_LINE_DATA \
+ "dXNlcj10ZXN0dXNlcjEBYXV0aD1CZWFyZXIgZXlKaGJHY2lPaUpTVXpJMU5pSXNJ" \
+ "blI1Y0NJZ09pQWlTbGRVSWl3aWEybGtJaUE2SUNKdVRIRlVlRnBXWVhKSlgwWndS" \
+ "a0Z3Umt3MloyUnhiak4xV1VSS2R6WnNWVjlMYVZoa2JWazJialpSSW4wLmV5Smxl" \
+ "SEFpT2pFMk16UTJNemMyTlRFc0ltbGhkQ0k2TVRZek5EWXpOek0xTVN3aWFuUnBJ" \
+ "am9pT1RFM1lUYzFaalF0WTJZME9DMDBOVEEyTFRnNVpXSXRNRE13WldaaU5tSTVO" \
+ "MlZrSWl3aWFYTnpJam9pYUhSMGNEb3ZMekU1TWk0eE5qZ3VNUzR5TVRveE9EQTRN" \
+ "QzloZFhSb0wzSmxZV3h0Y3k5eVpXeDBaWE4wSWl3aVlYVmtJam9pWVdOamIzVnVk" \
+ "Q0lzSW5OMVlpSTZJamhsWVRRME1UWTNMVGN6TTJVdE5EVTBZeTFpT0dJMUxXTmpa" \
+ "bVl3WkRnek1URTVaQ0lzSW5SNWNDSTZJa0psWVhKbGNpSXNJbUY2Y0NJNkltUnZk" \
+ "bVZqYjNRaUxDSnpaWE56YVc5dVgzTjBZWFJsSWpvaU1tTTNPVEUzWldJdE16QTFO" \
+ "UzAwTkRZeExXSXdZell0WTJVeFlUbGlNVEZoTWpReklpd2lZV055SWpvaU1TSXNJ" \
+ "bkpsWVd4dFgyRmpZMlZ6Y3lJNmV5SnliMnhsY3lJNld5SnZabVpzYVc1bFgyRmpZ" \
+ "MlZ6Y3lJc0luVnRZVjloZFhSb2IzSnBlbUYwYVc5dUlsMTlMQ0p5WlhOdmRYSmpa" \
+ "VjloWTJObGMzTWlPbnNpWVdOamIzVnVkQ0k2ZXlKeWIyeGxjeUk2V3lKdFlXNWha" \
+ "MlV0WVdOamIzVnVkQ0lzSW0xaGJtRm5aUzFoWTJOdmRXNTBMV3hwYm10eklpd2lk" \
+ "bWxsZHkxd2NtOW1hV3hsSWwxOWZTd2ljMk52Y0dVaU9pSndjbTltYVd4bElHVnRZ" \
+ "V2xzSWl3aVpXMWhhV3hmZG1WeWFXWnBaV1FpT21aaGJITmxMQ0p1WVcxbElqb2lk" \
+ "R1Z6ZEhWelpYSXhJRUYxZEc5SFpXNWxjbUYwWldRaUxDSndjbVZtWlhKeVpXUmZk" \
+ "WE5sY201aGJXVWlPaUowWlhOMGRYTmxjakVpTENKbmFYWmxibDl1WVcxbElqb2lk" \
+ "R1Z6ZEhWelpYSXhJaXdpWm1GdGFXeDVYMjVoYldVaU9pSkJkWFJ2UjJWdVpYSmhk" \
+ "R1ZrSWl3aVpXMWhhV3dpT2lKMFpYTjBkWE5sY2pGQWJYbGtiMjFoYVc0dWIzZ2lm" \
+ "US5ta2JGSURpT0FhbENCcVMwODRhVHJURjBIdDk1c1Z4cGlSbTFqZnhJd0JiN1hM" \
+ "M2gzWUJkdXVrVXlZdDJqX1pqUFlhMDhDcVVYNWFrLVBOSjdSVWRTUXNmUlgwM1Zi" \
+ "cXA4MHFZZjNGYzJpcDR0YmhHLXFEV0R6NzdhZDhWcEFNei16YWlSamZCclZ2R3hB" \
+ "T3ZsZnFDVWhaZTJDR3ZqWjZ1Q3RKTlFaS0dyazZHOXoxX2pqekZkTjBXWjUxbEZs" \
+ "US1JdE5LREpoTjNIekJ5SW93M19qQU9kWEI0R0w4R3JHM1hqU09rSFVRam5GTEQw" \
+ "QUF1QXY4SkxmTXY1NGc1a2tKaklxRFgxZlgyWVo0Y2JQOWV3TUp6UV84ZWdLeW5T" \
+ "VV9XSk8xRU9Qa1NVZjlMX19RX3FwY0dNbzFtTkxuTURKUlU2dmZFY3JrM2k0cVNz" \
+ "MXRPdHdLaHcBAQ"
+
+struct _long_auth_line_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void test_long_auth_line_client_input(struct client_connection *conn)
+{
+ struct _long_auth_line_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* EHLO reply */
+ i_assert(reply->status == 250);
+ break;
+ case 2: /* AUTH continue */
+ i_assert(reply->status == 334);
+ break;
+ case 3: /* AUTH reply */
+ switch (client_index) {
+ case 0:
+ i_assert(reply->status == 235);
+ break;
+ case 1:
+ i_assert(reply->status == 235);
+ break;
+ case 2:
+ i_assert(reply->status == 500);
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ break;
+ case 4: /* MAIL reply */
+ i_assert(client_index < 2);
+ i_assert(reply->status == 250);
+ break;
+ case 5: /* RCPT reply */
+ i_assert(client_index < 2);
+ i_assert(reply->status == 250);
+ break;
+ case 6: /* DATA initial reply */
+ i_assert(client_index < 2);
+ i_assert(reply->status == 354);
+ break;
+ case 7: /* DATA reply */
+ i_assert(client_index < 2);
+ i_assert(reply->status == 250);
+ break;
+ case 8: /* QUIT reply */
+ i_assert(client_index < 2);
+ i_assert(reply->status == 221);
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void test_long_auth_line_client_connected(struct client_connection *conn)
+{
+ struct _long_auth_line_client *ctx;
+ unsigned int i;
+
+ ctx = p_new(conn->pool, struct _long_auth_line_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ o_stream_nsend_str(
+ conn->conn.output,
+ "EHLO frop\r\n"
+ "AUTH XOAUTH2\r\n");
+ for (i = 0; i < (client_index > 1 ? 6 : 1); i++)
+ o_stream_nsend_str(conn->conn.output, _LONG_AUTH_LINE_DATA);
+ o_stream_nsend_str(
+ conn->conn.output,
+ "==");
+ if (client_index == 1) {
+ o_stream_nsend_str(
+ conn->conn.output,
+ " ");
+ }
+ o_stream_nsend_str(
+ conn->conn.output,
+ "\r\n"
+ "MAIL FROM:<user@example.com>\r\n"
+ "RCPT TO:<user@example.com>\r\n"
+ "DATA\r\n"
+ "frop\r\n"
+ ".\r\n"
+ "QUIT\r\n");
+}
+
+static void test_long_auth_line_client_deinit(struct client_connection *conn)
+{
+ struct _long_auth_line_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_long_auth_line(unsigned int index)
+{
+ test_client_input = test_long_auth_line_client_input;
+ test_client_connected = test_long_auth_line_client_connected;
+ test_client_deinit = test_long_auth_line_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _long_auth_line {
+ struct istream *payload_input;
+};
+
+static void
+test_server_long_auth_line_disconnect(void *context ATTR_UNUSED,
+ const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_long_auth_line_helo(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_cmd_helo *data ATTR_UNUSED)
+{
+ return 1;
+}
+
+static int
+test_server_long_auth_line_auth(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_cmd_auth *data ATTR_UNUSED)
+{
+ smtp_server_cmd_auth_send_challenge(cmd, "");
+ return 0;
+}
+
+static int
+test_server_long_auth_line_auth_continue(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd,
+ const char *response)
+{
+ if (strcmp(response, _LONG_AUTH_LINE_DATA"==") == 0)
+ smtp_server_cmd_auth_success(cmd, "user", NULL);
+ else {
+ smtp_server_reply(cmd, 535, "5.7.8",
+ "Authentication credentials invalid");
+ }
+ return 1;
+}
+
+static int
+test_server_long_auth_line_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ if (debug)
+ i_debug("RCPT");
+ return 1;
+}
+
+static int
+test_server_long_auth_line_data_begin(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans,
+ struct istream *data_input)
+{
+ struct _long_auth_line *ctx;
+
+ if (debug)
+ i_debug("DATA");
+
+ ctx = p_new(trans->pool, struct _long_auth_line, 1);
+ trans->context = ctx;
+
+ ctx->payload_input = data_input;
+ return 0;
+}
+
+static int
+test_server_long_auth_line_data_continue(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans)
+{
+ struct _long_auth_line *ctx = (struct _long_auth_line *)trans->context;
+ struct istream *data_input = ctx->payload_input;
+ size_t size;
+ ssize_t ret;
+
+ if (debug)
+ i_debug("DATA continue");
+
+ while ((ret = i_stream_read(data_input)) > 0 || ret == -2) {
+ (void)i_stream_get_data(data_input, &size);
+ i_stream_skip(data_input, size);
+ if (!smtp_server_cmd_data_check_size(cmd))
+ return -1;
+ }
+
+ if (ret == 0)
+ return 0;
+ if (ret < 0 && data_input->stream_errno != 0) {
+ /* Client probably disconnected */
+ return -1;
+ }
+
+ smtp_server_reply_all(cmd, 250, "2.0.0", "Accepted");
+ return 1;
+}
+
+static void
+test_server_long_auth_line(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_long_auth_line_disconnect;
+
+ server_callbacks.conn_cmd_helo =
+ test_server_long_auth_line_helo;
+ server_callbacks.conn_cmd_auth =
+ test_server_long_auth_line_auth;
+ server_callbacks.conn_cmd_auth_continue =
+ test_server_long_auth_line_auth_continue;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_long_auth_line_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_long_auth_line_data_begin;
+ server_callbacks.conn_cmd_data_continue =
+ test_server_long_auth_line_data_continue;
+ test_server_run(server_set);
+}
+
+static void
+test_server_long_auth_line_small_buf(
+ const struct smtp_server_settings *server_set)
+{
+ server_io_buffer_size = 1024;
+
+ server_callbacks.conn_disconnect =
+ test_server_long_auth_line_disconnect;
+
+ server_callbacks.conn_cmd_helo =
+ test_server_long_auth_line_helo;
+ server_callbacks.conn_cmd_auth =
+ test_server_long_auth_line_auth;
+ server_callbacks.conn_cmd_auth_continue =
+ test_server_long_auth_line_auth_continue;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_long_auth_line_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_long_auth_line_data_begin;
+ server_callbacks.conn_cmd_data_continue =
+ test_server_long_auth_line_data_continue;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_long_auth_line(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities = SMTP_CAPABILITY_AUTH;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("long auth line");
+ test_run_client_server(&smtp_server_set,
+ test_server_long_auth_line,
+ test_client_long_auth_line, 3);
+ test_end();
+}
+
+static void test_long_auth_line_small_buf(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities = SMTP_CAPABILITY_AUTH;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("long auth line (small i/o buffers)");
+ test_run_client_server(&smtp_server_set,
+ test_server_long_auth_line_small_buf,
+ test_client_long_auth_line, 3);
+ test_end();
+}
+
+
+/*
+ * Big data
+ */
+
+/* client */
+
+static void
+test_big_data_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "EHLO frop\r\n"
+ "MAIL FROM:<sender@example.com>\r\n"
+ "RCPT TO:<recipient@example.com>\r\n"
+ "DATA\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ ".\r\n");
+}
+
+static void test_client_big_data(unsigned int index)
+{
+ test_client_connected = test_big_data_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _big_data {
+ struct istream *payload_input;
+ struct io *io;
+};
+
+static void
+test_server_big_data_trans_free(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_transaction *trans)
+{
+ struct _big_data *ctx = (struct _big_data *)trans->context;
+
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_big_data_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt)
+{
+ if (debug) {
+ i_debug("RCPT TO:%s",
+ smtp_address_encode(rcpt->path));
+ }
+ return 1;
+}
+
+static int
+test_server_big_data_data_begin(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans,
+ struct istream *data_input)
+{
+ struct _big_data *ctx;
+
+ if (debug)
+ i_debug("DATA");
+
+ ctx = i_new(struct _big_data, 1);
+ trans->context = ctx;
+
+ ctx->payload_input = data_input;
+ return 0;
+}
+
+static int
+test_server_big_data_data_continue(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans)
+{
+ static const size_t max_size = 32;
+ struct _big_data *ctx = (struct _big_data *)trans->context;
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+
+ if (debug)
+ i_debug("DATA continue");
+
+ while (ctx->payload_input->v_offset < max_size &&
+ (ret = i_stream_read_data(ctx->payload_input,
+ &data, &size, 0)) > 0) {
+ if (ctx->payload_input->v_offset + size > max_size) {
+ if (ctx->payload_input->v_offset >= max_size)
+ size = 0;
+ else
+ size = max_size - ctx->payload_input->v_offset;
+ }
+ i_stream_skip(ctx->payload_input, size);
+
+ if (ctx->payload_input->v_offset >= max_size)
+ break;
+ }
+
+ if (ctx->payload_input->v_offset >= max_size) {
+ smtp_server_reply_early(cmd, 552, "5.3.4",
+ "Message too big for system");
+ return -1;
+ }
+
+ if (ret == 0)
+ return 0;
+
+ test_assert(FALSE);
+ return 1;
+}
+
+static void test_server_big_data(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_big_data_trans_free;
+
+ server_callbacks.conn_cmd_rcpt =
+ test_server_big_data_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_big_data_data_begin;
+ server_callbacks.conn_cmd_data_continue =
+ test_server_big_data_data_continue;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_big_data(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.command_limits.max_data_size = 64;
+
+ test_begin("big_data");
+ test_run_client_server(&smtp_server_set,
+ test_server_big_data,
+ test_client_big_data, 1);
+ test_end();
+}
+
+/*
+ * Bad HELO
+ */
+
+/* client */
+
+struct _bad_helo_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void test_bad_helo_client_input(struct client_connection *conn)
+{
+ struct _bad_helo_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ for (;;) {
+ if (ctx->reply != 1 ||
+ client_index == 0 || client_index == 2) {
+ ret = smtp_reply_parse_next(ctx->parser, FALSE, &reply,
+ &error);
+ } else {
+ ret = smtp_reply_parse_ehlo(ctx->parser, &reply,
+ &error);
+ }
+ if (ret <= 0)
+ break;
+
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* bad command reply */
+ switch (client_index) {
+ case 0: case 1:
+ i_assert(reply->status == 501);
+ break;
+ case 2: case 3:
+ i_assert(reply->status == 250);
+ break;
+ default:
+ i_unreached();
+ }
+ if (debug)
+ i_debug("REPLIED");
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void test_bad_helo_client_connected(struct client_connection *conn)
+{
+ struct _bad_helo_client *ctx;
+
+ ctx = p_new(conn->pool, struct _bad_helo_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(conn->conn.output, "HELO\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(conn->conn.output, "EHLO\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(conn->conn.output, "HELO frop\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(conn->conn.output, "EHLO frop\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void test_bad_helo_client_deinit(struct client_connection *conn)
+{
+ struct _bad_helo_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_bad_helo(unsigned int index)
+{
+ test_client_input = test_bad_helo_client_input;
+ test_client_connected = test_bad_helo_client_connected;
+ test_client_deinit = test_bad_helo_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _bad_helo {
+ struct istream *payload_input;
+ struct io *io;
+
+ bool serviced:1;
+};
+
+static void
+test_server_bad_helo_disconnect(void *context ATTR_UNUSED, const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_bad_helo_helo(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_cmd_helo *data ATTR_UNUSED)
+{
+ return 1;
+}
+
+static int
+test_server_bad_helo_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ return 1;
+}
+
+static int
+test_server_bad_helo_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ smtp_server_reply(cmd, 250, "2.0.0", "OK");
+ return 1;
+}
+
+static void test_server_bad_helo(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_bad_helo_disconnect;
+
+ server_callbacks.conn_cmd_helo =
+ test_server_bad_helo_helo;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_bad_helo_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_bad_helo_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_helo(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("bad HELO");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_helo,
+ test_client_bad_helo, 4);
+ test_end();
+}
+
+/*
+ * Bad MAIL
+ */
+
+/* client */
+
+struct _bad_mail_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void test_bad_mail_client_input(struct client_connection *conn)
+{
+ struct _bad_mail_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* bad command reply */
+ switch (client_index) {
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6:
+ i_assert(reply->status == 501);
+ break;
+ case 7: case 8:
+ i_assert(reply->status == 250);
+ break;
+ default:
+ i_unreached();
+ }
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void test_bad_mail_client_connected(struct client_connection *conn)
+{
+ struct _bad_mail_client *ctx;
+
+ ctx = p_new(conn->pool, struct _bad_mail_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: <hendrik@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:hendrik@example.com\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: hendrik@example.com\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\r\n");
+ break;
+ case 4:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: \r\n");
+ break;
+ case 5:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: BODY=7BIT\r\n");
+ break;
+ case 6:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: <>\r\n");
+ break;
+ case 7:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n");
+ break;
+ case 8:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<>\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void test_bad_mail_client_deinit(struct client_connection *conn)
+{
+ struct _bad_mail_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_bad_mail(unsigned int index)
+{
+ test_client_input = test_bad_mail_client_input;
+ test_client_connected = test_bad_mail_client_connected;
+ test_client_deinit = test_bad_mail_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_bad_mail_disconnect(void *context ATTR_UNUSED, const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_bad_mail_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_bad_mail_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void test_server_bad_mail(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_bad_mail_disconnect;
+
+ server_callbacks.conn_cmd_rcpt =
+ test_server_bad_mail_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_bad_mail_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_mail(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("bad MAIL");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_mail,
+ test_client_bad_mail, 9);
+ test_end();
+}
+
+/*
+ * Bad RCPT
+ */
+
+/* client */
+
+struct _bad_rcpt_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void
+test_bad_rcpt_client_input(struct client_connection *conn)
+{
+ struct _bad_rcpt_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* MAIL FROM */
+ i_assert(reply->status == 250);
+ break;
+ case 2: /* bad command reply */
+ switch (client_index) {
+ case 0: case 1: case 2: case 3: case 4: case 5:
+ i_assert(reply->status == 501);
+ break;
+ case 6:
+ i_assert(reply->status == 250);
+ break;
+ default:
+ i_unreached();
+ }
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void test_bad_rcpt_client_connected(struct client_connection *conn)
+{
+ struct _bad_rcpt_client *ctx;
+
+ ctx = p_new(conn->pool, struct _bad_rcpt_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: <harrie@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:harrie@example.com\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: harrie@example.com\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\r\n");
+ break;
+ case 4:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: \r\n");
+ break;
+ case 5:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: NOTIFY=NEVER\r\n");
+ break;
+ case 6:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:<harrie@example.com>\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void test_bad_rcpt_client_deinit(struct client_connection *conn)
+{
+ struct _bad_rcpt_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_bad_rcpt(unsigned int index)
+{
+ test_client_input = test_bad_rcpt_client_input;
+ test_client_connected = test_bad_rcpt_client_connected;
+ test_client_deinit = test_bad_rcpt_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_bad_rcpt_disconnect(void *context ATTR_UNUSED, const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_bad_rcpt_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ return 1;
+}
+
+static int
+test_server_bad_rcpt_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void test_server_bad_rcpt(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_bad_rcpt_disconnect;
+
+ server_callbacks.conn_cmd_rcpt =
+ test_server_bad_rcpt_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_bad_rcpt_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_rcpt(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("bad RCPT");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_rcpt,
+ test_client_bad_rcpt, 7);
+ test_end();
+}
+
+/*
+ * Bad VRFY
+ */
+
+/* client */
+
+struct _bad_vrfy_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void
+test_bad_vrfy_client_input(struct client_connection *conn)
+{
+ struct _bad_vrfy_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* bad command reply */
+ switch (client_index) {
+ case 0: case 1: case 2:
+ i_assert(reply->status == 501);
+ break;
+ case 3:
+ i_assert(smtp_reply_is_success(reply));
+ break;
+ default:
+ i_unreached();
+ }
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret == 0);
+}
+
+static void
+test_bad_vrfy_client_connected(struct client_connection *conn)
+{
+ struct _bad_vrfy_client *ctx;
+
+ ctx = p_new(conn->pool, struct _bad_vrfy_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(conn->conn.output,
+ "VRFY\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(conn->conn.output,
+ "VRFY \"hendrik\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(conn->conn.output,
+ "VRFY hen\"drik\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(conn->conn.output,
+ "VRFY \"hendrik\"\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void
+test_bad_vrfy_client_deinit(struct client_connection *conn)
+{
+ struct _bad_vrfy_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_bad_vrfy(unsigned int index)
+{
+ test_client_input = test_bad_vrfy_client_input;
+ test_client_connected = test_bad_vrfy_client_connected;
+ test_client_deinit = test_bad_vrfy_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_bad_vrfy_disconnect(void *context ATTR_UNUSED, const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_bad_vrfy_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_bad_vrfy_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_bad_vrfy(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect = test_server_bad_vrfy_disconnect;
+
+ server_callbacks.conn_cmd_rcpt = test_server_bad_vrfy_rcpt;
+ server_callbacks.conn_cmd_data_begin = test_server_bad_vrfy_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_vrfy(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("bad VRFY");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_vrfy,
+ test_client_bad_vrfy, 4);
+ test_end();
+}
+
+/*
+ * Bad NOOP
+ */
+
+/* client */
+
+struct _bad_noop_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void
+test_bad_noop_client_input(struct client_connection *conn)
+{
+ struct _bad_noop_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* bad command reply */
+ switch (client_index) {
+ case 1: case 2:
+ i_assert(reply->status == 501);
+ break;
+ case 0: case 3:
+ i_assert(smtp_reply_is_success(reply));
+ break;
+ default:
+ i_unreached();
+ }
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret == 0);
+}
+
+static void
+test_bad_noop_client_connected(struct client_connection *conn)
+{
+ struct _bad_noop_client *ctx;
+
+ ctx = p_new(conn->pool, struct _bad_noop_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(conn->conn.output,
+ "NOOP\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(conn->conn.output,
+ "NOOP \"frop\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(conn->conn.output,
+ "NOOP fr\"op\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(conn->conn.output,
+ "NOOP \"frop\"\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void
+test_bad_noop_client_deinit(struct client_connection *conn)
+{
+ struct _bad_noop_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_bad_noop(unsigned int index)
+{
+ test_client_input = test_bad_noop_client_input;
+ test_client_connected = test_bad_noop_client_connected;
+ test_client_deinit = test_bad_noop_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_bad_noop_disconnect(void *context ATTR_UNUSED, const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_bad_noop_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_bad_noop_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_bad_noop(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect = test_server_bad_noop_disconnect;
+
+ server_callbacks.conn_cmd_rcpt = test_server_bad_noop_rcpt;
+ server_callbacks.conn_cmd_data_begin = test_server_bad_noop_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_noop(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("bad NOOP");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_noop,
+ test_client_bad_noop, 4);
+ test_end();
+}
+
+/*
+ * MAIL workarounds
+ */
+
+/* client */
+
+struct _mail_workarounds_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void test_mail_workarounds_client_input(struct client_connection *conn)
+{
+ struct _mail_workarounds_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* bad command reply */
+ switch (client_index) {
+ case 5: case 6: case 7:
+ i_assert(reply->status == 501);
+ break;
+ case 0: case 1: case 2: case 3: case 4: case 8:
+ case 9: case 10:
+ i_assert(reply->status == 250);
+ break;
+ default:
+ i_unreached();
+ }
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void
+test_mail_workarounds_client_connected(struct client_connection *conn)
+{
+ struct _mail_workarounds_client *ctx;
+
+ ctx = p_new(conn->pool, struct _mail_workarounds_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: <hendrik@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t<hendrik@example.com>\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t <hendrik@example.com>\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:hendrik@example.com\r\n");
+ break;
+ case 4:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: hendrik@example.com\r\n");
+ break;
+ case 5:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\r\n");
+ break;
+ case 6:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: \r\n");
+ break;
+ case 7:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: BODY=7BIT\r\n");
+ break;
+ case 8:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: <>\r\n");
+ break;
+ case 9:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n");
+ break;
+ case 10:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<>\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void
+test_mail_workarounds_client_deinit(struct client_connection *conn)
+{
+ struct _mail_workarounds_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_mail_workarounds(unsigned int index)
+{
+ test_client_input = test_mail_workarounds_client_input;
+ test_client_connected = test_mail_workarounds_client_connected;
+ test_client_deinit = test_mail_workarounds_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_mail_workarounds_disconnect(void *context ATTR_UNUSED,
+ const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_mail_workarounds_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_mail_workarounds_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_mail_workarounds(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_mail_workarounds_disconnect;
+
+ server_callbacks.conn_cmd_rcpt =
+ test_server_mail_workarounds_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_mail_workarounds_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_mail_workarounds(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.workarounds =
+ SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH |
+ SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("MAIL workarounds");
+ test_run_client_server(&smtp_server_set,
+ test_server_mail_workarounds,
+ test_client_mail_workarounds, 11);
+ test_end();
+}
+
+/*
+ * RCPT workarounds
+ */
+
+/* client */
+
+struct _rcpt_workarounds_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void test_rcpt_workarounds_client_input(struct client_connection *conn)
+{
+ struct _rcpt_workarounds_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* MAIL FROM */
+ i_assert(reply->status == 250);
+ break;
+ case 2: /* bad command reply */
+ switch (client_index) {
+ case 5: case 6: case 7:
+ i_assert(reply->status == 501);
+ break;
+ case 0: case 1: case 2: case 3: case 4: case 8:
+ i_assert(reply->status == 250);
+ break;
+ default:
+ i_unreached();
+ }
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void
+test_rcpt_workarounds_client_connected(struct client_connection *conn)
+{
+ struct _rcpt_workarounds_client *ctx;
+
+ ctx = p_new(conn->pool, struct _rcpt_workarounds_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: <harrie@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\t<harrie@example.com>\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\t <harrie@example.com>\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:harrie@example.com\r\n");
+ break;
+ case 4:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: harrie@example.com\r\n");
+ break;
+ case 5:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:\r\n");
+ break;
+ case 6:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: \r\n");
+ break;
+ case 7:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO: NOTIFY=NEVER\r\n");
+ break;
+ case 8:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n"
+ "RCPT TO:<harrie@example.com>\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void test_rcpt_workarounds_client_deinit(struct client_connection *conn)
+{
+ struct _rcpt_workarounds_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_rcpt_workarounds(unsigned int index)
+{
+ test_client_input = test_rcpt_workarounds_client_input;
+ test_client_connected = test_rcpt_workarounds_client_connected;
+ test_client_deinit = test_rcpt_workarounds_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_rcpt_workarounds_disconnect(void *context ATTR_UNUSED,
+ const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_rcpt_workarounds_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ return 1;
+}
+
+static int
+test_server_rcpt_workarounds_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_rcpt_workarounds(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_rcpt_workarounds_disconnect;
+
+ server_callbacks.conn_cmd_rcpt =
+ test_server_rcpt_workarounds_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_rcpt_workarounds_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_rcpt_workarounds(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.workarounds =
+ SMTP_SERVER_WORKAROUND_WHITESPACE_BEFORE_PATH |
+ SMTP_SERVER_WORKAROUND_MAILBOX_FOR_PATH;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("RCPT workarounds");
+ test_run_client_server(&smtp_server_set,
+ test_server_rcpt_workarounds,
+ test_client_rcpt_workarounds, 9);
+ test_end();
+}
+
+/*
+ * Too many recipients
+ */
+
+/* client */
+
+static void test_too_many_recipients_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "EHLO frop\r\n"
+ "MAIL FROM:<sender@example.com>\r\n"
+ "RCPT TO:<recipient1@example.com>\r\n"
+ "RCPT TO:<recipient2@example.com>\r\n"
+ "RCPT TO:<recipient3@example.com>\r\n"
+ "RCPT TO:<recipient4@example.com>\r\n"
+ "RCPT TO:<recipient5@example.com>\r\n"
+ "RCPT TO:<recipient6@example.com>\r\n"
+ "RCPT TO:<recipient7@example.com>\r\n"
+ "RCPT TO:<recipient8@example.com>\r\n"
+ "RCPT TO:<recipient9@example.com>\r\n"
+ "RCPT TO:<recipient10@example.com>\r\n"
+ "RCPT TO:<recipient11@example.com>\r\n"
+ "DATA\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ "0123456789ABCDEF0123456789ABCDEF\r\n"
+ ".\r\n");
+}
+
+static void test_client_too_many_recipients(unsigned int index)
+{
+ test_client_connected = test_too_many_recipients_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_too_many_recipients_trans_free(
+ void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED)
+{
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_too_many_recipients_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt)
+{
+ if (debug) {
+ i_debug("RCPT TO:%s",
+ smtp_address_encode(rcpt->path));
+ }
+ return 1;
+}
+
+static int
+test_server_too_many_recipients_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(array_count(&trans->rcpt_to) == 10);
+
+ smtp_server_reply(cmd, 250, "2.0.0", "OK");
+ return 1;
+}
+
+static void
+test_server_too_many_recipients(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_too_many_recipients_trans_free;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_too_many_recipients_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_too_many_recipients_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_too_many_recipients(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_recipients = 10;
+
+ test_begin("too many recipients");
+ test_run_client_server(&smtp_server_set,
+ test_server_too_many_recipients,
+ test_client_too_many_recipients, 1);
+ test_end();
+}
+
+/*
+ * DATA without MAIL
+ */
+
+/* client */
+
+static void test_data_no_mail_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "EHLO frop\r\n"
+ "DATA\r\n"
+ ".\r\n"
+ "RSET\r\n");
+}
+
+static void test_client_data_no_mail(unsigned int index)
+{
+ test_client_connected = test_data_no_mail_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+static int
+test_server_data_no_mail_rcpt(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ /* not supposed to get here */
+ i_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_data_no_mail_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ /* not supposed to get here */
+ i_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_data_no_mail_rset(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd ATTR_UNUSED)
+{
+ io_loop_stop(ioloop);
+ return 1;
+}
+
+static void
+test_server_data_no_mail(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_cmd_rcpt =
+ test_server_data_no_mail_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_data_no_mail_data_begin;
+ server_callbacks.conn_cmd_rset =
+ test_server_data_no_mail_rset;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_data_no_mail(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities =
+ SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_recipients = 10;
+
+ test_begin("DATA without MAIL");
+ test_run_client_server(&smtp_server_set,
+ test_server_data_no_mail,
+ test_client_data_no_mail, 1);
+ test_end();
+}
+
+/*
+ * DATA without RCPT
+ */
+
+/* client */
+
+static void test_data_no_rcpt_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "EHLO frop\r\n"
+ "MAIL FROM:<sender@example.com>\r\n"
+ "DATA\r\n"
+ ".\r\n"
+ "RSET\r\n");
+}
+
+static void test_client_data_no_rcpt(unsigned int index)
+{
+ test_client_connected = test_data_no_rcpt_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_data_no_rcpt_trans_free(
+ void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED)
+{
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_data_no_rcpt_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ /* not supposed to get here */
+ i_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_data_no_rcpt_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ /* not supposed to get here */
+ i_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_data_no_rcpt(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_data_no_rcpt_trans_free;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_data_no_rcpt_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_data_no_rcpt_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_data_no_rcpt(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities =
+ SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_recipients = 10;
+
+ test_begin("DATA without RCPT");
+ test_run_client_server(&smtp_server_set,
+ test_server_data_no_rcpt,
+ test_client_data_no_rcpt, 1);
+ test_end();
+}
+
+/*
+ * Bad pipelined DATA
+ */
+
+/* client */
+
+static void test_bad_pipelined_data_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "MAIL FROM:<senderp@example.com>\r\n"
+ "RCPT TO:<<recipient1@example.com>\r\n"
+ "DATA\r\n"
+ "FROP!\r\n"
+ "DATA\r\n"
+ "FROP!\r\n"
+ ".\r\n"
+ "QUIT\r\n");
+}
+
+static void test_client_bad_pipelined_data(unsigned int index)
+{
+ test_client_connected = test_bad_pipelined_data_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_bad_pipelined_data_trans_free(
+ void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED)
+{
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_bad_pipelined_data_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ /* not supposed to get here */
+ i_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_bad_pipelined_data_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ /* not supposed to get here */
+ i_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_bad_pipelined_data(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_bad_pipelined_data_trans_free;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_bad_pipelined_data_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_bad_pipelined_data_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_pipelined_data(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities =
+ SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_recipients = 10;
+ smtp_server_set.max_pipelined_commands = 16;
+
+ test_begin("Bad pipelined DATA");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_pipelined_data,
+ test_client_bad_pipelined_data, 1);
+ test_end();
+}
+
+/*
+ * Bad pipelined DATA #2
+ */
+
+/* client */
+
+static void test_bad_pipelined_data2_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "MAIL FROM:<frop@example.com>\r\n"
+ "DATA\r\n"
+ "DATA\r\n"
+ "RCPT TO:<frop@example.com>\r\n"
+ "BDAT 0\r\n");
+}
+
+static void test_client_bad_pipelined_data2(unsigned int index)
+{
+ test_client_connected = test_bad_pipelined_data2_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _bad_pipelined_data2 {
+ struct istream *payload_input;
+ struct io *io;
+};
+
+static void
+test_server_bad_pipelined_data2_trans_free(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_transaction *trans)
+{
+ struct _bad_pipelined_data2 *ctx = trans->context;
+
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_bad_pipelined_data2_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt)
+{
+ if (debug) {
+ i_debug("RCPT TO:%s",
+ smtp_address_encode(rcpt->path));
+ }
+ return 1;
+}
+
+static int
+test_server_bad_pipelined_data2_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans, struct istream *data_input)
+{
+ struct _bad_pipelined_data2 *ctx;
+
+ if (debug)
+ i_debug("DATA");
+
+ ctx = i_new(struct _bad_pipelined_data2, 1);
+ trans->context = ctx;
+
+ ctx->payload_input = data_input;
+ return 0;
+}
+
+static int
+test_server_bad_pipelined_data2_data_continue(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans ATTR_UNUSED)
+{
+ struct _bad_pipelined_data2 *ctx = trans->context;
+ size_t size;
+ ssize_t ret;
+
+ while ((ret = i_stream_read(ctx->payload_input)) > 0 || ret == -2) {
+ size = i_stream_get_data_size(ctx->payload_input);
+ i_stream_skip(ctx->payload_input, size);
+ }
+
+ if (ret == 0)
+ return 0;
+ if (ret < 0 && ctx->payload_input->stream_errno != 0) {
+ /* Client probably disconnected */
+ return -1;
+ }
+
+ smtp_server_reply_all(cmd, 250, "2.0.0", "Accepted");
+ return 1;
+}
+
+static void
+test_server_bad_pipelined_data2(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_bad_pipelined_data2_trans_free;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_bad_pipelined_data2_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_bad_pipelined_data2_data_begin;
+ server_callbacks.conn_cmd_data_continue =
+ test_server_bad_pipelined_data2_data_continue;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_pipelined_data2(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities =
+ SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_recipients = 10;
+ smtp_server_set.max_pipelined_commands = 16;
+
+ test_begin("Bad pipelined DATA #2");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_pipelined_data2,
+ test_client_bad_pipelined_data2, 1);
+ test_end();
+}
+
+/*
+ * DATA with BINARYMIME
+ */
+
+/* client */
+
+static void test_data_binarymime_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "EHLO frop\r\n"
+ "MAIL FROM:<sender@example.com> BODY=BINARYMIME\r\n"
+ "RCPT TO:<recipient1@example.com>\r\n"
+ "DATA\r\n"
+ ".\r\n"
+ "RSET\r\n");
+}
+
+static void test_client_data_binarymime(unsigned int index)
+{
+ test_client_connected = test_data_binarymime_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_data_binarymime_trans_free(
+ void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED)
+{
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_data_binarymime_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt)
+{
+ if (debug) {
+ i_debug("RCPT TO:%s",
+ smtp_address_encode(rcpt->path));
+ }
+ return 1;
+}
+
+static int
+test_server_data_binarymime_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ /* not supposed to get here */
+ i_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_data_binarymime(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_data_binarymime_trans_free;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_data_binarymime_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_data_binarymime_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_data_binarymime(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities =
+ SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_recipients = 10;
+
+ test_begin("DATA with BINARYMIME");
+ test_run_client_server(&smtp_server_set,
+ test_server_data_binarymime,
+ test_client_data_binarymime, 1);
+ test_end();
+}
+
+/*
+ * MAIL broken path
+ */
+
+/* client */
+
+struct _mail_broken_path_client {
+ struct smtp_reply_parser *parser;
+ unsigned int reply;
+
+ bool replied:1;
+};
+
+static void
+test_mail_broken_path_client_input(struct client_connection *conn)
+{
+ struct _mail_broken_path_client *ctx = conn->context;
+ struct smtp_reply *reply;
+ const char *error;
+ int ret;
+
+ while ((ret = smtp_reply_parse_next(ctx->parser, FALSE,
+ &reply, &error)) > 0) {
+ if (debug)
+ i_debug("REPLY: %s", smtp_reply_log(reply));
+
+ switch (ctx->reply++) {
+ case 0: /* greeting */
+ i_assert(reply->status == 220);
+ break;
+ case 1: /* bad command reply */
+ switch (client_index) {
+ case 0: case 1: case 2: case 3: case 4: case 5:
+ case 6: case 7: case 8: case 11: case 14: case 16:
+ i_assert(reply->status == 501);
+ break;
+ case 9: case 10: case 12: case 13: case 15: case 17:
+ i_assert(reply->status == 250);
+ break;
+ default:
+ i_info("STATUS: %u", reply->status);
+ i_unreached();
+ }
+ ctx->replied = TRUE;
+ io_loop_stop(ioloop);
+ connection_disconnect(&conn->conn);
+ return;
+ default:
+ i_unreached();
+ }
+ }
+
+ i_assert(ret >= 0);
+}
+
+static void
+test_mail_broken_path_client_connected(struct client_connection *conn)
+{
+ struct _mail_broken_path_client *ctx;
+
+ ctx = p_new(conn->pool, struct _mail_broken_path_client, 1);
+ ctx->parser = smtp_reply_parser_init(conn->conn.input, SIZE_MAX);
+ conn->context = ctx;
+
+ switch (client_index) {
+ case 0:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: <hendrik@example.com>\r\n");
+ break;
+ case 1:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t<hendrik@example.com>\r\n");
+ break;
+ case 2:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\t <hendrik@example.com>\r\n");
+ break;
+ case 3:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:hendrik@example.com\r\n");
+ break;
+ case 4:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: hendrik@example.com\r\n");
+ break;
+ case 5:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:\r\n");
+ break;
+ case 6:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: \r\n");
+ break;
+ case 7:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: BODY=7BIT\r\n");
+ break;
+ case 8:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM: <>\r\n");
+ break;
+ case 9:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<hendrik@example.com>\r\n");
+ break;
+ case 10:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<>\r\n");
+ break;
+ case 11:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:bla$die%bla@die&bla\r\n");
+ break;
+ case 12:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<u\"ser>\r\n");
+ break;
+ case 13:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<u\"ser@domain.tld>\r\n");
+ break;
+ case 14:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:/@)$@)BLAARGH!@#$$\r\n");
+ break;
+ case 15:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:</@)$@)BLAARGH!@#$$>\r\n");
+ break;
+ case 16:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4\r\n");
+ break;
+ case 17:
+ o_stream_nsend_str(
+ conn->conn.output,
+ "MAIL FROM:<f\xc3\xb6\xc3\xa4@\xc3\xb6\xc3\xa4>\r\n");
+ break;
+ default:
+ i_unreached();
+ }
+}
+
+static void test_mail_broken_path_client_deinit(struct client_connection *conn)
+{
+ struct _mail_broken_path_client *ctx = conn->context;
+
+ i_assert(ctx->replied);
+ smtp_reply_parser_deinit(&ctx->parser);
+}
+
+static void test_client_mail_broken_path(unsigned int index)
+{
+ test_client_input = test_mail_broken_path_client_input;
+ test_client_connected = test_mail_broken_path_client_connected;
+ test_client_deinit = test_mail_broken_path_client_deinit;
+ test_client_run(index);
+}
+
+/* server */
+
+static void
+test_server_mail_broken_path_disconnect(void *context ATTR_UNUSED,
+ const char *reason)
+{
+ if (debug)
+ i_debug("Disconnect: %s", reason);
+}
+
+static int
+test_server_mail_broken_path_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static int
+test_server_mail_broken_path_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans ATTR_UNUSED,
+ struct istream *data_input ATTR_UNUSED)
+{
+ test_assert(FALSE);
+ return 1;
+}
+
+static void
+test_server_mail_broken_path(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_disconnect =
+ test_server_mail_broken_path_disconnect;
+
+ server_callbacks.conn_cmd_rcpt =
+ test_server_mail_broken_path_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_mail_broken_path_data_begin;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_mail_broken_path(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.mail_path_allow_broken = TRUE;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+
+ test_begin("MAIL broken path");
+ test_run_client_server(&smtp_server_set,
+ test_server_mail_broken_path,
+ test_client_mail_broken_path, 18);
+ test_end();
+}
+
+/*
+ * Bad pipelined MAIL
+ */
+
+/* client */
+
+static void test_bad_pipelined_mail_connected(struct client_connection *conn)
+{
+ o_stream_nsend_str(conn->conn.output,
+ "MAIL FROM:<user1@example.com>\r\n"
+ "RCPT TO:<user2@example.com>\r\n"
+ "RCPT TO:<user3@example.com>\r\n"
+ "MAIL FROM:<user4@example.com>\r\n"
+ "DATA\r\n"
+ "FROP!\r\n"
+ ".\r\n"
+ "QUIT\r\n");
+}
+
+static void test_client_bad_pipelined_mail(unsigned int index)
+{
+ test_client_connected = test_bad_pipelined_mail_connected;
+ test_client_run(index);
+}
+
+/* server */
+
+struct _bad_pipelined_mail {
+ struct istream *payload_input;
+ struct io *io;
+};
+
+static void
+test_server_bad_pipelined_mail_trans_free(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_transaction *trans)
+{
+ struct _bad_pipelined_mail *ctx = trans->context;
+
+ i_free(ctx);
+ io_loop_stop(ioloop);
+}
+
+static int
+test_server_bad_pipelined_mail_rcpt(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_recipient *rcpt)
+{
+ if (debug) {
+ i_debug("RCPT TO:%s",
+ smtp_address_encode(rcpt->path));
+ }
+ return 1;
+}
+
+static int
+test_server_bad_pipelined_mail_data_begin(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd ATTR_UNUSED,
+ struct smtp_server_transaction *trans, struct istream *data_input)
+{
+ struct _bad_pipelined_mail *ctx;
+
+ if (debug)
+ i_debug("DATA");
+
+ ctx = i_new(struct _bad_pipelined_mail, 1);
+ trans->context = ctx;
+
+ ctx->payload_input = data_input;
+ return 0;
+}
+
+static int
+test_server_bad_pipelined_mail_data_continue(
+ void *conn_ctx ATTR_UNUSED, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_transaction *trans ATTR_UNUSED)
+{
+ struct _bad_pipelined_mail *ctx = trans->context;
+ size_t size;
+ ssize_t ret;
+
+ while ((ret = i_stream_read(ctx->payload_input)) > 0 || ret == -2) {
+ size = i_stream_get_data_size(ctx->payload_input);
+ i_stream_skip(ctx->payload_input, size);
+ }
+
+ if (ret == 0)
+ return 0;
+ if (ret < 0 && ctx->payload_input->stream_errno != 0) {
+ /* Client probably disconnected */
+ return -1;
+ }
+
+ smtp_server_reply_all(cmd, 250, "2.0.0", "Accepted");
+ return 1;
+}
+
+static void
+test_server_bad_pipelined_mail(const struct smtp_server_settings *server_set)
+{
+ server_callbacks.conn_trans_free =
+ test_server_bad_pipelined_mail_trans_free;
+ server_callbacks.conn_cmd_rcpt =
+ test_server_bad_pipelined_mail_rcpt;
+ server_callbacks.conn_cmd_data_begin =
+ test_server_bad_pipelined_mail_data_begin;
+ server_callbacks.conn_cmd_data_continue =
+ test_server_bad_pipelined_mail_data_continue;
+ test_server_run(server_set);
+}
+
+/* test */
+
+static void test_bad_pipelined_mail(void)
+{
+ struct smtp_server_settings smtp_server_set;
+
+ test_server_defaults(&smtp_server_set);
+ smtp_server_set.capabilities =
+ SMTP_CAPABILITY_BINARYMIME | SMTP_CAPABILITY_CHUNKING;
+ smtp_server_set.max_client_idle_time_msecs = 1000;
+ smtp_server_set.max_recipients = 10;
+ smtp_server_set.max_pipelined_commands = 16;
+
+ test_begin("Bad pipelined MAIL");
+ test_run_client_server(&smtp_server_set,
+ test_server_bad_pipelined_mail,
+ test_client_bad_pipelined_mail, 1);
+ test_end();
+}
+
+/*
+ * All tests
+ */
+
+static void (*const test_functions[])(void) = {
+ test_slow_server,
+ test_slow_client,
+ test_hanging_command_payload,
+ test_bad_command,
+ test_many_bad_commands,
+ test_long_command,
+ test_long_auth_line,
+ test_long_auth_line_small_buf,
+ test_big_data,
+ test_bad_helo,
+ test_bad_mail,
+ test_bad_rcpt,
+ test_bad_vrfy,
+ test_bad_noop,
+ test_mail_workarounds,
+ test_rcpt_workarounds,
+ test_too_many_recipients,
+ test_data_no_mail,
+ test_data_no_rcpt,
+ test_bad_pipelined_data,
+ test_bad_pipelined_data2,
+ test_data_binarymime,
+ test_mail_broken_path,
+ test_bad_pipelined_mail,
+ NULL
+};
+
+/*
+ * Test client
+ */
+
+/* client connection */
+
+static void client_connection_input(struct connection *_conn)
+{
+ struct client_connection *conn = (struct client_connection *)_conn;
+
+ if (test_client_input != NULL)
+ test_client_input(conn);
+}
+
+static void client_connection_connected(struct connection *_conn, bool success)
+{
+ struct client_connection *conn = (struct client_connection *)_conn;
+
+ if (debug)
+ i_debug("Client connected");
+
+ if (success && test_client_connected != NULL)
+ test_client_connected(conn);
+}
+
+static void client_connection_init(const struct ip_addr *ip, in_port_t port)
+{
+ struct client_connection *conn;
+ pool_t pool;
+
+ pool = pool_alloconly_create("client connection", 256);
+ conn = p_new(pool, struct client_connection, 1);
+ conn->pool = pool;
+
+ connection_init_client_ip(client_conn_list, &conn->conn, NULL,
+ ip, port);
+ (void)connection_client_connect(&conn->conn);
+}
+
+static void client_connection_deinit(struct client_connection **_conn)
+{
+ struct client_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ if (test_client_deinit != NULL)
+ test_client_deinit(conn);
+ connection_deinit(&conn->conn);
+ pool_unref(&conn->pool);
+}
+
+static void client_connection_destroy(struct connection *_conn)
+{
+ struct client_connection *conn = (struct client_connection *)_conn;
+
+ client_connection_deinit(&conn);
+}
+
+/* */
+
+static struct connection_settings client_connection_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = TRUE
+};
+
+static const struct connection_vfuncs client_connection_vfuncs = {
+ .destroy = client_connection_destroy,
+ .client_connected = client_connection_connected,
+ .input = client_connection_input
+};
+
+static void test_client_run(unsigned int index)
+{
+ client_index = index;
+
+ if (debug)
+ i_debug("client connecting to %u", bind_port);
+
+ client_conn_list = connection_list_init(&client_connection_set,
+ &client_connection_vfuncs);
+
+ client_connection_init(&bind_ip, bind_port);
+
+ io_loop_run(ioloop);
+
+ /* close server socket */
+ io_remove(&io_listen);
+
+ connection_list_deinit(&client_conn_list);
+}
+
+/*
+ * Test server
+ */
+
+static void test_server_defaults(struct smtp_server_settings *smtp_set)
+{
+ /* server settings */
+ i_zero(smtp_set);
+ smtp_set->max_client_idle_time_msecs = 5*1000;
+ smtp_set->max_pipelined_commands = 1;
+ smtp_set->auth_optional = TRUE;
+ smtp_set->debug = debug;
+}
+
+/* client connection */
+
+static void server_connection_free(void *context)
+{
+ struct server_connection *sconn = (struct server_connection *)context;
+
+ if (debug)
+ i_debug("Connection freed");
+
+ if (--server_pending == 0)
+ io_loop_stop(ioloop);
+ i_free(sconn);
+}
+
+static void server_connection_accept(void *context ATTR_UNUSED)
+{
+ struct smtp_server_connection *conn;
+ struct server_connection *sconn;
+ 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");
+ }
+
+ if (debug)
+ i_debug("Accepted connection");
+
+ net_set_nonblock(fd, TRUE);
+
+ sconn = i_new(struct server_connection, 1);
+
+ server_callbacks.conn_free = server_connection_free;
+
+ if (server_io_buffer_size == 0) {
+ conn = smtp_server_connection_create(smtp_server, fd, fd,
+ NULL, 0, FALSE, NULL,
+ &server_callbacks, sconn);
+ } else {
+ struct istream *input;
+ struct ostream *output;
+
+ input = i_stream_create_fd(fd, server_io_buffer_size);
+ output = o_stream_create_fd(fd, server_io_buffer_size);
+ o_stream_set_no_error_handling(output, TRUE);
+
+ conn = smtp_server_connection_create_from_streams(
+ smtp_server, input, output, NULL, 0, NULL,
+ &server_callbacks, sconn);
+
+ i_stream_unref(&input);
+ o_stream_unref(&output);
+ }
+ smtp_server_connection_start(conn);
+}
+
+/* */
+
+static void test_server_timeout(void *context ATTR_UNUSED)
+{
+ i_fatal("Server timed out");
+}
+
+static void test_server_run(const struct smtp_server_settings *smtp_set)
+{
+ struct timeout *to;
+
+ to = timeout_add(SERVER_MAX_TIMEOUT_MSECS,
+ test_server_timeout, NULL);
+
+ /* open server socket */
+ io_listen = io_add(fd_listen, IO_READ, server_connection_accept, NULL);
+
+ smtp_server = smtp_server_init(smtp_set);
+
+ io_loop_run(ioloop);
+
+ if (debug)
+ i_debug("Server finished");
+
+ /* close server socket */
+ io_remove(&io_listen);
+ timeout_remove(&to);
+
+ smtp_server_deinit(&smtp_server);
+}
+
+/*
+ * Tests
+ */
+
+struct test_client_data {
+ unsigned int index;
+ test_client_init_t client_test;
+};
+
+static int test_open_server_fd(void)
+{
+ 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_client(struct test_client_data *data)
+{
+ i_set_failure_prefix("CLIENT[%u]: ", data->index + 1);
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ /* wait a little for server setup */
+ i_sleep_msecs(100);
+
+ ioloop = io_loop_create();
+ data->client_test(data->index);
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+
+ main_deinit();
+ return 0;
+}
+
+static void
+test_run_server(const struct smtp_server_settings *server_set,
+ test_server_init_t server_test,
+ unsigned int client_tests_count)
+{
+ i_set_failure_prefix("SERVER: ");
+
+ if (debug)
+ i_debug("PID=%s", my_pid);
+
+ i_zero(&server_callbacks);
+
+ server_pending = client_tests_count;
+ ioloop = io_loop_create();
+ server_test(server_set);
+ io_loop_destroy(&ioloop);
+
+ if (debug)
+ i_debug("Terminated");
+}
+
+static void
+test_run_client_server(const struct smtp_server_settings *server_set,
+ test_server_init_t server_test,
+ test_client_init_t client_test,
+ unsigned int client_tests_count)
+{
+ unsigned int i;
+
+ server_io_buffer_size = 0;
+
+ fd_listen = test_open_server_fd();
+
+ for (i = 0; i < client_tests_count; i++) {
+ struct test_client_data data;
+
+ i_zero(&data);
+ data.index = i;
+ data.client_test = client_test;
+
+ /* Fork client */
+ test_subprocess_fork(test_run_client, &data, FALSE);
+
+ }
+
+ /* Run server */
+ test_run_server(server_set, server_test, client_tests_count);
+
+ i_unset_failure_prefix();
+ i_close_fd(&fd_listen);
+ test_subprocess_kill_all(CLIENT_KILL_TIMEOUT_SECS);
+}
+
+/*
+ * Main
+ */
+
+static void main_init(void)
+{
+ /* nothing yet */
+}
+
+static void main_deinit(void)
+{
+ /* nothing yet; also called from sub-processes */
+}
+
+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;
+}