summaryrefslogtreecommitdiffstats
path: root/src/auth/auth-master-connection.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:36:47 +0000
commit0441d265f2bb9da249c7abf333f0f771fadb4ab5 (patch)
tree3f3789daa2f6db22da6e55e92bee0062a7d613fe /src/auth/auth-master-connection.c
parentInitial commit. (diff)
downloaddovecot-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/auth/auth-master-connection.c')
-rw-r--r--src/auth/auth-master-connection.c855
1 files changed, 855 insertions, 0 deletions
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 <unistd.h>
+
+#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], "=<hidden>", 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;
+
+ /* <id> <client-pid> <client-id> <cookie> [<parameters>] */
+ 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;
+
+ /* <id> [<user> [<user> [..]] */
+ 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;
+
+ /* <id> <userid> [<parameters>] */
+ 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;
+
+ /* <id> [<parameters>] */
+ 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);
+ }
+}