diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 17:36:47 +0000 |
commit | 0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch) | |
tree | 3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/lib-auth/auth-client-connection.c | |
parent | Initial commit. (diff) | |
download | dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.tar.xz dovecot-0441d265f2bb9da249c7abf333f0f771fadb4ab5.zip |
Adding upstream version 1:2.3.21+dfsg1.upstream/1%2.3.21+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-auth/auth-client-connection.c')
-rw-r--r-- | src/lib-auth/auth-client-connection.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/src/lib-auth/auth-client-connection.c b/src/lib-auth/auth-client-connection.c new file mode 100644 index 0000000..3a1e82c --- /dev/null +++ b/src/lib-auth/auth-client-connection.c @@ -0,0 +1,552 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "hash.h" +#include "hostpid.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "net.h" +#include "strescape.h" +#include "eacces-error.h" +#include "auth-client-private.h" + +#include <unistd.h> + +#define AUTH_SERVER_CONN_MAX_LINE_LENGTH AUTH_CLIENT_MAX_LINE_LENGTH +#define AUTH_SERVER_RECONNECT_TIMEOUT_SECS 5 + +static void auth_client_connection_connected(struct connection *_conn, + bool success); +static int +auth_client_connection_input_line(struct connection *_conn, + const char *line); +static int +auth_client_connection_handshake_line(struct connection *_conn, + const char *line); +static void auth_client_connection_handshake_ready(struct connection *_conn); +static void auth_client_connection_destroy(struct connection *_conn); +static void +auth_client_connection_reconnect(struct auth_client_connection *conn, + const char *disconnect_reason); + +static const struct connection_vfuncs auth_client_connection_vfuncs = { + .destroy = auth_client_connection_destroy, + .handshake_line = auth_client_connection_handshake_line, + .handshake_ready = auth_client_connection_handshake_ready, + .input_line = auth_client_connection_input_line, + .client_connected = auth_client_connection_connected, +}; + +static const struct connection_settings auth_client_connection_set = { + .dont_send_version = TRUE, + .service_name_in = "auth-client", + .service_name_out = "auth-client", + .major_version = AUTH_CLIENT_PROTOCOL_MAJOR_VERSION, + .minor_version = AUTH_CLIENT_PROTOCOL_MINOR_VERSION, + .unix_client_connect_msecs = 1000, + .input_max_size = AUTH_SERVER_CONN_MAX_LINE_LENGTH, + .output_max_size = SIZE_MAX, + .client = TRUE, +}; + +struct connection_list * +auth_client_connection_list_init(void) +{ + return connection_list_init(&auth_client_connection_set, + &auth_client_connection_vfuncs); +} + +static int +auth_server_input_mech(struct auth_client_connection *conn, + const char *const *args) +{ + struct auth_mech_desc mech_desc; + + if (args[0] == NULL) { + e_error(conn->conn.event, + "BUG: Authentication server sent broken MECH line"); + return -1; + } + + i_zero(&mech_desc); + mech_desc.name = p_strdup(conn->pool, args[0]); + + if (strcmp(mech_desc.name, "PLAIN") == 0) + conn->has_plain_mech = TRUE; + + for (args++; *args != NULL; args++) { + if (strcmp(*args, "private") == 0) + mech_desc.flags |= MECH_SEC_PRIVATE; + else if (strcmp(*args, "anonymous") == 0) + mech_desc.flags |= MECH_SEC_ANONYMOUS; + else if (strcmp(*args, "plaintext") == 0) + mech_desc.flags |= MECH_SEC_PLAINTEXT; + else if (strcmp(*args, "dictionary") == 0) + mech_desc.flags |= MECH_SEC_DICTIONARY; + else if (strcmp(*args, "active") == 0) + mech_desc.flags |= MECH_SEC_ACTIVE; + else if (strcmp(*args, "forward-secrecy") == 0) + mech_desc.flags |= MECH_SEC_FORWARD_SECRECY; + else if (strcmp(*args, "mutual-auth") == 0) + mech_desc.flags |= MECH_SEC_MUTUAL_AUTH; + } + array_push_back(&conn->available_auth_mechs, &mech_desc); + return 0; +} + +static int +auth_server_input_spid(struct auth_client_connection *conn, + const char *const *args) +{ + if (str_to_uint(args[0], &conn->server_pid) < 0) { + e_error(conn->conn.event, + "BUG: Authentication server sent invalid PID"); + return -1; + } + return 0; +} + +static int +auth_server_input_cuid(struct auth_client_connection *conn, + const char *const *args) +{ + if (args[0] == NULL || + str_to_uint(args[0], &conn->connect_uid) < 0) { + e_error(conn->conn.event, + "BUG: Authentication server sent broken CUID line"); + return -1; + } + return 0; +} + +static int +auth_server_input_cookie(struct auth_client_connection *conn, + const char *const *args) +{ + if (conn->cookie != NULL) { + e_error(conn->conn.event, + "BUG: Authentication server already sent cookie"); + return -1; + } + conn->cookie = p_strdup(conn->pool, args[0]); + return 0; +} + +static int auth_server_input_done(struct auth_client_connection *conn) +{ + if (array_count(&conn->available_auth_mechs) == 0) { + e_error(conn->conn.event, + "BUG: Authentication server returned no mechanisms"); + return -1; + } + if (conn->cookie == NULL) { + e_error(conn->conn.event, + "BUG: Authentication server didn't send a cookie"); + return -1; + } + return 1; +} + +static int +auth_client_connection_handshake_line(struct connection *_conn, + const char *line) +{ + struct auth_client_connection *conn = + container_of(_conn, struct auth_client_connection, conn); + unsigned int major_version, minor_version; + const char *const *args; + + args = t_strsplit_tabescaped(line); + if (strcmp(args[0], "VERSION") == 0 && + args[1] != NULL && args[2] != NULL) { + if (str_to_uint(args[1], &major_version) < 0 || + str_to_uint(args[2], &minor_version) < 0) { + e_error(conn->conn.event, + "Auth server sent invalid version line: %s", + line); + return -1; + } + + if (connection_verify_version(_conn, "auth-client", + major_version, + minor_version) < 0) { + return -1; + } + + return 0; + } else if (strcmp(args[0], "MECH") == 0) { + return auth_server_input_mech(conn, args + 1); + } else if (strcmp(args[0], "SPID") == 0) { + return auth_server_input_spid(conn, args + 1); + } else if (strcmp(args[0], "CUID") == 0) { + return auth_server_input_cuid(conn, args + 1); + } else if (strcmp(args[0], "COOKIE") == 0) { + return auth_server_input_cookie(conn, args + 1); + } else if (strcmp(args[0], "DONE") == 0) { + return auth_server_input_done(conn); + } + + e_error(conn->conn.event, "Auth server sent unknown handshake: %s", line); + return -1; +} + +static void auth_client_connection_handshake_ready(struct connection *_conn) +{ + struct auth_client_connection *conn = + container_of(_conn, struct auth_client_connection, conn); + + timeout_remove(&conn->to); + if (conn->client->connect_notify_callback != NULL) { + conn->client->connect_notify_callback(conn->client, TRUE, + conn->client->connect_notify_context); + } +} + +static int +auth_server_lookup_request(struct auth_client_connection *conn, + const char *id_arg, bool remove, + struct auth_client_request **request_r) +{ + struct auth_client_request *request; + unsigned int id; + + if (id_arg == NULL || str_to_uint(id_arg, &id) < 0) { + e_error(conn->conn.event, + "BUG: Authentication server input missing ID"); + return -1; + } + + request = hash_table_lookup(conn->requests, POINTER_CAST(id)); + if (request == NULL) { + e_error(conn->conn.event, + "Authentication server sent unknown id %u", id); + return 0; + } + if (remove || auth_client_request_is_aborted(request)) + hash_table_remove(conn->requests, POINTER_CAST(id)); + + *request_r = request; + return 1; +} + +static int +auth_server_input_ok(struct auth_client_connection *conn, + const char *const *args) +{ + struct auth_client_request *request; + int ret; + + if ((ret = auth_server_lookup_request(conn, args[0], TRUE, &request)) <= 0) + return ret; + auth_client_request_server_input(request, AUTH_REQUEST_STATUS_OK, + args + 1); + return 0; +} + +static int auth_server_input_cont(struct auth_client_connection *conn, + const char *const *args) +{ + struct auth_client_request *request; + int ret; + + if (str_array_length(args) < 2) { + e_error(conn->conn.event, + "BUG: Authentication server sent broken CONT line"); + return -1; + } + + if ((ret = auth_server_lookup_request(conn, args[0], FALSE, &request)) <= 0) + return ret; + auth_client_request_server_input(request, AUTH_REQUEST_STATUS_CONTINUE, + args + 1); + return 0; +} + +static int auth_server_input_fail(struct auth_client_connection *conn, + const char *const *args) +{ + struct auth_client_request *request; + int ret; + + if ((ret = auth_server_lookup_request(conn, args[0], TRUE, &request)) <= 0) + return ret; + auth_client_request_server_input(request, AUTH_REQUEST_STATUS_FAIL, + args + 1); + return 0; +} + +static int +auth_client_connection_handle_line(struct auth_client_connection *conn, + const char *line) +{ + const char *const *args; + + e_debug(conn->conn.event, "auth input: %s", line); + + args = t_strsplit_tabescaped(line); + if (args[0] == NULL) { + e_error(conn->conn.event, "Auth server sent empty line"); + return -1; + } + if (strcmp(args[0], "OK") == 0) + return auth_server_input_ok(conn, args + 1); + else if (strcmp(args[0], "CONT") == 0) + return auth_server_input_cont(conn, args + 1); + else if (strcmp(args[0], "FAIL") == 0) + return auth_server_input_fail(conn, args + 1); + else { + e_error(conn->conn.event, + "Auth server sent unknown response: %s", args[0]); + return -1; + } +} + +static int +auth_client_connection_input_line(struct connection *_conn, + const char *line) +{ + struct auth_client_connection *conn = + container_of(_conn, struct auth_client_connection, conn); + int ret; + + ret = auth_client_connection_handle_line(conn, line); + if (ret < 0) { + auth_client_connection_disconnect(conn, t_strdup_printf( + "Received broken input: %s", line)); + return -1; + } + return 1; +} + +struct auth_client_connection * +auth_client_connection_init(struct auth_client *client) +{ + struct auth_client_connection *conn; + pool_t pool; + + pool = pool_alloconly_create("auth server connection", 1024); + conn = p_new(pool, struct auth_client_connection, 1); + conn->pool = pool; + + conn->client = client; + + conn->conn.event_parent = client->event; + connection_init_client_unix(client->clist, &conn->conn, + client->auth_socket_path); + + hash_table_create_direct(&conn->requests, pool, 100); + i_array_init(&conn->available_auth_mechs, 8); + return conn; +} + +static void +auth_client_connection_remove_requests(struct auth_client_connection *conn, + const char *disconnect_reason) +{ + static const char *const temp_failure_args[] = { "temp", NULL }; + struct hash_iterate_context *iter; + void *key; + struct auth_client_request *request; + time_t created, oldest = 0; + unsigned int request_count = 0; + + if (hash_table_count(conn->requests) == 0) + return; + + iter = hash_table_iterate_init(conn->requests); + while (hash_table_iterate(iter, conn->requests, &key, &request)) { + if (!auth_client_request_is_aborted(request)) { + request_count++; + created = auth_client_request_get_create_time(request); + if (oldest > created || oldest == 0) + oldest = created; + } + + auth_client_request_server_input(request, + AUTH_REQUEST_STATUS_INTERNAL_FAIL, + temp_failure_args); + } + hash_table_iterate_deinit(&iter); + hash_table_clear(conn->requests, FALSE); + + if (request_count > 0) { + e_warning(conn->conn.event, + "Auth connection closed with %u pending requests " + "(max %u secs, pid=%s, %s)", request_count, + (unsigned int)(ioloop_time - oldest), + my_pid, disconnect_reason); + } +} + +void auth_client_connection_disconnect(struct auth_client_connection *conn, + const char *reason) ATTR_NULL(2) +{ + if (reason == NULL) + reason = "Disconnected from auth server, aborting"; + + if (conn->connected) + connection_disconnect(&conn->conn); + conn->connected = FALSE; + + conn->has_plain_mech = FALSE; + conn->server_pid = 0; + conn->connect_uid = 0; + conn->cookie = NULL; + array_clear(&conn->available_auth_mechs); + + timeout_remove(&conn->to); + + auth_client_connection_remove_requests(conn, reason); + + if (conn->client->connect_notify_callback != NULL) { + conn->client->connect_notify_callback(conn->client, FALSE, + conn->client->connect_notify_context); + } +} + +static void auth_client_connection_destroy(struct connection *_conn) +{ + struct auth_client_connection *conn = + container_of(_conn, struct auth_client_connection, conn); + + switch (_conn->disconnect_reason) { + case CONNECTION_DISCONNECT_HANDSHAKE_FAILED: + auth_client_connection_disconnect( + conn, "Handshake with auth service failed"); + break; + case CONNECTION_DISCONNECT_BUFFER_FULL: + /* buffer full - can't happen unless auth is buggy */ + e_error(conn->conn.event, + "BUG: Auth server sent us more than %d bytes of data", + AUTH_SERVER_CONN_MAX_LINE_LENGTH); + auth_client_connection_disconnect(conn, "Buffer full"); + break; + default: + /* disconnected */ + auth_client_connection_reconnect( + conn, (conn->conn.input->stream_errno != 0 ? + strerror(conn->conn.input->stream_errno) : + "EOF")); + } +} + +static void auth_server_reconnect_timeout(struct auth_client_connection *conn) +{ + (void)auth_client_connection_connect(conn); +} + +static void +auth_client_connection_reconnect(struct auth_client_connection *conn, + const char *disconnect_reason) +{ + time_t next_connect; + + auth_client_connection_disconnect(conn, disconnect_reason); + + next_connect = conn->last_connect + AUTH_SERVER_RECONNECT_TIMEOUT_SECS; + conn->to = timeout_add(ioloop_time >= next_connect ? 0 : + (next_connect - ioloop_time) * 1000, + auth_server_reconnect_timeout, conn); +} + +void auth_client_connection_deinit(struct auth_client_connection **_conn) +{ + struct auth_client_connection *conn = *_conn; + + *_conn = NULL; + + auth_client_connection_disconnect(conn, "deinitializing"); + i_assert(hash_table_count(conn->requests) == 0); + hash_table_destroy(&conn->requests); + timeout_remove(&conn->to); + array_free(&conn->available_auth_mechs); + connection_deinit(&conn->conn); + pool_unref(&conn->pool); +} + +static void auth_client_handshake_timeout(struct auth_client_connection *conn) +{ + e_error(conn->conn.event, "Timeout waiting for handshake from auth server. " + "my pid=%u, input bytes=%"PRIuUOFF_T, + conn->client->client_pid, conn->conn.input->v_offset); + auth_client_connection_reconnect(conn, "auth server timeout"); +} + +static void +auth_client_connection_connected(struct connection *_conn, bool success) +{ + struct auth_client_connection *conn = + container_of(_conn, struct auth_client_connection, conn); + + /* Cannot get here unless connect() was successful */ + i_assert(success); + + conn->connected = TRUE; +} + +int auth_client_connection_connect(struct auth_client_connection *conn) +{ + const char *handshake; + + i_assert(!conn->connected); + + conn->last_connect = ioloop_time; + timeout_remove(&conn->to); + + /* max. 1 second wait here. */ + if (connection_client_connect(&conn->conn) < 0) { + if (errno == EACCES) { + e_error(conn->conn.event, "%s", + eacces_error_get("connect", + conn->client->auth_socket_path)); + } else { + e_error(conn->conn.event, "connect(%s) failed: %m", + conn->client->auth_socket_path); + }; + return -1; + } + + handshake = t_strdup_printf("VERSION\t%u\t%u\nCPID\t%u\n", + AUTH_CLIENT_PROTOCOL_MAJOR_VERSION, + AUTH_CLIENT_PROTOCOL_MINOR_VERSION, + conn->client->client_pid); + if (o_stream_send_str(conn->conn.output, handshake) < 0) { + e_warning(conn->conn.event, + "Error sending handshake to auth server: %s", + o_stream_get_error(conn->conn.output)); + auth_client_connection_disconnect(conn, + o_stream_get_error(conn->conn.output)); + return -1; + } + + conn->to = timeout_add(conn->client->connect_timeout_msecs, + auth_client_handshake_timeout, conn); + return 0; +} + +unsigned int +auth_client_connection_add_request(struct auth_client_connection *conn, + struct auth_client_request *request) +{ + unsigned int id; + + i_assert(conn->conn.handshake_received); + + id = ++conn->client->request_id_counter; + if (id == 0) { + /* wrapped - ID 0 not allowed */ + id = ++conn->client->request_id_counter; + } + i_assert(hash_table_lookup(conn->requests, POINTER_CAST(id)) == NULL); + hash_table_insert(conn->requests, POINTER_CAST(id), request); + return id; +} + +void auth_client_connection_remove_request(struct auth_client_connection *conn, + unsigned int id) +{ + i_assert(conn->conn.handshake_received); + hash_table_remove(conn->requests, POINTER_CAST(id)); +} |