summaryrefslogtreecommitdiffstats
path: root/src/imap-hibernate/imap-hibernate-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap-hibernate/imap-hibernate-client.c')
-rw-r--r--src/imap-hibernate/imap-hibernate-client.c304
1 files changed, 304 insertions, 0 deletions
diff --git a/src/imap-hibernate/imap-hibernate-client.c b/src/imap-hibernate/imap-hibernate-client.c
new file mode 100644
index 0000000..4065adc
--- /dev/null
+++ b/src/imap-hibernate/imap-hibernate-client.c
@@ -0,0 +1,304 @@
+/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "connection.h"
+#include "istream.h"
+#include "istream-unix.h"
+#include "ostream.h"
+#include "buffer.h"
+#include "base64.h"
+#include "strescape.h"
+#include "master-service.h"
+#include "imap-client.h"
+#include "imap-hibernate-client.h"
+
+struct imap_hibernate_client {
+ struct connection conn;
+ struct imap_client *imap_client;
+ bool imap_client_created;
+ bool finished;
+ bool debug;
+};
+
+struct imap_hibernate_input {
+ /* input we've already read from the IMAP client. */
+ buffer_t *client_input;
+ /* IMAP connection state */
+ buffer_t *state;
+};
+
+static struct connection_list *hibernate_clients = NULL;
+
+static void imap_hibernate_client_destroy(struct connection *conn)
+{
+ struct imap_hibernate_client *client = (struct imap_hibernate_client *)conn;
+
+ if (!client->imap_client_created)
+ master_service_client_connection_destroyed(master_service);
+ else if (client->finished)
+ imap_client_create_finish(client->imap_client);
+ connection_deinit(conn);
+ i_free(conn);
+}
+
+static int
+imap_hibernate_client_parse_input(const char *const *args, pool_t pool,
+ struct imap_client_state *state_r,
+ const char **error_r)
+{
+ const char *key, *value;
+ unsigned int peer_dev_major = 0, peer_dev_minor = 0;
+
+ i_zero(state_r);
+ if (args[0] == NULL) {
+ *error_r = "Missing username in input";
+ return -1;
+ }
+ state_r->username = args[0]; args++;
+ if (args[0] == NULL) {
+ *error_r = "Missing mail_log_prefix in input";
+ return -1;
+ }
+ state_r->mail_log_prefix = args[0]; args++;
+
+ for (; *args != NULL; args++) {
+ value = strchr(*args, '=');
+ if (value != NULL)
+ key = t_strdup_until(*args, value++);
+ else {
+ key = *args;
+ value = "";
+ }
+ if (strcmp(key, "lip") == 0) {
+ if (net_addr2ip(value, &state_r->local_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid lip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "lport") == 0) {
+ if (net_str2port(value, &state_r->local_port) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid lport value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "rip") == 0) {
+ if (net_addr2ip(value, &state_r->remote_ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid rip value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "rport") == 0) {
+ if (net_str2port(value, &state_r->remote_port) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid rport value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_dev_major") == 0) {
+ if (str_to_uint(value, &peer_dev_major) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_dev_major value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_dev_minor") == 0) {
+ if (str_to_uint(value, &peer_dev_minor) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_dev_minor value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "peer_ino") == 0) {
+ if (str_to_ino(value, &state_r->peer_ino) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid peer_ino value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "uid") == 0) {
+ if (str_to_uid(value, &state_r->uid) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid uid value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "gid") == 0) {
+ if (str_to_gid(value, &state_r->gid) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid gid value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "stats") == 0) {
+ state_r->stats = value;
+ } else if (strcmp(key, "idle-cmd") == 0) {
+ state_r->idle_cmd = TRUE;
+ } else if (strcmp(key, "session") == 0) {
+ state_r->session_id = value;
+ } else if (strcmp(key, "mailbox") == 0) {
+ state_r->mailbox_vname = value;
+ } else if (strcmp(key, "session_created") == 0) {
+ if (str_to_time(value, &state_r->session_created) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid session_created value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "userdb_fields") == 0) {
+ state_r->userdb_fields = value;
+ } else if (strcmp(key, "notify_fd") == 0) {
+ state_r->have_notify_fd = TRUE;
+ } else if (strcmp(key, "idle_notify_interval") == 0) {
+ if (str_to_uint(value, &state_r->imap_idle_notify_interval) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid idle_notify_interval value: %s", value);
+ return -1;
+ }
+ } else if (strcmp(key, "tag") == 0) {
+ state_r->tag = i_strdup(value);
+ } else if (strcmp(key, "state") == 0) {
+ buffer_t *state_buf;
+
+ state_buf = buffer_create_dynamic(pool, 1024);
+ if (base64_decode(value, strlen(value), NULL,
+ state_buf) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid state base64 value: %s", value);
+ return -1;
+ }
+ state_r->state = state_buf->data;
+ state_r->state_size = state_buf->used;
+ }
+ }
+ if (state_r->tag == NULL) {
+ *error_r = "Missing tag";
+ return -1;
+ }
+ if (peer_dev_major != 0 || peer_dev_minor != 0)
+ state_r->peer_dev = makedev(peer_dev_major, peer_dev_minor);
+ return 0;
+}
+
+static int
+imap_hibernate_client_input_args(struct connection *conn,
+ const char *const *args, int fd, pool_t pool)
+{
+ struct imap_hibernate_client *client =
+ (struct imap_hibernate_client *)conn;
+ struct imap_client_state state;
+ const char *error;
+
+ if (imap_hibernate_client_parse_input(args, pool, &state, &error) < 0) {
+ e_error(conn->event, "Failed to parse client input: %s", error);
+ o_stream_nsend_str(conn->output, t_strdup_printf(
+ "-Failed to parse client input: %s\n", error));
+ return -1;
+ }
+ client->imap_client = imap_client_create(fd, &state);
+ /* the transferred imap client fd is now counted as the client. */
+ client->imap_client_created = TRUE;
+ return state.have_notify_fd ? 0 : 1;
+}
+
+static int
+imap_hibernate_client_input_line(struct connection *conn, const char *line)
+{
+ struct imap_hibernate_client *client =
+ (struct imap_hibernate_client *)conn;
+ int fd = -1, ret;
+
+ if (!conn->version_received) {
+ if (connection_handshake_args_default(
+ conn, t_strsplit_tabescaped(line)) < 0)
+ return -1;
+ conn->version_received = TRUE;
+ return 1;
+ }
+ if (client->finished) {
+ e_error(conn->event, "Received unexpected line: %s", line);
+ return -1;
+ }
+
+ if (client->imap_client == NULL) {
+ char *const *args;
+ pool_t pool;
+
+ fd = i_stream_unix_get_read_fd(conn->input);
+ if (fd == -1) {
+ e_error(conn->event, "IMAP client fd not received");
+ return -1;
+ }
+
+ pool = pool_alloconly_create("client cmd", 1024);
+ args = p_strsplit_tabescaped(pool, line);
+ ret = imap_hibernate_client_input_args(conn, (const void *)args,
+ fd, pool);
+ if (ret >= 0 && client->debug)
+ e_debug(conn->event, "Create client with input: %s", line);
+ pool_unref(&pool);
+ } else {
+ fd = i_stream_unix_get_read_fd(conn->input);
+ if (fd == -1) {
+ e_error(conn->event,
+ "IMAP notify fd not received (input: %s)", line);
+ ret = -1;
+ } else if (line[0] != '\0') {
+ e_error(conn->event,
+ "Expected empty notify fd line from client, but got: %s", line);
+ o_stream_nsend_str(conn->output,
+ "Expected empty notify fd line");
+ ret = -1;
+ } else {
+ imap_client_add_notify_fd(client->imap_client, fd);
+ ret = 1;
+ }
+ }
+
+ if (ret < 0) {
+ if (client->imap_client != NULL)
+ imap_client_destroy(&client->imap_client, NULL);
+ i_close_fd(&fd);
+ return -1;
+ } else if (ret == 0) {
+ /* still need to read another fd */
+ i_stream_unix_set_read_fd(conn->input);
+ } else {
+ /* finished - wait for disconnection from imap before finishing.
+ this way the old imap process will have time to destroy
+ itself before we have a chance to create another one. */
+ client->finished = TRUE;
+ }
+ o_stream_nsend_str(conn->output, "+\n");
+ return 1;
+}
+
+void imap_hibernate_client_create(int fd, bool debug)
+{
+ struct imap_hibernate_client *client;
+
+ client = i_new(struct imap_hibernate_client, 1);
+ client->debug = debug;
+ client->conn.unix_socket = TRUE;
+ connection_init_server(hibernate_clients, &client->conn,
+ "imap-hibernate", fd, fd);
+ i_stream_unix_set_read_fd(client->conn.input);
+}
+
+static struct connection_settings client_set = {
+ .service_name_in = "imap-hibernate",
+ .service_name_out = "imap-hibernate",
+ .major_version = 1,
+ .minor_version = 0,
+
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs client_vfuncs = {
+ .destroy = imap_hibernate_client_destroy,
+ .input_line = imap_hibernate_client_input_line
+};
+
+void imap_hibernate_clients_init(void)
+{
+ hibernate_clients = connection_list_init(&client_set, &client_vfuncs);
+}
+
+void imap_hibernate_clients_deinit(void)
+{
+ connection_list_deinit(&hibernate_clients);
+}