]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
imap: Improve imap-client-hibernate unit test
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Fri, 6 Nov 2020 15:07:35 +0000 (17:07 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Fri, 6 Nov 2020 15:08:27 +0000 (17:08 +0200)
src/imap/imap-client-hibernate.c
src/imap/imap-client.h
src/imap/test-imap-client-hibernate.c

index cb56eb31036ca6a72d7577425b883548833793cf..0709e4a244c4e81917478dcdc0599ba06f49d113 100644 (file)
@@ -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;
index 868436352c6f367db58b84a339f2371ce8dc0656..4e591d5c7c1527c44831eb09b2658c58c5ab8aa3 100644 (file)
@@ -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);
index ee4a0f008c94dd768cf9571a625011560ff09bc4..3dc198ed3882ee469989d10336ea57fb943f74ae 100644 (file)
 
 #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 <sys/stat.h>
+
+#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;
 }