From: Timo Sirainen Date: Fri, 6 Nov 2020 15:07:35 +0000 (+0200) Subject: imap: Improve imap-client-hibernate unit test X-Git-Tag: 2.3.13~34 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c2122e0063761cbaf33a76c27c1b7de963e0ca8f;p=thirdparty%2Fdovecot%2Fcore.git imap: Improve imap-client-hibernate unit test --- diff --git a/src/imap/imap-client-hibernate.c b/src/imap/imap-client-hibernate.c index cb56eb3103..0709e4a244 100644 --- a/src/imap/imap-client-hibernate.c +++ b/src/imap/imap-client-hibernate.c @@ -42,8 +42,8 @@ imap_hibernate_handshake(int fd, const char *path, const char **error_r) return -1; } -void imap_hibernate_write_cmd(struct client *client, string_t *cmd, - const buffer_t *state, int fd_notify) +static void imap_hibernate_write_cmd(struct client *client, string_t *cmd, + const buffer_t *state, int fd_notify) { struct mail_user *user = client->user; struct stat peer_st; diff --git a/src/imap/imap-client.h b/src/imap/imap-client.h index 868436352c..4e591d5c7c 100644 --- a/src/imap/imap-client.h +++ b/src/imap/imap-client.h @@ -344,9 +344,6 @@ void client_continue_pending_input(struct client *client); void client_add_missing_io(struct client *client); const char *client_stats(struct client *client); -void imap_hibernate_write_cmd(struct client *client, string_t *cmd, - const buffer_t *state, int fd_notify); - void client_input(struct client *client); bool client_handle_input(struct client *client); int client_output(struct client *client); diff --git a/src/imap/test-imap-client-hibernate.c b/src/imap/test-imap-client-hibernate.c index ee4a0f008c..3dc198ed38 100644 --- a/src/imap/test-imap-client-hibernate.c +++ b/src/imap/test-imap-client-hibernate.c @@ -2,98 +2,289 @@ #include "lib.h" #include "test-common.h" +#include "test-subprocess.h" #include "istream.h" -#include "ostream.h" -#include "str.h" +#include "istream-unix.h" #include "strescape.h" +#include "path-util.h" +#include "unlink-directory.h" +#include "settings-parser.h" +#include "master-service.h" +#include "smtp-submit.h" +#include "mail-storage-service.h" #include "mail-storage-private.h" #include "imap-common.h" +#include "imap-settings.h" #include "imap-client.h" +#include + +#define TEMP_DIRNAME ".test-imap-client-hibernate" + #define EVILSTR "\t\r\n\001" -#define EVILSTR_ESCAPED "\001t\001r\001n\0011" + +struct test_imap_client_hibernate { + struct client *client; + int fd_listen; + bool has_mailbox; + const char *reply; +}; imap_client_created_func_t *hook_client_created = NULL; bool imap_debug = FALSE; +static const char *tmpdir; +static struct mail_storage_service_ctx *storage_service; + void imap_refresh_proctitle(void) { } int client_create_from_input(const struct mail_storage_service_input *input ATTR_UNUSED, int fd_in ATTR_UNUSED, int fd_out ATTR_UNUSED, struct client **client_r ATTR_UNUSED, const char **error_r ATTR_UNUSED) { return -1; } -static void test_imap_client_hibernate(void) +static int imap_hibernate_server(struct test_imap_client_hibernate *ctx) { - buffer_t *state = buffer_create_dynamic(pool_datastack_create(), 0); - struct mail_user_settings mail_set = { - .mail_log_prefix = EVILSTR"%u", - }; - struct mail_user mail_user = { - .set = &mail_set, - .conn = { - .local_ip = t_new(struct ip_addr, 1), - .remote_ip = t_new(struct ip_addr, 1), - }, - .username = EVILSTR"testuser", - .session_id = EVILSTR"session", - .session_create_time = 1234567, - .pool = pool_datastack_create(), - .uid = 4000, - .gid = 4001, - }; - struct imap_settings imap_set = { - .imap_idle_notify_interval = 120, - .imap_logout_format = "", - }; - struct client_command_context queue = { - .tag = EVILSTR"tag", - .name = "IDLE", - }; - struct client client = { - .user = &mail_user, - .set = &imap_set, - .fd_in = dev_null_fd, - .input = i_stream_create_from_data("", 0), - .output = o_stream_create_buffer(state), - .command_queue = &queue, - }; - test_begin("imap client hibernate"); - test_assert(net_addr2ip("127.0.0.1", mail_user.conn.local_ip) == 0); - test_assert(net_addr2ip("127.0.0.2", mail_user.conn.remote_ip) == 0); + i_set_failure_prefix("SERVER: "); + + int fd = net_accept(ctx->fd_listen, NULL, NULL); + i_assert(fd > 0); + struct istream *input = i_stream_create_unix(fd, SIZE_MAX); + i_stream_unix_set_read_fd(input); + + /* send handshake */ + const char *str = "VERSION\timap-hibernate\t1\t0\n"; + if (write(fd, str, strlen(str)) != (ssize_t)strlen(str)) + i_fatal("write(imap-hibernate client handshake) failed: %m"); + + /* read handshake */ + const char *line; + if ((line = i_stream_read_next_line(input)) == NULL) + i_fatal("read(imap-hibernate client handshake) failed: %s", + i_stream_get_error(input)); + if (strcmp(line, "VERSION\timap-hibernate\t1\t0") != 0) + i_fatal("VERSION not received"); + /* read command */ + if ((line = i_stream_read_next_line(input)) == NULL) + i_fatal("read(imap-hibernate client command) failed: %s", + i_stream_get_error(input)); + int fd2 = i_stream_unix_get_read_fd(input); + test_assert(fd2 != -1); + i_close_fd(&fd2); + const char *const *args = t_strsplit_tabescaped(line); + + /* write reply */ + if (write(fd, ctx->reply, strlen(ctx->reply)) != (ssize_t)strlen(ctx->reply)) + i_fatal("write(imap-hibernate client command) failed: %m"); - string_t *cmd = t_str_new(256); - imap_hibernate_write_cmd(&client, cmd, state, -1); + if (ctx->has_mailbox) { + /* read mailbox notify fd */ + i_stream_unix_set_read_fd(input); + if ((line = i_stream_read_next_line(input)) == NULL) + i_fatal("read(imap-hibernate notify fd) failed: %s", + i_stream_get_error(input)); + + fd2 = i_stream_unix_get_read_fd(input); + test_assert(fd2 != -1); + i_close_fd(&fd2); + + if (write(fd, "+\n", 2) != 2) + i_fatal("write(imap-hibernate client command) failed: %m"); + } - const char *const *args = t_strsplit(str_c(cmd), "\t"); unsigned int i = 0; - test_assert_strcmp(args[i++], EVILSTR_ESCAPED"testuser"); - test_assert_strcmp(args[i++], EVILSTR_ESCAPED"%u"); + test_assert_strcmp(args[i++], EVILSTR"testuser"); + test_assert_strcmp(args[i++], EVILSTR"%u"); test_assert_strcmp(args[i++], "idle_notify_interval=120"); - test_assert(strncmp(args[i++], "peer_dev_major=", 15) == 0); - test_assert(strncmp(args[i++], "peer_dev_minor=", 15) == 0); - test_assert(strncmp(args[i++], "peer_ino=", 9) == 0); - test_assert_strcmp(args[i++], "session="EVILSTR_ESCAPED"session"); - test_assert_strcmp(args[i++], "session_created=1234567"); + test_assert(str_begins(args[i++], "peer_dev_major=")); + test_assert(str_begins(args[i++], "peer_dev_minor=")); + test_assert(str_begins(args[i++], "peer_ino=")); + test_assert_strcmp(args[i++], "session="EVILSTR"session"); + test_assert(str_begins(args[i++], "session_created=")); test_assert_strcmp(args[i++], "lip=127.0.0.1"); + test_assert_strcmp(args[i++], "lport=1234"); test_assert_strcmp(args[i++], "rip=127.0.0.2"); - test_assert_strcmp(args[i++], "uid=4000"); - test_assert_strcmp(args[i++], "gid=4001"); - test_assert_strcmp(args[i++], "tag="EVILSTR_ESCAPED"tag"); - test_assert(strncmp(args[i++], "stats=", 6) == 0); + test_assert_strcmp(args[i++], "rport=5678"); + test_assert(str_begins(args[i++], "uid=")); + test_assert(str_begins(args[i++], "gid=")); + if (ctx->has_mailbox) + test_assert_strcmp(args[i++], "mailbox="EVILSTR"mailbox"); + test_assert_strcmp(args[i++], "tag="EVILSTR"tag"); + test_assert(str_begins(args[i++], "stats=")); test_assert_strcmp(args[i++], "idle-cmd"); - test_assert(strncmp(args[i++], "state=", 6) == 0); + if (ctx->has_mailbox) + test_assert_strcmp(args[i++], "notify_fd"); + test_assert(str_begins(args[i++], "state=")); test_assert(args[i] == NULL); - i_stream_destroy(&client.input); - o_stream_destroy(&client.output); + i_stream_unref(&input); + i_close_fd(&ctx->fd_listen); + i_close_fd(&fd); + + ctx->client->hibernated = TRUE; /* prevent disconnect Info message */ + client_destroy(ctx->client, NULL); + + mail_storage_service_deinit(&storage_service); + master_service_deinit_forked(&master_service); + return 0; +} + +static void +mailbox_notify_callback(struct mailbox *box ATTR_UNUSED, + struct client *client ATTR_UNUSED) +{ +} + +static void test_imap_client_hibernate(void) +{ + struct client *client; + struct smtp_submit_settings smtp_set; + struct mail_storage_service_user *service_user; + struct mail_user *mail_user; + struct test_imap_client_hibernate ctx; + const char *error; + + storage_service = mail_storage_service_init(master_service, NULL, + MAIL_STORAGE_SERVICE_FLAG_ALLOW_ROOT | + MAIL_STORAGE_SERVICE_FLAG_NO_LOG_INIT | + MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR | + MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS); + + const char *const input_userdb[] = { + "mailbox_list_index=no", + t_strdup_printf("mail=mbox:%s/mbox", tmpdir), + NULL + }; + struct mail_storage_service_input input = { + .username = EVILSTR"testuser", + .local_port = 1234, + .remote_port = 5678, + .userdb_fields = input_userdb, + }; + test_assert(net_addr2ip("127.0.0.1", &input.local_ip) == 0); + test_assert(net_addr2ip("127.0.0.2", &input.remote_ip) == 0); + test_assert(mail_storage_service_lookup_next(storage_service, &input, + &service_user, &mail_user, &error) == 1); + mail_user->set->base_dir = tmpdir; + mail_user->set->mail_log_prefix = EVILSTR"%u"; + mail_user->session_id = EVILSTR"session"; + i_zero(&smtp_set); + i_zero(&ctx); + + struct event *event = event_create(NULL); + int client_fd = dup(dev_null_fd); + client = client_create(client_fd, client_fd, event, + mail_user, service_user, + imap_setting_parser_info.defaults, &smtp_set); + ctx.client = client; + + /* can't hibernate without IDLE */ + test_begin("imap client hibernate: non-IDLE"); + test_assert(!imap_client_hibernate(&client, &error)); + test_assert_strcmp(error, "Non-IDLE connections not supported currently"); + test_end(); + + struct client_command_context *cmd = client_command_alloc(client); + cmd->tag = EVILSTR"tag"; + cmd->name = "IDLE"; + event_unref(&event); + + /* imap-hibernate socket doesn't exist */ + test_begin("imap client hibernate: socket not found"); + test_expect_error_string("/"TEMP_DIRNAME"/imap-hibernate) failed: No such file or directory"); + test_assert(!imap_client_hibernate(&client, &error)); + test_expect_no_more_errors(); + test_assert(strstr(error, "net_connect_unix") != NULL); + test_end(); + + /* imap-hibernate socket times out */ + const char *socket_path = t_strdup_printf("%s/imap-hibernate", tmpdir); + ctx.fd_listen = net_listen_unix(socket_path, 1); + if (ctx.fd_listen == -1) + i_fatal("net_listen_unix(%s) failed: %m", socket_path); + fd_set_nonblock(ctx.fd_listen, FALSE); + + /* imap-hibernate socket returns failure */ + test_begin("imap client hibernate: error returned"); + ctx.reply = "-notgood\n"; + test_subprocess_fork(imap_hibernate_server, &ctx, FALSE); + + test_expect_error_string(TEMP_DIRNAME"/imap-hibernate returned failure: notgood"); + test_assert(!imap_client_hibernate(&client, &error)); + test_expect_no_more_errors(); + test_assert(strstr(error, "notgood") != NULL); + test_end(); + + /* create and open evil mailbox */ + client->mailbox = mailbox_alloc(client->user->namespaces->list, + "testbox", 0); + struct mailbox_update update = { + .uid_validity = 12345678, + }; + memset(update.mailbox_guid, 0x12, sizeof(update.mailbox_guid)); + test_assert(mailbox_create(client->mailbox, &update, FALSE) == 0); + test_assert(mailbox_open(client->mailbox) == 0); + client->mailbox->vname = EVILSTR"mailbox"; + + /* successful hibernation */ + test_begin("imap client hibernate: success"); + ctx.reply = "+\n"; + ctx.has_mailbox = TRUE; + test_subprocess_fork(imap_hibernate_server, &ctx, FALSE); + /* start notification only after forking or we'll have trouble + deinitializing cleanly */ + mailbox_notify_changes(client->mailbox, mailbox_notify_callback, client); + test_assert(imap_client_hibernate(&client, &error)); test_end(); + + i_close_fd(&ctx.fd_listen); + mail_storage_service_deinit(&storage_service); +} + +static void test_cleanup(void) +{ + const char *error; + + if (unlink_directory(tmpdir, UNLINK_DIRECTORY_FLAG_RMDIR, &error) < 0) + i_error("unlink_directory() failed: %s", error); } -int main(void) +static void test_init(void) { - static void (*test_functions[])(void) = { + const char *cwd, *error; + + test_assert(t_get_working_dir(&cwd, &error) == 0); + tmpdir = t_strconcat(cwd, "/"TEMP_DIRNAME, NULL); + + test_cleanup(); + if (mkdir(tmpdir, 0700) < 0) + i_fatal("mkdir() failed: %m"); + + test_subprocesses_init(FALSE); +} + +int main(int argc, char *argv[]) +{ + const enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS | + MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_STD_CLIENT | + MASTER_SERVICE_FLAG_DONT_SEND_STATS; + int ret; + + master_service = master_service_init("test-imap-client-hibernate", + service_flags, &argc, &argv, "D"); + + master_service_init_finish(master_service); + test_init(); + + static void (*const test_functions[])(void) = { test_imap_client_hibernate, NULL }; - return test_run(test_functions); + ret = test_run(test_functions); + + test_subprocesses_deinit(); + test_cleanup(); + master_service_deinit(&master_service); + return ret; }