X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=src%2Fmod_auth_kerb.c;h=97b4761cd04b17825b2d247a79d31468b3bfdd1a;hb=1be580d3f50107243f4276ec84f192521e02871d;hp=c035c6e992ff490183e11ccd11da9af75995bc9e;hpb=41ad4f9234d5383ac7b9be7431783546c677c7b4;p=mod_auth_kerb.git diff --git a/src/mod_auth_kerb.c b/src/mod_auth_kerb.c index c035c6e..97b4761 100644 --- a/src/mod_auth_kerb.c +++ b/src/mod_auth_kerb.c @@ -1,5 +1,79 @@ +/* + * Daniel Kouril + * + * Source and Documentation can be found at: + * http://modauthkerb.sourceforge.net/ + * + * Based on work by + * James E. Robinson, III + * Daniel Henninger + * Ludek Sulak + */ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + #ident "$Id$" +#include "config.h" + +#define MODAUTHKERB_VERSION "5.0-rc3" + #ifndef APXS1 #include "ap_compat.h" #include "apr_strings.h" @@ -13,11 +87,26 @@ #ifdef KRB5 #include -#include +#ifdef HEIMDAL +# include +#else +# include +# include +# define GSS_C_NT_USER_NAME gss_nt_user_name +# define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +# define krb5_get_err_text(context,code) error_message(code) +#endif +#include "spnegokrb5.h" #endif /* KRB5 */ #ifdef KRB4 +/*Prevent warning about closesocket redefinition (Apache's ap_config.h and + * MIT Kerberos' port-sockets.h both define it as close) */ +#ifdef closesocket +# undef closesocket +#endif #include +#include /* gethostbyname() */ #endif /* KRB4 */ #ifdef APXS1 @@ -32,21 +121,13 @@ module AP_MODULE_DECLARE_DATA auth_kerb_module; #ifdef APXS1 #define MK_POOL pool #define MK_TABLE_GET ap_table_get -#define MK_TABLE_SET ap_table_set -#define MK_TABLE_TYPE table -#define MK_PSTRDUP ap_pstrdup #define MK_USER r->connection->user #define MK_AUTH_TYPE r->connection->ap_auth_type -#define MK_ARRAY_HEADER array_header #else #define MK_POOL apr_pool_t #define MK_TABLE_GET apr_table_get -#define MK_TABLE_SET apr_table_set -#define MK_TABLE_TYPE apr_table_t -#define MK_PSTRDUP apr_pstrdup #define MK_USER r->user #define MK_AUTH_TYPE r->ap_auth_type -#define MK_ARRAY_HEADER apr_array_header_t #endif /* APXS1 */ @@ -55,15 +136,12 @@ module AP_MODULE_DECLARE_DATA auth_kerb_module; ***************************************************************************/ typedef struct { char *krb_auth_realms; - int krb_fail_status; - char *krb_force_instance; int krb_save_credentials; - char *krb_tmp_dir; - char *service_name; - char *krb_lifetime; + int krb_verify_kdc; + char *krb_service_name; + int krb_authoritative; #ifdef KRB5 char *krb_5_keytab; - int krb_forwardable; int krb_method_gssapi; int krb_method_k5pass; #endif @@ -89,46 +167,30 @@ krb5_save_realms(cmd_parms *cmd, kerb_auth_config *sec, char *arg); #endif static const command_rec kerb_auth_cmds[] = { - command("KrbAuthRealm", krb5_save_realms, krb_auth_realms, - RAW_ARGS, "Realms to attempt authentication against (can be multiple)."), - command("KrbAuthRealms", krb5_save_realms, krb_auth_realms, - RAW_ARGS, "Alias for KrbAuthRealm."), - -#if 0 - command("KrbFailStatus", kerb_set_fail_slot, krb_fail_status, - TAKE1, "If auth fails, return status set here."), -#endif + RAW_ARGS, "Realms to attempt authentication against (can be multiple)."), - command("KrbForceInstance", ap_set_string_slot, krb_force_instance, - TAKE1, "Force authentication against an instance specified here."), + command("KrbAuthRealm", krb5_save_realms, krb_auth_realms, + RAW_ARGS, "Alias for KrbAuthRealms."), command("KrbSaveCredentials", ap_set_flag_slot, krb_save_credentials, FLAG, "Save and store credentials/tickets retrieved during auth."), - command("KrbSaveTickets", ap_set_flag_slot, krb_save_credentials, - FLAG, "Alias for KrbSaveCredentials."), - - command("KrbTmpdir", ap_set_string_slot, krb_tmp_dir, - TAKE1, "Path to store ticket files and such in."), + command("KrbVerifyKDC", ap_set_flag_slot, krb_verify_kdc, + FLAG, "Verify tickets against keytab to prevent KDC spoofing attacks."), - command("KrbServiceName", ap_set_string_slot, service_name, - TAKE1, "Kerberos service name to be used by apache."), + command("KrbServiceName", ap_set_string_slot, krb_service_name, + TAKE1, "Service name to be used by Apache for authentication."), -#if 0 - command("KrbLifetime", ap_set_string_slot, krb_lifetime, - TAKE1, "Kerberos ticket lifetime."), -#endif + command("KrbAuthoritative", ap_set_flag_slot, krb_authoritative, + FLAG, "Set to 'off' to allow access control to be passed along to lower modules if the UserID is not known to this module."), #ifdef KRB5 command("Krb5Keytab", ap_set_file_slot, krb_5_keytab, TAKE1, "Location of Kerberos V5 keytab file."), - command("KrbForwardable", ap_set_flag_slot, krb_forwardable, - FLAG, "Credentials retrieved will be flagged as forwardable."), - - command("KrbMethodGSSAPI", ap_set_flag_slot, krb_method_gssapi, - FLAG, "Enable GSSAPI authentication."), + command("KrbMethodNegotiate", ap_set_flag_slot, krb_method_gssapi, + FLAG, "Enable Negotiate authentication method."), command("KrbMethodK5Pass", ap_set_flag_slot, krb_method_k5pass, FLAG, "Enable Kerberos V5 password authentication."), @@ -163,7 +225,9 @@ static void *kerb_dir_create_config(MK_POOL *p, char *d) kerb_auth_config *rec; rec = (kerb_auth_config *) ap_pcalloc(p, sizeof(kerb_auth_config)); - ((kerb_auth_config *)rec)->krb_fail_status = HTTP_UNAUTHORIZED; + ((kerb_auth_config *)rec)->krb_verify_kdc = 1; + ((kerb_auth_config *)rec)->krb_service_name = "khttp"; + ((kerb_auth_config *)rec)->krb_authoritative = 1; #ifdef KRB5 ((kerb_auth_config *)rec)->krb_method_k5pass = 1; ((kerb_auth_config *)rec)->krb_method_gssapi = 1; @@ -185,125 +249,206 @@ void log_rerror(const char *file, int line, int level, int status, const request_rec *r, const char *fmt, ...) { char errstr[1024]; + char errnostr[1024]; va_list ap; va_start(ap, fmt); vsnprintf(errstr, sizeof(errstr), fmt, ap); va_end(ap); + errnostr[0] = '\0'; + if (errno) + snprintf(errnostr, sizeof(errnostr), "%s: (%s)", errstr, strerror(errno)); + else + snprintf(errnostr, sizeof(errnostr), "%s", errstr); + #ifdef APXS1 - ap_log_rerror(file, line, level, r, "%s", errstr); + ap_log_rerror(file, line, level | APLOG_NOERRNO, r, "%s", errnostr); #else - ap_log_rerror(file, line, level, status, r, "%s", errstr); + ap_log_rerror(file, line, level | APLOG_NOERRNO, status, r, "%s", errnostr); #endif } -#if 0 -static const char *kerb_set_fail_slot(cmd_parms *cmd, void *struct_ptr, - const char *arg) -{ - int offset = (int) (long) cmd->info; - if (!strncasecmp(arg, "unauthorized", 12)) - *(int *) ((char *)struct_ptr + offset) = HTTP_UNAUTHORIZED; - else if (!strncasecmp(arg, "forbidden", 9)) - *(int *) ((char *)struct_ptr + offset) = HTTP_FORBIDDEN; - else if (!strncasecmp(arg, "declined", 8)) - *(int *) ((char *)struct_ptr + offset) = DECLINED; - else - return "KrbAuthFailStatus must be Forbidden, Unauthorized, or Declined."; - return NULL; -} -#endif - #ifdef KRB4 /*************************************************************************** Username/Password Validation for Krb4 ***************************************************************************/ -int kerb4_password_validate(request_rec *r, const char *user, const char *pass) +static int +verify_krb4_user(request_rec *r, char *name, char *instance, char *realm, + char *password, char *linstance, char *srvtab, int krb_verify_kdc) { - kerb_auth_config *conf = - (kerb_auth_config *)ap_get_module_config(r->per_dir_config, - &auth_kerb_module); - int ret; - int lifetime = DEFAULT_TKT_LIFE; - char *c, *tfname; - char *username = NULL; - char *instance = NULL; - char *realm = NULL; - - username = (char *)ap_pstrdup(r->pool, user); - if (!username) { - return 0; - } - - instance = strchr(username, '.'); - if (instance) { - *instance++ = '\0'; - } - else { - instance = ""; - } - - realm = strchr(username, '@'); - if (realm) { - *realm++ = '\0'; - } - else { - realm = ""; - } - - if (conf->krb_lifetime) { - lifetime = atoi(conf->krb_lifetime); - } - - if (conf->krb_force_instance) { - instance = conf->krb_force_instance; - } - - if (conf->krb_save_credentials) { - tfname = (char *)malloc(sizeof(char) * MAX_STRING_LEN); - sprintf(tfname, "%s/k5cc_ap_%s", - conf->krb_tmp_dir ? conf->krb_tmp_dir : "/tmp", - MK_USER); - - if (!strcmp(instance, "")) { - tfname = strcat(tfname, "."); - tfname = strcat(tfname, instance); - } - - if (!strcmp(realm, "")) { - tfname = strcat(tfname, "."); - tfname = strcat(tfname, realm); - } - - for (c = tfname + strlen(conf->krb_tmp_dir ? conf->krb_tmp_dir : - "/tmp") + 1; *c; c++) { - if (*c == '/') - *c = '.'; - } - - krb_set_tkt_string(tfname); - } - - if (!strcmp(realm, "")) { - realm = (char *)malloc(sizeof(char) * (REALM_SZ + 1)); - ret = krb_get_lrealm(realm, 1); - if (ret != KSUCCESS) - return 0; - } - - ret = krb_get_pw_in_tkt((char *)user, instance, realm, "krbtgt", realm, - lifetime, (char *)pass); - switch (ret) { - case INTK_OK: - case INTK_W_NOTALL: - return 1; - break; - - default: - return 0; - break; - } + int ret; + char *phost; + unsigned long addr; + struct hostent *hp; + const char *hostname; + KTEXT_ST ticket; + AUTH_DAT authdata; + char lrealm[REALM_SZ]; + + ret = krb_get_pw_in_tkt(name, instance, realm, "krbtgt", realm, + DEFAULT_TKT_LIFE, password); + if (ret) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Cannot get krb4 ticket: krb_get_pw_in_tkt() failed: %s", + krb_get_err_text(ret)); + return ret; + } + + if (!krb_verify_kdc) + return ret; + + hostname = ap_get_server_name(r); + + hp = gethostbyname(hostname); + if (hp == NULL) { + dest_tkt(); + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Cannot verify krb4 ticket: gethostbyname() failed: %s", + hstrerror(h_errno)); + return h_errno; + } + memcpy(&addr, hp->h_addr, sizeof(addr)); + + phost = krb_get_phost((char *)hostname); + + krb_get_lrealm(lrealm, 1); + + ret = krb_mk_req(&ticket, linstance, phost, lrealm, 0); + if (ret) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Cannot verify krb4 ticket: krb_mk_req() failed: %s", + krb_get_err_text(ret)); + dest_tkt(); + return ret; + } + + ret = krb_rd_req(&ticket, linstance, phost, addr, &authdata, srvtab); + if (ret) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Cannot verify krb4 ticket: krb_rd_req() failed: %s", + krb_get_err_text(ret)); + dest_tkt(); + } + + return ret; +} + +static int +krb4_cache_cleanup(void *data) +{ + char *tkt_file = (char *) data; + + krb_set_tkt_string(tkt_file); + dest_tkt(); + return OK; +} + +static int +authenticate_user_krb4pwd(request_rec *r, + kerb_auth_config *conf, + const char *auth_line) +{ + int ret; + const char *sent_pw; + const char *sent_name; + char *sent_instance; + char tkt_file[32]; + char *tkt_file_p = NULL; + int fd; + const char *realms; + const char *realm; + char *user; + char lrealm[REALM_SZ]; + int all_principals_unkown; + + sent_pw = ap_pbase64decode(r->pool, auth_line); + sent_name = ap_getword (r->pool, &sent_pw, ':'); + + /* do not allow user to override realm setting of server */ + if (strchr(sent_name, '@')) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "specifying realm in user name is prohibited"); + return HTTP_UNAUTHORIZED; + } + + sent_instance = strchr(sent_name, '.'); + if (sent_instance) + *sent_instance++ = '\0'; + + snprintf(tkt_file, sizeof(tkt_file), "/tmp/apache_tkt_XXXXXX"); + fd = mkstemp(tkt_file); + if (fd < 0) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Cannot create krb4 ccache: mkstemp() failed: %s", + strerror(errno)); + return HTTP_INTERNAL_SERVER_ERROR; + } + + tkt_file_p = ap_pstrdup(r->pool, tkt_file); + ap_register_cleanup(r->pool, tkt_file_p, + krb4_cache_cleanup, ap_null_cleanup); + + krb_set_tkt_string(tkt_file); + + all_principals_unkown = 1; + realms = conf->krb_auth_realms; + do { + memset(lrealm, 0, sizeof(lrealm)); + realm = NULL; + if (realms) + realm = ap_getword_white(r->pool, &realms); + + if (realm == NULL) { + ret = krb_get_lrealm(lrealm, 1); + realm = lrealm; + } + if (realm == NULL || *realm == '\0') + break; + + ret = verify_krb4_user(r, (char *)sent_name, + (sent_instance) ? sent_instance : "", + (char *)realm, (char *)sent_pw, + conf->krb_service_name, + conf->krb_4_srvtab, conf->krb_verify_kdc); + if (!conf->krb_authoritative && ret) { + /* if we're not authoritative, we allow authentication to pass on + * to another modules if (and only if) the user is not known to us */ + if (all_principals_unkown && ret != KDC_PR_UNKNOWN) + all_principals_unkown = 0; + } + + if (ret == 0) + break; + } while (realms && *realms); + + if (ret) { + /* XXX log only in the verify_krb4_user() call */ + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Verifying krb4 password failed"); + ret = (!conf->krb_authoritative && all_principals_unkown == 1 && ret == KDC_PR_UNKNOWN) ? + DECLINED : HTTP_UNAUTHORIZED; + goto end; + } + + user = ap_pstrdup(r->pool, sent_name); + if (sent_instance) + user = ap_pstrcat(r->pool, user, ".", sent_instance, NULL); + user = ap_pstrcat(r->pool, user, "@", realm, NULL); + + MK_USER = user; + MK_AUTH_TYPE = "Basic"; + ap_table_setn(r->subprocess_env, "KRBTKFILE", tkt_file_p); + + if (!conf->krb_save_credentials) + krb4_cache_cleanup(tkt_file); + +end: + if (ret) + krb4_cache_cleanup(tkt_file); + close(fd); + tf_close(); + + return ret; } #endif /* KRB4 */ @@ -311,58 +456,49 @@ int kerb4_password_validate(request_rec *r, const char *user, const char *pass) /*************************************************************************** Username/Password Validation for Krb5 ***************************************************************************/ -#ifndef HEIMDAL -krb5_error_code -krb5_verify_user(krb5_context context, krb5_principal principal, - krb5_ccache ccache, const char *password, krb5_boolean secure, - const char *service) +/* Inspired by krb5_verify_user from Heimdal */ +static krb5_error_code +verify_krb5_user(request_rec *r, krb5_context context, krb5_principal principal, + krb5_ccache ccache, const char *password, const char *service, + krb5_keytab keytab, int krb_verify_kdc) { - int ret; - krb5_context kcontext; - krb5_principal server, client; - krb5_timestamp now; - krb5_creds my_creds; - krb5_flags options = 0; - krb5_principal me = NULL; - krb5_data tgtname = { - 0, - KRB5_TGS_NAME_SIZE, - KRB5_TGS_NAME - }; - - memset((char *)&my_creds, 0, sizeof(my_creds)); - my_creds.client = principal; - - if (krb5_build_principal_ext(kcontext, &server, - krb5_princ_realm(kcontext, me)->length, - krb5_princ_realm(kcontext, me)->data, - tgtname.length, tgtname.data, - krb5_princ_realm(kcontext, me)->length, - krb5_princ_realm(kcontext, me)->data, - 0)) { - return ret; - } + krb5_creds creds; + krb5_principal server = NULL; + krb5_error_code ret; + krb5_verify_init_creds_opt opt; - my_creds.server = server; - if (krb5_timeofday(kcontext, &now)) - return -1; + memset(&creds, 0, sizeof(creds)); - my_creds.times.starttime = 0; - /* XXX - my_creds.times.endtime = now + lifetime; - my_creds.times.renew_till = now + renewal; - */ + ret = krb5_get_init_creds_password(context, &creds, principal, + (char *)password, krb5_prompter_posix, + NULL, 0, NULL, NULL); + if (ret) + return ret; - ret = krb5_get_in_tkt_with_password(kcontext, options, 0, NULL, 0, - password, ccache, &my_creds, 0); - if (ret) { - return ret; + ret = krb5_sname_to_principal(context, ap_get_server_name(r), service, + KRB5_NT_SRV_HST, &server); + if (ret) + goto end; + + krb5_verify_init_creds_opt_init(&opt); + krb5_verify_init_creds_opt_set_ap_req_nofail(&opt, krb_verify_kdc); + + ret = krb5_verify_init_creds(context, &creds, server, keytab, NULL, &opt); + if (ret) + goto end; + + if (ccache) { + ret = krb5_cc_initialize(context, ccache, principal); + if (ret == 0) + ret = krb5_cc_store_cred(context, ccache, &creds); } - return 0; +end: + krb5_free_cred_contents(context, &creds); + if (server) + krb5_free_principal(context, server); + return ret; } -#endif - static int krb5_cache_cleanup(void *data) @@ -398,53 +534,54 @@ create_krb5_ccache(krb5_context kcontext, krb5_principal princ, krb5_ccache *ccache) { - char *c, ccname[MAX_STRING_LEN]; - krb5_error_code problem; - int ret; - krb5_ccache tmp_ccache = NULL; - - snprintf(ccname, sizeof(ccname), "FILE:%s/k5cc_ap_%s", - conf->krb_tmp_dir ? conf->krb_tmp_dir : "/tmp", - MK_USER); - - for (c = ccname + strlen(conf->krb_tmp_dir ? conf->krb_tmp_dir : - "/tmp") + 1; *c; c++) { - if (*c == '/') - *c = '.'; - } - - problem = krb5_cc_resolve(kcontext, ccname, &tmp_ccache); - if (problem) { - log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Cannot create krb5 ccache: krb5_cc_resolve() failed: %s", - krb5_get_err_text(kcontext, problem)); - ret = HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - problem = krb5_cc_initialize(kcontext, tmp_ccache, princ); - if (problem) { - log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Cannot create krb5 ccache: krb5_cc_initialize() failed: %s", - krb5_get_err_text(kcontext, problem)); - ret = HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname); - ap_register_cleanup(r->pool, ccname, - krb5_cache_cleanup, ap_null_cleanup); - - *ccache = tmp_ccache; - tmp_ccache = NULL; - - ret = OK; + char *ccname; + krb5_error_code problem; + int ret; + krb5_ccache tmp_ccache = NULL; + +#ifdef HAVE_KRB5_CC_GEN_NEW + problem = krb5_cc_gen_new(kcontext, &krb5_fcc_ops, &tmp_ccache); +#else + /* only older MIT seem to not have the krb5_cc_gen_new() call, so we use + * MIT specific call here */ + problem = krb5_fcc_generate_new(kcontext, &tmp_ccache); + /* krb5_fcc_generate_new() doesn't set KRB5_TC_OPENCLOSE, which makes + krb5_cc_initialize() fail */ + krb5_fcc_set_flags(kcontext, tmp_ccache, KRB5_TC_OPENCLOSE); +#endif + if (problem) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Cannot create file for new krb5 ccache: %s", + krb5_get_err_text(kcontext, problem)); + ret = HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + ccname = ap_pstrdup(r->pool, krb5_cc_get_name(kcontext, tmp_ccache)); + + problem = krb5_cc_initialize(kcontext, tmp_ccache, princ); + if (problem) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Cannot initialize krb5 ccache %s: krb5_cc_initialize() failed: %s", + ccname, krb5_get_err_text(kcontext, problem)); + ret = HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + ap_table_setn(r->subprocess_env, "KRB5CCNAME", ccname); + ap_register_cleanup(r->pool, ccname, + krb5_cache_cleanup, ap_null_cleanup); + + *ccache = tmp_ccache; + tmp_ccache = NULL; + + ret = OK; end: - if (tmp_ccache) - krb5_cc_destroy(kcontext, tmp_ccache); + if (tmp_ccache) + krb5_cc_destroy(kcontext, tmp_ccache); - return ret; + return ret; } static int @@ -472,10 +609,14 @@ store_krb5_creds(krb5_context kcontext, return ret; } +#ifdef HEIMDAL problem = krb5_cc_copy_cache(kcontext, delegated_cred, ccache); +#else + problem = krb5_cc_copy_creds(kcontext, delegated_cred, ccache); +#endif krb5_free_principal(kcontext, princ); if (problem) { - snprintf(errstr, sizeof(errstr), "krb5_cc_copy_cache() failed: %s", + snprintf(errstr, sizeof(errstr), "Failed to store credentials: %s", krb5_get_err_text(kcontext, problem)); krb5_cc_destroy(kcontext, ccache); return HTTP_INTERNAL_SERVER_ERROR; @@ -491,12 +632,16 @@ int authenticate_user_krb5pwd(request_rec *r, const char *auth_line) { const char *sent_pw = NULL; + const char *sent_name = NULL; const char *realms = NULL; krb5_context kcontext = NULL; krb5_error_code code; krb5_principal client = NULL; krb5_ccache ccache = NULL; + krb5_keytab keytab = NULL; int ret; + char *name = NULL; + int all_principals_unkown; code = krb5_init_context(&kcontext); if (code) { @@ -506,20 +651,20 @@ int authenticate_user_krb5pwd(request_rec *r, } sent_pw = ap_pbase64decode(r->pool, auth_line); - MK_USER = ap_getword (r->pool, &sent_pw, ':'); - MK_AUTH_TYPE = "Basic"; - + sent_name = ap_getword (r->pool, &sent_pw, ':'); /* do not allow user to override realm setting of server */ - if (strchr(MK_USER, '@')) { + if (strchr(sent_name, '@')) { log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "specifying realm in user name is prohibited"); ret = HTTP_UNAUTHORIZED; goto end; } -#ifdef HEIMDAL +#ifdef HAVE_KRB5_CC_GEN_NEW code = krb5_cc_gen_new(kcontext, &krb5_mcc_ops, &ccache); #else + /* only older MIT seem to not have the krb5_cc_gen_new() call, so we use + * MIT specific call here */ code = krb5_mcc_generate_new(kcontext, &ccache); #endif if (code) { @@ -531,23 +676,33 @@ int authenticate_user_krb5pwd(request_rec *r, } if (conf->krb_5_keytab) - /* setenv("KRB5_KTNAME", conf->krb_5_keytab, 1); */ - kcontext->default_keytab = conf->krb_5_keytab; + krb5_kt_resolve(kcontext, conf->krb_5_keytab, &keytab); + all_principals_unkown = 1; realms = conf->krb_auth_realms; do { if (realms && (code = krb5_set_default_realm(kcontext, ap_getword_white(r->pool, &realms)))) continue; - code = krb5_parse_name(kcontext, MK_USER, &client); + if (client) { + krb5_free_principal(kcontext, client); + client = NULL; + } + code = krb5_parse_name(kcontext, sent_name, &client); if (code) continue; - code = krb5_verify_user(kcontext, client, ccache, sent_pw, 1, - (conf->service_name) ? conf->service_name : "khttp"); - krb5_free_principal(kcontext, client); - client = NULL; + code = verify_krb5_user(r, kcontext, client, ccache, sent_pw, + conf->krb_service_name, + keytab, conf->krb_verify_kdc); + if (!conf->krb_authoritative && code) { + /* if we're not authoritative, we allow authentication to pass on + * to another modules if (and only if) the user is not known to us */ + if (all_principals_unkown && code != KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN) + all_principals_unkown = 0; + } + if (code == 0) break; @@ -558,18 +713,31 @@ int authenticate_user_krb5pwd(request_rec *r, memset((char *)sent_pw, 0, strlen(sent_pw)); if (code) { + /* XXX log only in the verify_krb5_user() call */ log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Verifying krb5 password failed: %s", krb5_get_err_text(kcontext, code)); - ret = HTTP_UNAUTHORIZED; + if (!conf->krb_authoritative && all_principals_unkown == 1 && code == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN) + ret = DECLINED; + else + ret = HTTP_UNAUTHORIZED; + goto end; } - if (conf->krb_save_credentials) { - ret = store_krb5_creds(kcontext, r, conf, ccache); - if (ret) /* Ignore error ?? */ - goto end; + code = krb5_unparse_name(kcontext, client, &name); + if (code) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "krb5_unparse_name() failed: %s", + krb5_get_err_text(kcontext, code)); + ret = HTTP_UNAUTHORIZED; + goto end; } + MK_USER = ap_pstrdup (r->pool, name); + MK_AUTH_TYPE = "Basic"; + free(name); + + if (conf->krb_save_credentials) + store_krb5_creds(kcontext, r, conf, ccache); ret = OK; @@ -578,6 +746,8 @@ end: krb5_free_principal(kcontext, client); if (ccache) krb5_cc_destroy(kcontext, ccache); + if (keytab) + krb5_kt_close(kcontext, keytab); krb5_free_context(kcontext); return ret; @@ -629,6 +799,8 @@ cleanup_gss_connection(void *data) if (gss_conn->server_creds != GSS_C_NO_CREDENTIAL) gss_release_cred(&minor_status, &gss_conn->server_creds); + gss_connection = NULL; + return OK; } @@ -692,18 +864,15 @@ get_gss_creds(request_rec *r, gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; OM_uint32 major_status, minor_status, minor_status2; gss_name_t server_name = GSS_C_NO_NAME; + char buf[1024]; + + snprintf(buf, sizeof(buf), "%s/%s", conf->krb_service_name, ap_get_server_name(r)); + + input_token.value = buf; + input_token.length = strlen(buf) + 1; - if (conf->service_name) { - input_token.value = conf->service_name; - input_token.length = strlen(conf->service_name) + 1; - } - else { - input_token.value = "khttp"; - input_token.length = 6; - } major_status = gss_import_name(&minor_status, &input_token, - (conf->service_name) ? - GSS_C_NT_USER_NAME : GSS_C_NT_HOSTBASED_SERVICE, + GSS_C_NT_USER_NAME, &server_name); if (GSS_ERROR(major_status)) { log_rerror(APLOG_MARK, APLOG_ERR, 0, r, @@ -727,6 +896,33 @@ get_gss_creds(request_rec *r, } static int +cmp_gss_type(gss_buffer_t token, gss_OID oid) +{ + unsigned char *p; + size_t len; + + if (token->length == 0) + return GSS_S_DEFECTIVE_TOKEN; + + p = token->value; + if (*p++ != 0x60) + return GSS_S_DEFECTIVE_TOKEN; + len = *p++; + if (len & 0x80) { + if ((len & 0x7f) > 4) + return GSS_S_DEFECTIVE_TOKEN; + p += len & 0x7f; + } + if (*p++ != 0x06) + return GSS_S_DEFECTIVE_TOKEN; + + if (((OM_uint32) *p++) != oid->length) + return GSS_S_DEFECTIVE_TOKEN; + + return memcmp(p, oid->elements, oid->length); +} + +static int authenticate_user_gss(request_rec *r, kerb_auth_config *conf, const char *auth_line) @@ -738,6 +934,11 @@ authenticate_user_gss(request_rec *r, int ret; gss_name_t client_name = GSS_C_NO_NAME; gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL; + OM_uint32 (*accept_sec_token)(); + gss_OID_desc spnego_oid; + + spnego_oid.length = 6; + spnego_oid.elements = (void *)"\x2b\x06\x01\x05\x05\x02"; if (gss_connection == NULL) { gss_connection = ap_pcalloc(r->connection->pool, sizeof(*gss_connection)); @@ -751,8 +952,20 @@ authenticate_user_gss(request_rec *r, ap_register_cleanup(r->connection->pool, gss_connection, cleanup_gss_connection, ap_null_cleanup); } - if (conf->krb_5_keytab) - setenv("KRB5_KTNAME", conf->krb_5_keytab, 1); + if (conf->krb_5_keytab) { + char *ktname; + /* we don't use the ap_* calls here, since the string passed to putenv() + * will become part of the enviroment and shouldn't be free()ed by apache + */ + ktname = malloc(strlen("KRB5_KTNAME=") + strlen(conf->krb_5_keytab) + 1); + if (ktname == NULL) { + log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "malloc() failed: not enough memory"); + ret = HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + sprintf(ktname, "KRB5_KTNAME=%s", conf->krb_5_keytab); + putenv(ktname); + } if (gss_connection->server_creds == GSS_C_NO_CREDENTIAL) { ret = get_gss_creds(r, conf, &gss_connection->server_creds); @@ -779,17 +992,20 @@ authenticate_user_gss(request_rec *r, } input_token.length = ap_base64decode(input_token.value, auth_param); - major_status = gss_accept_sec_context(&minor_status, - &gss_connection->context, - gss_connection->server_creds, - &input_token, - GSS_C_NO_CHANNEL_BINDINGS, - &client_name, - NULL, - &output_token, - NULL, - NULL, - &delegated_cred); + accept_sec_token = (cmp_gss_type(&input_token, &spnego_oid) == 0) ? + gss_accept_sec_context_spnego : gss_accept_sec_context; + + major_status = accept_sec_token(&minor_status, + &gss_connection->context, + gss_connection->server_creds, + &input_token, + GSS_C_NO_CHANNEL_BINDINGS, + &client_name, + NULL, + &output_token, + NULL, + NULL, + &delegated_cred); if (output_token.length) { char *token = NULL; size_t len; @@ -806,7 +1022,7 @@ authenticate_user_gss(request_rec *r, ap_base64encode(token, output_token.value, output_token.length); token[len] = '\0'; ap_table_set(r->err_headers_out, "WWW-Authenticate", - ap_pstrcat(r->pool, "GSS-Negotiate ", token, NULL)); + ap_pstrcat(r->pool, "Negotiate ", token, NULL)); gss_release_buffer(&minor_status2, &output_token); } @@ -843,28 +1059,6 @@ authenticate_user_gss(request_rec *r, gss_release_buffer(&minor_status, &output_token); - -#if 0 - /* If the user comes from a realm specified by configuration don't include - its realm name in the username so that the authorization routine could - work for both Password-based and Ticket-based authentication. It's - administrators responsibility to include only such realm that have - unified principal instances, i.e. if the same principal name occures in - multiple realms, it must be always assigned to a single user. - */ - p = strchr(r->connection->user, '@'); - if (p != NULL) { - const char *realms = conf->gss_krb5_realms; - - while (realms && *realms) { - if (strcmp(p+1, ap_getword_white(r->pool, &realms)) == 0) { - *p = '\0'; - break; - } - } - } -#endif - ret = OK; end: @@ -877,32 +1071,46 @@ end: if (client_name != GSS_C_NO_NAME) gss_release_name(&minor_status, &client_name); + cleanup_gss_connection(gss_connection); + return ret; } #endif /* KRB5 */ +static int +already_succeeded(request_rec *r) +{ + if (ap_is_initial_req(r) || MK_AUTH_TYPE == NULL) + return 0; + if (strcmp(MK_AUTH_TYPE, "Negotiate") || + (strcmp(MK_AUTH_TYPE, "Basic") && strchr(MK_USER, '@'))) + return 1; + return 0; +} static void -note_kerb_auth_failure(request_rec *r, const kerb_auth_config *conf) +note_kerb_auth_failure(request_rec *r, const kerb_auth_config *conf, + int use_krb4, int use_krb5) { const char *auth_name = NULL; + int set_basic = 0; /* get the user realm specified in .htaccess */ auth_name = ap_auth_name(r); - /* XXX check AuthType */ - /* XXX should the WWW-Authenticate header be cleared first? */ #ifdef KRB5 - if (conf->krb_method_gssapi) - ap_table_add(r->err_headers_out, "WWW-Authenticate", "GSS-Negotiate "); - if (conf->krb_method_k5pass) + if (use_krb5 && conf->krb_method_gssapi) + ap_table_add(r->err_headers_out, "WWW-Authenticate", "Negotiate"); + if (use_krb5 && conf->krb_method_k5pass) { ap_table_add(r->err_headers_out, "WWW-Authenticate", ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL)); + set_basic = 1; + } #endif #ifdef KRB4 - if (conf->krb_method_k4pass) + if (use_krb4 && conf->krb_method_k4pass && !set_basic) ap_table_add(r->err_headers_out, "WWW-Authenticate", ap_pstrcat(r->pool, "Basic realm=\"", auth_name, "\"", NULL)); #endif @@ -918,6 +1126,7 @@ int kerb_authenticate_user(request_rec *r) const char *type = NULL; int use_krb5 = 0, use_krb4 = 0; int ret; + static int last_return = HTTP_UNAUTHORIZED; /* get the type specified in .htaccess */ type = ap_auth_type(r); @@ -926,7 +1135,7 @@ int kerb_authenticate_user(request_rec *r) use_krb5 = use_krb4 = 1; else if(type && strcasecmp(type, "KerberosV5") == 0) use_krb4 = 0; - else if (type && strcasecmp(type, "KerberosV4") == 0) + else if(type && strcasecmp(type, "KerberosV4") == 0) use_krb5 = 0; else return DECLINED; @@ -934,16 +1143,19 @@ int kerb_authenticate_user(request_rec *r) /* get what the user sent us in the HTTP header */ auth_line = MK_TABLE_GET(r->headers_in, "Authorization"); if (!auth_line) { - note_kerb_auth_failure(r, conf); + note_kerb_auth_failure(r, conf, use_krb4, use_krb5); return HTTP_UNAUTHORIZED; } auth_type = ap_getword_white(r->pool, &auth_line); + if (already_succeeded(r)) + return last_return; + ret = HTTP_UNAUTHORIZED; #ifdef KRB5 if (use_krb5 && conf->krb_method_gssapi && - strcasecmp(auth_type, "GSS-Negotiate") == 0) { + strcasecmp(auth_type, "Negotiate") == 0) { ret = authenticate_user_gss(r, conf, auth_line); } else if (use_krb5 && conf->krb_method_k5pass && strcasecmp(auth_type, "Basic") == 0) { @@ -958,8 +1170,9 @@ int kerb_authenticate_user(request_rec *r) #endif if (ret == HTTP_UNAUTHORIZED) - note_kerb_auth_failure(r, conf); + note_kerb_auth_failure(r, conf, use_krb4, use_krb5); + last_return = ret; return ret; } @@ -990,8 +1203,17 @@ module MODULE_VAR_EXPORT auth_kerb_module = { NULL /* [ 1] post read_request handling */ }; #else +static int +kerb_init_handler(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + ap_add_version_component(p, "mod_auth_kerb/" MODAUTHKERB_VERSION); + return OK; +} + void kerb_register_hooks(apr_pool_t *p) { + ap_hook_post_config(kerb_init_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_check_user_id(kerb_authenticate_user, NULL, NULL, APR_HOOK_MIDDLE); }