changeset 3683:28cca6317829 HEAD

Added GSSAPI support. Patch by Jelmer Vernooij and some fixes by pod@herald.ox.ac.uk
author Timo Sirainen <tss@iki.fi>
date Thu, 27 Oct 2005 17:57:50 +0300
parents 0207808033ad
children 59b8ac4c031e
files AUTHORS configure.in dovecot-example.conf src/auth/Makefile.am src/auth/auth.c src/auth/mech-anonymous.c src/auth/mech-apop.c src/auth/mech-cram-md5.c src/auth/mech-digest-md5.c src/auth/mech-gssapi.c src/auth/mech-login.c src/auth/mech-ntlm.c src/auth/mech-plain.c src/auth/mech-rpa.c src/auth/mech.c src/auth/mech.h src/master/auth-process.c src/master/master-settings.c src/master/master-settings.h
diffstat 19 files changed, 461 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Thu Oct 27 17:29:21 2005 +0300
+++ b/AUTHORS	Thu Oct 27 17:57:50 2005 +0300
@@ -12,6 +12,8 @@
 Joshua Goodall <joshua@roughtrade.net> (src/auth/password-scheme-cram-md5.c,
   src/util/dovecotpw.c)
 
+Jelmer Vernooij <jelmer@samba.org> (src/auth/mech-gssapi.c)
+
 This product includes software developed by Computing Services
 at Carnegie Mellon University (http://www.cmu.edu/computing/).
 (src/lib/base64.c, src/lib/mkgmtime.c)
--- a/configure.in	Thu Oct 27 17:29:21 2005 +0300
+++ b/configure.in	Thu Oct 27 17:57:50 2005 +0300
@@ -118,6 +118,15 @@
 	fi,
 	want_bsdauth=yes)
 
+AC_ARG_WITH(gssapi,
+[  --with-gssapi           Build with GSSAPI authentication support (default)],
+    if test x$withval = xno; then
+		want_gssapi=no
+	else
+		want_gssapi_yes
+	fi,
+	want_gssapi=yes)
+
 AC_ARG_WITH(ldap,
 [  --with-ldap             Build with LDAP support],
 	if test x$withval = xno; then
@@ -1192,6 +1201,17 @@
 	])
 fi
 
