changeset 4030:faf83f3e19b5 HEAD

Added support for "master users" who can log in as other people. Currently works only with SASL PLAIN authentication by giving it authorization ID string.
author Timo Sirainen <timo.sirainen@movial.fi>
date Mon, 20 Feb 2006 15:14:58 +0200
parents 9bdd186d7b7d
children 357175e274b4
files dovecot-example.conf src/auth/auth-request.c src/auth/auth-request.h src/auth/auth-worker-client.c src/auth/auth.c src/auth/auth.h src/auth/mech-plain.c src/auth/passdb-blocking.c src/auth/passdb.c src/auth/passdb.h src/master/auth-process.c src/master/master-settings.c src/master/master-settings.h
diffstat 13 files changed, 186 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Sat Feb 18 14:14:32 2006 +0200
+++ b/dovecot-example.conf	Mon Feb 20 15:14:58 2006 +0200
@@ -625,6 +625,12 @@
   # allow both system users (/etc/passwd) and virtual users to login without
   # duplicating the system users into virtual database.
   #
+  # By adding master=yes setting inside a passdb you make the passdb a list
+  # of "master users", who can log in as anyone else. The destination user
+  # is still looked up from passdb without password verification, but this
+  # doesn't work with eg. PAM, so you can also set master_no_passdb=yes to
+  # skip this step and rely on userdb catching the invalid usernames.
+  #
   # http://wiki.dovecot.org/Authentication
   #
 
--- a/src/auth/auth-request.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/auth-request.c	Mon Feb 20 15:14:58 2006 +0200
@@ -110,6 +110,11 @@
 	str_append(str, "\tservice=");
 	str_append(str, request->service);
 
