The attached patch makes it possible for Kerberos principals to be
associated with a password database entry by adding a new "k5principals"
passdb setting. A client that successfully authenticates using GSSAPI
will be able to log in as any user who has been associated with the
client's Kerberos principal. This means that users can now use their
Kerberos identities to access virtual mail accounts.

The patch definitely needs review by someone familiar with Dovecot. It
works for me on a small test installation using the passwd-file backend.

Things that should probably be improved:

     1. The list of authorized principals is stored in struct
        auth_request. I would prefer to store it in struct
        gssapi_auth_request, but auth-request.c does not know about
        structs that are specific to the different authentication
        plugins. This could be fixed in a more general way by adding a
        new function to struct mech_module to allow authplugins to read
        fields during passdb lookups.
     2. The gssapi authplugin now does a credential lookup in order to
        trigger parsing of the k5principals setting. In order for this
        to work, auth_request_set_username is now called before
        mech_gssapi_userok. AFAICT the only impact of this is that
        messages logged by this function (and the functions it calls)
        will now use the name of the virtual account.
     3. The credentials lookup triggers an info log message saying that
        credentials for GSSAPI were requested, "but we have only (e.g.)
        MD5-CRYPT". The authplugin doesn't actually want the credential,
        but I think that the only way the authplugin can trigger a
        passdb lookup is by requesting it.
     4. The final part of the code in mech_gssapi_unwrap was moved to
        the callback that's triggered when the credentials lookup is
        complete. The code still needs access to the GSSAPI data, so the
        buffer pointer & length are now stored in struct
        gssapi_auth_request, making the inbuf parameter to the
        mech_gssapi_{sec_context,wrap,unwrap} functions superfluous. The
        parameters should be removed.
     5. The k5principals list won't be processed on Solaris. The code
        added to the end of mech_gssapi_krb5_userok would have to be
        moved to a separate function and then be called from the Solaris
        code.
     6. GCC tells me about assignment to incompatible pointer types in
        the code that iterates through gssapi_k5principals. I must be
        missing something.

The patch is licensed under the MIT license. Please let me know what you
think.

-- 
Sam Morris <s...@robots.org.uk>
Index: dovecot-2.0.15/src/auth/auth-request.c
===================================================================
--- dovecot-2.0.15.orig/src/auth/auth-request.c	2012-03-05 11:38:34.000000000 +0000
+++ dovecot-2.0.15/src/auth/auth-request.c	2012-03-05 17:40:25.000000000 +0000
@@ -45,6 +45,8 @@
 	request->refcount = 1;
 	request->last_access = ioloop_time;
 
+	p_array_init (&request->gssapi_k5principals, request->pool, 0);
+
 	request->set = global_auth_settings;
 	request->mech = mech;
 	request->mech_name = mech == NULL ? NULL : mech->mech_name;
@@ -63,6 +65,8 @@
 	request->state = AUTH_REQUEST_STATE_NEW;
 	auth_request_state_count[AUTH_REQUEST_STATE_NEW]++;
 
+	p_array_init (&request->gssapi_k5principals, request->pool, 0);
+
 	request->refcount = 1;
 	request->last_access = ioloop_time;
 	request->set = global_auth_settings;
@@ -1157,6 +1161,17 @@
 	return TRUE;
 }
 
+static void
+auth_request_store_k5principals(struct auth_request *request,
+                                const char* value)
+{
+	for (const char* const* pr = t_strsplit_spaces (value, ","); *pr != NULL; ++pr) {
+		char* pr_in_pool = p_strdup(request->pool, *pr);
+		auth_request_log_debug(request, "auth", "k5 principal: %s may access %s", pr_in_pool, request->user);
+		array_append(&request->gssapi_k5principals, &pr_in_pool, 1);
+	}
+}
+
 void auth_request_set_field(struct auth_request *request,
 			    const char *name, const char *value,
 			    const char *default_scheme)
@@ -1203,6 +1218,9 @@
 		if (request->userdb_reply == NULL)
 			auth_request_init_userdb_reply(request);
 		auth_request_set_userdb_field(request, name + 7, value);
+	} else if (strcmp(name, "k5principals") == 0) {
+		auth_request_log_debug(request, "auth", "k5 principal: storing");
+		auth_request_store_k5principals(request, value);
 	} else {
 		/* these fields are returned to client */
 		auth_request_set_reply_field(request, name, value);
Index: dovecot-2.0.15/src/auth/auth-request.h
===================================================================
--- dovecot-2.0.15.orig/src/auth/auth-request.h	2012-03-05 11:58:50.000000000 +0000
+++ dovecot-2.0.15/src/auth/auth-request.h	2012-03-05 12:04:35.000000000 +0000
@@ -1,6 +1,8 @@
 #ifndef AUTH_REQUEST_H
 #define AUTH_REQUEST_H
 
+#include "../lib/array.h"
+
 #include "network.h"
 #include "mech.h"
 #include "userdb.h"
@@ -114,6 +116,8 @@
 	unsigned int removed_from_handler:1;
 
 	/* ... mechanism specific data ... */
+	//char* gssapi_k5principals[];
+	ARRAY_DEFINE(gssapi_k5principals, char*);
 };
 
 extern unsigned int auth_request_state_count[AUTH_REQUEST_STATE_MAX];