+have_gssapi=no
+if test $want_gssapi = yes; then
+	AC_CHECK_PROG(KRB5CONFIG, krb5-config, YES, NO)
+	if test $KRB5CONFIG = YES; then
+		AUTH_LIBS="$AUTH_LIBS `krb5-config --libs gssapi`"
+		AUTH_CFLAGS="$AUTH_CFLAGS `krb5-config --cflags gssapi`"
+		AC_DEFINE(HAVE_GSSAPI,, Build with GSSAPI support)
+		have_gssapi=yes
+	fi
+fi
+
 if test $want_ldap = yes; then
 	AC_CHECK_LIB(ldap, ldap_init, [
 		AC_CHECK_HEADER(ldap.h, [
@@ -1486,5 +1506,6 @@
 echo "Building with IPv6 support .......... : $want_ipv6"
 echo "Building with pop3 server ........... : $want_pop3d"
 echo "Building with mail delivery agent  .. : $want_deliver"
+echo "Building with GSSAPI support ........ : $have_gssapi"
 echo "Building with user database modules . :$userdb"
 echo "Building with password lookup modules :$passdb"
--- a/dovecot-example.conf	Thu Oct 27 17:29:21 2005 +0300
+++ b/dovecot-example.conf	Thu Oct 27 17:57:50 2005 +0300
@@ -569,9 +569,13 @@
 # automatically created and destroyed as needed.
 #auth_worker_max_count = 30
 
+# Kerberos keytab to use for the GSSAPI mechanism. Will use the system 
+# default (usually /etc/krb5.keytab) if not specified.
+#auth_krb5_keytab = 
+
 auth default {
   # Space separated list of wanted authentication mechanisms:
-  #   plain digest-md5 cram-md5 apop anonymous
+  #   plain digest-md5 cram-md5 apop anonymous gssapi
   mechanisms = plain
 
   #
--- a/src/auth/Makefile.am	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/Makefile.am	Thu Oct 27 17:57:50 2005 +0300
@@ -54,6 +54,7 @@
 	mech-cram-md5.c \
 	mech-digest-md5.c \
 	mech-ntlm.c \
+	mech-gssapi.c \
 	mech-rpa.c \
 	mech-apop.c \
 	passdb.c \
--- a/src/auth/auth.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/auth.c	Thu Oct 27 17:57:50 2005 +0300
@@ -56,8 +56,6 @@
 	}
 	t_pop();
 
-	if (auth->passdbs == NULL)
-		i_fatal("No password databases set");
 	if (auth->userdbs == NULL)
 		i_fatal("No user databases set");
 	return auth;
@@ -130,6 +128,8 @@
 	struct mech_module_list *list;
 
 	for (list = auth->mech_modules; list != NULL; list = list->next) {
+		if (list->module.need_passdb && auth->passdbs == NULL)
+			break;
 		if (list->module.passdb_need_plain &&
 		    !auth_passdb_list_have_plain(auth))
 			break;
--- a/src/auth/mech-anonymous.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-anonymous.c	Thu Oct 27 17:57:50 2005 +0300
@@ -57,6 +57,7 @@
 
 	MEMBER(flags) MECH_SEC_ANONYMOUS,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) FALSE,
 	MEMBER(passdb_need_credentials) FALSE,
 
--- a/src/auth/mech-apop.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-apop.c	Thu Oct 27 17:57:50 2005 +0300
@@ -162,6 +162,7 @@
 
 	MEMBER(flags) MECH_SEC_PRIVATE | MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) FALSE,
 	MEMBER(passdb_need_credentials) TRUE,
 
--- a/src/auth/mech-cram-md5.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-cram-md5.c	Thu Oct 27 17:57:50 2005 +0300
@@ -191,6 +191,7 @@
 
 	MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) FALSE,
 	MEMBER(passdb_need_credentials) TRUE,
 
--- a/src/auth/mech-digest-md5.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-digest-md5.c	Thu Oct 27 17:57:50 2005 +0300
@@ -619,6 +619,7 @@
 	MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE |
 		MECH_SEC_MUTUAL_AUTH,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) FALSE,
 	MEMBER(passdb_need_credentials) TRUE,
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/auth/mech-gssapi.c	Thu Oct 27 17:57:50 2005 +0300
@@ -0,0 +1,407 @@
+/*
+ * GSSAPI Module
+ *
+ * Copyright (c) 2005 Jelmer Vernooij <jelmer@samba.org>
+ *
+ * Related standards:
+ * - draft-ietf-sasl-gssapi-03 
+ * - RFC2222
+ *
+ * Some parts inspired by an older patch from Colin Walters
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published 
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "common.h"
+#include "mech.h"
+#include "passdb.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "buffer.h"
+#include "hex-binary.h"
+#include "safe-memset.h"
+#include "hostpid.h"
+
+#ifdef HAVE_GSSAPI
+
+#include <gssapi/gssapi.h>
+
+/* Non-zero flags defined in RFC 2222 */
+enum sasl_gssapi_qop {
+	SASL_GSSAPI_QOP_UNSPECIFIED = 0x00,
+	SASL_GSSAPI_QOP_AUTH_ONLY   = 0x01,
+	SASL_GSSAPI_QOP_AUTH_INT    = 0x02,
+	SASL_GSSAPI_QOP_AUTH_CONF   = 0x04
+};
+
+struct gssapi_auth_request {
+	struct auth_request auth_request;
+	gss_ctx_id_t gss_ctx;
+	gss_cred_id_t service_cred;
+
+	enum { 
+		GSS_STATE_SEC_CONTEXT, 
+		GSS_STATE_WRAP, 
+		GSS_STATE_UNWRAP
+	} sasl_gssapi_state;
+
+	gss_name_t authn_name;
+	gss_name_t authz_name;
+		
+	pool_t pool;
+};
+
+static void auth_request_log_gss_error(struct auth_request *request,
+				       OM_uint32 status_value, int status_type,
+				       const char *description)
+{
+	OM_uint32 message_context = 0;
+	OM_uint32 major_status, minor_status;
+	gss_buffer_desc status_string;
+
+	do {
+		major_status = gss_display_status(&minor_status, status_value, 
+						  status_type, GSS_C_NO_OID,
+						  &message_context,
+						  &status_string);
+	
+		auth_request_log_error(request, "gssapi",
+			"While %s: %s", description,
+			str_sanitize(status_string.value, (size_t)-1));
+
+		major_status = gss_release_buffer(&minor_status,
+						  &status_string);
+	} while (message_context != 0);
+}
+
+static struct auth_request *mech_gssapi_auth_new(void)
+{
+	struct gssapi_auth_request *request;
+	pool_t pool;
+
+	pool = pool_alloconly_create("gssapi_auth_request", 512);
+	request = p_new(pool, struct gssapi_auth_request, 1);
+	request->pool = pool;
+
+	request->gss_ctx = GSS_C_NO_CONTEXT;
+
+	request->auth_request.pool = pool;
+	return &request->auth_request;
+}
+
+static OM_uint32 obtain_service_credentials(struct auth_request *request,
+					    gss_cred_id_t *ret)
+{
+	OM_uint32 major_status, minor_status;
+	string_t *principal_name;
+	gss_buffer_desc inbuf;
+	gss_name_t gss_principal;
+
+	principal_name = t_str_new(128);
+	str_append(principal_name, t_str_lcase(request->service));
+	str_append_c(principal_name, '@');
+	str_append(principal_name, my_hostname); 
+
+	auth_request_log_info(request, "gssapi",
+		"Obtaining credentials for %s", str_c(principal_name));
+
+	inbuf.length = str_len(principal_name);
+	inbuf.value = str_c_modifyable(principal_name);
+
+	major_status = gss_import_name(&minor_status, &inbuf, 
+				       GSS_C_NT_HOSTBASED_SERVICE,
+				       &gss_principal);
+
+	str_free(principal_name);
+
+	if (GSS_ERROR(major_status)) {
+		auth_request_log_gss_error(request, major_status,
+					   GSS_C_GSS_CODE,
+					   "importing principal name");
+		return major_status;
+	}
+
+	major_status = gss_acquire_cred(&minor_status, gss_principal, 0, 
+					GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
+					ret, NULL, NULL);
+
+	if (GSS_ERROR(major_status)) {
+		auth_request_log_gss_error(request, major_status,
+					   GSS_C_GSS_CODE,
+					   "acquiring service credentials");
+		auth_request_log_gss_error(request, minor_status,
+					   GSS_C_MECH_CODE,
+					   "acquiring service credentials");
+		return major_status;
+	}
+
+	gss_release_name(&minor_status, gss_principal);
+
+	return major_status;
+}
+
+static gss_name_t
+import_name(struct auth_request *request, void *str, size_t len)
+{
+	OM_uint32 major_status, minor_status;
+	gss_buffer_desc name_buf;
+	gss_name_t name;
+
+	name_buf.value = str;
+	name_buf.length = len;
+	major_status = gss_import_name(&minor_status,
+				       &name_buf,
+				       GSS_C_NO_OID,
+				       &name);
+	if (GSS_ERROR(major_status)) {
+		auth_request_log_gss_error(request, major_status,
+					   GSS_C_GSS_CODE, "gss_import_name");
+		return GSS_C_NO_NAME;
+	}
+
+	return name;
+}
+
+static void gssapi_sec_context(struct gssapi_auth_request *request,
+			       gss_buffer_desc inbuf)
+{
+	OM_uint32 major_status, minor_status;
+	gss_buffer_desc outbuf;
+
+	major_status = gss_accept_sec_context (
+		&minor_status,
+		&request->gss_ctx,
+		request->service_cred,
+		&inbuf,
+		GSS_C_NO_CHANNEL_BINDINGS,
+		&request->authn_name, 
+		NULL, /* mech_type */
+		&outbuf,
+		NULL, /* ret_flags */
+		NULL, /* time_rec */
+		NULL  /* delegated_cred_handle */
+	);
+	
+	if (GSS_ERROR(major_status)) {
+		auth_request_log_gss_error(&request->auth_request, major_status,
+					   GSS_C_GSS_CODE,
+					   "processing incoming data");
+		auth_request_log_gss_error(&request->auth_request, minor_status,
+					   GSS_C_MECH_CODE,
+					   "processing incoming data");
+
+		auth_request_fail(&request->auth_request);
+		return;
+	} 
+
+	if (major_status == GSS_S_COMPLETE) {
+		request->sasl_gssapi_state = GSS_STATE_WRAP;
+		auth_request_log_info(&request->auth_request, "gssapi", 
+				      "security context state completed.");
+	} else {
+		auth_request_log_info(&request->auth_request, "gssapi", 
+				      "Processed incoming packet correctly, "
+				      "waiting for another.");
+	}
+
+	request->auth_request.callback(&request->auth_request,
+				       AUTH_CLIENT_RESULT_CONTINUE,
+				       outbuf.value, outbuf.length);
+
+	major_status = gss_release_buffer(&minor_status, &outbuf);
+}
+
+static void gssapi_wrap(struct gssapi_auth_request *request,
+			gss_buffer_desc inbuf)
+{
+	OM_uint32 major_status, minor_status;
+	gss_buffer_desc outbuf;
+	unsigned char ret[4];
+
+	/* The clients return data should be empty here */
+	
+	/* Only authentication, no integrity or confidentiality
+	   protection (yet?) */
+	ret[0] = (SASL_GSSAPI_QOP_UNSPECIFIED |
+                  SASL_GSSAPI_QOP_AUTH_ONLY);
+	ret[1] = 0xFF;
+	ret[2] = 0xFF;
+	ret[3] = 0xFF;
+
+	inbuf.length = 4;
+	inbuf.value = ret;
+	
+	major_status = gss_wrap(&minor_status, request->gss_ctx, 0,
+				GSS_C_QOP_DEFAULT, &inbuf, NULL, &outbuf);
+
+	if (GSS_ERROR(major_status)) {
+		auth_request_log_gss_error(&request->auth_request, major_status,
+			GSS_C_GSS_CODE, "sending security layer negotiation");
+		auth_request_log_gss_error(&request->auth_request, minor_status,
+			GSS_C_MECH_CODE, "sending security layer negotiation");
+		auth_request_fail(&request->auth_request);
+		return;
+	} 
+
+	auth_request_log_info(&request->auth_request, "gssapi", 
+			      "Negotiated security layer");
+
+	request->auth_request.callback(&request->auth_request,
+				       AUTH_CLIENT_RESULT_CONTINUE,
+				       outbuf.value, outbuf.length);
+
+	major_status = gss_release_buffer(&minor_status, &outbuf);
+
+	request->sasl_gssapi_state = GSS_STATE_UNWRAP;
+}
+
+static void gssapi_unwrap(struct gssapi_auth_request *request,
+			  gss_buffer_desc inbuf)
+{
+	OM_uint32 major_status, minor_status;
+	gss_buffer_desc outbuf;
+	int equal_authn_authz = 0;
+
+	major_status = gss_unwrap(&minor_status, request->gss_ctx, 
+				  &inbuf, &outbuf, NULL, NULL);
+
+	if (GSS_ERROR(major_status)) {
+		auth_request_log_gss_error(&request->auth_request, major_status,
+					   GSS_C_GSS_CODE,
+					   "final negotiation: gss_unwrap");
+		auth_request_fail(&request->auth_request);
+		return;
+	} 
+
+	if (outbuf.length <= 4) {
+		auth_request_log_error(&request->auth_request, "gssapi",
+				       "Invalid response length");
+		auth_request_fail(&request->auth_request);
+		return;
+	}
+
+	request->authz_name = import_name(&request->auth_request,
+					  (unsigned char *)outbuf.value + 4,
+					  outbuf.length - 4);
+	if ((request->authn_name == GSS_C_NO_NAME) ||
+	    (request->authz_name == GSS_C_NO_NAME)) {
+		/* XXX (pod): is this check necessary? */
+		auth_request_log_error(&request->auth_request, "gssapi",
+			"one of authn_name or authz_name not determined");
+		auth_request_fail(&request->auth_request);
+		return;
+	}
+	major_status = gss_compare_name(&minor_status,
+					request->authn_name,
+					request->authz_name,
+					&equal_authn_authz);
+	if (equal_authn_authz == 0) {
+		auth_request_log_error(&request->auth_request, "gssapi",
+			"authn_name and authz_name differ: not supported");
+		auth_request_fail(&request->auth_request);
+		return;
+	}
+
+	request->auth_request.user =
+		p_strndup(request->auth_request.pool,
+			  (unsigned char *)outbuf.value + 4,
+			  outbuf.length - 4);
+
+	auth_request_success(&request->auth_request, NULL, 0);
+}
+
+static void
+mech_gssapi_auth_continue(struct auth_request *request,
+			  const unsigned char *data, size_t data_size)
+{
+	struct gssapi_auth_request *gssapi_request = 
+		(struct gssapi_auth_request *)request;
+	gss_buffer_desc inbuf;
+
+	inbuf.value = (void *)data;
+	inbuf.length = data_size;
+
+	switch (gssapi_request->sasl_gssapi_state) {
+	case GSS_STATE_SEC_CONTEXT:
+		gssapi_sec_context(gssapi_request, inbuf);
+		break;
+	case GSS_STATE_WRAP:
+		gssapi_wrap(gssapi_request, inbuf);
+		break;
+	case GSS_STATE_UNWRAP:
+		gssapi_unwrap(gssapi_request, inbuf);
+		break;
+	} 
+}
+
+static void
+mech_gssapi_auth_initial(struct auth_request *request,
+		       const unsigned char *data, size_t data_size)
+{
+	OM_uint32 major_status;
+	struct gssapi_auth_request *gssapi_request = 
+		(struct gssapi_auth_request *)request;
+	
+	major_status =
+		obtain_service_credentials(request,
+					   &gssapi_request->service_cred);
+
+	if (GSS_ERROR(major_status)) {
+		auth_request_internal_failure(request);
+		return;
+	}
+	gssapi_request->authn_name = GSS_C_NO_NAME;
+	gssapi_request->authz_name = GSS_C_NO_NAME;
+
+	gssapi_request->sasl_gssapi_state = GSS_STATE_SEC_CONTEXT;
+
+	if (data_size == 0) {
+		/* The client should go first */
+		request->callback(request, AUTH_CLIENT_RESULT_CONTINUE,
+				  NULL, 0);
+	} else {
+		mech_gssapi_auth_continue(request, data, data_size);
+	}
+}
+
+
+static void
+mech_gssapi_auth_free(struct auth_request *request)
+{
+	OM_uint32 major_status, minor_status;
+	struct gssapi_auth_request *gssapi_request = 
+		(struct gssapi_auth_request *)request;
+
+	major_status = gss_delete_sec_context(&minor_status, 
+					      &gssapi_request->gss_ctx,
+					      GSS_C_NO_BUFFER);
+
+	major_status = gss_release_cred(&minor_status,
+					&gssapi_request->service_cred);
+	major_status = gss_release_name(&minor_status,
+					&gssapi_request->authn_name);
+	major_status = gss_release_name(&minor_status,
+					&gssapi_request->authz_name);
+
+	pool_unref(request->pool);
+}
+
+const struct mech_module mech_gssapi = {
+	"GSSAPI",
+
+	MEMBER(flags) 0,
+
+	MEMBER(need_passdb) FALSE,
+	MEMBER(passdb_need_plain) FALSE, 
+	MEMBER(passdb_need_credentials) FALSE, 
+
+	mech_gssapi_auth_new,
+	mech_gssapi_auth_initial,
+	mech_gssapi_auth_continue,
+	mech_gssapi_auth_free
+};
+
+#endif
--- a/src/auth/mech-login.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-login.c	Thu Oct 27 17:57:50 2005 +0300
@@ -87,6 +87,7 @@
 
 	MEMBER(flags) MECH_SEC_PLAINTEXT,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) TRUE,
 	MEMBER(passdb_need_credentials) FALSE,
 