+        if (request->master_user != NULL) {
+                str_append(str, "master_user=");
+                str_append(str, request->master_user);
+        }
+
 	if (request->local_ip.family != 0) {
 		str_append(str, "\tlip=");
 		str_append(str, net_ip2addr(&request->local_ip));
@@ -125,6 +130,8 @@
 {
 	if (strcmp(key, "user") == 0)
 		request->user = p_strdup(request->pool, value);
+	else if (strcmp(key, "master_user") == 0)
+		request->master_user = p_strdup(request->pool, value);
 	else if (strcmp(key, "cert_username") == 0) {
 		if (request->auth->ssl_username_from_cert) {
 			/* get username from SSL certificate. it overrides
@@ -244,6 +251,30 @@
 	auth_cache_insert(passdb_cache, request, passdb->cache_key, str_c(str));
 }
 
+static bool auth_request_master_lookup_finish(struct auth_request *request)
+{
+	/* master login successful. update user and master_user variables. */
+	auth_request_log_info(request, "passdb", "Master user logging in as %s",
+			      request->requested_login_user);
+
+	request->master_user = request->user;
+	request->user = request->requested_login_user;
+	request->requested_login_user = NULL;
+
+	request->skip_password_check = TRUE;
+	request->passdb_password = NULL;
+
+	if (request->passdb->master_no_passdb) {
+		/* skip the passdb lookup, we're authenticated now. */
+		return TRUE;
+	}
+
+	/* the authentication continues with passdb lookup for the
+	   requested_login_user. */
+	request->passdb = request->auth->passdbs;
+	return FALSE;
+}
+
 static bool
 auth_request_handle_passdb_callback(enum passdb_result *result,
 				    struct auth_request *request)
@@ -262,11 +293,19 @@
 					      "User found from deny passdb");
 			*result = PASSDB_RESULT_USER_DISABLED;
 		}
-	} else if (*result != PASSDB_RESULT_OK &&
-		   *result != PASSDB_RESULT_USER_DISABLED &&
-		   request->passdb->next != NULL) {
+	} else if (*result == PASSDB_RESULT_OK) {
+		/* success */
+		if (request->requested_login_user != NULL) {
+			/* this was a master user lookup. */
+			if (!auth_request_master_lookup_finish(request))
+				return FALSE;
+		}
+	} else if (request->passdb->next != NULL &&
+		   *result != PASSDB_RESULT_USER_DISABLED) {
 		/* try next passdb. */
-		if (*result == PASSDB_RESULT_INTERNAL_FAILURE) {
+                request->passdb = request->passdb->next;
+
+                if (*result == PASSDB_RESULT_INTERNAL_FAILURE) {
 			/* remember that we have had an internal failure. at
 			   the end return internal failure if we couldn't
 			   successfully login. */
@@ -276,11 +315,10 @@
 			auth_stream_reply_reset(request->extra_fields);
 
 		return FALSE;
-	} else if (request->passdb_internal_failure &&
-		   *result != PASSDB_RESULT_OK) {
-		/* one of the passdb lookups returned internal failure.
-		   it may have had the correct password, so return internal
-		   failure instead of plain failure. */
+	} else if (request->passdb_internal_failure) {
+		/* last passdb lookup returned internal failure. it may have
+		   had the correct password, so return internal failure
+		   instead of plain failure. */
 		*result = PASSDB_RESULT_INTERNAL_FAILURE;
 	}
 
@@ -312,7 +350,6 @@
 
 	if (!auth_request_handle_passdb_callback(&result, request)) {
 		/* try next passdb */
-		request->passdb = request->passdb->next;
 		auth_request_verify_plain(request, request->mech_password,
 			request->private_callback.verify_plain);
 	} else {
@@ -328,12 +365,20 @@
 			       const char *password,
 			       verify_plain_callback_t *callback)
 {
-	struct passdb_module *passdb = request->passdb->passdb;
+	struct passdb_module *passdb;
 	enum passdb_result result;
 	const char *cache_key;
 
 	i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE);
 
+        if (request->passdb == NULL) {
+                /* no masterdbs, master logins not supported */
+                i_assert(request->requested_login_user != NULL);
+                callback(PASSDB_RESULT_USER_UNKNOWN, request);
+		return;
+        }
+
+        passdb = request->passdb->passdb;
 	if (request->mech_password == NULL)
 		request->mech_password = p_strdup(request->pool, password);
 	else
@@ -391,7 +436,6 @@
 
 	if (!auth_request_handle_passdb_callback(&result, request)) {
 		/* try next passdb */
-		request->passdb = request->passdb->next;
 		auth_request_lookup_credentials(request, request->credentials,
                 	request->private_callback.lookup_credentials);
 	} else {
@@ -474,11 +518,35 @@
 		userdb->iface->lookup(request, auth_request_userdb_callback);
 }
 
+static char *
+auth_request_fix_username(struct auth_request *request, const char *username,
+                          const char **error_r)
+{
+	unsigned char *p;
+        char *user;
+
+	if (strchr(username, '@') == NULL &&
+	    request->auth->default_realm != NULL) {
+		user = p_strconcat(request->pool, username, "@",
+                                   request->auth->default_realm, NULL);
+	} else {
+		user = p_strdup(request->pool, username);
+	}
+
+        for (p = (unsigned char *)user; *p != '\0'; p++) {
+		if (request->auth->username_translation[*p & 0xff] != 0)
+			*p = request->auth->username_translation[*p & 0xff];
+		if (request->auth->username_chars[*p & 0xff] == 0) {
+			*error_r = "Username contains disallowed characters";
+			return NULL;
+		}
+        }
+        return user;
+}
+
 bool auth_request_set_username(struct auth_request *request,
 			       const char *username, const char **error_r)
 {
-	unsigned char *p;
-
 	if (request->cert_username) {
 		/* cert_username overrides the username given by
 		   authentication mechanism. */
@@ -491,24 +559,22 @@
 		return FALSE;
 	}
 
-	if (strchr(username, '@') == NULL &&
-	    request->auth->default_realm != NULL) {
-		request->user = p_strconcat(request->pool, username, "@",
-					    request->auth->default_realm, NULL);
-	} else {
-		request->user = p_strdup(request->pool, username);
-	}
+        request->user = auth_request_fix_username(request, username, error_r);
+        return request->user != NULL;
+}
 
-	for (p = (unsigned char *)request->user; *p != '\0'; p++) {
-		if (request->auth->username_translation[*p & 0xff] != 0)
-			*p = request->auth->username_translation[*p & 0xff];
-		if (request->auth->username_chars[*p & 0xff] == 0) {
-			*error_r = "Username contains disallowed characters";
-			return FALSE;
-		}
-	}
+bool auth_request_set_login_username(struct auth_request *request,
+                                     const char *username,
+                                     const char **error_r)
+{
+        i_assert(*username != '\0');
 
-	return TRUE;
+        /* lookup request->user from masterdb first */
+        request->passdb = request->auth->masterdbs;
+
+        request->requested_login_user =
+                auth_request_fix_username(request, username, error_r);
+        return request->requested_login_user != NULL;
 }
 
 void auth_request_set_field(struct auth_request *request,
@@ -585,6 +651,12 @@
 {
 	int ret;
 
+	if (request->skip_password_check) {
+		/* currently this can happen only with master logins */
+		i_assert(request->master_user != NULL);
+		return 1;
+	}
+
 	ret = password_verify(plain_password, crypted_password, scheme,
 			      request->user);
 	if (ret < 0) {
@@ -673,6 +745,8 @@
 		str_append_c(str, ',');
 		str_append(str, ip);
 	}
+	if (auth_request->requested_login_user != NULL)
+		str_append(str, ",master");
 	str_append(str, "): ");
 	str_vprintfa(str, format, va);
 	return str_c(str);
--- a/src/auth/auth-request.h	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/auth-request.h	Mon Feb 20 15:14:58 2006 +0200
@@ -21,7 +21,13 @@
 
 	pool_t pool;
         enum auth_request_state state;
-	char *user;
+        /* user contains the user who is being authenticated.
+           When master user is logging in as someone else, it gets more
+           complicated. Initially user is set to master's username and the
+           requested_login_user is set to destination username. After masterdb
+           has validated user as a valid master user, master_user is set to
+           user and user is set to requested_login_user. */
+        char *user, *requested_login_user, *master_user;
 	char *mech_password; /* set if verify_plain() is called */
 	char *passdb_password; /* set after password lookup if successful */
         struct auth_stream_reply *extra_fields;
@@ -58,6 +64,7 @@
 	unsigned int no_failure_delay:1;
 	unsigned int no_login:1;
 	unsigned int no_password:1;
+	unsigned int skip_password_check:1;
 	unsigned int proxy:1;
 	unsigned int cert_username:1;
 
@@ -96,6 +103,9 @@
 
 bool auth_request_set_username(struct auth_request *request,
 			       const char *username, const char **error_r);
+bool auth_request_set_login_username(struct auth_request *request,
+                                     const char *username,
+                                     const char **error_r);
 
 void auth_request_set_field(struct auth_request *request,
 			    const char *name, const char *value,
--- a/src/auth/auth-worker-client.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/auth-worker-client.c	Mon Feb 20 15:14:58 2006 +0200
@@ -111,9 +111,9 @@
 	/* verify plaintext password */
 	struct auth_request *auth_request;
 	const char *password;
-	unsigned int num;
+	unsigned int passdb_id;
 
-	num = atoi(t_strcut(args, '\t'));
+	passdb_id = atoi(t_strcut(args, '\t'));
 	args = strchr(args, '\t');
 	if (args == NULL) {
 		i_error("BUG: Auth worker server sent us invalid PASSV");
@@ -135,10 +135,10 @@
 		return;
 	}
 
-	for (; num > 0; num--) {
+	while (auth_request->passdb->id != passdb_id) {
 		auth_request->passdb = auth_request->passdb->next;
 		if (auth_request->passdb == NULL) {
-			i_error("BUG: PASSV had invalid passdb num");
+			i_error("BUG: PASSV had invalid passdb ID");
 			auth_request_unref(&auth_request);
 			return;
 		}
--- a/src/auth/auth.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/auth.c	Mon Feb 20 15:14:58 2006 +0200
@@ -17,7 +17,7 @@
 struct auth *auth_preinit(void)
 {
 	struct auth *auth;
-	struct auth_passdb *auth_passdb;
+	struct auth_passdb *auth_passdb, **passdb_p, **masterdb_p;
 	const char *driver, *args;
 	pool_t pool;
 	unsigned int i;
@@ -32,17 +32,32 @@
 		getenv("VERBOSE_DEBUG_PASSWORDS") != NULL;
 
 	t_push();
+	passdb_p = &auth->passdbs;
+	masterdb_p = &auth->masterdbs;
 	for (i = 1; ; i++) {
 		driver = getenv(t_strdup_printf("PASSDB_%u_DRIVER", i));
 		if (driver == NULL)
 			break;
 
                 args = getenv(t_strdup_printf("PASSDB_%u_ARGS", i));
-		auth_passdb = passdb_preinit(auth, driver, args);
+		auth_passdb = passdb_preinit(auth, driver, args, i);
+
+                auth_passdb->deny =
+                        getenv(t_strdup_printf("PASSDB_%u_DENY", i)) != NULL;
+                auth_passdb->master_no_passdb =
+                        getenv(t_strdup_printf("PASSDB_%u_MASTER_NO_PASSDB",
+                                               i)) != NULL;
 
-		if (getenv(t_strdup_printf("PASSDB_%u_DENY", i)) != NULL)
-			auth_passdb->deny = TRUE;
+		if (getenv(t_strdup_printf("PASSDB_%u_MASTER", i)) == NULL) {
+                        *passdb_p = auth_passdb;
+			passdb_p = &auth_passdb->next;
+                } else {
+			if (auth_passdb->deny)
+				i_fatal("Master passdb can't have deny=yes");
 
+			*masterdb_p = auth_passdb;
+			masterdb_p = &auth_passdb->next;
+		}
 	}
 	t_pop();
 
@@ -157,6 +172,8 @@
 	const char *const *mechanisms;
 	const char *env;
 
+	for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next)
+		passdb_init(passdb);
 	for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next)
 		passdb_init(passdb);
 	for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next)
@@ -239,6 +256,8 @@
 	*_auth = NULL;
 
 	passdb_cache_deinit();
+	for (passdb = auth->masterdbs; passdb != NULL; passdb = passdb->next)
+		passdb_deinit(passdb);
 	for (passdb = auth->passdbs; passdb != NULL; passdb = passdb->next)
 		passdb_deinit(passdb);
 	for (userdb = auth->userdbs; userdb != NULL; userdb = userdb->next)
--- a/src/auth/auth.h	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/auth.h	Mon Feb 20 15:14:58 2006 +0200
@@ -5,7 +5,8 @@
 	struct auth *auth;
 	struct auth_passdb *next;
 
-	unsigned int num;
+        /* id is used by blocking passdb to identify the passdb */
+	unsigned int id;
 	const char *args;
 	struct passdb_module *passdb;
 #ifdef HAVE_MODULES
@@ -13,6 +14,8 @@
 #endif
         /* if user is found from this passdb, deny authentication immediately */
 	unsigned int deny:1;
+	/* masterdb: no passdb lookup for user wanted */
+	unsigned int master_no_passdb:1;
 };
 
 struct auth_userdb {
@@ -33,6 +36,7 @@
 	struct mech_module_list *mech_modules;
 	buffer_t *mech_handshake;
 
+	struct auth_passdb *masterdbs;
 	struct auth_passdb *passdbs;
 	struct auth_userdb *userdbs;
 
--- a/src/auth/mech-plain.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/mech-plain.c	Mon Feb 20 15:14:58 2006 +0200
@@ -54,19 +54,23 @@
 		/* invalid input */
 		auth_request_log_info(request, "plain", "invalid input");
 		auth_request_fail(request);
-	} else {
-		if (!auth_request_set_username(request, authenid, &error)) {
-			/* invalid username */
-                        auth_request_log_info(request, "plain", "%s", error);
-			auth_request_fail(request);
-		} else {
-			auth_request_verify_plain(request, pass,
-						  verify_callback);
-		}
+	} else if (!auth_request_set_username(request, authenid, &error)) {
+                /* invalid username */
+                auth_request_log_info(request, "plain", "%s", error);
+                auth_request_fail(request);
+        } else if (*authid != '\0' &&
+                   !auth_request_set_login_username(request, authid, &error)) {
+                /* invalid login username */
+                auth_request_log_info(request, "plain",
+                                      "login user: %s", error);
+                auth_request_fail(request);
+        } else {
+                auth_request_verify_plain(request, pass,
+                                          verify_callback);
+	}
 
-		/* make sure it's cleared */
-		safe_memset(pass, 0, strlen(pass));
-	}
+        /* make sure it's cleared */
+        safe_memset(pass, 0, strlen(pass));
 }
 
 static void
--- a/src/auth/passdb-blocking.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/passdb-blocking.c	Mon Feb 20 15:14:58 2006 +0200
@@ -119,7 +119,7 @@
 	i_assert(request->extra_fields == NULL);
 
 	str = t_str_new(64);
-	str_printfa(str, "PASSV\t%u\t", request->passdb->num);
+	str_printfa(str, "PASSV\t%u\t", request->passdb->id);
 	str_append(str, request->mech_password);
 	str_append_c(str, '\t');
 	auth_request_export(request, str);
@@ -152,7 +152,7 @@
 
 	str = t_str_new(64);
 	str_printfa(str, "PASSL\t%u\t%d\t",
-		    request->passdb->num, request->credentials);
+		    request->passdb->id, request->credentials);
 	auth_request_export(request, str);
 
 	auth_worker_call(request, str_c(str), lookup_credentials_callback);
--- a/src/auth/passdb.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/passdb.c	Mon Feb 20 15:14:58 2006 +0200
@@ -122,20 +122,17 @@
 }
 
 struct auth_passdb *passdb_preinit(struct auth *auth, const char *driver,
-				   const char *args)
+				   const char *args, unsigned int id)
 {
 	struct passdb_module_interface **p, *iface;
-        struct auth_passdb *auth_passdb, **dest;
+        struct auth_passdb *auth_passdb;
 
 	if (args == NULL) args = "";
 
 	auth_passdb = p_new(auth->pool, struct auth_passdb, 1);
 	auth_passdb->auth = auth;
-	auth_passdb->args = p_strdup(auth->pool, args);
-
-	for (dest = &auth->passdbs; *dest != NULL; dest = &(*dest)->next)
-		auth_passdb->num++;
-	*dest = auth_passdb;
+        auth_passdb->args = p_strdup(auth->pool, args);
+        auth_passdb->id = id;
 
 	iface = NULL;
 	for (p = passdb_interfaces; *p != NULL; p++) {
--- a/src/auth/passdb.h	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/auth/passdb.h	Mon Feb 20 15:14:58 2006 +0200
@@ -77,7 +77,7 @@
 const char *passdb_credentials_to_str(enum passdb_credentials credentials);
 
 struct auth_passdb *passdb_preinit(struct auth *auth, const char *driver,
-				   const char *args);
+				   const char *args, unsigned int id);
 void passdb_init(struct auth_passdb *passdb);
 void passdb_deinit(struct auth_passdb *passdb);
 
--- a/src/master/auth-process.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/master/auth-process.c	Mon Feb 20 15:14:58 2006 +0200
@@ -431,6 +431,12 @@
 		}
 		if (ap->deny)
 			env_put(t_strdup_printf("PASSDB_%u_DENY=1", i));
