-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Hi, Attached is a patch against current CVS that adds support for the GSSAPI SASL mechanism. It was written from scratch, after reading the patch from Colin Walters against a much older version of dovecot. Other then support for the 'GSSAPI' mechanism, it contains the following changes: - - Added 'auth_krb5_keytab' option for overriding default keytab location. The gssapi library already uses the environment variable KRB5_KTNAME to allow overriding of the keytab location, so I didn't have to make any changes on the login process side. - - Added 'need_passdb' member to mech_module and allow having no passdb's specified if no mechanisms need them (only useful for GSSAPI at the moment) Cheers, Jelmer - -- Jelmer Vernooij <jelmer@samba.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.2 (GNU/Linux) iD8DBQFDVgaEPa9Uoh7vUnYRAscGAJ9Fa4E5ze5IX3UeAfjwg7kzj5tgtQCcDva7 O5VTeOeJ7zpmHullxQN6q1Y=60w7 -----END PGP SIGNATURE----- -------------- next part -------------- Index: configure.in ==================================================================RCS file: /home/cvs/dovecot/configure.in,v retrieving revision 1.214 diff -u -r1.214 configure.in --- configure.in 23 Sep 2005 13:28:40 -0000 1.214 +++ configure.in 19 Oct 2005 08:25:31 -0000 @@ -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(MECH_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" Index: dovecot-example.conf ==================================================================RCS file: /home/cvs/dovecot/dovecot-example.conf,v retrieving revision 1.155 diff -u -r1.155 dovecot-example.conf --- dovecot-example.conf 16 Oct 2005 14:59:12 -0000 1.155 +++ dovecot-example.conf 19 Oct 2005 08:25:31 -0000 @@ -568,9 +568,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 # Index: src/auth/Makefile.am ==================================================================RCS file: /home/cvs/dovecot/src/auth/Makefile.am,v retrieving revision 1.47 diff -u -r1.47 Makefile.am --- src/auth/Makefile.am 7 Aug 2005 11:41:19 -0000 1.47 +++ src/auth/Makefile.am 19 Oct 2005 08:25:31 -0000 @@ -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 \ Index: src/auth/auth.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/auth.c,v retrieving revision 1.19 diff -u -r1.19 auth.c --- src/auth/auth.c 16 Oct 2005 14:06:59 -0000 1.19 +++ src/auth/auth.c 19 Oct 2005 08:25:31 -0000 @@ -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,9 @@ struct mech_module_list *list; for (list = auth->mech_modules; list != NULL; list = list->next) { + if (list->module.need_passdb && + !auth->passdbs) + break; if (list->module.passdb_need_plain && !auth_passdb_list_have_plain(auth)) break; Index: src/auth/mech-anonymous.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-anonymous.c,v retrieving revision 1.13 diff -u -r1.13 mech-anonymous.c --- src/auth/mech-anonymous.c 23 Apr 2005 09:11:49 -0000 1.13 +++ src/auth/mech-anonymous.c 19 Oct 2005 08:25:31 -0000 @@ -57,6 +57,7 @@ MEMBER(flags) MECH_SEC_ANONYMOUS, + MEMBER(need_passdb) TRUE, MEMBER(passdb_need_plain) FALSE, MEMBER(passdb_need_credentials) FALSE, Index: src/auth/mech-apop.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-apop.c,v retrieving revision 1.16 diff -u -r1.16 mech-apop.c --- src/auth/mech-apop.c 23 Apr 2005 09:11:49 -0000 1.16 +++ src/auth/mech-apop.c 19 Oct 2005 08:25:31 -0000 @@ -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, Index: src/auth/mech-cram-md5.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-cram-md5.c,v retrieving revision 1.20 diff -u -r1.20 mech-cram-md5.c --- src/auth/mech-cram-md5.c 8 Jan 2005 21:37:32 -0000 1.20 +++ src/auth/mech-cram-md5.c 19 Oct 2005 08:25:32 -0000 @@ -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, Index: src/auth/mech-digest-md5.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-digest-md5.c,v retrieving revision 1.34 diff -u -r1.34 mech-digest-md5.c --- src/auth/mech-digest-md5.c 8 Jan 2005 21:37:32 -0000 1.34 +++ src/auth/mech-digest-md5.c 19 Oct 2005 08:25:32 -0000 @@ -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, Index: src/auth/mech-login.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-login.c,v retrieving revision 1.12 diff -u -r1.12 mech-login.c --- src/auth/mech-login.c 23 Apr 2005 09:11:49 -0000 1.12 +++ src/auth/mech-login.c 19 Oct 2005 08:25:32 -0000 @@ -87,6 +87,7 @@ MEMBER(flags) MECH_SEC_PLAINTEXT, + MEMBER(need_passdb) TRUE, MEMBER(passdb_need_plain) TRUE, MEMBER(passdb_need_credentials) FALSE, Index: src/auth/mech-ntlm.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-ntlm.c,v retrieving revision 1.19 diff -u -r1.19 mech-ntlm.c --- src/auth/mech-ntlm.c 23 Apr 2005 09:11:49 -0000 1.19 +++ src/auth/mech-ntlm.c 19 Oct 2005 08:25:32 -0000 @@ -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, Index: src/auth/mech-plain.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-plain.c,v retrieving revision 1.31 diff -u -r1.31 mech-plain.c --- src/auth/mech-plain.c 23 Apr 2005 09:11:49 -0000 1.31 +++ src/auth/mech-plain.c 19 Oct 2005 08:25:32 -0000 @@ -103,6 +103,7 @@ MEMBER(flags) MECH_SEC_PLAINTEXT, + MEMBER(need_passdb) TRUE, MEMBER(passdb_need_plain) TRUE, MEMBER(passdb_need_credentials) FALSE, Index: src/auth/mech-rpa.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech-rpa.c,v retrieving revision 1.19 diff -u -r1.19 mech-rpa.c --- src/auth/mech-rpa.c 23 Apr 2005 09:11:49 -0000 1.19 +++ src/auth/mech-rpa.c 19 Oct 2005 08:25:32 -0000 @@ -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, Index: src/auth/mech.c ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech.c,v retrieving revision 1.54 diff -u -r1.54 mech.c --- src/auth/mech.c 7 Jan 2005 19:55:50 -0000 1.54 +++ src/auth/mech.c 19 Oct 2005 08:25:32 -0000 @@ -54,6 +54,9 @@ extern struct mech_module mech_ntlm; extern struct mech_module mech_rpa; extern struct mech_module mech_anonymous; +#ifdef MECH_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 MECH_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 MECH_GSSAPI + mech_unregister_module(&mech_gssapi); +#endif } Index: src/auth/mech.h ==================================================================RCS file: /home/cvs/dovecot/src/auth/mech.h,v retrieving revision 1.35 diff -u -r1.35 mech.h --- src/auth/mech.h 9 Jan 2005 16:54:48 -0000 1.35 +++ src/auth/mech.h 19 Oct 2005 08:25:32 -0000 @@ -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; Index: src/master/auth-process.c ==================================================================RCS file: /home/cvs/dovecot/src/master/auth-process.c,v retrieving revision 1.82 diff -u -r1.82 auth-process.c --- src/master/auth-process.c 1 Oct 2005 10:52:16 -0000 1.82 +++ src/master/auth-process.c 19 Oct 2005 08:25:33 -0000 @@ -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); } Index: src/master/master-settings.c ==================================================================RCS file: /home/cvs/dovecot/src/master/master-settings.c,v retrieving revision 1.92 diff -u -r1.92 master-settings.c --- src/master/master-settings.c 16 Oct 2005 14:59:17 -0000 1.92 +++ src/master/master-settings.c 19 Oct 2005 08:25:33 -0000 @@ -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, Index: src/master/master-settings.h ==================================================================RCS file: /home/cvs/dovecot/src/master/master-settings.h,v retrieving revision 1.60 diff -u -r1.60 master-settings.h --- src/master/master-settings.h 1 Oct 2005 10:52:16 -0000 1.60 +++ src/master/master-settings.h 19 Oct 2005 08:25:33 -0000 @@ -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; --- /dev/null 2005-10-17 12:49:06.896857000 +0200 +++ src/auth/mech-gssapi.c 2005-10-19 10:17:45.000000000 +0200 @@ -0,0 +1,321 @@ +/* + * 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 "buffer.h" +#include "hex-binary.h" +#include "safe-memset.h" +#include "hostpid.h" + +#ifdef MECH_GSSAPI + +#include <gssapi/gssapi.h> + +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 src_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, (char *)status_string.value); + + 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(40); + str_append(principal_name, t_str_lcase(request->service)); + str_append(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 = (void*) str_c(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 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->src_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; + char ret[4]; + + /* The clients return data should be empty here */ + + ret[0] = 0x01; /* Only authentication, no integrity or confidentiality protection (yet?) */ + 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; + + 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->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 = (char*) 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->src_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); + + 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 Index: AUTHORS ==================================================================RCS file: /home/cvs/dovecot/AUTHORS,v retrieving revision 1.11 diff -u -r1.11 AUTHORS --- AUTHORS 19 Aug 2004 03:56:01 -0000 1.11 +++ AUTHORS 19 Oct 2005 08:38:49 -0000 @@ -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)
pod@herald.ox.ac.uk
2005-Oct-19 16:11 UTC
[Dovecot] [PATCH] Support for GSSAPI SASL Mechanism
>>>>> "JV" == Jelmer Vernooij <jelmer@samba.org> writes:JV> Attached is a patch against current CVS that adds support for the JV> GSSAPI SASL mechanism. It was written from scratch, after reading JV> the patch from Colin Walters against a much older version of JV> dovecot. I too have been working on getting a working GSSAPI patch against current CVS and have taken a similar approach. Any idea if this is going to make it's way into CVS? I notice that its auth only and you don't have any SASL security layer integrity or protection stuff, same as DIGEST-MD5. This is the point which I've got to and have been considering how to implement the 'integrity-proxy' (name coined from the Colin Walters patch) part of things. Work on this would have implications for mech-digest-md5.c as well. Want to discuss ideas? Timo, do you have any ideas on a good way to implement this? I have been considering: - start up two pipe connected processes, a network filter and libexec/dovecot/imap, the filter does the gss_wrap, gss_unwrap etc - create a io library filter layer - keep the imap-login process around but have it re-exec as the filter (would be running as login_user, probably not ideal)
pod@herald.ox.ac.uk
2005-Oct-20 18:34 UTC
[Dovecot] [PATCH] Support for GSSAPI SASL Mechanism
I believe there is a significant privacy issue with the recently posted updated GSSAPI patch when used on multiuser systems (indeed this problem is present in the previous patch as well). The username, and consequently the uid that the imap process is eventually run as, is taken from the authorization id in the (unwrapped) buffer supplied by the client as part of the SASL security layer negotiation. The short of it is that with the patch as it stands user1 can authenticate as user1 but supply user2 as the authorization id and dovecot will fire up an imap process running as user2 looking at user2's mail. I'm attaching a patch that I believe fixes the immediate problem but it is really only a bandaid over more general SASL issues. The patch does the following: - Adds in a QOP negotiation flags enum (somewhat cosmetic really) - Renames src_name in gssapi_auth_request to authn_name and adds authz_name - Adds a helper function to import a gss_name_t from a buffer - Imports the authorization id (authz_name) and compares it with the authentication id (authn_name). If they are different authentication is declined. I'm not entirely happy that the username passed back from the auth process is still copied from the authorization id buffer because it feels like the username should come from gss_display_name. Unfortunately that will tend to return names like user@REALM which opens up another can of worms and is too much for my little brain to cope with right now. -------------- next part -------------- diff -ru dovecot-jv/src/auth/mech-gssapi.c dovecot-jv-pod/src/auth/mech-gssapi.c --- dovecot-jv/src/auth/mech-gssapi.c 2005-10-19 18:58:34.000000000 +0100 +++ dovecot-jv-pod/src/auth/mech-gssapi.c 2005-10-20 16:03:30.000000000 +0100 @@ -28,6 +28,14 @@ #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; @@ -39,7 +47,8 @@ GSS_STATE_UNWRAP } sasl_gssapi_state; - gss_name_t src_name; + gss_name_t authn_name; + gss_name_t authz_name; pool_t pool; }; @@ -122,6 +131,28 @@ 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) { @@ -134,7 +165,7 @@ request->service_cred, &inbuf, GSS_C_NO_CHANNEL_BINDINGS, - &request->src_name, + &request->authn_name, NULL, /* mech_type */ &outbuf, NULL, /* ret_flags */ @@ -177,7 +208,9 @@ /* The clients return data should be empty here */ - ret[0] = 0x01; /* Only authentication, no integrity or confidentiality protection (yet?) */ + /* 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; @@ -215,6 +248,7 @@ { 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); @@ -233,6 +267,27 @@ 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); @@ -272,7 +327,8 @@ auth_request_internal_failure(request); return; } - gssapi_request->src_name = GSS_C_NO_NAME; + 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; @@ -299,6 +355,8 @@ 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); }