--- a/src/auth/mech-ntlm.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-ntlm.c	Thu Oct 27 17:57:50 2005 +0300
@@ -281,6 +281,7 @@
 
 	MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) FALSE,
 	MEMBER(passdb_need_credentials) TRUE,
 
--- a/src/auth/mech-plain.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-plain.c	Thu Oct 27 17:57:50 2005 +0300
@@ -103,6 +103,7 @@
 
 	MEMBER(flags) MECH_SEC_PLAINTEXT,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) TRUE,
 	MEMBER(passdb_need_credentials) FALSE,
 
--- a/src/auth/mech-rpa.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech-rpa.c	Thu Oct 27 17:57:50 2005 +0300
@@ -614,6 +614,7 @@
 	MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE |
 		MECH_SEC_MUTUAL_AUTH,
 
+	MEMBER(need_passdb) TRUE,
 	MEMBER(passdb_need_plain) FALSE,
 	MEMBER(passdb_need_credentials) TRUE,
 
--- a/src/auth/mech.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech.c	Thu Oct 27 17:57:50 2005 +0300
@@ -54,6 +54,9 @@
 extern struct mech_module mech_ntlm;
 extern struct mech_module mech_rpa;
 extern struct mech_module mech_anonymous;
+#ifdef HAVE_GSSAPI
+extern struct mech_module mech_gssapi;
+#endif
 
 void mech_init(void)
 {
@@ -65,6 +68,9 @@
 	mech_register_module(&mech_ntlm);
 	mech_register_module(&mech_rpa);
 	mech_register_module(&mech_anonymous);
+#ifdef HAVE_GSSAPI
+	mech_register_module(&mech_gssapi);
+#endif
 }
 
 void mech_deinit(void)
