imap-client-hibernate.c revision bcb4e51a409d94ae670de96afb8483a4f7855294
bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "imap-common.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "fdpass.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "net.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "ostream.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "write-full.h"
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen#include "base64.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "str.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "strescape.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "master-service.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "mailbox-watch.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "imap-state.h"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#include "imap-client.h"
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen#include <sys/stat.h>
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#define IMAP_HIBERNATE_SOCKET_NAME "imap-hibernate"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#define IMAP_HIBERNATE_SEND_TIMEOUT_SECS 10
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen#define IMAP_HIBERNATE_HANDSHAKE "VERSION\timap-hibernate\t1\t0\n"
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen
4ded3d18fa391ae5908f9834f1390cf55e8c99d5Timo Sirainenstatic int imap_hibernate_handshake(int fd, const char *path)
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen{
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen char buf[1024];
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen ssize_t ret;
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen if (write_full(fd, IMAP_HIBERNATE_HANDSHAKE,
2a628a8c90839439baff5b45116f89f2b3cd9e37Aki Tuomi strlen(IMAP_HIBERNATE_HANDSHAKE)) < 0) {
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen i_error("write(%s) failed: %m", path);
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen return -1;
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen } else if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) {
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen i_error("read(%s) failed: %m", path);
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen return -1;
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen } else if (ret > 0 && buf[ret-1] == '\n') {
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen buf[ret-1] = '\0';
3f442e48e5708f2f80cc937cd722c56a4fbd1c70Timo Sirainen if (version_string_verify(buf, "imap-hibernate", 1))
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen return 0;
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen }
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen i_error("%s sent invalid VERSION handshake: %s", path, buf);
4ded3d18fa391ae5908f9834f1390cf55e8c99d5Timo Sirainen return -1;
4ded3d18fa391ae5908f9834f1390cf55e8c99d5Timo Sirainen}
4ded3d18fa391ae5908f9834f1390cf55e8c99d5Timo Sirainen
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainenstatic void imap_hibernate_write_cmd(struct client *client, string_t *cmd,
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen const buffer_t *state, int fd_notify)
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen{
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen struct mail_user *user = client->user;
ba153863e55d75a7c2f28c9c010a905b8887b62bTimo Sirainen struct stat peer_st;
b3a069922c8150a1cb14ec7683444f60dee98b55Timo Sirainen const char *tag;
3f442e48e5708f2f80cc937cd722c56a4fbd1c70Timo Sirainen
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen tag = client->command_queue == NULL ? NULL : client->command_queue->tag;
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen str_append_tabescaped(cmd, user->username);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_append_c(cmd, '\t');
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_append_tabescaped(cmd, user->set->mail_log_prefix);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(cmd, "\tidle_notify_interval=%u",
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen client->set->imap_idle_notify_interval);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (fstat(client->fd_in, &peer_st) == 0) {
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(cmd, "\tpeer_dev_major=%lu\tpeer_dev_minor=%lu\tpeer_ino=%llu",
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen (unsigned long)major(peer_st.st_dev),
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen (unsigned long)minor(peer_st.st_dev),
7b9f0c6aba07358e5520dc60c058126a4cae4056Timo Sirainen (unsigned long long)peer_st.st_ino);
7b9f0c6aba07358e5520dc60c058126a4cae4056Timo Sirainen }
7b9f0c6aba07358e5520dc60c058126a4cae4056Timo Sirainen
7b9f0c6aba07358e5520dc60c058126a4cae4056Timo Sirainen if (client->session_id != NULL) {
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen str_append(cmd, "\tsession=");
4307c886579381dbb1897ea1388ae6978c96f560Timo Sirainen str_append_tabescaped(cmd, client->session_id);
46b823ac3bce2c0f9f0fc73911e48d3a77b04fbeTimo Sirainen }
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen if (user->session_create_time != 0) {
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(cmd, "\tsession_created=%s",
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen dec2str(user->session_create_time));
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen }
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen if (user->conn.local_ip != NULL)
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen str_printfa(cmd, "\tlip=%s", net_ip2addr(user->conn.local_ip));
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (user->conn.remote_ip != NULL)
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_printfa(cmd, "\trip=%s", net_ip2addr(user->conn.remote_ip));
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (client->userdb_fields != NULL) {
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen string_t *userdb_fields = t_str_new(256);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen unsigned int i;
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen for (i = 0; client->userdb_fields[i] != NULL; i++) {
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (i > 0)
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_append_c(userdb_fields, '\t');
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_append_tabescaped(userdb_fields, client->userdb_fields[i]);
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen }
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen str_append(cmd, "\tuserdb_fields=");
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen str_append_tabescaped(cmd, str_c(userdb_fields));
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen }
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen if (user->uid != (uid_t)-1)
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen str_printfa(cmd, "\tuid=%s", dec2str(user->uid));
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen if (user->gid != (gid_t)-1)
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen str_printfa(cmd, "\tgid=%s", dec2str(user->gid));
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen if (tag != NULL)
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen str_printfa(cmd, "\ttag=%s", tag);
128ab2c52a29068be87e12ab5aebbb8fdc933adfTimo Sirainen str_append(cmd, "\tstats=");
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_append_tabescaped(cmd, client_stats(client));
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen if (client->command_queue != NULL &&
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen strcasecmp(client->command_queue->name, "IDLE") == 0)
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen str_append(cmd, "\tidle-cmd");
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen if (fd_notify != -1)
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen str_append(cmd, "\tnotify_fd");
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen str_append(cmd, "\tstate=");
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainen base64_encode(state->data, state->used, cmd);
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen str_append_c(cmd, '\n');
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen}
033e315cc9396332a09b98bee7824b8e6641c2fcTimo Sirainen
e156adefc1260d31a145df2f5e9b3c82050d4163Timo Sirainenstatic int
imap_hibernate_process_send_cmd(int fd_socket, const char *path,
const string_t *cmd, int fd_client)
{
ssize_t ret;
i_assert(fd_socket != -1);
i_assert(str_len(cmd) > 1);
if (imap_hibernate_handshake(fd_socket, path) < 0)
return -1;
if ((ret = fd_send(fd_socket, fd_client, str_data(cmd), 1)) < 0) {
i_error("fd_send(%s) failed: %m", path);
return -1;
}
if ((ret = write_full(fd_socket, str_data(cmd)+1, str_len(cmd)-1)) < 0) {
i_error("write(%s) failed: %m", path);
return -1;
}
return 0;
}
static int imap_hibernate_process_read(int fd, const char *path)
{
char buf[1024];
ssize_t ret;
if ((ret = read(fd, buf, sizeof(buf)-1)) < 0) {
i_error("read(%s) failed: %m", path);
return -1;
} else if (ret == 0) {
i_error("%s disconnected", path);
return -1;
} else if (buf[0] != '+') {
buf[ret] = '\0';
i_error("%s returned failure: %s", path,
ret > 0 && buf[0] == '-' ? buf+1 : buf);
return -1;
} else {
return 0;
}
}
static int
imap_hibernate_process_send(struct client *client,
const buffer_t *state, int fd_notify, int *fd_r)
{
string_t *cmd = t_str_new(512);
const char *path;
ssize_t ret = 0;
int fd;
i_assert(state->used > 0);
*fd_r = -1;
path = t_strconcat(client->user->set->base_dir,
"/"IMAP_HIBERNATE_SOCKET_NAME, NULL);
fd = net_connect_unix_with_retries(path, 1000);
if (fd == -1) {
i_error("net_connect_unix(%s) failed: %m", path);
return -1;
}
net_set_nonblock(fd, FALSE);
imap_hibernate_write_cmd(client, cmd, state, fd_notify);
alarm(IMAP_HIBERNATE_SEND_TIMEOUT_SECS);
if (imap_hibernate_process_send_cmd(fd, path, cmd, client->fd_in) < 0 ||
imap_hibernate_process_read(fd, path) < 0)
ret = -1;
else if (fd_notify != -1) {
if ((ret = fd_send(fd, fd_notify, "\n", 1)) < 0)
i_error("fd_send(%s) failed: %m", path);
else
ret = imap_hibernate_process_read(fd, path);
}
alarm(0);
if (ret < 0) {
net_disconnect(fd);
return -1;
}
*fd_r = fd;
return 0;
}
bool imap_client_hibernate(struct client **_client)
{
struct client *client = *_client;
buffer_t *state;
const char *error;
int ret, fd_notify = -1, fd_hibernate = -1;
if (client->fd_in != client->fd_out) {
/* we won't try to hibernate stdio clients */
return FALSE;
}
if (o_stream_get_buffer_used_size(client->output) > 0) {
/* wait until we've sent the pending output to client */
return FALSE;
}
state = buffer_create_dynamic(default_pool, 1024);
ret = imap_state_export_internal(client, state, &error);
if (ret < 0) {
i_error("Couldn't hibernate imap client: "
"Couldn't export state: %s (mailbox=%s)", error,
client->mailbox == NULL ? "" :
mailbox_get_vname(client->mailbox));
} else if (ret == 0 && client->user->mail_debug) {
i_debug("Couldn't hibernate imap client: "
"Couldn't export state: %s (mailbox=%s)", error,
client->mailbox == NULL ? "" :
mailbox_get_vname(client->mailbox));
}
if (ret > 0 && client->mailbox != NULL) {
fd_notify = mailbox_watch_extract_notify_fd(client->mailbox,
&error);
if (fd_notify == -1) {
if (client->user->mail_debug) {
i_debug("Couldn't hibernate imap client: "
"Couldn't extract notifications fd: %s",
error);
}
ret = -1;
}
}
if (ret > 0) {
if (imap_hibernate_process_send(client, state, fd_notify, &fd_hibernate) < 0)
ret = -1;
}
i_close_fd(&fd_notify);
if (ret > 0) {
/* hide the disconnect log message, because the client didn't
actually log out */
if (client->user->mail_debug) {
i_debug("Successfully hibernated imap client in mailbox %s",
client->mailbox == NULL ? "<none>" :
mailbox_get_vname(client->mailbox));
}
client->disconnected = TRUE;
client->hibernated = TRUE;
client_destroy(client, NULL);
*_client = NULL;
}
/* notify imap-hibernate that we're done by closing the connection.
do this only after client is destroyed. this way imap-hibernate
won't try to launch another imap process too early and cause
problems (like sending duplicate session ID to stats process) */
if (fd_hibernate != -1)
net_disconnect(fd_hibernate);
buffer_free(&state);
return ret > 0;
}