changeset 12888:03b8a8fe1959

auth: If auth-userdb socket is 0666, allow peer to do only USER lookups whose uid matches its. Anything else results in failure. If userdb doesn't return uid, the lookups also fail. To truly give full permissions for everyone, use 0777 mode.
author Timo Sirainen <tss@iki.fi>
date Tue, 05 Apr 2011 12:28:32 +0300
parents 352999078d83
children bd869a7053c5
files src/auth/auth-master-connection.c src/auth/auth-master-connection.h src/auth/main.c
diffstat 3 files changed, 172 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/src/auth/auth-master-connection.c	Tue Apr 05 12:24:59 2011 +0300
+++ b/src/auth/auth-master-connection.c	Tue Apr 05 12:28:32 2011 +0300
@@ -162,6 +162,36 @@
 	return 1;
 }
 
+static int
+user_verify_restricted_uid(struct auth_request *auth_request)
+{
+	struct auth_master_connection *conn = auth_request->master;
+	struct auth_stream_reply *reply = auth_request->userdb_reply;
+	const char *value, *reason;
+	uid_t uid;
+
+	if (conn->userdb_restricted_uid == 0)
+		return 0;
+
+	value = auth_stream_reply_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 "
+		"(change userdb socket permissions)", reason);
+	return -1;
+}
+
 static void
 user_callback(enum userdb_result result,
 	      struct auth_request *auth_request)
@@ -174,6 +204,11 @@
 	if (auth_request->userdb_lookup_failed)
 		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:
@@ -281,6 +316,13 @@
 		auth_request_log_info(auth_request, "passdb", "%s", error);
 		pass_callback(PASSDB_RESULT_USER_UNKNOWN,
 			      NULL, 0, auth_request);
+	} else if (conn->userdb_restricted_uid != 0) {
+		/* no permissions to do this lookup */
+		auth_request_log_error(auth_request, "passdb",
+			"Remote client doesn't have permissions to do "
+			"a PASS lookup");
+		pass_callback(PASSDB_RESULT_INTERNAL_FAILURE,
+			      NULL, 0, auth_request);
 	} else {
 		auth_request_set_state(auth_request,
 				       AUTH_REQUEST_STATE_MECH_CONTINUE);
@@ -374,6 +416,13 @@
 		return FALSE;
 	}
 