@@ -77,4 +83,7 @@
 	mech_unregister_module(&mech_ntlm);
 	mech_unregister_module(&mech_rpa);
 	mech_unregister_module(&mech_anonymous);
+#ifdef HAVE_GSSAPI
+	mech_unregister_module(&mech_gssapi);
+#endif
 }
--- a/src/auth/mech.h	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/auth/mech.h	Thu Oct 27 17:57:50 2005 +0300
@@ -24,6 +24,7 @@
 	const char *mech_name;
 
         enum mech_security_flags flags;
+	unsigned int need_passdb:1;
 	unsigned int passdb_need_plain:1;
 	unsigned int passdb_need_credentials:1;
 
--- a/src/master/auth-process.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/master/auth-process.c	Thu Oct 27 17:57:50 2005 +0300
@@ -458,6 +458,8 @@
 		env_put("SSL_REQUIRE_CLIENT_CERT=1");
 	if (set->ssl_username_from_cert)
 		env_put("SSL_USERNAME_FROM_CERT=1");
+	if (set->krb5_keytab)
+		env_put(t_strconcat("KRB5_KTNAME=", set->krb5_keytab, NULL));
 
 	restrict_process_size(set->process_size, (unsigned int)-1);
 }
--- a/src/master/master-settings.c	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/master/master-settings.c	Thu Oct 27 17:57:50 2005 +0300
@@ -155,6 +155,7 @@
 	DEF(SET_STR, username_chars),
 	DEF(SET_STR, username_translation),
 	DEF(SET_STR, anonymous_username),
+	DEF(SET_STR, krb5_keytab),
 
 	DEF(SET_BOOL, verbose),
 	DEF(SET_BOOL, debug),
@@ -353,6 +354,7 @@
 	MEMBER(username_chars) "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890.-_@",
 	MEMBER(username_translation) "",
 	MEMBER(anonymous_username) "anonymous",
+	MEMBER(krb5_keytab) NULL,
 
 	MEMBER(verbose) FALSE,
 	MEMBER(debug) FALSE,
--- a/src/master/master-settings.h	Thu Oct 27 17:29:21 2005 +0300
+++ b/src/master/master-settings.h	Thu Oct 27 17:57:50 2005 +0300
@@ -162,6 +162,7 @@
 	const char *username_chars;
 	const char *username_translation;
 	const char *anonymous_username;
+	const char *krb5_keytab;
 
 	int verbose, debug;
 	int ssl_require_client_cert;