summaryrefslogtreecommitdiffstats
path: root/src/imap/test-imap-client-hibernate.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/test-imap-client-hibernate.c')
-rw-r--r--src/imap/test-imap-client-hibernate.c291
1 files changed, 291 insertions, 0 deletions
diff --git a/src/imap/test-imap-client-hibernate.c b/src/imap/test-imap-client-hibernate.c
new file mode 100644
index 0000000..6136019
--- /dev/null
+++ b/src/imap/test-imap-client-hibernate.c
@@ -0,0 +1,291 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "test-subprocess.h"
+#include "istream.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"
+
+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) { }
+void imap_refresh_proctitle_delayed(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 int imap_hibernate_server(struct test_imap_client_hibernate *ctx)
+{
+ 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");
+
+ if (ctx->has_mailbox) {
+ /* read mailbox notify fd */
+ i_stream_unix_set_read_fd(input);
+ if (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");
+ }
+
+ unsigned int i = 0;
+ 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(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++], "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");
+ 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_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);
+}
+
+static void test_init(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
+ };
+ ret = test_run(test_functions);
+
+ test_subprocesses_deinit();
+ test_cleanup();
+ master_service_deinit(&master_service);
+ return ret;
+}