+	if (conn->userdb_restricted_uid != 0) {
+		i_error("Remote client doesn't have permissions to list users");
+		str = t_strdup_printf("DONE\t%u\tfail\n", id);
+		(void)o_stream_send_str(conn->output, str);
+		return TRUE;
+	}
+
 	while (userdb != NULL && userdb->userdb->iface->iterate_init == NULL)
 		userdb = userdb->next;
 	if (userdb == NULL) {
@@ -409,6 +458,7 @@
 		return master_input_pass(conn, line + 5);
 
 	if (!conn->userdb_only) {
+		i_assert(conn->userdb_restricted_uid == 0);
 		if (strncmp(line, "REQUEST\t", 8) == 0)
 			return master_input_request(conn, line + 8);
 		if (strncmp(line, "CPID\t", 5) == 0) {
@@ -487,8 +537,42 @@
 	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) {
+		i_error("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, bool userdb_only)
+auth_master_connection_create(struct auth *auth, int fd,
+			      const struct stat *socket_st, bool userdb_only)
 {
 	struct auth_master_connection *conn;
 	const char *line;
@@ -508,8 +592,12 @@
 			       AUTH_MASTER_PROTOCOL_MINOR_VERSION,
 			       my_pid);
 	(void)o_stream_send_str(conn->output, line);
+	array_append(&auth_master_connections, &conn, 1);
 
-	array_append(&auth_master_connections, &conn, 1);
+	if (auth_master_connection_set_permissions(conn, socket_st) < 0) {
+		auth_master_connection_destroy(&conn);
+		return NULL;
+	}
 	return conn;
 }
 
--- a/src/auth/auth-master-connection.h	Tue Apr 05 12:24:59 2011 +0300
+++ b/src/auth/auth-master-connection.h	Tue Apr 05 12:28:32 2011 +0300
@@ -1,6 +1,7 @@
 #ifndef AUTH_MASTER_CONNECTION_H
 #define AUTH_MASTER_CONNECTION_H
 
+struct stat;
 struct auth_stream_reply;
 
 struct auth_master_connection {
@@ -13,6 +14,9 @@
 	struct io *io;
 
 	struct auth_request_list *requests;
+	/* If non-zero, allow only USER lookups whose returned uid matches
+	   this uid. Don't allow LIST/PASS lookups. */
+	uid_t userdb_restricted_uid;
 
 	unsigned int version_received:1;
 	unsigned int destroyed:1;
@@ -23,7 +27,8 @@
 extern ARRAY_TYPE(auth_master_connections) auth_master_connections;
 
 struct auth_master_connection *
-auth_master_connection_create(struct auth *auth, int fd, bool userdb_only);
+auth_master_connection_create(struct auth *auth, int fd,
+			      const struct stat *socket_st, bool userdb_only);
 void auth_master_connection_destroy(struct auth_master_connection **conn);
 
 void auth_master_connection_ref(struct auth_master_connection *conn);
--- a/src/auth/main.c	Tue Apr 05 12:24:59 2011 +0300
+++ b/src/auth/main.c	Tue Apr 05 12:28:32 2011 +0300
@@ -27,6 +27,7 @@
 #include "auth-client-connection.h"
 
 #include <unistd.h>
+#include <sys/stat.h>
 
 #define AUTH_PENALTY_ANVIL_PATH "anvil-auth-penalty"
 
@@ -38,6 +39,11 @@
 	AUTH_SOCKET_USERDB
 };
 
+struct auth_socket_listener {
+	enum auth_socket_type type;
+	struct stat st;
+};
+
 bool worker = FALSE, shutdown_request = FALSE;
 time_t process_start_time;
 struct auth_penalty *auth_penalty;
@@ -45,7 +51,7 @@
 static pool_t auth_set_pool;
 static struct module *modules = NULL;
 static struct mechanisms_register *mech_reg;
-static ARRAY_DEFINE(listen_fd_types, enum auth_socket_type);
+static ARRAY_DEFINE(listeners, struct auth_socket_listener);
 
 void auth_refresh_proctitle(void)
 {
@@ -82,6 +88,65 @@
 	return services;
 }
 
+static enum auth_socket_type
+auth_socket_type_get(int listen_fd, const char **path_r)
+{
+	const char *path, *name, *suffix;
+
+	/* figure out if this is a server or network socket by
+	   checking the socket path name. */
+	if (net_getunixname(listen_fd, &path) < 0) {
+		if (errno != ENOTSOCK)
+			i_fatal("getunixname(%d) failed: %m", listen_fd);
+		/* not UNIX socket. let's just assume it's an
+		   auth client. */
+		*path_r = NULL;
+		return AUTH_SOCKET_CLIENT;
+	}
+	*path_r = path;
+
+	name = strrchr(path, '/');
+	if (name == NULL)
+		name = path;
+	else
+		name++;
+
+	suffix = strrchr(name, '-');
+	if (suffix == NULL)
+		suffix = name;
+	else
+		suffix++;
+
+	if (strcmp(suffix, "login") == 0)
+		return AUTH_SOCKET_LOGIN_CLIENT;
+	else if (strcmp(suffix, "master") == 0)
+		return AUTH_SOCKET_MASTER;
+	else if (strcmp(suffix, "userdb") == 0)
+		return AUTH_SOCKET_USERDB;
+	else
+		return AUTH_SOCKET_CLIENT;
+}
+
+static void listeners_init(void)
+{
+	unsigned int i, n;
+	const char *path;
+
+	i_array_init(&listeners, 8);
+	n = master_service_get_socket_count(master_service);
+	for (i = 0; i < n; i++) {
+		int fd = MASTER_LISTEN_FD_FIRST + i;
+		struct auth_socket_listener *l;
+
+		l = array_idx_modifiable(&listeners, fd);
+		l->type = auth_socket_type_get(fd, &path);
+		if (l->type == AUTH_SOCKET_USERDB) {
+			if (stat(path, &l->st) < 0)
+				i_error("stat(%s) failed: %m", path);
+		}
+	}
+}
+
 static void main_preinit(void)
 {
 	struct module_dir_load_settings mod_set;
@@ -118,6 +183,8 @@
 	auths_preinit(global_auth_settings, auth_set_pool,
 		      mech_reg, services);
 
+	listeners_init();
+
 	/* Password lookups etc. may require roots, allow it. */
 	restrict_access_by_env(NULL, FALSE);
 	restrict_access_allow_coredumps(TRUE);
@@ -125,8 +192,6 @@
 
 static void main_init(void)
 {
-	i_array_init(&listen_fd_types, 8);
-
         process_start_time = ioloop_time;
 
 	/* If auth caches aren't used, just ignore these signals */
@@ -186,7 +251,7 @@
 	sql_drivers_deinit();
 	random_deinit();
 
-	array_free(&listen_fd_types);
+	array_free(&listeners);
 	pool_unref(&auth_set_pool);
 }
 
@@ -201,59 +266,21 @@
 	(void)auth_worker_client_create(auth_find_service(NULL), conn->fd);
 }
 
-static enum auth_socket_type
-auth_socket_type_get(int listen_fd)
-{
-	const char *path, *name, *suffix;
-
-	/* figure out if this is a server or network socket by
-	   checking the socket path name. */
-	if (net_getunixname(listen_fd, &path) < 0) {
-		if (errno != ENOTSOCK)
-			i_fatal("getunixname(%d) failed: %m", listen_fd);
-		/* not UNIX socket. let's just assume it's an
-		   auth client. */
-		return AUTH_SOCKET_CLIENT;
-	}
-
-	name = strrchr(path, '/');
-	if (name == NULL)
-		name = path;
-	else
-		name++;
-
-	suffix = strrchr(name, '-');
-	if (suffix == NULL)
-		suffix = name;
-	else
-		suffix++;
-
-	if (strcmp(suffix, "login") == 0)
-		return AUTH_SOCKET_LOGIN_CLIENT;
-	else if (strcmp(suffix, "master") == 0)
-		return AUTH_SOCKET_MASTER;
-	else if (strcmp(suffix, "userdb") == 0)
-		return AUTH_SOCKET_USERDB;
-	else
-		return AUTH_SOCKET_CLIENT;
-}
-
 static void client_connected(struct master_service_connection *conn)
 {
-	enum auth_socket_type *type;
+	struct auth_socket_listener *l;
 	struct auth *auth;
 
-	type = array_idx_modifiable(&listen_fd_types, conn->listen_fd);
-	if (*type == AUTH_SOCKET_UNKNOWN)
-		*type = auth_socket_type_get(conn->listen_fd);
-
+	l = array_idx_modifiable(&listeners, conn->listen_fd);
 	auth = auth_find_service(NULL);
-	switch (*type) {
+	switch (l->type) {
 	case AUTH_SOCKET_MASTER:
-		(void)auth_master_connection_create(auth, conn->fd, FALSE);
+		(void)auth_master_connection_create(auth, conn->fd,
+						    NULL, FALSE);
 		break;
 	case AUTH_SOCKET_USERDB:
-		(void)auth_master_connection_create(auth, conn->fd, TRUE);
+		(void)auth_master_connection_create(auth, conn->fd,
+						    &l->st, TRUE);
 		break;
 	case AUTH_SOCKET_LOGIN_CLIENT:
 		(void)auth_client_connection_create(auth, conn->fd, TRUE);