+		if (ap->master)
+                        env_put(t_strdup_printf("PASSDB_%u_MASTER=1", i));
+                if (ap->master_no_passdb) {
+                        env_put(t_strdup_printf("PASSDB_%u_MASTER_NO_PASSDB=1",
+                                                i));
+                }
 	}
 	for (au = set->userdbs, i = 1; au != NULL; au = au->next, i++) {
 		env_put(t_strdup_printf("USERDB_%u_DRIVER=%s", i, au->driver));
--- a/src/master/master-settings.c	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/master/master-settings.c	Mon Feb 20 15:14:58 2006 +0200
@@ -214,6 +214,8 @@
 	DEF(SET_STR, driver),
 	DEF(SET_STR, args),
 	DEF(SET_BOOL, deny),
+	DEF(SET_BOOL, master),
+	DEF(SET_BOOL, master_no_passdb),
 
 	{ 0, NULL, 0 }
 };
--- a/src/master/master-settings.h	Sat Feb 18 14:14:32 2006 +0200
+++ b/src/master/master-settings.h	Mon Feb 20 15:14:58 2006 +0200
@@ -145,6 +145,8 @@
 	const char *driver;
 	const char *args;
 	bool deny;
+	bool master;
+	bool master_no_passdb;
 };
 
 struct auth_userdb_settings {