From 0441d265f2bb9da249c7abf333f0f771fadb4ab5 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:36:47 +0200 Subject: Adding upstream version 1:2.3.21+dfsg1. Signed-off-by: Daniel Baumann --- src/auth/auth-master-connection.c | 855 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 855 insertions(+) create mode 100644 src/auth/auth-master-connection.c (limited to 'src/auth/auth-master-connection.c') diff --git a/src/auth/auth-master-connection.c b/src/auth/auth-master-connection.c new file mode 100644 index 0000000..3f439b8 --- /dev/null +++ b/src/auth/auth-master-connection.c @@ -0,0 +1,855 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "buffer.h" +#include "hash.h" +#include "llist.h" +#include "str.h" +#include "strescape.h" +#include "str-sanitize.h" +#include "time-util.h" +#include "hostpid.h" +#include "hex-binary.h" +#include "ioloop.h" +#include "net.h" +#include "istream.h" +#include "ostream.h" +#include "ipwd.h" +#include "master-service.h" +#include "userdb.h" +#include "userdb-blocking.h" +#include "master-interface.h" +#include "passdb-cache.h" +#include "auth-request-handler.h" +#include "auth-client-connection.h" +#include "auth-master-connection.h" + +#include + +#define MAX_INBUF_SIZE 1024 +#define MAX_OUTBUF_SIZE (1024*50) + +struct master_userdb_request { + struct auth_master_connection *conn; + struct auth_request *auth_request; +}; + +struct master_list_iter_ctx { + struct auth_master_connection *conn; + struct userdb_iterate_context *iter; + struct auth_request *auth_request; + bool failed; +}; + +static void master_input(struct auth_master_connection *conn); + +static struct auth_master_connection *auth_master_connections; + +static const char * +auth_master_reply_hide_passwords(struct auth_master_connection *conn, + const char *str) +{ + char **args, *p, *p2; + unsigned int i; + + if (conn->auth->set->debug_passwords) + return str; + + /* hide all parameters that have "pass" in their key */ + args = p_strsplit(pool_datastack_create(), str, "\t"); + for (i = 0; args[i] != NULL; i++) { + p = strstr(args[i], "pass"); + p2 = strchr(args[i], '='); + if (p != NULL && p < p2) { + *p2 = '\0'; + args[i] = p_strconcat(pool_datastack_create(), + args[i], "=", NULL); + } + } + return t_strarray_join((void *)args, "\t"); +} + +void auth_master_request_callback(const char *reply, struct auth_master_connection *conn) +{ + struct const_iovec iov[2]; + + e_debug(auth_event, "master userdb out: %s", + auth_master_reply_hide_passwords(conn, reply)); + + iov[0].iov_base = reply; + iov[0].iov_len = strlen(reply); + iov[1].iov_base = "\n"; + iov[1].iov_len = 1; + + o_stream_nsendv(conn->output, iov, 2); +} + +static const char * +auth_master_event_log_callback(struct auth_master_connection *conn, + enum log_type log_type ATTR_UNUSED, + const char *message) +{ + string_t *str = t_str_new(128); + + str_printfa(str, "auth-master client: %s (created %d msecs ago", message, + timeval_diff_msecs(&ioloop_timeval, &conn->create_time)); + if (conn->handshake_time.tv_sec != 0) { + str_printfa(str, ", handshake %d msecs ago", + timeval_diff_msecs(&ioloop_timeval, &conn->create_time)); + } + str_append_c(str, ')'); + return str_c(str); +} + +static bool +master_input_request(struct auth_master_connection *conn, const char *args) +{ + struct auth_client_connection *client_conn; + const char *const *list, *const *params; + unsigned int id, client_pid, client_id; + uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]; + buffer_t buf; + + /* [] */ + list = t_strsplit_tabescaped(args); + if (str_array_length(list) < 4 || + str_to_uint(list[0], &id) < 0 || + str_to_uint(list[1], &client_pid) < 0 || + str_to_uint(list[2], &client_id) < 0) { + e_error(conn->event, "BUG: Master sent broken REQUEST"); + return FALSE; + } + + buffer_create_from_data(&buf, cookie, sizeof(cookie)); + if (hex_to_binary(list[3], &buf) < 0) { + e_error(conn->event, "BUG: Master sent broken REQUEST cookie"); + return FALSE; + } + params = list + 4; + + client_conn = auth_client_connection_lookup(client_pid); + if (client_conn == NULL) { + e_error(conn->event, + "Master requested auth for nonexistent client %u", + client_pid); + o_stream_nsend_str(conn->output, + t_strdup_printf("FAIL\t%u\n", id)); + } else if (!mem_equals_timing_safe(client_conn->cookie, cookie, sizeof(cookie))) { + e_error(conn->event, + "Master requested auth for client %u with invalid cookie", + client_pid); + o_stream_nsend_str(conn->output, + t_strdup_printf("FAIL\t%u\n", id)); + } else if (!auth_request_handler_master_request( + client_conn->request_handler, conn, id, client_id, params)) { + e_error(conn->event, + "Master requested auth for non-login client %u", + client_pid); + o_stream_nsend_str(conn->output, + t_strdup_printf("FAIL\t%u\n", id)); + } + return TRUE; +} + +static bool +master_input_cache_flush(struct auth_master_connection *conn, const char *args) +{ + const char *const *list; + unsigned int count; + + /* [ [ [..]] */ + list = t_strsplit_tabescaped(args); + if (list[0] == NULL) { + e_error(conn->event, "BUG: doveadm sent broken CACHE-FLUSH"); + return FALSE; + } + + if (passdb_cache == NULL) { + /* cache disabled */ + count = 0; + } else if (list[1] == NULL) { + /* flush the whole cache */ + count = auth_cache_clear(passdb_cache); + } else { + count = auth_cache_clear_users(passdb_cache, list+1); + } + o_stream_nsend_str(conn->output, + t_strdup_printf("OK\t%s\t%u\n", list[0], count)); + return TRUE; +} + +static int +master_input_auth_request(struct auth_master_connection *conn, const char *args, + const char *cmd, struct auth_request **request_r, + const char **error_r) +{ + struct auth_request *auth_request; + const char *const *list, *name, *arg, *username; + unsigned int id; + + /* [] */ + list = t_strsplit_tabescaped(args); + if (list[0] == NULL || list[1] == NULL || + str_to_uint(list[0], &id) < 0) { + e_error(conn->event, "BUG: Master sent broken %s", cmd); + return -1; + } + + auth_request = auth_request_new_dummy(auth_event); + auth_request->id = id; + auth_request->master = conn; + auth_master_connection_ref(conn); + username = list[1]; + + for (list += 2; *list != NULL; list++) { + arg = strchr(*list, '='); + if (arg == NULL) { + name = *list; + arg = ""; + } else { + name = t_strdup_until(*list, arg); + arg++; + } + + (void)auth_request_import_info(auth_request, name, arg); + } + + if (auth_request->fields.service == NULL) { + e_error(conn->event, + "BUG: Master sent %s request without service", cmd); + auth_request_unref(&auth_request); + auth_master_connection_unref(&conn); + return -1; + } + + auth_request_init(auth_request); + + if (!auth_request_set_username(auth_request, username, error_r)) { + *request_r = auth_request; + return 0; + } + *request_r = auth_request; + return 1; +} + +static int +user_verify_restricted_uid(struct auth_request *auth_request) +{ + struct auth_master_connection *conn = auth_request->master; + struct auth_fields *reply = auth_request->fields.userdb_reply; + const char *value, *reason; + uid_t uid; + + if (conn->userdb_restricted_uid == 0) + return 0; + + value = auth_fields_find(reply, "uid"); + if (value == NULL) + reason = "userdb reply doesn't contain uid"; + else if (str_to_uid(value, &uid) < 0) + reason = "userdb reply contains invalid uid"; + else if (uid != conn->userdb_restricted_uid) { + reason = t_strdup_printf( + "userdb uid (%s) doesn't match peer uid (%s)", + dec2str(uid), dec2str(conn->userdb_restricted_uid)); + } else { + return 0; + } + + auth_request_log_error(auth_request, "userdb", + "client doesn't have lookup permissions for this user: %s " + "(to bypass this check, set: service auth { unix_listener %s { mode=0777 } })", + reason, conn->path); + return -1; +} + +static void +user_callback(enum userdb_result result, + struct auth_request *auth_request) +{ + struct auth_master_connection *conn = auth_request->master; + string_t *str; + const char *value; + + if (auth_request->userdb_lookup_tempfailed) + result = USERDB_RESULT_INTERNAL_FAILURE; + + if (result == USERDB_RESULT_OK) { + if (user_verify_restricted_uid(auth_request) < 0) + result = USERDB_RESULT_INTERNAL_FAILURE; + } + + str = t_str_new(128); + switch (result) { + case USERDB_RESULT_INTERNAL_FAILURE: + str_printfa(str, "FAIL\t%u", auth_request->id); + if (auth_request->userdb_lookup_tempfailed) { + value = auth_fields_find(auth_request->fields.userdb_reply, + "reason"); + if (value != NULL) + str_printfa(str, "\treason=%s", value); + } + break; + case USERDB_RESULT_USER_UNKNOWN: + str_printfa(str, "NOTFOUND\t%u", auth_request->id); + break; + case USERDB_RESULT_OK: + str_printfa(str, "USER\t%u\t", auth_request->id); + str_append_tabescaped(str, auth_request->fields.user); + str_append_c(str, '\t'); + auth_fields_append(auth_request->fields.userdb_reply, str, + AUTH_FIELD_FLAG_HIDDEN, 0); + break; + } + + e_debug(auth_event, "userdb out: %s", + auth_master_reply_hide_passwords(conn, str_c(str))); + + str_append_c(str, '\n'); + o_stream_nsend(conn->output, str_data(str), str_len(str)); + + auth_request_unref(&auth_request); + auth_master_connection_unref(&conn); +} + +static bool +master_input_user(struct auth_master_connection *conn, const char *args) +{ + struct auth_request *auth_request; + const char *error; + int ret; + + ret = master_input_auth_request(conn, args, "USER", + &auth_request, &error); + if (ret <= 0) { + if (ret < 0) + return FALSE; + auth_request_log_info(auth_request, "userdb", "%s", error); + user_callback(USERDB_RESULT_USER_UNKNOWN, auth_request); + } else { + auth_request_set_state(auth_request, AUTH_REQUEST_STATE_USERDB); + auth_request_lookup_user(auth_request, user_callback); + } + return TRUE; +} + +static void pass_callback_finish(struct auth_request *auth_request, + enum passdb_result result) +{ + struct auth_master_connection *conn = auth_request->master; + string_t *str; + + str = t_str_new(128); + switch (result) { + case PASSDB_RESULT_OK: + if (auth_request->failed || !auth_request->passdb_success) { + str_printfa(str, "FAIL\t%u", auth_request->id); + break; + } + str_printfa(str, "PASS\t%u\tuser=", auth_request->id); + str_append_tabescaped(str, auth_request->fields.user); + if (!auth_fields_is_empty(auth_request->fields.extra_fields)) { + str_append_c(str, '\t'); + auth_fields_append(auth_request->fields.extra_fields, + str, AUTH_FIELD_FLAG_HIDDEN, 0); + } + break; + case PASSDB_RESULT_USER_UNKNOWN: + case PASSDB_RESULT_USER_DISABLED: + case PASSDB_RESULT_PASS_EXPIRED: + str_printfa(str, "NOTFOUND\t%u", auth_request->id); + break; + case PASSDB_RESULT_NEXT: + case PASSDB_RESULT_PASSWORD_MISMATCH: + case PASSDB_RESULT_INTERNAL_FAILURE: + str_printfa(str, "FAIL\t%u", auth_request->id); + break; + case PASSDB_RESULT_SCHEME_NOT_AVAILABLE: + str_printfa(str, "FAIL\t%u\treason=Configured passdbs don't support credentials lookups", + auth_request->id); + break; + } + + e_debug(auth_event, "passdb out: %s", str_c(str)); + + str_append_c(str, '\n'); + o_stream_nsend(conn->output, str_data(str), str_len(str)); + + auth_request_unref(&auth_request); + auth_master_connection_unref(&conn); +} + +static void +auth_master_pass_proxy_finish(bool success, struct auth_request *auth_request) +{ + pass_callback_finish(auth_request, success ? PASSDB_RESULT_OK : + PASSDB_RESULT_INTERNAL_FAILURE); +} + +static void +pass_callback(enum passdb_result result, + const unsigned char *credentials ATTR_UNUSED, + size_t size ATTR_UNUSED, + struct auth_request *auth_request) +{ + int ret; + + if (result != PASSDB_RESULT_OK) + auth_request_proxy_finish_failure(auth_request); + else { + ret = auth_request_proxy_finish(auth_request, + auth_master_pass_proxy_finish); + if (ret == 0) + return; + if (ret < 0) + result = PASSDB_RESULT_INTERNAL_FAILURE; + } + pass_callback_finish(auth_request, result); +} + +static const char *auth_restricted_reason(struct auth_master_connection *conn) +{ + struct passwd pw; + const char *namestr; + + if (i_getpwuid(conn->userdb_restricted_uid, &pw) <= 0) + namestr = ""; + else + namestr = t_strdup_printf("(%s)", pw.pw_name); + return t_strdup_printf("%s mode=0666, but not owned by UID %lu%s", + conn->path, + (unsigned long)conn->userdb_restricted_uid, + namestr); +} + +static bool +master_input_pass(struct auth_master_connection *conn, const char *args) +{ + struct auth_request *auth_request; + const char *error; + int ret; + + ret = master_input_auth_request(conn, args, "PASS", + &auth_request, &error); + if (ret <= 0) { + if (ret < 0) + return FALSE; + auth_request_log_info(auth_request, "passdb", "%s", error); + pass_callback(PASSDB_RESULT_USER_UNKNOWN, + uchar_empty_ptr, 0, auth_request); + } else if (conn->userdb_restricted_uid != 0) { + /* no permissions to do this lookup */ + auth_request_log_error(auth_request, "passdb", + "Auth client doesn't have permissions to do " + "a PASS lookup: %s", auth_restricted_reason(conn)); + pass_callback(PASSDB_RESULT_INTERNAL_FAILURE, + uchar_empty_ptr, 0, auth_request); + } else { + auth_request_set_state(auth_request, + AUTH_REQUEST_STATE_MECH_CONTINUE); + auth_request_lookup_credentials(auth_request, "", + pass_callback); + } + return TRUE; +} + +static void master_input_list_finish(struct master_list_iter_ctx *ctx) +{ + i_assert(ctx->conn->iter_ctx == ctx); + + ctx->conn->iter_ctx = NULL; + ctx->conn->io = io_add(ctx->conn->fd, IO_READ, master_input, ctx->conn); + + if (ctx->iter != NULL) + (void)userdb_blocking_iter_deinit(&ctx->iter); + o_stream_uncork(ctx->conn->output); + o_stream_unset_flush_callback(ctx->conn->output); + auth_request_unref(&ctx->auth_request); + auth_master_connection_unref(&ctx->conn); + i_free(ctx); +} + +static int master_output_list(struct master_list_iter_ctx *ctx) +{ + int ret; + + if ((ret = o_stream_flush(ctx->conn->output)) < 0) { + master_input_list_finish(ctx); + return 1; + } + if (ret > 0) { + o_stream_cork(ctx->conn->output); + userdb_blocking_iter_next(ctx->iter); + } + return 1; +} + +static void master_input_list_callback(const char *user, void *context) +{ + struct master_list_iter_ctx *ctx = context; + struct auth_userdb *userdb = ctx->auth_request->userdb; + int ret; + + if (user == NULL) { + if (userdb_blocking_iter_deinit(&ctx->iter) < 0) + ctx->failed = TRUE; + + do { + userdb = userdb->next; + } while (userdb != NULL && + userdb->userdb->iface->iterate_init == NULL); + if (userdb == NULL) { + /* iteration is finished */ + const char *str; + + str = t_strdup_printf("DONE\t%u\t%s\n", + ctx->auth_request->id, + ctx->failed ? "fail" : ""); + o_stream_nsend_str(ctx->conn->output, str); + master_input_list_finish(ctx); + return; + } + + /* continue iterating next userdb */ + ctx->auth_request->userdb = userdb; + ctx->iter = userdb_blocking_iter_init(ctx->auth_request, + master_input_list_callback, ctx); + return; + } + + T_BEGIN { + const char *str; + + str = t_strdup_printf("LIST\t%u\t%s\n", ctx->auth_request->id, + str_tabescape(user)); + ret = o_stream_send_str(ctx->conn->output, str); + } T_END; + if (o_stream_get_buffer_used_size(ctx->conn->output) >= MAX_OUTBUF_SIZE) + ret = o_stream_flush(ctx->conn->output); + if (ret < 0) { + /* disconnected, don't bother finishing */ + master_input_list_finish(ctx); + return; + } + if (o_stream_get_buffer_used_size(ctx->conn->output) < MAX_OUTBUF_SIZE) + userdb_blocking_iter_next(ctx->iter); + else + o_stream_uncork(ctx->conn->output); +} + +static bool +master_input_list(struct auth_master_connection *conn, const char *args) +{ + struct auth_userdb *userdb = conn->auth->userdbs; + struct auth_request *auth_request; + struct master_list_iter_ctx *ctx; + const char *str, *name, *arg, *const *list; + unsigned int id; + + /* [] */ + list = t_strsplit_tabescaped(args); + if (list[0] == NULL || str_to_uint(list[0], &id) < 0) { + e_error(conn->event, "BUG: Master sent broken LIST"); + return FALSE; + } + list++; + + if (conn->iter_ctx != NULL) { + e_error(conn->event, + "Auth client is already iterating users"); + str = t_strdup_printf("DONE\t%u\tfail\n", id); + o_stream_nsend_str(conn->output, str); + return TRUE; + } + + if (conn->userdb_restricted_uid != 0) { + e_error(conn->event, + "Auth client doesn't have permissions to list users: %s", + auth_restricted_reason(conn)); + str = t_strdup_printf("DONE\t%u\tfail\n", id); + o_stream_nsend_str(conn->output, str); + return TRUE; + } + + while (userdb != NULL && userdb->userdb->iface->iterate_init == NULL) + userdb = userdb->next; + if (userdb == NULL) { + e_error(conn->event, + "Trying to iterate users, but userdbs don't support it"); + str = t_strdup_printf("DONE\t%u\tfail\n", id); + o_stream_nsend_str(conn->output, str); + return TRUE; + } + + auth_request = auth_request_new_dummy(auth_event); + auth_request->id = id; + auth_request->master = conn; + auth_master_connection_ref(conn); + + for (; *list != NULL; list++) { + arg = strchr(*list, '='); + if (arg == NULL) { + name = *list; + arg = ""; + } else { + name = t_strdup_until(*list, arg); + arg++; + } + + if (!auth_request_import_info(auth_request, name, arg) && + strcmp(name, "user") == 0) { + /* username mask */ + auth_request_set_username_forced(auth_request, arg); + } + } + + /* rest of the code doesn't like NULL user or service */ + if (auth_request->fields.user == NULL) + auth_request_set_username_forced(auth_request, ""); + if (auth_request->fields.service == NULL) { + if (!auth_request_import(auth_request, "service", "")) + i_unreached(); + i_assert(auth_request->fields.service != NULL); + } + + ctx = i_new(struct master_list_iter_ctx, 1); + ctx->conn = conn; + ctx->auth_request = auth_request; + ctx->auth_request->userdb = userdb; + + io_remove(&conn->io); + o_stream_cork(conn->output); + o_stream_set_flush_callback(conn->output, master_output_list, ctx); + ctx->iter = userdb_blocking_iter_init(auth_request, + master_input_list_callback, ctx); + conn->iter_ctx = ctx; + return TRUE; +} + +static bool +auth_master_input_line(struct auth_master_connection *conn, const char *line) +{ + e_debug(auth_event, "master in: %s", line); + + if (str_begins(line, "USER\t")) + return master_input_user(conn, line + 5); + if (str_begins(line, "LIST\t")) + return master_input_list(conn, line + 5); + if (str_begins(line, "PASS\t")) + return master_input_pass(conn, line + 5); + + if (!conn->userdb_only) { + i_assert(conn->userdb_restricted_uid == 0); + if (str_begins(line, "REQUEST\t")) + return master_input_request(conn, line + 8); + if (str_begins(line, "CACHE-FLUSH\t")) + return master_input_cache_flush(conn, line + 12); + if (str_begins(line, "CPID\t")) { + e_error(conn->event, + "Authentication client trying to connect to " + "master socket"); + return FALSE; + } + } + + e_error(conn->event, "BUG: Unknown command in %s socket: %s", + conn->userdb_only ? "userdb" : "master", + str_sanitize(line, 80)); + return FALSE; +} + +static void master_input(struct auth_master_connection *conn) +{ + char *line; + bool ret; + + switch (i_stream_read(conn->input)) { + case 0: + return; + case -1: + /* disconnected */ + auth_master_connection_destroy(&conn); + return; + case -2: + /* buffer full */ + e_error(conn->event, "BUG: Master sent us more than %d bytes", + (int)MAX_INBUF_SIZE); + auth_master_connection_destroy(&conn); + return; + } + + if (!conn->version_received) { + line = i_stream_next_line(conn->input); + if (line == NULL) + return; + + /* make sure the major version matches */ + if (!str_begins(line, "VERSION\t") || + !str_uint_equals(t_strcut(line + 8, '\t'), + AUTH_MASTER_PROTOCOL_MAJOR_VERSION)) { + e_error(conn->event, + "Master not compatible with this server " + "(mixed old and new binaries?)"); + auth_master_connection_destroy(&conn); + return; + } + conn->version_received = TRUE; + conn->handshake_time = ioloop_timeval; + } + + while ((line = i_stream_next_line(conn->input)) != NULL) { + T_BEGIN { + ret = auth_master_input_line(conn, line); + } T_END; + if (!ret) { + auth_master_connection_destroy(&conn); + return; + } + } +} + +static int master_output(struct auth_master_connection *conn) +{ + if (o_stream_flush(conn->output) < 0) { + /* transmit error, probably master died */ + auth_master_connection_destroy(&conn); + return 1; + } + + if (conn->io == NULL && + o_stream_get_buffer_used_size(conn->output) <= MAX_OUTBUF_SIZE/2) { + /* allow input again */ + conn->io = io_add(conn->fd, IO_READ, master_input, conn); + } + return 1; +} + +static int +auth_master_connection_set_permissions(struct auth_master_connection *conn, + const struct stat *st) +{ + struct net_unix_cred cred; + + if (st == NULL) + return 0; + + /* figure out what permissions we want to give to this client */ + if ((st->st_mode & 0777) != 0666) { + /* permissions were already restricted by the socket + permissions. also +x bit indicates that we shouldn't do + any permission checks. */ + return 0; + } + + if (net_getunixcred(conn->fd, &cred) < 0) { + e_error(conn->event, + "userdb connection: Failed to get peer's credentials"); + return -1; + } + + if (cred.uid == st->st_uid || cred.gid == st->st_gid) { + /* full permissions */ + return 0; + } else { + /* restrict permissions: return only lookups whose returned + uid matches the peer's uid */ + conn->userdb_restricted_uid = cred.uid; + return 0; + } +} + +struct auth_master_connection * +auth_master_connection_create(struct auth *auth, int fd, + const char *path, const struct stat *socket_st, + bool userdb_only) +{ + struct auth_master_connection *conn; + const char *line; + + i_assert(path != NULL); + + conn = i_new(struct auth_master_connection, 1); + conn->refcount = 1; + conn->fd = fd; + conn->create_time = ioloop_timeval; + conn->path = i_strdup(path); + conn->auth = auth; + conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE); + conn->output = o_stream_create_fd(fd, SIZE_MAX); + o_stream_set_no_error_handling(conn->output, TRUE); + o_stream_set_flush_callback(conn->output, master_output, conn); + conn->io = io_add(fd, IO_READ, master_input, conn); + conn->userdb_only = userdb_only; + conn->event = event_create(auth_event); + event_set_log_message_callback(conn->event, auth_master_event_log_callback, conn); + + line = t_strdup_printf("VERSION\t%u\t%u\nSPID\t%s\n", + AUTH_MASTER_PROTOCOL_MAJOR_VERSION, + AUTH_MASTER_PROTOCOL_MINOR_VERSION, + my_pid); + o_stream_nsend_str(conn->output, line); + DLLIST_PREPEND(&auth_master_connections, conn); + + if (auth_master_connection_set_permissions(conn, socket_st) < 0) { + auth_master_connection_destroy(&conn); + return NULL; + } + return conn; +} + +void auth_master_connection_destroy(struct auth_master_connection **_conn) +{ + struct auth_master_connection *conn = *_conn; + + *_conn = NULL; + if (conn->destroyed) + return; + conn->destroyed = TRUE; + + DLLIST_REMOVE(&auth_master_connections, conn); + + if (conn->iter_ctx != NULL) + master_input_list_finish(conn->iter_ctx); + i_stream_close(conn->input); + o_stream_close(conn->output); + io_remove(&conn->io); + i_close_fd_path(&conn->fd, conn->path); + + master_service_client_connection_destroyed(master_service); + auth_master_connection_unref(&conn); +} + +void auth_master_connection_ref(struct auth_master_connection *conn) +{ + i_assert(conn->refcount > 0); + + conn->refcount++; +} + +void auth_master_connection_unref(struct auth_master_connection **_conn) +{ + struct auth_master_connection *conn = *_conn; + + *_conn = NULL; + i_assert(conn->refcount > 0); + + if (--conn->refcount > 0) + return; + + i_stream_unref(&conn->input); + o_stream_unref(&conn->output); + + event_unref(&conn->event); + i_free(conn->path); + i_free(conn); +} + +void auth_master_connections_destroy_all(void) +{ + struct auth_master_connection *conn; + + while (auth_master_connections != NULL) { + conn = auth_master_connections; + auth_master_connection_destroy(&conn); + } +} -- cgit v1.2.3