Index: dovecot-2.0.15/src/auth/mech-gssapi.c
===================================================================
--- dovecot-2.0.15.orig/src/auth/mech-gssapi.c	2012-03-05 12:52:04.000000000 +0000
+++ dovecot-2.0.15/src/auth/mech-gssapi.c	2012-03-05 18:36:55.000000000 +0000
@@ -71,6 +71,8 @@
 	gss_name_t authz_name;
 		
 	pool_t pool;
+
+	gss_buffer_desc inbuf;
 };
 
 static bool gssapi_initialized = FALSE;
@@ -419,9 +421,23 @@
 				      "krb5_parse_name() failed: %d",
 				      (int)krb5_err);
 	} else {
+		/* See if the principal is in the list of authorized
+		 * principals for the user */
+		auth_request_log_debug(&request->auth_request, "gssapi", "%u authorized princpals", array_count_i(&request->auth_request.gssapi_k5principals));
+		const char* const* authorized_principal;
+		array_foreach(&request->auth_request.gssapi_k5principals, authorized_principal) {
+			if (strcmp (princ_display_name, *authorized_principal) == 0) {
+				auth_request_log_debug(&request->auth_request, "gssapi", "authorized principal: %s", *authorized_principal);
+				ret = TRUE;
+				break;
+			}
+		}
+
 		/* See if the principal is authorized to act as the
-		   specified user */
-		ret = krb5_kuserok(ctx, princ, login_user);
+		   specified (UNIX) user */
+		if (!ret)
+			ret = krb5_kuserok(ctx, princ, login_user);
+
 		krb5_free_principal(ctx, princ);
 	}
 	krb5_free_context(ctx);
@@ -483,11 +499,33 @@
 #else
 	auth_request_log_info(auth_request, "gssapi",
 			      "Cross-realm authentication not supported "
-			      "(authz_name=%s)", login_user);
+			      "(authn_name=%s, authz_name=%s)", request->auth_request.original_username, login_user);
 	return -1;
 #endif
 }
 
+static void
+gssapi_credentials_callback (enum passdb_result result,
+		             const unsigned char* credentials,
+			     size_t size,
+			     struct auth_request* request)
+{
+	/* We don't care whether the lookup succeeded or not because GSSAPI
+	 * does not strictly require a passdb. But if a passdb is configured,
+	 * now the k5principals field will have been filled in. */
+	struct gssapi_auth_request *gssapi_request =
+		(struct gssapi_auth_request *)request;
+
+	if (mech_gssapi_userok(gssapi_request, request->user) == 0) {
+		auth_request_success(request, NULL, 0);
+	} else {
+		auth_request_fail(request);
+	}
+
+	gssapi_request->inbuf.value = NULL;
+	gssapi_request->inbuf.length = 0;
+}
+
 static int
 mech_gssapi_unwrap(struct gssapi_auth_request *request, gss_buffer_desc inbuf)
 {
@@ -531,16 +569,19 @@
 		return -1;
 	}
 
-	if (mech_gssapi_userok(request, login_user) < 0)
-		return -1;
-
+	/* Set username early, so that the credential lookup is for the
+	 * authorizing user. This means the user name in subsequent log
+	 * messagess will be the authorization name, not the authentication
+	 * name, which may mean that future log messages should be adjusted
+	 * to log the right thing. */
 	if (!auth_request_set_username(auth_request, login_user, &error)) {
 		auth_request_log_info(auth_request, "gssapi",
 				      "authz_name: %s", error);
 		return -1;
 	}
 
-	auth_request_success(auth_request, NULL, 0);
+	/* Continue in callback once auth_request is populated with passdb information. */
+	auth_request_lookup_credentials(&request->auth_request, "GSSAPI", gssapi_credentials_callback);
 	return 0;
 }
 
@@ -550,27 +591,29 @@
 {
 	struct gssapi_auth_request *gssapi_request = 
 		(struct gssapi_auth_request *)request;
-	gss_buffer_desc inbuf;
 	int ret = -1;
 
-	inbuf.value = (void *)data;
-	inbuf.length = data_size;
+	gssapi_request->inbuf.value = (void *)data;
+	gssapi_request->inbuf.length = data_size;
 
 	switch (gssapi_request->sasl_gssapi_state) {
 	case GSS_STATE_SEC_CONTEXT:
-		ret = mech_gssapi_sec_context(gssapi_request, inbuf);
+		ret = mech_gssapi_sec_context(gssapi_request, gssapi_request->inbuf);
 		break;
 	case GSS_STATE_WRAP:
-		ret = mech_gssapi_wrap(gssapi_request, inbuf);
+		ret = mech_gssapi_wrap(gssapi_request, gssapi_request->inbuf);
 		break;
 	case GSS_STATE_UNWRAP:
-		ret = mech_gssapi_unwrap(gssapi_request, inbuf);
+		ret = mech_gssapi_unwrap(gssapi_request, gssapi_request->inbuf);
 		break;
 	default:
 		i_unreached();
 	}
-	if (ret < 0)
+	if (ret < 0) {
 		auth_request_fail(request);
+		gssapi_request->inbuf.value = NULL;
+		gssapi_request->inbuf.length = 0;
+	}
 }
 
 static void

Reply via email to