Mercurial > dovecot > core-2.2
view src/director/doveadm-connection.c @ 19552:0f22db71df7a
global: freshen copyright
git ls-files | xargs perl -p -i -e 's/(\d+)-201[0-5]/$1-2016/g;s/ (201[0-5]) Dovecot/ $1-2016 Dovecot/'
author | Timo Sirainen <timo.sirainen@dovecot.fi> |
---|---|
date | Wed, 13 Jan 2016 12:24:03 +0200 |
parents | 1d2999cfe0a4 |
children | 076b552163d2 |
line wrap: on
line source
/* Copyright (c) 2010-2016 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "ioloop.h" #include "net.h" #include "istream.h" #include "ostream.h" #include "array.h" #include "str.h" #include "strescape.h" #include "llist.h" #include "master-service.h" #include "user-directory.h" #include "mail-host.h" #include "director.h" #include "director-host.h" #include "director-request.h" #include "director-connection.h" #include "doveadm-connection.h" #include <unistd.h> #define DOVEADM_PROTOCOL_VERSION_MAJOR 1 #define DOVEADM_HANDSHAKE "VERSION\tdirector-doveadm\t1\t0\n" #define MAX_VALID_VHOST_COUNT 1000 struct doveadm_connection { struct doveadm_connection *prev, *next; int fd; struct io *io; struct istream *input; struct ostream *output; struct director *dir; unsigned int handshaked:1; }; static struct doveadm_connection *doveadm_connections; static void doveadm_connection_deinit(struct doveadm_connection **_conn); static void doveadm_cmd_host_list(struct doveadm_connection *conn) { struct mail_host *const *hostp; string_t *str = t_str_new(1024); array_foreach(mail_hosts_get(conn->dir->mail_hosts), hostp) { str_printfa(str, "%s\t%u\t%u\t", net_ip2addr(&(*hostp)->ip), (*hostp)->vhost_count, (*hostp)->user_count); str_append_tabescaped(str, mail_host_get_tag(*hostp)); str_printfa(str, "\t%c\t%ld", (*hostp)->down ? 'D' : 'U', (long)(*hostp)->last_updown_change); str_append_c(str, '\n'); } str_append_c(str, '\n'); o_stream_nsend(conn->output, str_data(str), str_len(str)); } static void doveadm_cmd_host_list_removed(struct doveadm_connection *conn) { struct mail_host_list *orig_hosts_list; struct mail_host *const *orig_hosts, *const *cur_hosts; unsigned int i, j, orig_hosts_count, cur_hosts_count; string_t *str = t_str_new(1024); int ret; orig_hosts_list = mail_hosts_init(conn->dir->set->director_consistent_hashing); (void)mail_hosts_parse_and_add(orig_hosts_list, conn->dir->set->director_mail_servers); orig_hosts = array_get(mail_hosts_get(orig_hosts_list), &orig_hosts_count); cur_hosts = array_get(mail_hosts_get(conn->dir->mail_hosts), &cur_hosts_count); /* the hosts are sorted by IP */ for (i = j = 0; i < orig_hosts_count && j < cur_hosts_count; ) { ret = net_ip_cmp(&orig_hosts[i]->ip, &cur_hosts[j]->ip); if (ret == 0) i++, j++; else if (ret > 0) j++; else { str_printfa(str, "%s\n", net_ip2addr(&orig_hosts[i]->ip)); i++; } } for (; i < orig_hosts_count; i++) str_printfa(str, "%s\n", net_ip2addr(&orig_hosts[i]->ip)); str_append_c(str, '\n'); o_stream_nsend(conn->output, str_data(str), str_len(str)); mail_hosts_deinit(&orig_hosts_list); } static void doveadm_director_append_status(struct director *dir, string_t *str) { if (!dir->ring_handshaked) str_append(str, "handshaking"); else if (dir->ring_synced) str_append(str, "synced"); else { str_printfa(str, "syncing - last sync %d secs ago", (int)(ioloop_time - dir->ring_last_sync_time)); } } static void doveadm_director_connection_append_status(struct director_connection *conn, string_t *str) { if (!director_connection_is_handshaked(conn)) str_append(str, "handshaking"); else if (director_connection_is_synced(conn)) str_append(str, "synced"); else str_append(str, "syncing"); } static void doveadm_director_host_append_status(struct director *dir, const struct director_host *host, string_t *str) { struct director_connection *conn = NULL; if (dir->left != NULL && director_connection_get_host(dir->left) == host) conn = dir->left; else if (dir->right != NULL && director_connection_get_host(dir->right) == host) conn = dir->right; else { /* we might have a connection that is being connected */ struct director_connection *const *connp; array_foreach(&dir->connections, connp) { if (director_connection_get_host(*connp) == host) { conn = *connp; break; } } } if (conn != NULL) doveadm_director_connection_append_status(conn, str); } static void doveadm_cmd_director_list(struct doveadm_connection *conn) { struct director *dir = conn->dir; struct director_host *const *hostp; string_t *str = t_str_new(1024); const char *type; bool left, right; time_t last_failed; array_foreach(&dir->dir_hosts, hostp) { const struct director_host *host = *hostp; left = dir->left != NULL && director_connection_get_host(dir->left) == host; right = dir->right != NULL && director_connection_get_host(dir->right) == host; if (host->removed) type = "removed"; else if (dir->self_host == host) type = "self"; else if (left) type = right ? "l+r" : "left"; else if (right) type = "right"; else type = ""; last_failed = I_MAX(host->last_network_failure, host->last_protocol_failure); str_printfa(str, "%s\t%u\t%s\t%lu\t", net_ip2addr(&host->ip), host->port, type, (unsigned long)last_failed); if (dir->self_host == host) doveadm_director_append_status(dir, str); else doveadm_director_host_append_status(dir, host, str); str_append_c(str, '\n'); } str_append_c(str, '\n'); o_stream_nsend(conn->output, str_data(str), str_len(str)); } static bool doveadm_cmd_director_add(struct doveadm_connection *conn, const char *line) { const char *const *args; struct director_host *host; struct ip_addr ip; in_port_t port = conn->dir->self_port; args = t_strsplit_tab(line); if (args[0] == NULL || net_addr2ip(line, &ip) < 0 || (args[1] != NULL && net_str2port(args[1], &port) < 0)) { i_error("doveadm sent invalid DIRECTOR-ADD parameters"); return FALSE; } if (director_host_lookup(conn->dir, &ip, port) == NULL) { host = director_host_add(conn->dir, &ip, port); director_notify_ring_added(host, conn->dir->self_host); } o_stream_nsend(conn->output, "OK\n", 3); return TRUE; } static bool doveadm_cmd_director_remove(struct doveadm_connection *conn, const char *line) { const char *const *args; struct director_host *host; struct ip_addr ip; in_port_t port = 0; args = t_strsplit_tab(line); if (args[0] == NULL || net_addr2ip(line, &ip) < 0 || (args[1] != NULL && net_str2port(args[1], &port) < 0)) { i_error("doveadm sent invalid DIRECTOR-REMOVE parameters"); return FALSE; } host = port != 0 ? director_host_lookup(conn->dir, &ip, port) : director_host_lookup_ip(conn->dir, &ip); if (host == NULL) o_stream_nsend_str(conn->output, "NOTFOUND\n"); else { director_ring_remove(host, conn->dir->self_host); o_stream_nsend(conn->output, "OK\n", 3); } return TRUE; } static bool doveadm_cmd_host_set_or_update(struct doveadm_connection *conn, const char *line, bool update) { struct director *dir = conn->dir; const char *const *args, *ip_str, *tag = ""; struct mail_host *host; struct ip_addr ip; unsigned int vhost_count = UINT_MAX; args = t_strsplit_tab(line); ip_str = args[0]; if (ip_str != NULL) { tag = strchr(ip_str, '@'); if (tag == NULL) tag = ""; else ip_str = t_strdup_until(ip_str, tag++); } if (ip_str == NULL || net_addr2ip(ip_str, &ip) < 0 || (args[1] != NULL && str_to_uint(args[1], &vhost_count) < 0) || (args[1] == NULL && update)) { i_error("doveadm sent invalid %s parameters: %s", update ? "HOST-UPDATE" : "HOST-SET", line); return FALSE; } if (vhost_count > MAX_VALID_VHOST_COUNT && vhost_count != UINT_MAX) { o_stream_nsend_str(conn->output, "vhost count too large\n"); return TRUE; } host = mail_host_lookup(dir->mail_hosts, &ip); if (host == NULL) { if (update) { o_stream_nsend_str(conn->output, "NOTFOUND\n"); return TRUE; } host = mail_host_add_ip(dir->mail_hosts, &ip, tag); } else if (tag[0] != '\0' && strcmp(mail_host_get_tag(host), tag) != 0) { o_stream_nsend_str(conn->output, "host tag can't be changed\n"); return TRUE; } else if (host->desynced) { o_stream_nsend_str(conn->output, "host is already being updated - try again later\n"); return TRUE; } if (vhost_count != UINT_MAX) mail_host_set_vhost_count(host, vhost_count); /* NOTE: we don't support changing a tag for an existing host. it needs to be removed first. otherwise it would be a bit ugly to handle. */ director_update_host(dir, dir->self_host, NULL, host); o_stream_nsend(conn->output, "OK\n", 3); return TRUE; } static bool doveadm_cmd_host_set(struct doveadm_connection *conn, const char *line) { return doveadm_cmd_host_set_or_update(conn, line, FALSE); } static bool doveadm_cmd_host_update(struct doveadm_connection *conn, const char *line) { return doveadm_cmd_host_set_or_update(conn, line, TRUE); } static bool doveadm_cmd_host_updown(struct doveadm_connection *conn, bool down, const char *line) { struct mail_host *host; struct ip_addr ip; if (net_addr2ip(line, &ip) < 0) { i_error("doveadm sent invalid %s parameters: %s", down ? "HOST-DOWN" : "HOST-UP", line); return FALSE; } host = mail_host_lookup(conn->dir->mail_hosts, &ip); if (host == NULL) { o_stream_nsend_str(conn->output, "NOTFOUND\n"); return TRUE; } if (host->down == down) ; else if (host->desynced) { o_stream_nsend_str(conn->output, "host is already being updated - try again later\n"); return TRUE; } else { mail_host_set_down(host, down, ioloop_time); director_update_host(conn->dir, conn->dir->self_host, NULL, host); } o_stream_nsend(conn->output, "OK\n", 3); return TRUE; } static bool doveadm_cmd_host_remove(struct doveadm_connection *conn, const char *line) { struct mail_host *host; struct ip_addr ip; if (net_addr2ip(line, &ip) < 0) { i_error("doveadm sent invalid HOST-REMOVE parameters"); return FALSE; } host = mail_host_lookup(conn->dir->mail_hosts, &ip); if (host == NULL) o_stream_nsend_str(conn->output, "NOTFOUND\n"); else { director_remove_host(conn->dir, conn->dir->self_host, NULL, host); o_stream_nsend(conn->output, "OK\n", 3); } return TRUE; } static void doveadm_cmd_host_flush_all(struct doveadm_connection *conn) { struct mail_host *const *hostp; unsigned int total_user_count = 0; array_foreach(mail_hosts_get(conn->dir->mail_hosts), hostp) { total_user_count += (*hostp)->user_count; director_flush_host(conn->dir, conn->dir->self_host, NULL, *hostp); } i_warning("Flushed all backend hosts with %u users. This is an unsafe " "operation and may cause the same users to end up in multiple backends.", total_user_count); o_stream_nsend(conn->output, "OK\n", 3); } static bool doveadm_cmd_host_flush(struct doveadm_connection *conn, const char *line) { struct mail_host *host; struct ip_addr ip; if (*line == '\0') { doveadm_cmd_host_flush_all(conn); return TRUE; } if (net_addr2ip(line, &ip) < 0) { i_error("doveadm sent invalid HOST-FLUSH parameters"); return FALSE; } host = mail_host_lookup(conn->dir->mail_hosts, &ip); if (host == NULL) o_stream_nsend_str(conn->output, "NOTFOUND\n"); else { director_flush_host(conn->dir, conn->dir->self_host, NULL, host); o_stream_nsend(conn->output, "OK\n", 3); } return TRUE; } static void director_host_reset_users(struct director *dir, struct director_host *src, struct mail_host *host) { struct user_directory_iter *iter; struct user *user; struct mail_host *new_host; if (dir->right != NULL) director_connection_cork(dir->right); iter = user_directory_iter_init(dir->users); while ((user = user_directory_iter_next(iter)) != NULL) { if (user->host != host) continue; new_host = mail_host_get_by_hash(dir->mail_hosts, user->username_hash, mail_host_get_tag(host)); if (new_host != host) T_BEGIN { director_move_user(dir, src, NULL, user->username_hash, new_host); } T_END; } user_directory_iter_deinit(&iter); if (dir->right != NULL) director_connection_uncork(dir->right); } static void doveadm_cmd_host_reset_users_all(struct doveadm_connection *conn) { struct mail_host *const *hostp; array_foreach(mail_hosts_get(conn->dir->mail_hosts), hostp) director_host_reset_users(conn->dir, conn->dir->self_host, *hostp); o_stream_nsend(conn->output, "OK\n", 3); } static bool doveadm_cmd_host_reset_users(struct doveadm_connection *conn, const char *line) { struct mail_host *host; struct ip_addr ip; if (line[0] == '\0') { doveadm_cmd_host_reset_users_all(conn); return TRUE; } if (net_addr2ip(line, &ip) < 0) { i_error("doveadm sent invalid HOST-RESET-USERS parameters"); return FALSE; } host = mail_host_lookup(conn->dir->mail_hosts, &ip); if (host == NULL) o_stream_nsend_str(conn->output, "NOTFOUND\n"); else { director_host_reset_users(conn->dir, conn->dir->self_host, host); o_stream_nsend(conn->output, "OK\n", 3); } return TRUE; } static bool doveadm_cmd_user_lookup(struct doveadm_connection *conn, const char *line) { struct user *user; struct mail_host *host; const char *username, *tag, *const *args; unsigned int username_hash; string_t *str = t_str_new(256); args = t_strsplit_tab(line); if (args[0] == NULL) { username = ""; tag = ""; } else { username = args[0]; tag = args[1] != NULL ? args[1] : ""; } if (str_to_uint(username, &username_hash) < 0) username_hash = user_directory_get_username_hash(conn->dir->users, username); /* get user's current host */ user = user_directory_lookup(conn->dir->users, username_hash); if (user == NULL) str_append(str, "\t0"); else { str_printfa(str, "%s\t%u", net_ip2addr(&user->host->ip), user->timestamp + conn->dir->set->director_user_expire); } /* get host if it wasn't in user directory */ host = mail_host_get_by_hash(conn->dir->mail_hosts, username_hash, tag); if (host == NULL) str_append(str, "\t"); else str_printfa(str, "\t%s", net_ip2addr(&host->ip)); /* get host with default configuration */ host = mail_host_get_by_hash(conn->dir->orig_config_hosts, username_hash, tag); if (host == NULL) str_append(str, "\t\n"); else str_printfa(str, "\t%s\n", net_ip2addr(&host->ip)); o_stream_nsend(conn->output, str_data(str), str_len(str)); return TRUE; } static bool doveadm_cmd_user_list(struct doveadm_connection *conn, const char *line) { struct user_directory_iter *iter; struct user *user; struct ip_addr ip; if (*line != '\0') { if (net_addr2ip(line, &ip) < 0) { i_error("doveadm sent invalid USER-LIST parameters"); return FALSE; } } else { ip.family = 0; } iter = user_directory_iter_init(conn->dir->users); while ((user = user_directory_iter_next(iter)) != NULL) { if (ip.family == 0 || net_ip_compare(&ip, &user->host->ip)) T_BEGIN { unsigned int expire_time = user->timestamp + conn->dir->set->director_user_expire; o_stream_nsend_str(conn->output, t_strdup_printf( "%u\t%u\t%s\n", user->username_hash, expire_time, net_ip2addr(&user->host->ip))); } T_END; } user_directory_iter_deinit(&iter); o_stream_nsend(conn->output, "\n", 1); return TRUE; } static bool doveadm_cmd_user_move(struct doveadm_connection *conn, const char *line) { unsigned int username_hash; const char *const *args; struct user *user; struct mail_host *host; struct ip_addr ip; args = t_strsplit_tab(line); if (args[0] == NULL || args[1] == NULL || net_addr2ip(args[1], &ip) < 0) { i_error("doveadm sent invalid USER-MOVE parameters: %s", line); return FALSE; } host = mail_host_lookup(conn->dir->mail_hosts, &ip); if (host == NULL) { o_stream_nsend_str(conn->output, "NOTFOUND\n"); return TRUE; } if (str_to_uint(args[0], &username_hash) < 0) username_hash = user_directory_get_username_hash(conn->dir->users, line); user = user_directory_lookup(conn->dir->users, username_hash); if (user != NULL && user->kill_state != USER_KILL_STATE_NONE) { o_stream_nsend_str(conn->output, "TRYAGAIN\n"); return TRUE; } director_move_user(conn->dir, conn->dir->self_host, NULL, username_hash, host); o_stream_nsend(conn->output, "OK\n", 3); return TRUE; } static bool doveadm_cmd_user_kick(struct doveadm_connection *conn, const char *line) { const char *const *args; args = t_strsplit_tab(line); if (args[0] == NULL) { i_error("doveadm sent invalid USER-KICK parameters: %s", line); return FALSE; } director_kick_user(conn->dir, conn->dir->self_host, NULL, args[0]); o_stream_nsend(conn->output, "OK\n", 3); return TRUE; } static void doveadm_connection_input(struct doveadm_connection *conn) { const char *line, *cmd, *args; bool ret = TRUE; if (!conn->handshaked) { if ((line = i_stream_read_next_line(conn->input)) == NULL) { if (conn->input->eof || conn->input->stream_errno != 0) doveadm_connection_deinit(&conn); return; } if (!version_string_verify(line, "director-doveadm", DOVEADM_PROTOCOL_VERSION_MAJOR)) { i_error("doveadm not compatible with this server " "(mixed old and new binaries?)"); doveadm_connection_deinit(&conn); return; } conn->handshaked = TRUE; } while ((line = i_stream_read_next_line(conn->input)) != NULL && ret) { args = strchr(line, '\t'); if (args == NULL) { cmd = line; args = ""; } else { cmd = t_strdup_until(line, args); args++; } if (strcmp(cmd, "HOST-LIST") == 0) doveadm_cmd_host_list(conn); else if (strcmp(cmd, "HOST-LIST-REMOVED") == 0) doveadm_cmd_host_list_removed(conn); else if (strcmp(cmd, "DIRECTOR-LIST") == 0) doveadm_cmd_director_list(conn); else if (strcmp(cmd, "DIRECTOR-ADD") == 0) ret = doveadm_cmd_director_add(conn, args); else if (strcmp(cmd, "DIRECTOR-REMOVE") == 0) ret = doveadm_cmd_director_remove(conn, args); else if (strcmp(cmd, "HOST-SET") == 0) ret = doveadm_cmd_host_set(conn, args); else if (strcmp(cmd, "HOST-UPDATE") == 0) ret = doveadm_cmd_host_update(conn, args); else if (strcmp(cmd, "HOST-UP") == 0) ret = doveadm_cmd_host_updown(conn, FALSE, args); else if (strcmp(cmd, "HOST-DOWN") == 0) ret = doveadm_cmd_host_updown(conn, TRUE, args); else if (strcmp(cmd, "HOST-REMOVE") == 0) ret = doveadm_cmd_host_remove(conn, args); else if (strcmp(cmd, "HOST-FLUSH") == 0) ret = doveadm_cmd_host_flush(conn, args); else if (strcmp(cmd, "HOST-RESET-USERS") == 0) ret = doveadm_cmd_host_reset_users(conn, args); else if (strcmp(cmd, "USER-LOOKUP") == 0) ret = doveadm_cmd_user_lookup(conn, args); else if (strcmp(cmd, "USER-LIST") == 0) ret = doveadm_cmd_user_list(conn, args); else if (strcmp(cmd, "USER-MOVE") == 0) ret = doveadm_cmd_user_move(conn, args); else if (strcmp(cmd, "USER-KICK") == 0) ret = doveadm_cmd_user_kick(conn, args); else { i_error("doveadm sent unknown command: %s", line); ret = FALSE; } } if (conn->input->eof || conn->input->stream_errno != 0 || !ret) doveadm_connection_deinit(&conn); } struct doveadm_connection * doveadm_connection_init(struct director *dir, int fd) { struct doveadm_connection *conn; conn = i_new(struct doveadm_connection, 1); conn->fd = fd; conn->dir = dir; conn->input = i_stream_create_fd(conn->fd, 1024, FALSE); conn->output = o_stream_create_fd(conn->fd, (size_t)-1, FALSE); o_stream_set_no_error_handling(conn->output, TRUE); conn->io = io_add(conn->fd, IO_READ, doveadm_connection_input, conn); o_stream_nsend_str(conn->output, DOVEADM_HANDSHAKE); DLLIST_PREPEND(&doveadm_connections, conn); return conn; } static void doveadm_connection_deinit(struct doveadm_connection **_conn) { struct doveadm_connection *conn = *_conn; *_conn = NULL; DLLIST_REMOVE(&doveadm_connections, conn); io_remove(&conn->io); i_stream_unref(&conn->input); o_stream_unref(&conn->output); if (close(conn->fd) < 0) i_error("close(doveadm connection) failed: %m"); i_free(conn); master_service_client_connection_destroyed(master_service); } void doveadm_connections_deinit(void) { while (doveadm_connections != NULL) { struct doveadm_connection *conn = doveadm_connections; doveadm_connection_deinit(&conn); } }