Mercurial > dovecot > core-2.2
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;