diff options
Diffstat (limited to 'src/lib-master/master-login-auth.c')
-rw-r--r-- | src/lib-master/master-login-auth.c | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/src/lib-master/master-login-auth.c b/src/lib-master/master-login-auth.c new file mode 100644 index 0000000..42de091 --- /dev/null +++ b/src/lib-master/master-login-auth.c @@ -0,0 +1,642 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "ioloop.h" +#include "eacces-error.h" +#include "hostpid.h" +#include "istream.h" +#include "ostream.h" +#include "llist.h" +#include "hex-binary.h" +#include "hash.h" +#include "str.h" +#include "strescape.h" +#include "time-util.h" +#include "connection.h" +#include "auth-client-private.h" +#include "master-interface.h" +#include "master-service.h" +#include "master-auth.h" +#include "master-login-auth.h" + + +#define AUTH_MAX_INBUF_SIZE 8192 + +struct master_login_auth_request { + struct master_login_auth_request *prev, *next; + struct event *event; + + unsigned int id; + struct timeval create_stamp; + + pid_t auth_pid; + unsigned int auth_id; + unsigned int client_pid; + uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]; + + master_login_auth_request_callback_t *callback; + void *context; + + bool aborted:1; +}; + +struct master_login_auth { + struct connection conn; + struct connection_list *clist; + struct event *event; + pool_t pool; + int refcount; + + const char *auth_socket_path; + + struct timeval connect_time, handshake_time; + + struct timeout *to; + + unsigned int id_counter; + HASH_TABLE(void *, struct master_login_auth_request *) requests; + /* linked list of requests, ordered by create_stamp */ + struct master_login_auth_request *request_head, *request_tail; + + pid_t auth_server_pid; + + unsigned int timeout_msecs; + + bool connected:1; + bool request_auth_token:1; +}; + +static int +master_login_auth_input_args(struct connection *_conn, const char *const *args); +static int +master_login_auth_handshake_line(struct connection *_conn, const char *line); +static void master_login_auth_destroy(struct connection *_conn); + +static void master_login_auth_update_timeout(struct master_login_auth *auth); +static void master_login_auth_check_spids(struct master_login_auth *auth); + +static const struct connection_vfuncs master_login_auth_vfuncs = { + .destroy = master_login_auth_destroy, + .handshake_line = master_login_auth_handshake_line, + .input_args = master_login_auth_input_args, +}; + +static const struct connection_settings master_login_auth_set = { + .dont_send_version = TRUE, + .service_name_in = "auth-master", + .service_name_out = "auth-master", + .major_version = AUTH_MASTER_PROTOCOL_MAJOR_VERSION, + .minor_version = AUTH_MASTER_PROTOCOL_MINOR_VERSION, + .unix_client_connect_msecs = 1000, + .input_max_size = AUTH_MAX_INBUF_SIZE, + .output_max_size = SIZE_MAX, + .client = TRUE, +}; + +static int +master_login_auth_connect(struct master_login_auth *auth); + +struct master_login_auth * +master_login_auth_init(const char *auth_socket_path, bool request_auth_token) +{ + struct master_login_auth *auth; + pool_t pool; + + pool = pool_alloconly_create("master login auth", 1024); + auth = p_new(pool, struct master_login_auth, 1); + auth->pool = pool; + auth->auth_socket_path = p_strdup(pool, auth_socket_path); + auth->request_auth_token = request_auth_token; + auth->refcount = 1; + hash_table_create_direct(&auth->requests, pool, 0); + auth->id_counter = i_rand_limit(32767) * 131072U; + + auth->clist = connection_list_init(&master_login_auth_set, + &master_login_auth_vfuncs); + + auth->event = event_create(NULL); + event_add_category(auth->event, &event_category_auth_client); + event_set_append_log_prefix(auth->event, "auth-master: login: "); + + auth->conn.event_parent = auth->event; + connection_init_client_unix(auth->clist, &auth->conn, + auth->auth_socket_path); + + auth->timeout_msecs = 1000 * MASTER_AUTH_LOOKUP_TIMEOUT_SECS; + master_login_auth_connect(auth); + return auth; +} + +static void request_failure(struct master_login_auth *auth, + struct master_login_auth_request *request, + const char *log_reason, const char *client_reason) +{ + string_t *str = t_str_new(128); + + str_printfa(str, "auth connected %u msecs ago", + timeval_diff_msecs(&ioloop_timeval, &auth->connect_time)); + if (auth->handshake_time.tv_sec != 0) { + str_printfa(str, ", handshake %u msecs ago", + timeval_diff_msecs(&ioloop_timeval, &auth->handshake_time)); + } + str_printfa(str, ", request took %u msecs, client-pid=%u client-id=%u", + timeval_diff_msecs(&ioloop_timeval, &request->create_stamp), + request->client_pid, request->auth_id); + + struct event_passthrough *e = + event_create_passthrough(request->event)-> + set_name("auth_master_client_login_finished"); + e->add_str("error", log_reason); + e_error(e->event(), "Login auth request failed: %s (%s)", + log_reason, str_c(str)); + + request->callback(NULL, client_reason, request->context); +} + +static void +request_internal_failure(struct master_login_auth *auth, + struct master_login_auth_request *request, + const char *reason) +{ + request_failure(auth, request, reason, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE); +} + +static void request_free(struct master_login_auth_request **_request) +{ + struct master_login_auth_request *request = *_request; + + *_request = NULL; + + event_unref(&request->event); + i_free(request); +} + +static void +master_login_auth_fail(struct master_login_auth *auth, + const char *reason) ATTR_NULL(2) +{ + struct master_login_auth_request *request; + + if (reason == NULL) + reason = "Disconnected from auth server, aborting"; + + if (auth->connected) + connection_disconnect(&auth->conn); + auth->connected = FALSE; + + while (auth->request_head != NULL) { + request = auth->request_head; + DLLIST2_REMOVE(&auth->request_head, + &auth->request_tail, request); + + request_internal_failure(auth, request, reason); + request_free(&request); + } + hash_table_clear(auth->requests, FALSE); + + timeout_remove(&auth->to); + i_zero(&auth->connect_time); + i_zero(&auth->handshake_time); +} + +void master_login_auth_disconnect(struct master_login_auth *auth) +{ + master_login_auth_fail(auth, NULL); +} + +static void master_login_auth_unref(struct master_login_auth **_auth) +{ + struct master_login_auth *auth = *_auth; + struct connection_list *clist = auth->clist; + + *_auth = NULL; + + i_assert(auth->refcount > 0); + if (--auth->refcount > 0) + return; + + hash_table_destroy(&auth->requests); + connection_deinit(&auth->conn); + connection_list_deinit(&clist); + event_unref(&auth->event); + pool_unref(&auth->pool); +} + +void master_login_auth_deinit(struct master_login_auth **_auth) +{ + struct master_login_auth *auth = *_auth; + + *_auth = NULL; + + master_login_auth_disconnect(auth); + master_login_auth_unref(&auth); +} + +void master_login_auth_set_timeout(struct master_login_auth *auth, + unsigned int msecs) +{ + auth->timeout_msecs = msecs; +} + +static void master_login_auth_destroy(struct connection *_conn) +{ + struct master_login_auth *auth = + container_of(_conn, struct master_login_auth, conn); + + switch (_conn->disconnect_reason) { + case CONNECTION_DISCONNECT_HANDSHAKE_FAILED: + master_login_auth_fail(auth, + "Handshake with auth service failed"); + break; + case CONNECTION_DISCONNECT_BUFFER_FULL: + /* buffer full */ + e_error(auth->event, "Auth server sent us too long line"); + master_login_auth_fail(auth, NULL); + break; + default: + /* disconnected. stop accepting new connections, because in + default configuration we no longer have permissions to + connect back to auth-master */ + master_service_stop_new_connections(master_service); + master_login_auth_fail(auth, NULL); + } +} + +static unsigned int auth_get_next_timeout_msecs(struct master_login_auth *auth) +{ + struct timeval expires; + int diff; + + expires = auth->request_head->create_stamp; + timeval_add_msecs(&expires, auth->timeout_msecs); + + diff = timeval_diff_msecs(&expires, &ioloop_timeval); + return (diff <= 0 ? 0 : (unsigned int)diff); +} + +static void master_login_auth_timeout(struct master_login_auth *auth) +{ + struct master_login_auth_request *request; + const char *reason; + + while (auth->request_head != NULL && + auth_get_next_timeout_msecs(auth) == 0) { + int msecs; + + request = auth->request_head; + DLLIST2_REMOVE(&auth->request_head, + &auth->request_tail, request); + hash_table_remove(auth->requests, POINTER_CAST(request->id)); + + msecs = timeval_diff_msecs(&ioloop_timeval, + &request->create_stamp); + reason = t_strdup_printf( + "Auth server request timed out after %u.%03u secs", + msecs/1000, msecs%1000); + request_internal_failure(auth, request, reason); + request_free(&request); + } + timeout_remove(&auth->to); + master_login_auth_update_timeout(auth); +} + +static void master_login_auth_update_timeout(struct master_login_auth *auth) +{ + i_assert(auth->to == NULL); + + if (auth->request_head != NULL) { + auth->to = timeout_add(auth_get_next_timeout_msecs(auth), + master_login_auth_timeout, auth); + } +} + +static int +master_login_auth_handshake_line(struct connection *_conn, const char *line) +{ + struct master_login_auth *auth = + container_of(_conn, struct master_login_auth, conn); + const char *const *tmp; + unsigned int major_version, minor_version; + + tmp = t_strsplit_tabescaped(line); + if (!auth->conn.version_received && strcmp(tmp[0], "VERSION") == 0 && + tmp[1] != NULL && tmp[2] != NULL) { + if (str_to_uint(tmp[1], &major_version) < 0 || + str_to_uint(tmp[2], &minor_version) < 0) { + e_error(auth->event, + "Auth server sent invalid version line: %s", + line); + return -1; + } + + if (connection_verify_version(_conn, "auth-master", + major_version, + minor_version) < 0) + return -1; + return 0; + } + if (strcmp(tmp[0], "SPID") != 0 || + str_to_pid(tmp[1], &auth->auth_server_pid) < 0) { + e_error(auth->event, + "Auth server did not send valid SPID: %s", line); + return -1; + } + + master_login_auth_check_spids(auth); + return 1; +} + +static void +master_login_auth_request_remove(struct master_login_auth *auth, + struct master_login_auth_request *request) +{ + bool update_timeout; + + update_timeout = request->prev == NULL; + + hash_table_remove(auth->requests, POINTER_CAST(request->id)); + DLLIST2_REMOVE(&auth->request_head, &auth->request_tail, request); + + if (update_timeout) { + timeout_remove(&auth->to); + master_login_auth_update_timeout(auth); + } +} + +static struct master_login_auth_request * +master_login_auth_lookup_request(struct master_login_auth *auth, + unsigned int id) +{ + struct master_login_auth_request *request; + + request = hash_table_lookup(auth->requests, POINTER_CAST(id)); + if (request == NULL) { + e_error(auth->event, + "Auth server sent reply with unknown ID %u", id); + return NULL; + } + master_login_auth_request_remove(auth, request); + if (request->aborted) { + request->callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE, + request->context); + request_free(&request); + return NULL; + } + return request; +} + +static void +master_login_auth_input_user(struct master_login_auth *auth, unsigned int id, + const char *const *args) +{ + struct master_login_auth_request *request; + + /* USER <id> <userid> [..] */ + request = master_login_auth_lookup_request(auth, id); + if (request != NULL) { + struct event_passthrough *e = + event_create_passthrough(request->event)-> + set_name("auth_master_client_login_finished"); + if (args[0] != NULL && *args[0] != '\0') + e->add_str("user", args[0]); + e_debug(e->event(), "Login auth request successful"); + + request->callback(args, NULL, request->context); + request_free(&request); + } +} + +static void +master_login_auth_input_notfound(struct master_login_auth *auth, + unsigned int id, + const char *const *args ATTR_UNUSED) +{ + struct master_login_auth_request *request; + + /* NOTFOUND <id> */ + request = master_login_auth_lookup_request(auth, id); + if (request != NULL) { + const char *reason = t_strdup_printf( + "Authenticated user not found from userdb, " + "auth lookup id=%u", id); + request_internal_failure(auth, request, reason); + request_free(&request); + } +} + +static void +master_login_auth_input_fail(struct master_login_auth *auth, unsigned int id, + const char *const *args) +{ + struct master_login_auth_request *request; + const char *error = NULL; + unsigned int i; + + /* FAIL <id> [..] [reason=<error>] [..] */ + for (i = 0; args[i] != NULL; i++) { + if (str_begins(args[i], "reason=")) + error = args[i] + 7; + } + + request = master_login_auth_lookup_request(auth, id); + if (request != NULL) { + if (error == NULL) { + request_internal_failure(auth, request, + "Internal auth failure"); + } else { + const char *log_reason = t_strdup_printf( + "Internal auth failure: %s", error); + request_failure(auth, request, log_reason, error); + } + request_free(&request); + } +} + +static int +master_login_auth_input_args(struct connection *_conn, const char *const *args) +{ + struct master_login_auth *auth = + container_of(_conn, struct master_login_auth, conn); + unsigned int id; + + if (args[0] != NULL && strcmp(args[0], "CUID") == 0) { + e_error(auth->event, "%s is an auth client socket. " + "It should be a master socket.", + auth->auth_socket_path); + return -1; + } + + if (args[0] == NULL || args[1] == NULL || + str_to_uint(args[1], &id) < 0) { + e_error(auth->event, "BUG: Unexpected input: %s", + t_strarray_join(args, "\t")); + return -1; + } + + auth->refcount++; + if (strcmp(args[0], "USER") == 0) + master_login_auth_input_user(auth, id, &args[2]); + else if (strcmp(args[0], "NOTFOUND") == 0) + master_login_auth_input_notfound(auth, id, &args[2]); + else if (strcmp(args[0], "FAIL") == 0) + master_login_auth_input_fail(auth, id, &args[2]); + master_login_auth_unref(&auth); + + return 1; +} + +static int +master_login_auth_connect(struct master_login_auth *auth) +{ + i_assert(!auth->connected); + + if (connection_client_connect(&auth->conn) < 0) { + if (errno == EACCES) { + e_error(auth->event, "%s", + eacces_error_get("connect", + auth->auth_socket_path)); + } else { + e_error(auth->event, "connect(%s) failed: %m", + auth->auth_socket_path); + } + return -1; + } + io_loop_time_refresh(); + auth->connect_time = ioloop_timeval; + auth->connected = TRUE; + + o_stream_nsend_str(auth->conn.output, + t_strdup_printf("VERSION\t%u\t%u\n", + AUTH_MASTER_PROTOCOL_MAJOR_VERSION, + AUTH_MASTER_PROTOCOL_MINOR_VERSION)); + return 0; +} + +static bool +auth_request_check_spid(struct master_login_auth *auth, + struct master_login_auth_request *req) +{ + if (auth->auth_server_pid != req->auth_pid && + auth->conn.handshake_received) { + /* auth server was restarted. don't even attempt a login. */ + e_warning(auth->event, + "Auth server restarted (pid %u -> %u), aborting auth", + (unsigned int)req->auth_pid, + (unsigned int)auth->auth_server_pid); + return FALSE; + } + return TRUE; +} + +static void master_login_auth_check_spids(struct master_login_auth *auth) +{ + struct master_login_auth_request *req, *next; + + for (req = auth->request_head; req != NULL; req = next) { + next = req->next; + if (!auth_request_check_spid(auth, req)) + req->aborted = TRUE; + } +} + +static void +master_login_auth_send_request(struct master_login_auth *auth, + struct master_login_auth_request *req) +{ + string_t *str; + + if (!auth_request_check_spid(auth, req)) { + master_login_auth_request_remove(auth, req); + req->callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE, + req->context); + request_free(&req); + return; + } + + str = t_str_new(128); + str_printfa(str, "REQUEST\t%u\t%u\t%u\t", req->id, + req->client_pid, req->auth_id); + binary_to_hex_append(str, req->cookie, sizeof(req->cookie)); + str_printfa(str, "\tsession_pid=%s", my_pid); + if (auth->request_auth_token) + str_append(str, "\trequest_auth_token"); + str_append_c(str, '\n'); + o_stream_nsend(auth->conn.output, str_data(str), str_len(str)); +} + +void master_login_auth_request(struct master_login_auth *auth, + const struct master_auth_request *req, + master_login_auth_request_callback_t *callback, + void *context) +{ + struct master_login_auth_request *login_req; + unsigned int id; + + if (!auth->connected) { + if (master_login_auth_connect(auth) < 0) { + /* we couldn't connect to auth now, + so we probably can't in future either. */ + master_service_stop_new_connections(master_service); + callback(NULL, MASTER_AUTH_ERRMSG_INTERNAL_FAILURE, + context); + return; + } + } + + id = ++auth->id_counter; + if (id == 0) + id++; + + io_loop_time_refresh(); + login_req = i_new(struct master_login_auth_request, 1); + login_req->create_stamp = ioloop_timeval; + login_req->id = id; + login_req->auth_pid = req->auth_pid; + login_req->client_pid = req->client_pid; + login_req->auth_id = req->auth_id; + memcpy(login_req->cookie, req->cookie, sizeof(login_req->cookie)); + login_req->callback = callback; + login_req->context = context; + i_assert(hash_table_lookup(auth->requests, POINTER_CAST(id)) == NULL); + hash_table_insert(auth->requests, POINTER_CAST(id), login_req); + DLLIST2_APPEND(&auth->request_head, &auth->request_tail, login_req); + + login_req->event = event_create(auth->event); + event_add_int(login_req->event, "id", login_req->id); + event_set_append_log_prefix(login_req->event, + t_strdup_printf("request [%u]: ", + login_req->id)); + + if (req->local_ip.family != 0) { + event_add_str(login_req->event, "local_ip", + net_ip2addr(&req->local_ip)); + } + if (req->local_port != 0) { + event_add_int(login_req->event, "local_port", + req->local_port); + } + if (req->remote_ip.family != 0) { + event_add_str(login_req->event, "remote_ip", + net_ip2addr(&req->remote_ip)); + } + if (req->remote_port != 0) { + event_add_int(login_req->event, "remote_port", + req->remote_port); + } + + struct event_passthrough *e = + event_create_passthrough(login_req->event)-> + set_name("auth_master_client_login_started"); + e_debug(e->event(), "Started login auth request"); + + if (auth->to == NULL) + master_login_auth_update_timeout(auth); + + master_login_auth_send_request(auth, login_req); +} + +unsigned int master_login_auth_request_count(struct master_login_auth *auth) +{ + return hash_table_count(auth->requests); +} |