/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "iostream.h" #include "iostream-proxy.h" #include "iostream-rawlog.h" #include "iostream-ssl.h" #include "llist.h" #include "array.h" #include "str.h" #include "strescape.h" #include "time-util.h" #include "master-service.h" #include "master-service-ssl-settings.h" #include "ipc-server.h" #include "mail-user-hash.h" #include "client-common.h" #include "login-proxy-state.h" #include "login-proxy.h" #define MAX_PROXY_INPUT_SIZE 4096 #define PROXY_MAX_OUTBUF_SIZE 1024 #define LOGIN_PROXY_DIE_IDLE_SECS 2 #define LOGIN_PROXY_IPC_PATH "ipc-proxy" #define LOGIN_PROXY_IPC_NAME "proxy" #define LOGIN_PROXY_KILL_PREFIX "Disconnected by proxy: " #define KILLED_BY_ADMIN_REASON "Kicked by admin" #define KILLED_BY_DIRECTOR_REASON "Kicked via director" #define KILLED_BY_SHUTDOWN_REASON "Process shutting down" #define LOGIN_PROXY_SIDE_SELF "proxy" /* Wait this long before retrying on reconnect */ #define PROXY_CONNECT_RETRY_MSECS 1000 /* Don't even try to reconnect if proxying will timeout in less than this. */ #define PROXY_CONNECT_RETRY_MIN_MSECS (PROXY_CONNECT_RETRY_MSECS + 100) #define PROXY_DISCONNECT_INTERVAL_MSECS 100 #define LOGIN_PROXY_SIDE_CLIENT IOSTREAM_PROXY_SIDE_LEFT #define LOGIN_PROXY_SIDE_SERVER IOSTREAM_PROXY_SIDE_RIGHT enum login_proxy_free_flags { LOGIN_PROXY_FREE_FLAG_DELAYED = BIT(0) }; struct login_proxy { struct login_proxy *prev, *next; struct client *client; struct event *event; int server_fd; struct io *client_wait_io, *server_io; struct istream *client_input, *server_input; struct ostream *client_output, *server_output; struct iostream_proxy *iostream_proxy; struct ssl_iostream *server_ssl_iostream; struct timeval created; struct timeout *to, *to_notify; struct login_proxy_record *state_rec; struct ip_addr ip, source_ip; char *host; in_port_t port; unsigned int connect_timeout_msecs; unsigned int notify_refresh_secs; unsigned int host_immediate_failure_after_secs; unsigned int reconnect_count; enum login_proxy_ssl_flags ssl_flags; char *rawlog_dir; login_proxy_input_callback_t *input_callback; login_proxy_failure_callback_t *failure_callback; bool connected:1; bool detached:1; bool destroying:1; bool delayed_disconnect:1; bool disable_reconnect:1; bool num_waiting_connections_updated:1; }; static struct login_proxy_state *proxy_state; static struct login_proxy *login_proxies = NULL; static struct login_proxy *login_proxies_pending = NULL; static struct login_proxy *login_proxies_disconnecting = NULL; static struct ipc_server *login_proxy_ipc_server; static unsigned int detached_login_proxies_count = 0; static int login_proxy_connect(struct login_proxy *proxy); static void login_proxy_disconnect(struct login_proxy *proxy); static void login_proxy_ipc_cmd(struct ipc_cmd *cmd, const char *line); static void login_proxy_free_final(struct login_proxy *proxy); static void ATTR_NULL(2) login_proxy_free_full(struct login_proxy **_proxy, const char *log_msg, const char *disconnect_side, const char *disconnect_reason, enum login_proxy_free_flags flags); static time_t proxy_last_io(struct login_proxy *proxy) { struct timeval tv1, tv2, tv3, tv4; i_stream_get_last_read_time(proxy->client_input, &tv1); i_stream_get_last_read_time(proxy->server_input, &tv2); o_stream_get_last_write_time(proxy->client_output, &tv3); o_stream_get_last_write_time(proxy->server_output, &tv4); return I_MAX(tv1.tv_sec, I_MAX(tv2.tv_sec, I_MAX(tv3.tv_sec, tv4.tv_sec))); } static void login_proxy_free_errstr(struct login_proxy **_proxy, const char *errstr, bool server) { struct login_proxy *proxy = *_proxy; string_t *log_msg = t_str_new(128); const char *disconnect_side = server ? "server" : "client"; str_printfa(log_msg, "Disconnected by %s", disconnect_side); if (errstr[0] != '\0') str_printfa(log_msg, ": %s", errstr); str_printfa(log_msg, " (%ds idle, in=%"PRIuUOFF_T", out=%"PRIuUOFF_T, (int)(ioloop_time - proxy_last_io(proxy)), proxy->server_output->offset, proxy->client_output->offset); if (o_stream_get_buffer_used_size(proxy->client_output) > 0) { str_printfa(log_msg, "+%zu", o_stream_get_buffer_used_size(proxy->client_output)); } if (iostream_proxy_is_waiting_output(proxy->iostream_proxy, LOGIN_PROXY_SIDE_SERVER)) str_append(log_msg, ", client output blocked"); if (iostream_proxy_is_waiting_output(proxy->iostream_proxy, LOGIN_PROXY_SIDE_CLIENT)) str_append(log_msg, ", server output blocked"); str_append_c(log_msg, ')'); login_proxy_free_full(_proxy, str_c(log_msg), errstr, disconnect_side, server ? LOGIN_PROXY_FREE_FLAG_DELAYED : 0); } static void proxy_client_disconnected_input(struct login_proxy *proxy) { /* we're already disconnected from server. either wait for disconnection timeout or for client to disconnect itself. */ if (i_stream_read(proxy->client_input) < 0) login_proxy_free_final(proxy); else { i_stream_skip(proxy->client_input, i_stream_get_data_size(proxy->client_input)); } } static void proxy_prelogin_input(struct login_proxy *proxy) { proxy->input_callback(proxy->client); } static void proxy_plain_connected(struct login_proxy *proxy) { proxy->server_input = i_stream_create_fd(proxy->server_fd, MAX_PROXY_INPUT_SIZE); proxy->server_output = o_stream_create_fd(proxy->server_fd, SIZE_MAX); o_stream_set_no_error_handling(proxy->server_output, TRUE); proxy->server_io = io_add(proxy->server_fd, IO_READ, proxy_prelogin_input, proxy); if (proxy->rawlog_dir != NULL) { if (iostream_rawlog_create(proxy->rawlog_dir, &proxy->server_input, &proxy->server_output) < 0) i_free(proxy->rawlog_dir); } } static void proxy_fail_connect(struct login_proxy *proxy) { i_assert(!proxy->num_waiting_connections_updated); if (timeval_cmp(&proxy->created, &proxy->state_rec->last_success) < 0) { /* there was a successful connection done since we started connecting. perhaps this is just a temporary one-off failure. */ } else { proxy->state_rec->last_failure = ioloop_timeval; } i_assert(proxy->state_rec->num_waiting_connections > 0); proxy->state_rec->num_waiting_connections--; proxy->num_waiting_connections_updated = TRUE; } void login_proxy_append_success_log_info(struct login_proxy *proxy, string_t *str) { int msecs = timeval_diff_msecs(&ioloop_timeval, &proxy->created); str_printfa(str, " (%d.%03d secs", msecs/1000, msecs%1000); if (proxy->reconnect_count > 0) str_printfa(str, ", %u reconnects", proxy->reconnect_count); str_append_c(str, ')'); } static void proxy_connect_error_append(struct login_proxy *proxy, string_t *str) { struct ip_addr local_ip; in_port_t local_port; if (!proxy->connected) { str_printfa(str, "connect(%s, %u) failed: %m", net_ip2addr(&proxy->ip), proxy->port); } else { str_printfa(str, "Login timed out in state=%s", client_proxy_get_state(proxy->client)); } str_printfa(str, " (after %u secs", (unsigned int)(ioloop_time - proxy->created.tv_sec)); if (proxy->reconnect_count > 0) str_printfa(str, ", %u reconnects", proxy->reconnect_count); if (proxy->server_fd != -1 && net_getsockname(proxy->server_fd, &local_ip, &local_port) == 0) { str_printfa(str, ", local=%s:%u", net_ip2addr(&local_ip), local_port); } else if (proxy->source_ip.family != 0) { str_printfa(str, ", local=%s", net_ip2addr(&proxy->source_ip)); } str_append_c(str, ')'); } static void proxy_reconnect_timeout(struct login_proxy *proxy) { timeout_remove(&proxy->to); (void)login_proxy_connect(proxy); } static bool proxy_try_reconnect(struct login_proxy *proxy) { int since_started_msecs, left_msecs; if (proxy->reconnect_count >= proxy->client->set->login_proxy_max_reconnects) return FALSE; if (proxy->disable_reconnect) return FALSE; since_started_msecs = timeval_diff_msecs(&ioloop_timeval, &proxy->created); if (since_started_msecs < 0) return FALSE; /* time moved backwards */ left_msecs = (int)proxy->connect_timeout_msecs - since_started_msecs; if (left_msecs <= PROXY_CONNECT_RETRY_MIN_MSECS) return FALSE; login_proxy_disconnect(proxy); proxy->to = timeout_add(PROXY_CONNECT_RETRY_MSECS, proxy_reconnect_timeout, proxy); proxy->reconnect_count++; return TRUE; } static bool proxy_connect_failed(struct login_proxy *proxy) { string_t *str = t_str_new(128); if (!proxy->connected) proxy_fail_connect(proxy); proxy_connect_error_append(proxy, str); return login_proxy_failed(proxy, proxy->event, LOGIN_PROXY_FAILURE_TYPE_CONNECT, str_c(str)); } static void proxy_wait_connect(struct login_proxy *proxy) { errno = net_geterror(proxy->server_fd); if (errno != 0) { (void)proxy_connect_failed(proxy); return; } proxy->connected = TRUE; proxy->num_waiting_connections_updated = TRUE; proxy->state_rec->last_success = ioloop_timeval; i_assert(proxy->state_rec->num_waiting_connections > 0); proxy->state_rec->num_waiting_connections--; proxy->state_rec->num_proxying_connections++; proxy->state_rec->num_disconnects_since_ts = 0; io_remove(&proxy->server_io); proxy_plain_connected(proxy); if ((proxy->ssl_flags & PROXY_SSL_FLAG_YES) != 0 && (proxy->ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) { if (login_proxy_starttls(proxy) < 0) { /* proxy is already destroyed */ } } } static void proxy_connect_timeout(struct login_proxy *proxy) { errno = ETIMEDOUT; (void)proxy_connect_failed(proxy); } static int login_proxy_connect(struct login_proxy *proxy) { struct login_proxy_record *rec = proxy->state_rec; e_debug(proxy->event, "Connecting to <%s>", login_proxy_get_ip_str(proxy->client->login_proxy)); /* this needs to be done early, since login_proxy_free() shrinks num_waiting_connections. */ proxy->num_waiting_connections_updated = FALSE; rec->num_waiting_connections++; if (proxy->client->proxy_ttl <= 1) { login_proxy_failed(proxy, proxy->event, LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG, "TTL reached zero - proxies appear to be looping?"); return -1; } if (rec->last_success.tv_sec == 0) { /* first connect to this IP. don't start immediately failing the check below. */ rec->last_success.tv_sec = ioloop_timeval.tv_sec - 1; } if (proxy->host_immediate_failure_after_secs != 0 && timeval_cmp(&rec->last_failure, &rec->last_success) > 0 && (unsigned int)(rec->last_failure.tv_sec - rec->last_success.tv_sec) > proxy->host_immediate_failure_after_secs && rec->num_waiting_connections > 1) { /* the server is down. fail immediately */ proxy->disable_reconnect = TRUE; login_proxy_failed(proxy, proxy->event, LOGIN_PROXY_FAILURE_TYPE_CONNECT, "Host is down"); return -1; } proxy->server_fd = net_connect_ip(&proxy->ip, proxy->port, proxy->source_ip.family == 0 ? NULL : &proxy->source_ip); if (proxy->server_fd == -1) { if (!proxy_connect_failed(proxy)) return -1; /* trying to reconnect later */ return 0; } in_port_t source_port; if (net_getsockname(proxy->server_fd, NULL, &source_port) == 0) event_add_int(proxy->event, "source_port", source_port); proxy->server_io = io_add(proxy->server_fd, IO_WRITE, proxy_wait_connect, proxy); if (proxy->connect_timeout_msecs != 0) { proxy->to = timeout_add(proxy->connect_timeout_msecs, proxy_connect_timeout, proxy); } return 0; } int login_proxy_new(struct client *client, struct event *event, const struct login_proxy_settings *set, login_proxy_input_callback_t *input_callback, login_proxy_failure_callback_t *failure_callback) { struct login_proxy *proxy; i_assert(set->host != NULL && set->host[0] != '\0'); i_assert(client->login_proxy == NULL); proxy = i_new(struct login_proxy, 1); proxy->client = client; proxy->event = event; proxy->server_fd = -1; proxy->created = ioloop_timeval; proxy->ip = set->ip; proxy->source_ip = set->source_ip; proxy->host = i_strdup(set->host); proxy->port = set->port; proxy->connect_timeout_msecs = set->connect_timeout_msecs; proxy->notify_refresh_secs = set->notify_refresh_secs; proxy->host_immediate_failure_after_secs = set->host_immediate_failure_after_secs; proxy->ssl_flags = set->ssl_flags; proxy->state_rec = login_proxy_state_get(proxy_state, &proxy->ip, proxy->port); proxy->rawlog_dir = i_strdup_empty(set->rawlog_dir); /* add event fields */ event_add_str(proxy->event, "source_ip", login_proxy_get_source_host(proxy)); event_add_str(proxy->event, "dest_ip", net_ip2addr(&proxy->ip)); event_add_int(proxy->event, "dest_port", login_proxy_get_port(proxy)); event_add_str(event, "dest_host", set->host); event_add_str(event, "master_user", client->proxy_master_user); client_ref(client); event_ref(proxy->event); DLLIST_PREPEND(&login_proxies_pending, proxy); proxy->input_callback = input_callback; proxy->failure_callback = failure_callback; client->login_proxy = proxy; struct event_passthrough *e = event_create_passthrough(proxy->event)-> set_name("proxy_session_started"); e_debug(e->event(), "Created proxy session to <%s>", login_proxy_get_ip_str(proxy->client->login_proxy)); return login_proxy_connect(proxy); } static void login_proxy_disconnect(struct login_proxy *proxy) { timeout_remove(&proxy->to); timeout_remove(&proxy->to_notify); if (!proxy->num_waiting_connections_updated) { i_assert(proxy->state_rec->num_waiting_connections > 0); proxy->state_rec->num_waiting_connections--; proxy->num_waiting_connections_updated = TRUE; } if (proxy->connected) { i_assert(proxy->state_rec->num_proxying_connections > 0); proxy->state_rec->num_proxying_connections--; } iostream_proxy_unref(&proxy->iostream_proxy); ssl_iostream_destroy(&proxy->server_ssl_iostream); io_remove(&proxy->server_io); i_stream_destroy(&proxy->server_input); o_stream_destroy(&proxy->server_output); if (proxy->server_fd != -1) { net_disconnect(proxy->server_fd); proxy->server_fd = -1; } proxy->connected = FALSE; } static void login_proxy_free_final(struct login_proxy *proxy) { i_assert(proxy->server_ssl_iostream == NULL); if (proxy->delayed_disconnect) { DLLIST_REMOVE(&login_proxies_disconnecting, proxy); i_assert(proxy->state_rec->num_delayed_client_disconnects > 0); if (--proxy->state_rec->num_delayed_client_disconnects == 0) proxy->state_rec->num_disconnects_since_ts = 0; timeout_remove(&proxy->to); } io_remove(&proxy->client_wait_io); i_stream_destroy(&proxy->client_input); o_stream_destroy(&proxy->client_output); client_unref(&proxy->client); event_unref(&proxy->event); i_free(proxy->host); i_free(proxy->rawlog_dir); i_free(proxy); } static unsigned int login_proxy_delay_disconnect(struct login_proxy *proxy) { struct login_proxy_record *rec = proxy->state_rec; const unsigned int max_delay = proxy->client->set->login_proxy_max_disconnect_delay; struct timeval disconnect_time_offset; unsigned int max_disconnects_per_sec, delay_msecs_since_ts, max_conns; int delay_msecs; if (rec->num_disconnects_since_ts == 0) { rec->disconnect_timestamp = ioloop_timeval; /* start from a slightly random timestamp. this way all proxy processes will disconnect at slightly different times to spread the load. */ timeval_add_msecs(&rec->disconnect_timestamp, i_rand_limit(PROXY_DISCONNECT_INTERVAL_MSECS)); } rec->num_disconnects_since_ts++; if (proxy->to != NULL) { /* we were already lazily disconnecting this */ return 0; } if (max_delay == 0) { /* delaying is disabled */ return 0; } max_conns = rec->num_proxying_connections + rec->num_disconnects_since_ts; max_disconnects_per_sec = (max_conns + max_delay-1) / max_delay; if (rec->num_disconnects_since_ts <= max_disconnects_per_sec && rec->num_delayed_client_disconnects == 0) { /* wait delaying until we have 1 second's worth of clients disconnected */ return 0; } /* see at which time we should be disconnecting the client. do it in 100ms intervals so the timeouts are triggered together. */ disconnect_time_offset = rec->disconnect_timestamp; delay_msecs_since_ts = PROXY_DISCONNECT_INTERVAL_MSECS * (max_delay * rec->num_disconnects_since_ts * (1000/PROXY_DISCONNECT_INTERVAL_MSECS) / max_conns); timeval_add_msecs(&disconnect_time_offset, delay_msecs_since_ts); delay_msecs = timeval_diff_msecs(&disconnect_time_offset, &ioloop_timeval); if (delay_msecs <= 0) { /* we already reached the time */ return 0; } rec->num_delayed_client_disconnects++; proxy->delayed_disconnect = TRUE; proxy->to = timeout_add(delay_msecs, login_proxy_free_final, proxy); DLLIST_PREPEND(&login_proxies_disconnecting, proxy); return delay_msecs; } static void ATTR_NULL(2) login_proxy_free_full(struct login_proxy **_proxy, const char *log_msg, const char *disconnect_reason, const char *disconnect_side, enum login_proxy_free_flags flags) { struct login_proxy *proxy = *_proxy; struct client *client = proxy->client; unsigned int delay_ms = 0; *_proxy = NULL; if (proxy->destroying) return; proxy->destroying = TRUE; struct event_passthrough *e = event_create_passthrough(proxy->event)-> add_str("disconnect_reason", disconnect_reason)-> add_str("disconnect_side", disconnect_side)-> set_name("proxy_session_finished"); if (proxy->detached) { i_assert(proxy->connected); e->add_int("idle_secs", ioloop_time - proxy_last_io(proxy)); e->add_int("bytes_in", proxy->server_output->offset); e->add_int("bytes_out", proxy->client_output->offset); } /* we'll disconnect server side in any case. */ login_proxy_disconnect(proxy); if (proxy->detached) { /* detached proxy */ i_assert(log_msg != NULL || proxy->client->destroyed); DLLIST_REMOVE(&login_proxies, proxy); if ((flags & LOGIN_PROXY_FREE_FLAG_DELAYED) != 0) delay_ms = login_proxy_delay_disconnect(proxy); if (delay_ms == 0) e_info(e->event(), "%s", log_msg); else { e_info(e->add_int("delay_ms", delay_ms)->event(), "%s - disconnecting client in %ums", log_msg, delay_ms); } i_assert(detached_login_proxies_count > 0); detached_login_proxies_count--; } else { i_assert(proxy->client_input == NULL); i_assert(proxy->client_output == NULL); if (log_msg != NULL) e_debug(e->event(), "%s", log_msg); else e_debug(e->event(), "Failed to connect to %s", login_proxy_get_ip_str(proxy)); DLLIST_REMOVE(&login_proxies_pending, proxy); } client->login_proxy = NULL; if (delay_ms == 0) login_proxy_free_final(proxy); else { i_assert(proxy->client_wait_io == NULL); proxy->client_wait_io = io_add_istream(proxy->client_input, proxy_client_disconnected_input, proxy); } } void login_proxy_free(struct login_proxy **_proxy) { struct login_proxy *proxy = *_proxy; i_assert(!proxy->detached || proxy->client->destroyed); /* Note: The NULL error is never even attempted to be used here. */ login_proxy_free_full(_proxy, NULL, "", LOGIN_PROXY_SIDE_SELF, 0); } bool login_proxy_failed(struct login_proxy *proxy, struct event *event, enum login_proxy_failure_type type, const char *reason) { const char *log_prefix; bool try_reconnect = TRUE; event_add_str(event, "error", reason); switch (type) { case LOGIN_PROXY_FAILURE_TYPE_INTERNAL: log_prefix = "Aborting due to internal error: "; try_reconnect = FALSE; break; case LOGIN_PROXY_FAILURE_TYPE_INTERNAL_CONFIG: log_prefix = ""; try_reconnect = FALSE; break; case LOGIN_PROXY_FAILURE_TYPE_CONNECT: log_prefix = ""; break; case LOGIN_PROXY_FAILURE_TYPE_REMOTE_CONFIG: try_reconnect = FALSE; /* fall through */ case LOGIN_PROXY_FAILURE_TYPE_REMOTE: log_prefix = "Aborting due to remote server: "; break; case LOGIN_PROXY_FAILURE_TYPE_PROTOCOL: log_prefix = "Remote server sent invalid input: "; break; case LOGIN_PROXY_FAILURE_TYPE_AUTH: log_prefix = ""; try_reconnect = FALSE; break; case LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL: log_prefix = ""; break; default: i_unreached(); } if (try_reconnect && proxy_try_reconnect(proxy)) { event_add_int(event, "reconnect_attempts", proxy->reconnect_count); e_debug(event, "%s%s - reconnecting (attempt #%d)", log_prefix, reason, proxy->reconnect_count); proxy->failure_callback(proxy->client, type, reason, TRUE); return TRUE; } if (type != LOGIN_PROXY_FAILURE_TYPE_AUTH && type != LOGIN_PROXY_FAILURE_TYPE_AUTH_TEMPFAIL) e_error(event, "%s%s", log_prefix, reason); else if (proxy->client->set->auth_verbose) client_proxy_log_failure(proxy->client, reason); proxy->failure_callback(proxy->client, type, reason, FALSE); return FALSE; } bool login_proxy_is_ourself(const struct client *client, const char *host, in_port_t port, const char *destuser) { struct ip_addr ip; if (port != client->local_port) return FALSE; if (net_addr2ip(host, &ip) < 0) return FALSE; if (!net_ip_compare(&ip, &client->local_ip)) return FALSE; return strcmp(client->virtual_user, destuser) == 0; } struct istream *login_proxy_get_istream(struct login_proxy *proxy) { return proxy->server_input; } struct ostream *login_proxy_get_ostream(struct login_proxy *proxy) { return proxy->server_output; } struct event *login_proxy_get_event(struct login_proxy *proxy) { return proxy->event; } const char *login_proxy_get_source_host(const struct login_proxy *proxy) { return net_ip2addr(&proxy->source_ip); } const char *login_proxy_get_host(const struct login_proxy *proxy) { return proxy->host; } const char *login_proxy_get_ip_str(const struct login_proxy *proxy) { return net_ip2addr(&proxy->ip); } in_port_t login_proxy_get_port(const struct login_proxy *proxy) { return proxy->port; } enum login_proxy_ssl_flags login_proxy_get_ssl_flags(const struct login_proxy *proxy) { return proxy->ssl_flags; } static void login_proxy_finished(enum iostream_proxy_side side, enum iostream_proxy_status status, struct login_proxy *proxy) { const char *errstr; bool server_side; server_side = side == LOGIN_PROXY_SIDE_SERVER; switch (status) { case IOSTREAM_PROXY_STATUS_INPUT_EOF: /* success */ errstr = ""; break; case IOSTREAM_PROXY_STATUS_INPUT_ERROR: errstr = side == LOGIN_PROXY_SIDE_CLIENT ? i_stream_get_error(proxy->client_input) : i_stream_get_error(proxy->server_input); break; case IOSTREAM_PROXY_STATUS_OTHER_SIDE_OUTPUT_ERROR: server_side = !server_side; errstr = side == LOGIN_PROXY_SIDE_CLIENT ? o_stream_get_error(proxy->server_output) : o_stream_get_error(proxy->client_output); break; default: i_unreached(); } login_proxy_free_errstr(&proxy, errstr, server_side); } static void login_proxy_notify(struct login_proxy *proxy) { login_proxy_state_notify(proxy_state, proxy->client->proxy_user); } void login_proxy_detach(struct login_proxy *proxy) { struct client *client = proxy->client; pool_unref(&proxy->client->preproxy_pool); i_assert(!proxy->detached); i_assert(proxy->server_input != NULL); i_assert(proxy->server_output != NULL); timeout_remove(&proxy->to); io_remove(&proxy->server_io); proxy->detached = TRUE; proxy->client_input = client->input; proxy->client_output = client->output; o_stream_set_max_buffer_size(client->output, PROXY_MAX_OUTBUF_SIZE); client->input = NULL; client->output = NULL; /* from now on, just do dummy proxying */ proxy->iostream_proxy = iostream_proxy_create(proxy->client_input, proxy->client_output, proxy->server_input, proxy->server_output); iostream_proxy_set_completion_callback(proxy->iostream_proxy, login_proxy_finished, proxy); iostream_proxy_start(proxy->iostream_proxy); if (proxy->notify_refresh_secs != 0) { proxy->to_notify = timeout_add(proxy->notify_refresh_secs * 1000, login_proxy_notify, proxy); } proxy->input_callback = NULL; proxy->failure_callback = NULL; if (login_proxy_ipc_server == NULL) { login_proxy_ipc_server = ipc_server_init(LOGIN_PROXY_IPC_PATH, LOGIN_PROXY_IPC_NAME, login_proxy_ipc_cmd); } DLLIST_REMOVE(&login_proxies_pending, proxy); DLLIST_PREPEND(&login_proxies, proxy); detached_login_proxies_count++; client->login_proxy = NULL; } int login_proxy_starttls(struct login_proxy *proxy) { struct ssl_iostream_context *ssl_ctx; struct ssl_iostream_settings ssl_set; const char *error; master_service_ssl_client_settings_to_iostream_set( proxy->client->ssl_set, pool_datastack_create(), &ssl_set); if ((proxy->ssl_flags & PROXY_SSL_FLAG_ANY_CERT) != 0) ssl_set.allow_invalid_cert = TRUE; /* NOTE: We're explicitly disabling ssl_client_ca_* settings for now at least. The main problem is that we're chrooted, so we can't read them at this point anyway. The second problem is that especially ssl_client_ca_dir does blocking disk I/O, which could cause unexpected hangs when login process handles multiple clients. */ ssl_set.ca_file = ssl_set.ca_dir = NULL; io_remove(&proxy->server_io); if (ssl_iostream_client_context_cache_get(&ssl_set, &ssl_ctx, &error) < 0) { const char *reason = t_strdup_printf( "Failed to create SSL client context: %s", error); login_proxy_failed(proxy, proxy->event, LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason); return -1; } if (io_stream_create_ssl_client(ssl_ctx, proxy->host, &ssl_set, &proxy->server_input, &proxy->server_output, &proxy->server_ssl_iostream, &error) < 0) { const char *reason = t_strdup_printf( "Failed to create SSL client: %s", error); login_proxy_failed(proxy, proxy->event, LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason); ssl_iostream_context_unref(&ssl_ctx); return -1; } ssl_iostream_context_unref(&ssl_ctx); if (ssl_iostream_handshake(proxy->server_ssl_iostream) < 0) { error = ssl_iostream_get_last_error(proxy->server_ssl_iostream); const char *reason = t_strdup_printf( "Failed to start SSL handshake: %s", ssl_iostream_get_last_error(proxy->server_ssl_iostream)); login_proxy_failed(proxy, proxy->event, LOGIN_PROXY_FAILURE_TYPE_INTERNAL, reason); return -1; } proxy->server_io = io_add_istream(proxy->server_input, proxy_prelogin_input, proxy); return 0; } static void proxy_kill_idle(struct login_proxy *proxy) { login_proxy_free_full(&proxy, LOGIN_PROXY_KILL_PREFIX KILLED_BY_SHUTDOWN_REASON, KILLED_BY_SHUTDOWN_REASON, LOGIN_PROXY_SIDE_SELF, 0); } void login_proxy_kill_idle(void) { struct login_proxy *proxy, *next; time_t now = time(NULL); time_t stop_timestamp = now - LOGIN_PROXY_DIE_IDLE_SECS; unsigned int stop_msecs; for (proxy = login_proxies; proxy != NULL; proxy = next) { next = proxy->next; time_t last_io = proxy_last_io(proxy); if (last_io <= stop_timestamp) proxy_kill_idle(proxy); else { i_assert(proxy->to == NULL); stop_msecs = (last_io - stop_timestamp) * 1000; proxy->to = timeout_add(stop_msecs, proxy_kill_idle, proxy); } } } static bool want_kick_host(struct login_proxy *proxy, const char *const *args, unsigned int key_idx ATTR_UNUSED) { return str_array_find(args, proxy->host); } static bool want_kick_virtual_user(struct login_proxy *proxy, const char *const *args, unsigned int key_idx ATTR_UNUSED) { return str_array_find(args, proxy->client->virtual_user); } static bool want_kick_alt_username(struct login_proxy *proxy, const char *const *args, unsigned int key_idx) { unsigned int i; struct client *client = proxy->client; if (client->alt_usernames == NULL) return FALSE; for (i = 0; i < key_idx; i++) { if (client->alt_usernames[i] == NULL) return FALSE; } if (client->alt_usernames[i] == NULL) return FALSE; return str_array_find(args, client->alt_usernames[i]); } static void login_proxy_cmd_kick_full(struct ipc_cmd *cmd, const char *const *args, bool (*want_kick)(struct login_proxy *, const char *const *, unsigned int), unsigned int key_idx) { struct login_proxy *proxy, *next; unsigned int count = 0; if (args[0] == NULL) { ipc_cmd_fail(&cmd, "Missing parameter"); return; } for (proxy = login_proxies; proxy != NULL; proxy = next) T_BEGIN { next = proxy->next; if (want_kick(proxy, args, key_idx)) { login_proxy_free_full(&proxy, LOGIN_PROXY_KILL_PREFIX KILLED_BY_ADMIN_REASON, KILLED_BY_ADMIN_REASON, LOGIN_PROXY_SIDE_SELF, LOGIN_PROXY_FREE_FLAG_DELAYED); count++; } } T_END; for (proxy = login_proxies_pending; proxy != NULL; proxy = next) T_BEGIN { next = proxy->next; if (want_kick(proxy, args, key_idx)) { client_disconnect(proxy->client, LOGIN_PROXY_KILL_PREFIX KILLED_BY_ADMIN_REASON, FALSE); client_destroy(proxy->client, NULL); count++; } } T_END; ipc_cmd_success_reply(&cmd, t_strdup_printf("%u", count)); } static void login_proxy_cmd_kick(struct ipc_cmd *cmd, const char *const *args) { login_proxy_cmd_kick_full(cmd, args, want_kick_virtual_user, 0); } static void login_proxy_cmd_kick_host(struct ipc_cmd *cmd, const char *const *args) { login_proxy_cmd_kick_full(cmd, args, want_kick_host, 0); } static void login_proxy_cmd_kick_alt(struct ipc_cmd *cmd, const char *const *args) { char *const *fields; unsigned int i, count; if (args[0] == NULL) { ipc_cmd_fail(&cmd, "Missing parameter"); return; } fields = array_get(&global_alt_usernames, &count); for (i = 0; i < count; i++) { if (strcmp(fields[i], args[0]) == 0) break; } if (i == count) { /* field doesn't exist, but it's not an error necessarily */ ipc_cmd_success_reply(&cmd, "0"); return; } login_proxy_cmd_kick_full(cmd, args+1, want_kick_alt_username, i); } static bool director_username_hash(struct client *client, unsigned int *hash_r) { const char *error; if (client->director_username_hash_cache != 0) { /* already set */ } else if (!mail_user_hash(client->virtual_user, client->set->director_username_hash, &client->director_username_hash_cache, &error)) { e_error(client->event, "Failed to expand director_username_hash=%s: %s", client->set->director_username_hash, error); return FALSE; } *hash_r = client->director_username_hash_cache; return TRUE; } static void login_proxy_cmd_kick_director_hash(struct ipc_cmd *cmd, const char *const *args) { struct login_proxy *proxy, *next; struct ip_addr except_ip; unsigned int hash, proxy_hash, count = 0; if (args[0] == NULL || str_to_uint(args[0], &hash) < 0) { ipc_cmd_fail(&cmd, "Invalid parameters"); return; } /* optional except_ip parameter specifies that we're not killing the connections that are proxying to the except_ip backend */ except_ip.family = 0; if (args[1] != NULL && args[1][0] != '\0' && net_addr2ip(args[1], &except_ip) < 0) { ipc_cmd_fail(&cmd, "Invalid except_ip parameter"); return; } for (proxy = login_proxies; proxy != NULL; proxy = next) { next = proxy->next; if (director_username_hash(proxy->client, &proxy_hash) && proxy_hash == hash && !net_ip_compare(&proxy->ip, &except_ip)) { login_proxy_free_full(&proxy, LOGIN_PROXY_KILL_PREFIX KILLED_BY_DIRECTOR_REASON, KILLED_BY_DIRECTOR_REASON, LOGIN_PROXY_SIDE_SELF, LOGIN_PROXY_FREE_FLAG_DELAYED); count++; } } for (proxy = login_proxies_pending; proxy != NULL; proxy = next) { next = proxy->next; if (director_username_hash(proxy->client, &proxy_hash) && proxy_hash == hash && !net_ip_compare(&proxy->ip, &except_ip)) { client_disconnect(proxy->client, LOGIN_PROXY_KILL_PREFIX KILLED_BY_DIRECTOR_REASON, FALSE); client_destroy(proxy->client, NULL); count++; } } ipc_cmd_success_reply(&cmd, t_strdup_printf("%u", count)); } static void login_proxy_cmd_list_reply(struct ipc_cmd *cmd, string_t *str, struct login_proxy *proxy) { unsigned int i, alt_count = array_count(&global_alt_usernames); str_truncate(str, 0); str_append_tabescaped(str, proxy->client->virtual_user); str_append_c(str, '\t'); i = 0; if (proxy->client->alt_usernames != NULL) { for (; proxy->client->alt_usernames[i] != NULL; i++) { str_append_tabescaped(str, proxy->client->alt_usernames[i]); str_append_c(str, '\t'); } i_assert(i <= alt_count); } for (; i < alt_count; i++) str_append_c(str, '\t'); str_printfa(str, "%s\t%s\t%s\t%u", login_binary->protocol, net_ip2addr(&proxy->client->ip), net_ip2addr(&proxy->ip), proxy->port); ipc_cmd_send(cmd, str_c(str)); } static void login_proxy_cmd_list(struct ipc_cmd *cmd, const char *const *args ATTR_UNUSED) { struct login_proxy *proxy; char *field; string_t *str = t_str_new(64); str_append(str, "username\t"); array_foreach_elem(&global_alt_usernames, field) { str_append_tabescaped(str, field); str_append_c(str, '\t'); } str_append(str, "service\tsrc-ip\tdest-ip\tdest-port"); ipc_cmd_send(cmd, str_c(str)); for (proxy = login_proxies; proxy != NULL; proxy = proxy->next) login_proxy_cmd_list_reply(cmd, str, proxy); for (proxy = login_proxies_pending; proxy != NULL; proxy = proxy->next) login_proxy_cmd_list_reply(cmd, str, proxy); ipc_cmd_success(&cmd); } static void login_proxy_ipc_cmd(struct ipc_cmd *cmd, const char *line) { const char *const *args = t_strsplit_tabescaped(line); const char *name = args[0]; args++; if (strcmp(name, "KICK") == 0) login_proxy_cmd_kick(cmd, args); else if (strcmp(name, "KICK-ALT") == 0) login_proxy_cmd_kick_alt(cmd, args); else if (strcmp(name, "KICK-DIRECTOR-HASH") == 0) login_proxy_cmd_kick_director_hash(cmd, args); else if (strcmp(name, "LIST-FULL") == 0) login_proxy_cmd_list(cmd, args); else if (strcmp(name, "KICK-HOST") == 0) login_proxy_cmd_kick_host(cmd, args); else ipc_cmd_fail(&cmd, "Unknown command"); } unsigned int login_proxies_get_detached_count(void) { return detached_login_proxies_count; } struct client *login_proxies_get_first_detached_client(void) { return login_proxies == NULL ? NULL : login_proxies->client; } void login_proxy_init(const char *proxy_notify_pipe_path) { proxy_state = login_proxy_state_init(proxy_notify_pipe_path); } void login_proxy_deinit(void) { struct login_proxy *proxy; while (login_proxies != NULL) { proxy = login_proxies; login_proxy_free_full(&proxy, LOGIN_PROXY_KILL_PREFIX KILLED_BY_SHUTDOWN_REASON, KILLED_BY_SHUTDOWN_REASON, LOGIN_PROXY_SIDE_SELF, 0); } i_assert(detached_login_proxies_count == 0); while (login_proxies_disconnecting != NULL) login_proxy_free_final(login_proxies_disconnecting); if (login_proxy_ipc_server != NULL) ipc_server_deinit(&login_proxy_ipc_server); login_proxy_state_deinit(&proxy_state); }