/* 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 #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:\r\n" "RCPT TO:\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:\r\n" "RCPT TO:\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:\r\n" "RCPT TO:\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: \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:\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:\r\n" "RCPT TO: \r\n"); break; case 1: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:harrie@example.com\r\n"); break; case 2: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO: harrie@example.com\r\n"); break; case 3: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:\r\n"); break; case 4: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO: \r\n"); break; case 5: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO: NOTIFY=NEVER\r\n"); break; case 6: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:\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: \r\n"); break; case 1: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\t\r\n"); break; case 2: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\t \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:\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:\r\n" "RCPT TO: \r\n"); break; case 1: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:\t\r\n"); break; case 2: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:\t \r\n"); break; case 3: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:harrie@example.com\r\n"); break; case 4: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO: harrie@example.com\r\n"); break; case 5: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:\r\n"); break; case 6: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO: \r\n"); break; case 7: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO: NOTIFY=NEVER\r\n"); break; case 8: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\r\n" "RCPT TO:\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:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "RCPT TO:\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:\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:\r\n" "RCPT TO:<\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:\r\n" "DATA\r\n" "DATA\r\n" "RCPT TO:\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: BODY=BINARYMIME\r\n" "RCPT TO:\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: \r\n"); break; case 1: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\t\r\n"); break; case 2: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\t \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:\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:\r\n"); break; case 13: o_stream_nsend_str( conn->conn.output, "MAIL FROM:\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:\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:\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:\r\n" "RCPT TO:\r\n" "RCPT TO:\r\n" "MAIL FROM:\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; }