From 7cabdeb7ec84c7c0b3e9b907e19f4e240b7fc4ca Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 29 Mar 2005 08:24:03 +0000 Subject: r6113: Move GENSEC and the kerberos code out of libcli/auth, and into auth/gensec and auth/kerberos. This also pulls the kerberos configure code out of libads (which is otherwise dead), and into auth/kerberos/kerberos.m4 Andrew Bartlett (This used to be commit e074d63f3dcf4f84239a10879112ebaf1cfa6c4f) --- source4/auth/kerberos/clikrb5.c | 478 +++++++++++++++++++ source4/auth/kerberos/gssapi_parse.c | 95 ++++ source4/auth/kerberos/kerberos.c | 788 ++++++++++++++++++++++++++++++++ source4/auth/kerberos/kerberos.h | 99 ++++ source4/auth/kerberos/kerberos.m4 | 491 ++++++++++++++++++++ source4/auth/kerberos/kerberos.mk | 10 + source4/auth/kerberos/kerberos_verify.c | 486 ++++++++++++++++++++ 7 files changed, 2447 insertions(+) create mode 100644 source4/auth/kerberos/clikrb5.c create mode 100644 source4/auth/kerberos/gssapi_parse.c create mode 100644 source4/auth/kerberos/kerberos.c create mode 100644 source4/auth/kerberos/kerberos.h create mode 100644 source4/auth/kerberos/kerberos.m4 create mode 100644 source4/auth/kerberos/kerberos.mk create mode 100644 source4/auth/kerberos/kerberos_verify.c (limited to 'source4/auth/kerberos') diff --git a/source4/auth/kerberos/clikrb5.c b/source4/auth/kerberos/clikrb5.c new file mode 100644 index 0000000000..ec8f60fbb3 --- /dev/null +++ b/source4/auth/kerberos/clikrb5.c @@ -0,0 +1,478 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5 routines for active directory + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/network.h" +#include "system/kerberos.h" +#include "system/time.h" +#include "auth/kerberos/kerberos.h" + +#ifdef HAVE_KRB5 + +#ifndef HAVE_KRB5_SET_REAL_TIME +/* + * This function is not in the Heimdal mainline. + */ + krb5_error_code krb5_set_real_time(krb5_context context, int32_t seconds, int32_t microseconds) +{ + krb5_error_code ret; + int32_t sec, usec; + + ret = krb5_us_timeofday(context, &sec, &usec); + if (ret) + return ret; + + context->kdc_sec_offset = seconds - sec; + context->kdc_usec_offset = microseconds - usec; + + return 0; +} +#endif + +#if defined(HAVE_KRB5_SET_DEFAULT_IN_TKT_ETYPES) && !defined(HAVE_KRB5_SET_DEFAULT_TGS_KTYPES) + krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc) +{ + return krb5_set_default_in_tkt_etypes(ctx, enc); +} +#endif + +#if defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) +/* HEIMDAL */ + void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr) +{ + pkaddr->addr_type = KRB5_ADDRESS_INET; + pkaddr->address.length = sizeof(((struct sockaddr_in *)paddr)->sin_addr); + pkaddr->address.data = (char *)&(((struct sockaddr_in *)paddr)->sin_addr); +} +#elif defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) +/* MIT */ + void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr) +{ + pkaddr->addrtype = ADDRTYPE_INET; + pkaddr->length = sizeof(((struct sockaddr_in *)paddr)->sin_addr); + pkaddr->contents = (krb5_octet *)&(((struct sockaddr_in *)paddr)->sin_addr); +} +#else +#error UNKNOWN_ADDRTYPE +#endif + +#if defined(HAVE_KRB5_PRINCIPAL2SALT) && defined(HAVE_KRB5_USE_ENCTYPE) && defined(HAVE_KRB5_STRING_TO_KEY) && defined(HAVE_KRB5_ENCRYPT_BLOCK) + int create_kerberos_key_from_string_direct(krb5_context context, + krb5_principal host_princ, + krb5_data *password, + krb5_keyblock *key, + krb5_enctype enctype) +{ + int ret; + krb5_data salt; + krb5_encrypt_block eblock; + + ret = krb5_principal2salt(context, host_princ, &salt); + if (ret) { + DEBUG(1,("krb5_principal2salt failed (%s)\n", error_message(ret))); + return ret; + } + krb5_use_enctype(context, &eblock, enctype); + ret = krb5_string_to_key(context, &eblock, key, password, &salt); + SAFE_FREE(salt.data); + return ret; +} +#elif defined(HAVE_KRB5_GET_PW_SALT) && defined(HAVE_KRB5_STRING_TO_KEY_SALT) + int create_kerberos_key_from_string_direct(krb5_context context, + krb5_principal host_princ, + krb5_data *password, + krb5_keyblock *key, + krb5_enctype enctype) +{ + int ret; + krb5_salt salt; + + ret = krb5_get_pw_salt(context, host_princ, &salt); + if (ret) { + DEBUG(1,("krb5_get_pw_salt failed (%s)\n", error_message(ret))); + return ret; + } + return krb5_string_to_key_salt(context, enctype, password->data, + salt, key); +} +#else +#error UNKNOWN_CREATE_KEY_FUNCTIONS +#endif + + int create_kerberos_key_from_string(krb5_context context, + krb5_principal host_princ, + krb5_data *password, + krb5_keyblock *key, + krb5_enctype enctype) +{ + krb5_principal salt_princ = NULL; + int ret; + /* + * Check if we've determined that the KDC is salting keys for this + * principal/enctype in a non-obvious way. If it is, try to match + * its behavior. + */ + salt_princ = kerberos_fetch_salt_princ_for_host_princ(context, host_princ, enctype); + ret = create_kerberos_key_from_string_direct(context, salt_princ ? salt_princ : host_princ, password, key, enctype); + if (salt_princ) { + krb5_free_principal(context, salt_princ); + } + return ret; +} + +#if defined(HAVE_KRB5_GET_PERMITTED_ENCTYPES) + krb5_error_code get_kerberos_allowed_etypes(krb5_context context, + krb5_enctype **enctypes) +{ + return krb5_get_permitted_enctypes(context, enctypes); +} +#elif defined(HAVE_KRB5_GET_DEFAULT_IN_TKT_ETYPES) + krb5_error_code get_kerberos_allowed_etypes(krb5_context context, + krb5_enctype **enctypes) +{ + return krb5_get_default_in_tkt_etypes(context, enctypes); +} +#else +#error UNKNOWN_GET_ENCTYPES_FUNCTIONS +#endif + + void free_kerberos_etypes(krb5_context context, + krb5_enctype *enctypes) +{ +#if defined(HAVE_KRB5_FREE_KTYPES) + krb5_free_ktypes(context, enctypes); + return; +#else + SAFE_FREE(enctypes); + return; +#endif +} + +#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) + krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context, + krb5_auth_context auth_context, + krb5_keyblock *keyblock) +{ + return krb5_auth_con_setkey(context, auth_context, keyblock); +} +#endif + + DATA_BLOB get_auth_data_from_tkt(TALLOC_CTX *mem_ctx, + krb5_ticket *tkt) +{ + DATA_BLOB auth_data = data_blob(NULL, 0); +#if defined(HAVE_KRB5_TKT_ENC_PART2) + if (tkt && tkt->enc_part2 + && tkt->enc_part2->authorization_data + && tkt->enc_part2->authorization_data[0] + && tkt->enc_part2->authorization_data[0]->length) + auth_data = data_blob(tkt->enc_part2->authorization_data[0]->contents, + tkt->enc_part2->authorization_data[0]->length); +#else + if (tkt && tkt->ticket.authorization_data && tkt->ticket.authorization_data->len) + auth_data = data_blob(tkt->ticket.authorization_data->val->ad_data.data, + tkt->ticket.authorization_data->val->ad_data.length); +#endif + return auth_data; +} + + krb5_const_principal get_principal_from_tkt(krb5_ticket *tkt) +{ +#if defined(HAVE_KRB5_TKT_ENC_PART2) + return tkt->enc_part2->client; +#else + return tkt->client; +#endif +} + +#if !defined(HAVE_KRB5_LOCATE_KDC) + krb5_error_code krb5_locate_kdc(krb5_context ctx, const krb5_data *realm, struct sockaddr **addr_pp, int *naddrs, int get_masters) +{ + krb5_krbhst_handle hnd; + krb5_krbhst_info *hinfo; + krb5_error_code rc; + int num_kdcs, i; + struct sockaddr *sa; + struct addrinfo *ai; + + *addr_pp = NULL; + *naddrs = 0; + + rc = krb5_krbhst_init(ctx, realm->data, KRB5_KRBHST_KDC, &hnd); + if (rc) { + DEBUG(0, ("krb5_locate_kdc: krb5_krbhst_init failed (%s)\n", error_message(rc))); + return rc; + } + + for ( num_kdcs = 0; (rc = krb5_krbhst_next(ctx, hnd, &hinfo) == 0); num_kdcs++) + ; + + krb5_krbhst_reset(ctx, hnd); + + if (!num_kdcs) { + DEBUG(0, ("krb5_locate_kdc: zero kdcs found !\n")); + krb5_krbhst_free(ctx, hnd); + return -1; + } + + sa = malloc_array_p(struct sockaddr, num_kdcs); + if (!sa) { + DEBUG(0, ("krb5_locate_kdc: malloc failed\n")); + krb5_krbhst_free(ctx, hnd); + naddrs = 0; + return -1; + } + + memset(sa, '\0', sizeof(struct sockaddr) * num_kdcs ); + + for (i = 0; i < num_kdcs && (rc = krb5_krbhst_next(ctx, hnd, &hinfo) == 0); i++) { + +#if defined(HAVE_KRB5_KRBHST_GET_ADDRINFO) + rc = krb5_krbhst_get_addrinfo(ctx, hinfo, &ai); + if (rc) { + DEBUG(0,("krb5_krbhst_get_addrinfo failed: %s\n", error_message(rc))); + continue; + } +#endif + if (hinfo->ai && hinfo->ai->ai_family == AF_INET) + memcpy(&sa[i], hinfo->ai->ai_addr, sizeof(struct sockaddr)); + } + + krb5_krbhst_free(ctx, hnd); + + *naddrs = num_kdcs; + *addr_pp = sa; + return 0; +} +#endif + +#if !defined(HAVE_KRB5_FREE_UNPARSED_NAME) + void krb5_free_unparsed_name(krb5_context context, char *val) +{ + SAFE_FREE(val); +} +#endif + + void kerberos_free_data_contents(krb5_context context, krb5_data *pdata) +{ +#if defined(HAVE_KRB5_FREE_DATA_CONTENTS) + if (pdata->data) { + krb5_free_data_contents(context, pdata); + } +#else + SAFE_FREE(pdata->data); +#endif +} + + void kerberos_set_creds_enctype(krb5_creds *pcreds, int enctype) +{ +#if defined(HAVE_KRB5_KEYBLOCK_IN_CREDS) + KRB5_KEY_TYPE((&pcreds->keyblock)) = enctype; +#elif defined(HAVE_KRB5_SESSION_IN_CREDS) + KRB5_KEY_TYPE((&pcreds->session)) = enctype; +#else +#error UNKNOWN_KEYBLOCK_MEMBER_IN_KRB5_CREDS_STRUCT +#endif +} + + BOOL kerberos_compatible_enctypes(krb5_context context, + krb5_enctype enctype1, + krb5_enctype enctype2) +{ +#if defined(HAVE_KRB5_C_ENCTYPE_COMPARE) + krb5_boolean similar = 0; + + krb5_c_enctype_compare(context, enctype1, enctype2, &similar); + return similar ? True : False; +#elif defined(HAVE_KRB5_ENCTYPES_COMPATIBLE_KEYS) + return krb5_enctypes_compatible_keys(context, enctype1, enctype2) ? True : False; +#endif +} + +static BOOL ads_cleanup_expired_creds(krb5_context context, + krb5_ccache ccache, + krb5_creds *credsp) +{ + krb5_error_code retval; + TALLOC_CTX *mem_ctx = talloc_init("ticket expied time"); + if (!mem_ctx) { + return False; + } + + DEBUG(3, ("Ticket in ccache[%s] expiration %s\n", + krb5_cc_default_name(context), + http_timestring(mem_ctx, credsp->times.endtime))); + + talloc_free(mem_ctx); + + /* we will probably need new tickets if the current ones + will expire within 10 seconds. + */ + if (credsp->times.endtime >= (time(NULL) + 10)) + return False; + + /* heimdal won't remove creds from a file ccache, and + perhaps we shouldn't anyway, since internally we + use memory ccaches, and a FILE one probably means that + we're using creds obtained outside of our exectuable + */ + if (StrCaseCmp(krb5_cc_get_type(context, ccache), "FILE") == 0) { + DEBUG(5, ("ads_cleanup_expired_creds: We do not remove creds from a FILE ccache\n")); + return False; + } + + retval = krb5_cc_remove_cred(context, ccache, 0, credsp); + if (retval) { + DEBUG(1, ("ads_cleanup_expired_creds: krb5_cc_remove_cred failed, err %s\n", + error_message(retval))); + /* If we have an error in this, we want to display it, + but continue as though we deleted it */ + } + return True; +} + +/* + we can't use krb5_mk_req because w2k wants the service to be in a particular format +*/ +krb5_error_code ads_krb5_mk_req(krb5_context context, + krb5_auth_context *auth_context, + const krb5_flags ap_req_options, + const char *principal, + krb5_ccache ccache, + krb5_data *outbuf) +{ + krb5_error_code retval; + krb5_principal server; + krb5_creds * credsp; + krb5_creds creds; + krb5_data in_data; + BOOL creds_ready = False; + + TALLOC_CTX *mem_ctx = NULL; + + retval = krb5_parse_name(context, principal, &server); + if (retval) { + DEBUG(1,("ads_krb5_mk_req: Failed to parse principal %s\n", principal)); + return retval; + } + + /* obtain ticket & session key */ + ZERO_STRUCT(creds); + if ((retval = krb5_copy_principal(context, server, &creds.server))) { + DEBUG(1,("krb5_copy_principal failed (%s)\n", + error_message(retval))); + goto cleanup_princ; + } + + if ((retval = krb5_cc_get_principal(context, ccache, &creds.client))) { + /* This can commonly fail on smbd startup with no ticket in the cache. + * Report at higher level than 1. */ + DEBUG(3,("ads_krb5_mk_req: krb5_cc_get_principal failed (%s)\n", + error_message(retval))); + goto cleanup_creds; + } + + while(!creds_ready) { + if ((retval = krb5_get_credentials(context, 0, ccache, + &creds, &credsp))) { + DEBUG(1,("ads_krb5_mk_req: krb5_get_credentials failed for %s (%s)\n", + principal, error_message(retval))); + goto cleanup_creds; + } + + /* cope with ticket being in the future due to clock skew */ + if ((unsigned)credsp->times.starttime > time(NULL)) { + time_t t = time(NULL); + int time_offset =(unsigned)credsp->times.starttime-t; + DEBUG(4,("ads_krb5_mk_req: Advancing clock by %d seconds to cope with clock skew\n", time_offset)); + krb5_set_real_time(context, t + time_offset + 1, 0); + } + + if (!ads_cleanup_expired_creds(context, ccache, credsp)) + creds_ready = True; + } + + mem_ctx = talloc_init("ticket expied time"); + if (!mem_ctx) { + retval = ENOMEM; + goto cleanup_creds; + } + DEBUG(10,("Ticket (%s) in ccache (%s) is valid until: (%s - %d)\n", + principal, krb5_cc_default_name(context), + http_timestring(mem_ctx, (unsigned)credsp->times.endtime), + (unsigned)credsp->times.endtime)); + + in_data.length = 0; + retval = krb5_mk_req_extended(context, auth_context, ap_req_options, + &in_data, credsp, outbuf); + if (retval) { + DEBUG(1,("ads_krb5_mk_req: krb5_mk_req_extended failed (%s)\n", + error_message(retval))); + } + + krb5_free_creds(context, credsp); + +cleanup_creds: + krb5_free_cred_contents(context, &creds); + +cleanup_princ: + krb5_free_principal(context, server); + + return retval; +} + +#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT) + const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i ) +{ + static krb5_data kdata; + + kdata.data = discard_const(krb5_principal_get_comp_string(context, principal, i)); + kdata.length = strlen(kdata.data); + return &kdata; +} +#endif + + krb5_error_code smb_krb5_kt_free_entry(krb5_context context, krb5_keytab_entry *kt_entry) +{ +#if defined(HAVE_KRB5_KT_FREE_ENTRY) + return krb5_kt_free_entry(context, kt_entry); +#elif defined(HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS) + return krb5_free_keytab_entry_contents(context, kt_entry); +#else +#error UNKNOWN_KT_FREE_FUNCTION +#endif +} + + char *smb_get_krb5_error_message(krb5_context context, krb5_error_code code, TALLOC_CTX *mem_ctx) +{ + char *ret; + +#if defined(HAVE_KRB5_GET_ERROR_STRING) && defined(HAVE_KRB5_FREE_ERROR_STRING) + char *context_error = krb5_get_error_string(context); + ret = talloc_asprintf(mem_ctx, "%s: %s", error_message(code), context_error); + krb5_free_error_string(context, context_error); +#else + ret = talloc_strdup(mem_ctx, error_message(code)); +#endif + return ret; +} + +#endif diff --git a/source4/auth/kerberos/gssapi_parse.c b/source4/auth/kerberos/gssapi_parse.c new file mode 100644 index 0000000000..2c2c4e17e5 --- /dev/null +++ b/source4/auth/kerberos/gssapi_parse.c @@ -0,0 +1,95 @@ +/* + Unix SMB/CIFS implementation. + + simple GSSAPI wrappers + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jim McDonough 2002 + Copyright (C) Luke Howard 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "asn_1.h" +#include "system/kerberos.h" +#include "auth/gensec/gensec.h" + +/* + generate a krb5 GSS-API wrapper packet given a ticket +*/ +DATA_BLOB gensec_gssapi_gen_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *ticket, const uint8_t tok_id[2]) +{ + struct asn1_data data; + DATA_BLOB ret = data_blob(NULL,0); + + if (!ticket->data) { + return ret; + } + + ZERO_STRUCT(data); + + asn1_push_tag(&data, ASN1_APPLICATION(0)); + asn1_write_OID(&data, GENSEC_OID_KERBEROS5); + + asn1_write(&data, tok_id, 2); + asn1_write(&data, ticket->data, ticket->length); + asn1_pop_tag(&data); + + if (data.has_error) { + DEBUG(1,("Failed to build krb5 wrapper at offset %d\n", (int)data.ofs)); + asn1_free(&data); + } + + ret = data_blob_talloc(mem_ctx, data.data, data.length); + asn1_free(&data); + + return ret; +} + +/* + parse a krb5 GSS-API wrapper packet giving a ticket +*/ +BOOL gensec_gssapi_parse_krb5_wrap(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, DATA_BLOB *ticket, uint8_t tok_id[2]) +{ + BOOL ret; + struct asn1_data data; + int data_remaining; + + asn1_load(&data, *blob); + asn1_start_tag(&data, ASN1_APPLICATION(0)); + asn1_check_OID(&data, GENSEC_OID_KERBEROS5); + + data_remaining = asn1_tag_remaining(&data); + + if (data_remaining < 3) { + data.has_error = True; + } else { + asn1_read(&data, tok_id, 2); + data_remaining -= 2; + *ticket = data_blob_talloc(mem_ctx, NULL, data_remaining); + asn1_read(&data, ticket->data, ticket->length); + } + + asn1_end_tag(&data); + + ret = !data.has_error; + + asn1_free(&data); + + return ret; +} + + diff --git a/source4/auth/kerberos/kerberos.c b/source4/auth/kerberos/kerberos.c new file mode 100644 index 0000000000..98b530e7cf --- /dev/null +++ b/source4/auth/kerberos/kerberos.c @@ -0,0 +1,788 @@ +/* + Unix SMB/CIFS implementation. + kerberos utility library + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Remus Koos 2001 + Copyright (C) Nalin Dahyabhai 2004. + Copyright (C) Jeremy Allison 2004. + Copyright (C) Andrew Bartlett 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "system/time.h" +#include "auth/kerberos/kerberos.h" +#include "secrets.h" +#include "pstring.h" +#include "ads.h" + +#ifdef HAVE_KRB5 + +#define LIBADS_CCACHE_NAME "MEMORY:libads" + +/* + we use a prompter to avoid a crash bug in the kerberos libs when + dealing with empty passwords + this prompter is just a string copy ... +*/ +static krb5_error_code +kerb_prompter(krb5_context ctx, void *data, + const char *name, + const char *banner, + int num_prompts, + krb5_prompt prompts[]) +{ + if (num_prompts == 0) return 0; + + memset(prompts[0].reply->data, '\0', prompts[0].reply->length); + if (prompts[0].reply->length > 0) { + if (data) { + strncpy(prompts[0].reply->data, data, prompts[0].reply->length-1); + prompts[0].reply->length = strlen(prompts[0].reply->data); + } else { + prompts[0].reply->length = 0; + } + } + return 0; +} + +/* + simulate a kinit, putting the tgt in the given credentials cache. + Orignally by remus@snapserver.com +*/ + int kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc, + const char *principal, const char *password, + time_t *expire_time, time_t *kdc_time) +{ + krb5_error_code code = 0; + krb5_principal me; + krb5_creds my_creds; + krb5_get_init_creds_opt options; + + if ((code = krb5_parse_name(ctx, principal, &me))) { + return code; + } + + krb5_get_init_creds_opt_init(&options); + + if ((code = krb5_get_init_creds_password(ctx, &my_creds, me, password, + kerb_prompter, + NULL, 0, NULL, &options))) { + krb5_free_principal(ctx, me); + return code; + } + + if ((code = krb5_cc_initialize(ctx, cc, me))) { + krb5_free_cred_contents(ctx, &my_creds); + krb5_free_principal(ctx, me); + return code; + } + + if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) { + krb5_free_cred_contents(ctx, &my_creds); + krb5_free_principal(ctx, me); + return code; + } + + if (expire_time) { + *expire_time = (time_t) my_creds.times.endtime; + } + + if (kdc_time) { + *kdc_time = (time_t) my_creds.times.starttime; + } + + krb5_free_cred_contents(ctx, &my_creds); + krb5_free_principal(ctx, me); + + return 0; +} + +/* + simulate a kinit, putting the tgt in the given credentials cache. + If cache_name == NULL place in default cache location. + + Orignally by remus@snapserver.com +*/ +int kerberos_kinit_password(const char *principal, + const char *password, + int time_offset, + time_t *expire_time, + const char *cache_name, + time_t *kdc_time) +{ + int code; + krb5_context ctx = NULL; + krb5_ccache cc = NULL; + + if ((code = krb5_init_context(&ctx))) + return code; + + if (time_offset != 0) { + krb5_set_real_time(ctx, time(NULL) + time_offset, 0); + } + + if ((code = krb5_cc_resolve(ctx, cache_name ? + cache_name : krb5_cc_default_name(ctx), &cc))) { + krb5_free_context(ctx); + return code; + } + + code = kerberos_kinit_password_cc(ctx, cc, principal, password, expire_time, kdc_time); + + krb5_cc_close(ctx, cc); + krb5_free_context(ctx); + + return code; +} + +/* run kinit to setup our ccache */ +int ads_kinit_password(struct ads_struct *ads) +{ + char *s; + int ret; + + if (asprintf(&s, "%s@%s", ads->auth.user_name, ads->auth.realm) == -1) { + return KRB5_CC_NOMEM; + } + + if (!ads->auth.password) { + return KRB5_LIBOS_CANTREADPWD; + } + + ret = kerberos_kinit_password(s, ads->auth.password, ads->auth.time_offset, + &ads->auth.expire, NULL, NULL); + + if (ret) { + DEBUG(0,("kerberos_kinit_password %s failed: %s\n", + s, error_message(ret))); + } + free(s); + return ret; +} + +int ads_kdestroy(const char *cc_name) +{ + krb5_error_code code; + krb5_context ctx = NULL; + krb5_ccache cc = NULL; + + if ((code = krb5_init_context (&ctx))) { + DEBUG(3, ("ads_kdestroy: kdb5_init_context failed: %s\n", + error_message(code))); + return code; + } + + if (!cc_name) { + if ((code = krb5_cc_default(ctx, &cc))) { + krb5_free_context(ctx); + return code; + } + } else { + if ((code = krb5_cc_resolve(ctx, cc_name, &cc))) { + DEBUG(3, ("ads_kdestroy: krb5_cc_resolve failed: %s\n", + error_message(code))); + krb5_free_context(ctx); + return code; + } + } + + if ((code = krb5_cc_destroy (ctx, cc))) { + DEBUG(3, ("ads_kdestroy: krb5_cc_destroy failed: %s\n", + error_message(code))); + } + + krb5_free_context (ctx); + return code; +} + +/************************************************************************ + Routine to fetch the salting principal for a service. Active + Directory may use a non-obvious principal name to generate the salt + when it determines the key to use for encrypting tickets for a service, + and hopefully we detected that when we joined the domain. + ************************************************************************/ + +static char *kerberos_secrets_fetch_salting_principal(const char *service, int enctype) +{ + char *ret = NULL; + +#if 0 + asprintf(&key, "%s/%s/enctype=%d", SECRETS_SALTING_PRINCIPAL, service, enctype); + if (!key) { + return NULL; + } + ret = (char *)secrets_fetch(key, NULL); + SAFE_FREE(key); +#endif + return ret; +} + +/************************************************************************ + Routine to get the salting principal for this service. Active + Directory may use a non-obvious principal name to generate the salt + when it determines the key to use for encrypting tickets for a service, + and hopefully we detected that when we joined the domain. + Caller must free if return is not null. + ************************************************************************/ + +krb5_principal kerberos_fetch_salt_princ_for_host_princ(krb5_context context, + krb5_principal host_princ, + int enctype) +{ + char *unparsed_name = NULL, *salt_princ_s = NULL; + krb5_principal ret_princ = NULL; + + if (krb5_unparse_name(context, host_princ, &unparsed_name) != 0) { + return (krb5_principal)NULL; + } + + if ((salt_princ_s = kerberos_secrets_fetch_salting_principal(unparsed_name, enctype)) == NULL) { + krb5_free_unparsed_name(context, unparsed_name); + return (krb5_principal)NULL; + } + + if (krb5_parse_name(context, salt_princ_s, &ret_princ) != 0) { + krb5_free_unparsed_name(context, unparsed_name); + SAFE_FREE(salt_princ_s); + return (krb5_principal)NULL; + } + krb5_free_unparsed_name(context, unparsed_name); + SAFE_FREE(salt_princ_s); + return ret_princ; +} + +/************************************************************************ + Routine to set the salting principal for this service. Active + Directory may use a non-obvious principal name to generate the salt + when it determines the key to use for encrypting tickets for a service, + and hopefully we detected that when we joined the domain. + Setting principal to NULL deletes this entry. + ************************************************************************/ + + BOOL kerberos_secrets_store_salting_principal(const char *service, + int enctype, + const char *principal) +{ + char *key = NULL; + BOOL ret = False; + krb5_context context = NULL; + krb5_principal princ = NULL; + char *princ_s = NULL; + char *unparsed_name = NULL; + + krb5_init_context(&context); + if (!context) { + return False; + } + if (strchr_m(service, '@')) { + asprintf(&princ_s, "%s", service); + } else { + asprintf(&princ_s, "%s@%s", service, lp_realm()); + } + + if (krb5_parse_name(context, princ_s, &princ) != 0) { + goto out; + + } + if (krb5_unparse_name(context, princ, &unparsed_name) != 0) { + goto out; + } + + asprintf(&key, "%s/%s/enctype=%d", SECRETS_SALTING_PRINCIPAL, unparsed_name, enctype); + if (!key) { + goto out; + } + +#if 0 + if ((principal != NULL) && (strlen(principal) > 0)) { + ret = secrets_store(key, principal, strlen(principal) + 1); + } else { + ret = secrets_delete(key); + } +#endif + + out: + + SAFE_FREE(key); + SAFE_FREE(princ_s); + + if (unparsed_name) { + krb5_free_unparsed_name(context, unparsed_name); + } + if (context) { + krb5_free_context(context); + } + + return ret; +} + +/************************************************************************ + Routine to get initial credentials as a service ticket for the local machine. + Returns a buffer initialized with krb5_mk_req_extended. + ************************************************************************/ + +static krb5_error_code get_service_ticket(krb5_context ctx, + krb5_ccache ccache, + const char *service_principal, + int enctype, + krb5_data *p_outbuf) +{ + krb5_creds creds, *new_creds = NULL; + char *service_s = NULL; + char *machine_account = NULL, *password = NULL; + krb5_data in_data; + krb5_auth_context auth_context = NULL; + krb5_error_code err = 0; + + ZERO_STRUCT(creds); + + asprintf(&machine_account, "%s$@%s", lp_netbios_name(), lp_realm()); + if (machine_account == NULL) { + goto out; + } + password = secrets_fetch_machine_password(lp_workgroup()); + if (password == NULL) { + goto out; + } + if ((err = kerberos_kinit_password(machine_account, password, 0, NULL, LIBADS_CCACHE_NAME, NULL)) != 0) { + DEBUG(0,("get_service_ticket: kerberos_kinit_password %s@%s failed: %s\n", + machine_account, + lp_realm(), + error_message(err))); + goto out; + } + + /* Ok - the above call has gotten a TGT. Now we need to get a service + ticket to ourselves. */ + + /* Set up the enctype and client and server principal fields for krb5_get_credentials. */ + kerberos_set_creds_enctype(&creds, enctype); + + if ((err = krb5_cc_get_principal(ctx, ccache, &creds.client))) { + DEBUG(3, ("get_service_ticket: krb5_cc_get_principal failed: %s\n", + error_message(err))); + goto out; + } + + if (strchr_m(service_principal, '@')) { + asprintf(&service_s, "%s", service_principal); + } else { + asprintf(&service_s, "%s@%s", service_principal, lp_realm()); + } + + if ((err = krb5_parse_name(ctx, service_s, &creds.server))) { + DEBUG(0,("get_service_ticket: krb5_parse_name %s failed: %s\n", + service_s, error_message(err))); + goto out; + } + + if ((err = krb5_get_credentials(ctx, 0, ccache, &creds, &new_creds))) { + DEBUG(5,("get_service_ticket: krb5_get_credentials for %s enctype %d failed: %s\n", + service_s, enctype, error_message(err))); + goto out; + } + + memset(&in_data, '\0', sizeof(in_data)); + if ((err = krb5_mk_req_extended(ctx, &auth_context, 0, &in_data, + new_creds, p_outbuf)) != 0) { + DEBUG(0,("get_service_ticket: krb5_mk_req_extended failed: %s\n", + error_message(err))); + goto out; + } + + out: + + if (auth_context) { + krb5_auth_con_free(ctx, auth_context); + } + if (new_creds) { + krb5_free_creds(ctx, new_creds); + } + if (creds.server) { + krb5_free_principal(ctx, creds.server); + } + if (creds.client) { + krb5_free_principal(ctx, creds.client); + } + + SAFE_FREE(service_s); + SAFE_FREE(password); + SAFE_FREE(machine_account); + return err; +} + +/************************************************************************ + Check if the machine password can be used in conjunction with the salting_principal + to generate a key which will successfully decrypt the AP_REQ already + gotten as a message to the local machine. + ************************************************************************/ + +static BOOL verify_service_password(krb5_context ctx, + int enctype, + const char *salting_principal, + krb5_data *in_data) +{ + BOOL ret = False; + krb5_principal salting_kprinc = NULL; + krb5_ticket *ticket = NULL; + krb5_keyblock key; + krb5_data passdata; + char *salting_s = NULL; + char *password = NULL; + krb5_auth_context auth_context = NULL; + krb5_error_code err; + + memset(&passdata, '\0', sizeof(passdata)); + memset(&key, '\0', sizeof(key)); + + password = secrets_fetch_machine_password(lp_workgroup()); + if (password == NULL) { + goto out; + } + + if (strchr_m(salting_principal, '@')) { + asprintf(&salting_s, "%s", salting_principal); + } else { + asprintf(&salting_s, "%s@%s", salting_principal, lp_realm()); + } + + if ((err = krb5_parse_name(ctx, salting_s, &salting_kprinc))) { + DEBUG(0,("verify_service_password: krb5_parse_name %s failed: %s\n", + salting_s, error_message(err))); + goto out; + } + + passdata.length = strlen(password); + passdata.data = (char*)password; + if ((err = create_kerberos_key_from_string_direct(ctx, salting_kprinc, &passdata, &key, enctype))) { + DEBUG(0,("verify_service_password: create_kerberos_key_from_string %d failed: %s\n", + enctype, error_message(err))); + goto out; + } + + if ((err = krb5_auth_con_init(ctx, &auth_context)) != 0) { + DEBUG(0,("verify_service_password: krb5_auth_con_init failed %s\n", error_message(err))); + goto out; + } + + if ((err = krb5_auth_con_setuseruserkey(ctx, auth_context, &key)) != 0) { + DEBUG(0,("verify_service_password: krb5_auth_con_setuseruserkey failed %s\n", error_message(err))); + goto out; + } + + if (!(err = krb5_rd_req(ctx, &auth_context, in_data, NULL, NULL, NULL, &ticket))) { + DEBUG(10,("verify_service_password: decrypted message with enctype %u salt %s!\n", + (unsigned int)enctype, salting_s)); + ret = True; + } + + out: + + memset(&passdata, 0, sizeof(passdata)); + krb5_free_keyblock_contents(ctx, &key); + if (ticket != NULL) { + krb5_free_ticket(ctx, ticket); + } + if (salting_kprinc) { + krb5_free_principal(ctx, salting_kprinc); + } + SAFE_FREE(salting_s); + SAFE_FREE(password); + return ret; +} + +/************************************************************************ + * + * From the current draft of kerberos-clarifications: + * + * It is not possible to reliably generate a user's key given a pass + * phrase without contacting the KDC, since it will not be known + * whether alternate salt or parameter values are required. + * + * And because our server has a password, we have this exact problem. We + * make multiple guesses as to which principal name provides the salt which + * the KDC is using. + * + ************************************************************************/ + +static void kerberos_derive_salting_principal_for_enctype(const char *service_principal, + krb5_context ctx, + krb5_ccache ccache, + krb5_enctype enctype, + krb5_enctype *enctypes) +{ + char *salting_principals[3] = {NULL, NULL, NULL}, *second_principal = NULL; + krb5_error_code err = 0; + krb5_data outbuf; + int i, j; + + memset(&outbuf, '\0', sizeof(outbuf)); + + /* Check that the service_principal is useful. */ + if ((service_principal == NULL) || (strlen(service_principal) == 0)) { + return; + } + + /* Generate our first guess -- the principal as-given. */ + asprintf(&salting_principals[0], "%s", service_principal); + if ((salting_principals[0] == NULL) || (strlen(salting_principals[0]) == 0)) { + return; + } + + /* Generate our second guess -- the computer's principal, as Win2k3. */ + asprintf(&second_principal, "host/%s.%s", lp_netbios_name(), lp_realm()); + if (second_principal != NULL) { + strlower_m(second_principal); + asprintf(&salting_principals[1], "%s@%s", second_principal, lp_realm()); + SAFE_FREE(second_principal); + } + if ((salting_principals[1] == NULL) || (strlen(salting_principals[1]) == 0)) { + goto out; + } + + /* Generate our third guess -- the computer's principal, as Win2k. */ + asprintf(&second_principal, "HOST/%s", lp_netbios_name()); + if (second_principal != NULL) { + strlower_m(second_principal + 5); + asprintf(&salting_principals[2], "%s@%s", + second_principal, lp_realm()); + SAFE_FREE(second_principal); + } + if ((salting_principals[2] == NULL) || (strlen(salting_principals[2]) == 0)) { + goto out; + } + + /* Get a service ticket for ourselves into our memory ccache. */ + /* This will commonly fail if there is no principal by that name (and we're trying + many names). So don't print a debug 0 error. */ + + if ((err = get_service_ticket(ctx, ccache, service_principal, enctype, &outbuf)) != 0) { + DEBUG(3, ("verify_service_password: get_service_ticket failed: %s\n", + error_message(err))); + goto out; + } + + /* At this point we have a message to ourselves, salted only the KDC knows how. We + have to work out what that salting is. */ + + /* Try and find the correct salting principal. */ + for (i = 0; i < sizeof(salting_principals) / sizeof(salting_principals[i]); i++) { + if (verify_service_password(ctx, enctype, salting_principals[i], &outbuf)) { + break; + } + } + + /* If we failed to get a match, return. */ + if (i >= sizeof(salting_principals) / sizeof(salting_principals[i])) { + goto out; + } + + /* If we succeeded, store the principal for use for all enctypes which + * share the same cipher and string-to-key function. Doing this here + * allows servers which just pass a keytab to krb5_rd_req() to work + * correctly. */ + for (j = 0; enctypes[j] != 0; j++) { + if (enctype != enctypes[j]) { + /* If this enctype isn't compatible with the one which + * we used, skip it. */ + + if (!kerberos_compatible_enctypes(ctx, enctypes[j], enctype)) + continue; + } + /* If the principal which gives us the proper salt is the one + * which we would normally guess, don't bother noting anything + * in the secrets tdb. */ + if (strcmp(service_principal, salting_principals[i]) != 0) { + kerberos_secrets_store_salting_principal(service_principal, + enctypes[j], + salting_principals[i]); + } + } + + out : + + kerberos_free_data_contents(ctx, &outbuf); + SAFE_FREE(salting_principals[0]); + SAFE_FREE(salting_principals[1]); + SAFE_FREE(salting_principals[2]); + SAFE_FREE(second_principal); +} + +/************************************************************************ + Go through all the possible enctypes for this principal. + ************************************************************************/ + +static void kerberos_derive_salting_principal_direct(krb5_context context, + krb5_ccache ccache, + krb5_enctype *enctypes, + char *service_principal) +{ + int i; + + /* Try for each enctype separately, because the rules are + * different for different enctypes. */ + for (i = 0; enctypes[i] != 0; i++) { + /* Delete secrets entry first. */ + kerberos_secrets_store_salting_principal(service_principal, 0, NULL); +#ifdef ENCTYPE_ARCFOUR_HMAC + if (enctypes[i] == ENCTYPE_ARCFOUR_HMAC) { + /* Of course this'll always work, so just save + * ourselves the effort. */ + continue; + } +#endif + /* Try to figure out what's going on with this + * principal. */ + kerberos_derive_salting_principal_for_enctype(service_principal, + context, + ccache, + enctypes[i], + enctypes); + } +} + +/************************************************************************ + Wrapper function for the above. + ************************************************************************/ + +BOOL kerberos_derive_salting_principal(char *service_principal) +{ + krb5_context context = NULL; + krb5_enctype *enctypes = NULL; + krb5_ccache ccache = NULL; + krb5_error_code ret = 0; + + initialize_krb5_error_table(); + if ((ret = krb5_init_context(&context)) != 0) { + DEBUG(1,("kerberos_derive_cifs_salting_principals: krb5_init_context failed. %s\n", + error_message(ret))); + return False; + } + if ((ret = get_kerberos_allowed_etypes(context, &enctypes)) != 0) { + DEBUG(1,("kerberos_derive_cifs_salting_principals: get_kerberos_allowed_etypes failed. %s\n", + error_message(ret))); + goto out; + } + + if ((ret = krb5_cc_resolve(context, LIBADS_CCACHE_NAME, &ccache)) != 0) { + DEBUG(3, ("get_service_ticket: krb5_cc_resolve for %s failed: %s\n", + LIBADS_CCACHE_NAME, error_message(ret))); + goto out; + } + + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service_principal); + + out: + if (enctypes) { + free_kerberos_etypes(context, enctypes); + } + if (ccache) { + krb5_cc_destroy(context, ccache); + } + if (context) { + krb5_free_context(context); + } + + return ret ? False : True; +} + +/************************************************************************ + Core function to try and determine what salt is being used for any keytab + keys. + ************************************************************************/ + +BOOL kerberos_derive_cifs_salting_principals(void) +{ + fstring my_fqdn; + char *service = NULL; + krb5_context context = NULL; + krb5_enctype *enctypes = NULL; + krb5_ccache ccache = NULL; + krb5_error_code ret = 0; + BOOL retval = False; + + initialize_krb5_error_table(); + if ((ret = krb5_init_context(&context)) != 0) { + DEBUG(1,("kerberos_derive_cifs_salting_principals: krb5_init_context failed. %s\n", + error_message(ret))); + return False; + } + if ((ret = get_kerberos_allowed_etypes(context, &enctypes)) != 0) { + DEBUG(1,("kerberos_derive_cifs_salting_principals: get_kerberos_allowed_etypes failed. %s\n", + error_message(ret))); + goto out; + } + + if ((ret = krb5_cc_resolve(context, LIBADS_CCACHE_NAME, &ccache)) != 0) { + DEBUG(3, ("get_service_ticket: krb5_cc_resolve for %s failed: %s\n", + LIBADS_CCACHE_NAME, error_message(ret))); + goto out; + } + + if (asprintf(&service, "%s$", lp_netbios_name()) != -1) { + strlower_m(service); + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service); + SAFE_FREE(service); + } + if (asprintf(&service, "cifs/%s", lp_netbios_name()) != -1) { + strlower_m(service); + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service); + SAFE_FREE(service); + } + if (asprintf(&service, "host/%s", lp_netbios_name()) != -1) { + strlower_m(service); + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service); + SAFE_FREE(service); + } + if (asprintf(&service, "cifs/%s.%s", lp_netbios_name(), lp_realm()) != -1) { + strlower_m(service); + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service); + SAFE_FREE(service); + } + if (asprintf(&service, "host/%s.%s", lp_netbios_name(), lp_realm()) != -1) { + strlower_m(service); + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service); + SAFE_FREE(service); + } + name_to_fqdn(my_fqdn, lp_netbios_name()); + if (asprintf(&service, "cifs/%s", my_fqdn) != -1) { + strlower_m(service); + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service); + SAFE_FREE(service); + } + if (asprintf(&service, "host/%s", my_fqdn) != -1) { + strlower_m(service); + kerberos_derive_salting_principal_direct(context, ccache, enctypes, service); + SAFE_FREE(service); + } + + retval = True; + + out: + if (enctypes) { + free_kerberos_etypes(context, enctypes); + } + if (ccache) { + krb5_cc_destroy(context, ccache); + } + if (context) { + krb5_free_context(context); + } + return retval; +} +#endif diff --git a/source4/auth/kerberos/kerberos.h b/source4/auth/kerberos/kerberos.h new file mode 100644 index 0000000000..4daf0ea07a --- /dev/null +++ b/source4/auth/kerberos/kerberos.h @@ -0,0 +1,99 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5 routines for active directory + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#if defined(HAVE_KRB5) + +/* not really ASN.1, but RFC 1964 */ +#define TOK_ID_KRB_AP_REQ "\x01\x00" +#define TOK_ID_KRB_AP_REP "\x02\x00" +#define TOK_ID_KRB_ERROR "\x03\x00" +#define TOK_ID_GSS_GETMIC "\x01\x01" +#define TOK_ID_GSS_WRAP "\x02\x01" + +#ifdef HAVE_KRB5_KEYBLOCK_KEYVALUE +#define KRB5_KEY_TYPE(k) ((k)->keytype) +#define KRB5_KEY_LENGTH(k) ((k)->keyvalue.length) +#define KRB5_KEY_DATA(k) ((k)->keyvalue.data) +#else +#define KRB5_KEY_TYPE(k) ((k)->enctype) +#define KRB5_KEY_LENGTH(k) ((k)->length) +#define KRB5_KEY_DATA(k) ((k)->contents) +#endif /* HAVE_KRB5_KEYBLOCK_KEYVALUE */ + +#ifndef HAVE_KRB5_SET_REAL_TIME +krb5_error_code krb5_set_real_time(krb5_context context, int32_t seconds, int32_t microseconds); +#endif + +#ifndef HAVE_KRB5_SET_DEFAULT_TGS_KTYPES +krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc); +#endif + +#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) +krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock *keyblock); +#endif + +#ifndef HAVE_KRB5_FREE_UNPARSED_NAME +void krb5_free_unparsed_name(krb5_context ctx, char *val); +#endif + +#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT) +const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i ); +#endif + +/* Samba wrapper function for krb5 functionality. */ +void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr); +int create_kerberos_key_from_string(krb5_context context, krb5_principal host_princ, krb5_data *password, krb5_keyblock *key, krb5_enctype enctype); +int create_kerberos_key_from_string_direct(krb5_context context, krb5_principal host_princ, krb5_data *password, krb5_keyblock *key, krb5_enctype enctype); +krb5_const_principal get_principal_from_tkt(krb5_ticket *tkt); +krb5_error_code krb5_locate_kdc(krb5_context ctx, const krb5_data *realm, struct sockaddr **addr_pp, int *naddrs, int get_masters); +krb5_error_code get_kerberos_allowed_etypes(krb5_context context, krb5_enctype **enctypes); +void free_kerberos_etypes(krb5_context context, krb5_enctype *enctypes); +BOOL get_krb5_smb_session_key(krb5_context context, krb5_auth_context auth_context, DATA_BLOB *session_key, BOOL remote); +krb5_error_code ads_krb5_mk_req(krb5_context context, + krb5_auth_context *auth_context, + const krb5_flags ap_req_options, + const char *principal, + krb5_ccache ccache, + krb5_data *outbuf); +DATA_BLOB get_auth_data_from_tkt(TALLOC_CTX *mem_ctx, + krb5_ticket *tkt); + +NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx, + krb5_context context, + krb5_auth_context auth_context, + const char *realm, const char *service, + const DATA_BLOB *ticket, + char **principal, DATA_BLOB *auth_data, + DATA_BLOB *ap_rep, + krb5_keyblock *keyblock); +int kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc, + const char *principal, const char *password, + time_t *expire_time, time_t *kdc_time); +krb5_principal kerberos_fetch_salt_princ_for_host_princ(krb5_context context, + krb5_principal host_princ, + int enctype); +void kerberos_set_creds_enctype(krb5_creds *pcreds, int enctype); +BOOL kerberos_compatible_enctypes(krb5_context context, krb5_enctype enctype1, krb5_enctype enctype2); +void kerberos_free_data_contents(krb5_context context, krb5_data *pdata); +krb5_error_code smb_krb5_kt_free_entry(krb5_context context, krb5_keytab_entry *kt_entry); +char *smb_get_krb5_error_message(krb5_context context, krb5_error_code code, TALLOC_CTX *mem_ctx); +#endif /* HAVE_KRB5 */ + diff --git a/source4/auth/kerberos/kerberos.m4 b/source4/auth/kerberos/kerberos.m4 new file mode 100644 index 0000000000..f18386a91a --- /dev/null +++ b/source4/auth/kerberos/kerberos.m4 @@ -0,0 +1,491 @@ +################################################# +# KRB5 support +KRB5_CFLAGS="" +KRB5_CPPFLAGS="" +KRB5_LDFLAGS="" +KRB5_LIBS="" +with_krb5_support=auto +krb5_withval=auto +AC_MSG_CHECKING([for KRB5 support]) + +# Do no harm to the values of CFLAGS and LIBS while testing for +# Kerberos support. +AC_ARG_WITH(krb5, +[ --with-krb5=base-dir Locate Kerberos 5 support (default=auto)], + [ case "$withval" in + no) + with_krb5_support=no + AC_MSG_RESULT(no) + krb5_withval=no + ;; + yes) + with_krb5_support=yes + AC_MSG_RESULT(yes) + krb5_withval=yes + ;; + auto) + with_krb5_support=auto + AC_MSG_RESULT(auto) + krb5_withval=auto + ;; + *) + with_krb5_support=yes + AC_MSG_RESULT(yes) + krb5_withval=$withval + KRB5CONFIG="$krb5_withval/bin/krb5-config" + ;; + esac ], + AC_MSG_RESULT($with_krb5_support) +) + +if test x$with_krb5_support != x"no"; then + FOUND_KRB5=no + FOUND_KRB5_VIA_CONFIG=no + + ################################################# + # check for krb5-config from recent MIT and Heimdal kerberos 5 + AC_MSG_CHECKING(for working specified location for krb5-config) + if test x$KRB5CONFIG != "x"; then + if test -x "$KRB5CONFIG"; then + ac_save_CFLAGS=$CFLAGS + CFLAGS="";export CFLAGS + ac_save_LDFLAGS=$LDFLAGS + LDFLAGS="";export LDFLAGS + KRB5_LIBS="`$KRB5CONFIG --libs gssapi`" + KRB5_CFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`" + KRB5_CPPFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`" + CFLAGS=$ac_save_CFLAGS;export CFLAGS + LDFLAGS=$ac_save_LDFLAGS;export LDFLAGS + FOUND_KRB5=yes + FOUND_KRB5_VIA_CONFIG=yes + AC_MSG_RESULT(yes. Found $KRB5CONFIG) + else + AC_MSG_RESULT(no. Fallback to specified directory) + fi + else + AC_MSG_RESULT(no. Fallback to finding krb5-config in path) + ################################################# + # check for krb5-config from recent MIT and Heimdal kerberos 5 + AC_PATH_PROG(KRB5CONFIG, krb5-config) + AC_MSG_CHECKING(for working krb5-config in path) + if test -x "$KRB5CONFIG"; then + ac_save_CFLAGS=$CFLAGS + CFLAGS="";export CFLAGS + ac_save_LDFLAGS=$LDFLAGS + LDFLAGS="";export LDFLAGS + KRB5_LIBS="`$KRB5CONFIG --libs gssapi`" + KRB5_CFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`" + KRB5_CPPFLAGS="`$KRB5CONFIG --cflags | sed s/@INCLUDE_des@//`" + CFLAGS=$ac_save_CFLAGS;export CFLAGS + LDFLAGS=$ac_save_LDFLAGS;export LDFLAGS + FOUND_KRB5=yes + FOUND_KRB5_VIA_CONFIG=yes + AC_MSG_RESULT(yes. Found $KRB5CONFIG) + else + AC_MSG_RESULT(no. Fallback to previous krb5 detection strategy) + fi + fi + + if test x$FOUND_KRB5 != x"yes"; then + ################################################# + # check for location of Kerberos 5 install + AC_MSG_CHECKING(for kerberos 5 install path) + case "$krb5_withval" in + no) + AC_MSG_RESULT(no krb5-path given) + ;; + yes) + AC_MSG_RESULT(/usr) + FOUND_KRB5=yes + ;; + *) + AC_MSG_RESULT($krb5_withval) + KRB5_CFLAGS="-I$krb5_withval/include" + KRB5_CPPFLAGS="-I$krb5_withval/include" + KRB5_LDFLAGS="-L$krb5_withval/lib" + FOUND_KRB5=yes + ;; + esac + fi + + if test x$FOUND_KRB5 != x"yes"; then + ################################################# + # see if this box has the SuSE location for the heimdal krb implementation + AC_MSG_CHECKING(for /usr/include/heimdal) + if test -d /usr/include/heimdal; then + if test -f /usr/lib/heimdal/lib/libkrb5.a; then + KRB5_CFLAGS="-I/usr/include/heimdal" + KRB5_CPPFLAGS="-I/usr/include/heimdal" + KRB5_LDFLAGS="-L/usr/lib/heimdal/lib" + AC_MSG_RESULT(yes) + else + KRB5_CFLAGS="-I/usr/include/heimdal" + KRB5_CPPFLAGS="-I/usr/include/heimdal" + AC_MSG_RESULT(yes) + fi + else + AC_MSG_RESULT(no) + fi + fi + + if test x$FOUND_KRB5 != x"yes"; then + ################################################# + # see if this box has the RedHat location for kerberos + AC_MSG_CHECKING(for /usr/kerberos) + if test -d /usr/kerberos -a -f /usr/kerberos/lib/libkrb5.a; then + KRB5_LDFLAGS="-L/usr/kerberos/lib" + KRB5_CFLAGS="-I/usr/kerberos/include" + KRB5_CPPFLAGS="-I/usr/kerberos/include" + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi + fi + + ac_save_CFLAGS=$CFLAGS + ac_save_CPPFLAGS=$CPPFLAGS + ac_save_LDFLAGS=$LDFLAGS + + #MIT needs this, to let us see 'internal' parts of the headers we use + KRB5_CFLAGS="${KRB5_CFLAGS} -DKRB5_PRIVATE -DKRB5_DEPRECATED" + + #Heimdal needs this + #TODO: we need to parse KRB5_LIBS for -L path + # and set -Wl,-rpath -Wl,path + + CFLAGS="$CFLAGS $KRB5_CFLAGS" + CPPFLAGS="$CPPFLAGS $KRB5_CPPFLAGS" + LDFLAGS="$LDFLAGS $KRB5_LDFLAGS" + + KRB5_LIBS="$KRB5_LDFLAGS $KRB5_LIBS" + + # now check for krb5.h. Some systems have the libraries without the headers! + # note that this check is done here to allow for different kerberos + # include paths + AC_CHECK_HEADERS(krb5.h) + + if test x"$ac_cv_header_krb5_h" = x"no"; then + # Give a warning if KRB5 support was not explicitly requested, + # i.e with_krb5_support = auto, otherwise die with an error. + if test x"$with_krb5_support" = x"yes"; then + AC_MSG_ERROR([KRB5 cannot be supported without krb5.h]) + else + AC_MSG_WARN([KRB5 cannot be supported without krb5.h]) + fi + # Turn off AD support and restore CFLAGS and LIBS variables + with_krb5_support="no" + fi + + CFLAGS=$ac_save_CFLAGS + CPPFLAGS=$ac_save_CPPFLAGS + LDFLAGS=$ac_save_LDFLAGS +fi + +# Now we have determined whether we really want KRB5 support + +if test x"$with_krb5_support" != x"no"; then + ac_save_CFLAGS=$CFLAGS + ac_save_CPPFLAGS=$CPPFLAGS + ac_save_LDFLAGS=$LDFLAGS + ac_save_LIBS=$LIBS + + CFLAGS="$CFLAGS $KRB5_CFLAGS" + CPPFLAGS="$CPPFLAGS $KRB5_CPPFLAGS" + LDFLAGS="$LDFLAGS $KRB5_LDFLAGS" + + # now check for gssapi headers. This is also done here to allow for + # different kerberos include paths + AC_CHECK_HEADERS(gssapi.h gssapi/gssapi_generic.h gssapi/gssapi.h com_err.h) + + ################################################################## + # we might need the k5crypto and com_err libraries on some systems + AC_CHECK_LIB_EXT(com_err, KRB5_LIBS, _et_list) + AC_CHECK_LIB_EXT(k5crypto, KRB5_LIBS, krb5_encrypt_data) + + # Heimdal checks. + # But only if we didn't have a krb5-config to tell us this already + if test x"$FOUND_KRB5_VIA_CONFIG" != x"yes"; then + AC_CHECK_LIB_EXT(crypto, KRB5_LIBS, des_set_key) + AC_CHECK_LIB_EXT(asn1, KRB5_LIBS, copy_Authenticator) + AC_CHECK_LIB_EXT(roken, KRB5_LIBS, roken_getaddrinfo_hostspec) + fi + + # Heimdal checks. On static Heimdal gssapi must be linked before krb5. + AC_CHECK_LIB_EXT(gssapi, KRB5_LIBS, gss_display_status,[],[], + AC_DEFINE(HAVE_GSSAPI,1,[Whether GSSAPI is available])) + + ######################################################## + # now see if we can find the krb5 libs in standard paths + # or as specified above + AC_CHECK_LIB_EXT(krb5, KRB5_LIBS, krb5_mk_req_extended) + AC_CHECK_LIB_EXT(krb5, KRB5_LIBS, krb5_kt_compare) + + ######################################################## + # now see if we can find the gssapi libs in standard paths + if test x"$ac_cv_lib_ext_gssapi_gss_display_status" != x"yes"; then + AC_CHECK_LIB_EXT(gssapi_krb5, KRB5_LIBS,gss_display_status,[],[], + AC_DEFINE(HAVE_GSSAPI,1,[Whether GSSAPI is available])) + fi + + AC_CHECK_FUNC_EXT(krb5_set_real_time, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_set_default_in_tkt_etypes, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_set_default_tgs_ktypes, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_principal2salt, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_use_enctype, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_string_to_key, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_get_pw_salt, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_string_to_key_salt, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_auth_con_setkey, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_auth_con_setuseruserkey, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_locate_kdc, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_get_permitted_enctypes, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_get_default_in_tkt_etypes, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_free_ktypes, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_free_data_contents, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_principal_get_comp_string, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_free_unparsed_name, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_free_keytab_entry_contents, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_kt_free_entry, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_krbhst_get_addrinfo, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_verify_checksum, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_c_verify_checksum, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_ticket_get_authorization_data_type, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_krbhst_get_addrinfo, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_c_enctype_compare, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_enctypes_compatible_keys, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_get_error_string, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_free_error_string, $KRB5_LIBS) + + LIBS="$LIBS $KRB5_LIBS" + + AC_CACHE_CHECK([for krb5_encrypt_block type], + samba_cv_HAVE_KRB5_ENCRYPT_BLOCK,[ + AC_TRY_COMPILE([#include ], + [krb5_encrypt_block block;], + samba_cv_HAVE_KRB5_ENCRYPT_BLOCK=yes, + samba_cv_HAVE_KRB5_ENCRYPT_BLOCK=no)]) + + if test x"$samba_cv_HAVE_KRB5_ENCRYPT_BLOCK" = x"yes"; then + AC_DEFINE(HAVE_KRB5_ENCRYPT_BLOCK,1, + [Whether the type krb5_encrypt_block exists]) + fi + + AC_CACHE_CHECK([for addrtype in krb5_address], + samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS,[ + AC_TRY_COMPILE([#include ], + [krb5_address kaddr; kaddr.addrtype = ADDRTYPE_INET;], + samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS=yes, + samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS=no)]) + if test x"$samba_cv_HAVE_ADDRTYPE_IN_KRB5_ADDRESS" = x"yes"; then + AC_DEFINE(HAVE_ADDRTYPE_IN_KRB5_ADDRESS,1, + [Whether the krb5_address struct has a addrtype property]) + fi + + AC_CACHE_CHECK([for addr_type in krb5_address], + samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS,[ + AC_TRY_COMPILE([#include ], + [krb5_address kaddr; kaddr.addr_type = KRB5_ADDRESS_INET;], + samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS=yes, + samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS=no)]) + if test x"$samba_cv_HAVE_ADDR_TYPE_IN_KRB5_ADDRESS" = x"yes"; then + AC_DEFINE(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS,1, + [Whether the krb5_address struct has a addr_type property]) + fi + + AC_CACHE_CHECK([for enc_part2 in krb5_ticket], + samba_cv_HAVE_KRB5_TKT_ENC_PART2,[ + AC_TRY_COMPILE([#include ], + [krb5_ticket tkt; tkt.enc_part2->authorization_data[0]->contents = NULL;], + samba_cv_HAVE_KRB5_TKT_ENC_PART2=yes, + samba_cv_HAVE_KRB5_TKT_ENC_PART2=no)]) + if test x"$samba_cv_HAVE_KRB5_TKT_ENC_PART2" = x"yes"; then + AC_DEFINE(HAVE_KRB5_TKT_ENC_PART2,1, + [Whether the krb5_ticket struct has a enc_part2 property]) + fi + + AC_CACHE_CHECK([for keyblock in krb5_creds], + samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS,[ + AC_TRY_COMPILE([#include ], + [krb5_creds creds; krb5_keyblock kb; creds.keyblock = kb;], + samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS=yes, + samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS=no)]) + + if test x"$samba_cv_HAVE_KRB5_KEYBLOCK_IN_CREDS" = x"yes"; then + AC_DEFINE(HAVE_KRB5_KEYBLOCK_IN_CREDS,1, + [Whether the krb5_creds struct has a keyblock property]) + fi + + AC_CACHE_CHECK([for session in krb5_creds], + samba_cv_HAVE_KRB5_SESSION_IN_CREDS,[ + AC_TRY_COMPILE([#include ], + [krb5_creds creds; krb5_keyblock kb; creds.session = kb;], + samba_cv_HAVE_KRB5_SESSION_IN_CREDS=yes, + samba_cv_HAVE_KRB5_SESSION_IN_CREDS=no)]) + + if test x"$samba_cv_HAVE_KRB5_SESSION_IN_CREDS" = x"yes"; then + AC_DEFINE(HAVE_KRB5_SESSION_IN_CREDS,1, + [Whether the krb5_creds struct has a session property]) + fi + + AC_CACHE_CHECK([for keyvalue in krb5_keyblock], + samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE,[ + AC_TRY_COMPILE([#include ], + [krb5_keyblock key; key.keyvalue.data = NULL;], + samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE=yes, + samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE=no)]) + if test x"$samba_cv_HAVE_KRB5_KEYBLOCK_KEYVALUE" = x"yes"; then + AC_DEFINE(HAVE_KRB5_KEYBLOCK_KEYVALUE,1, + [Whether the krb5_keyblock struct has a keyvalue property]) + fi + + AC_CACHE_CHECK([for ENCTYPE_ARCFOUR_HMAC_MD5], + samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5,[ + AC_TRY_COMPILE([#include ], + [krb5_enctype enctype; enctype = ENCTYPE_ARCFOUR_HMAC_MD5;], + samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5=yes, + samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5=no)]) + AC_CACHE_CHECK([for KEYTYPE_ARCFOUR_56], + samba_cv_HAVE_KEYTYPE_ARCFOUR_56,[ + AC_TRY_COMPILE([#include ], + [krb5_keytype keytype; keytype = KEYTYPE_ARCFOUR_56;], + samba_cv_HAVE_KEYTYPE_ARCFOUR_56=yes, + samba_cv_HAVE_KEYTYPE_ARCFOUR_56=no)]) + # Heimdals with KEYTYPE_ARCFOUR but not KEYTYPE_ARCFOUR_56 are broken + # w.r.t. arcfour and windows, so we must not enable it here + if test x"$samba_cv_HAVE_ENCTYPE_ARCFOUR_HMAC_MD5" = x"yes" -a\ + x"$samba_cv_HAVE_KEYTYPE_ARCFOUR_56" = x"yes"; then + AC_DEFINE(HAVE_ENCTYPE_ARCFOUR_HMAC_MD5,1, + [Whether the ENCTYPE_ARCFOUR_HMAC_MD5 key type is available]) + fi + + AC_CACHE_CHECK([for AP_OPTS_USE_SUBKEY], + samba_cv_HAVE_AP_OPTS_USE_SUBKEY,[ + AC_TRY_COMPILE([#include ], + [krb5_flags ap_options; ap_options = AP_OPTS_USE_SUBKEY;], + samba_cv_HAVE_AP_OPTS_USE_SUBKEY=yes, + samba_cv_HAVE_AP_OPTS_USE_SUBKEY=no)]) + if test x"$samba_cv_HAVE_AP_OPTS_USE_SUBKEY" = x"yes"; then + AC_DEFINE(HAVE_AP_OPTS_USE_SUBKEY,1, + [Whether the AP_OPTS_USE_SUBKEY ap option is available]) + fi + + AC_CACHE_CHECK([for KV5M_KEYTAB], + samba_cv_HAVE_KV5M_KEYTAB,[ + AC_TRY_COMPILE([#include ], + [krb5_keytab_entry entry; entry.magic = KV5M_KEYTAB;], + samba_cv_HAVE_KV5M_KEYTAB=yes, + samba_cv_HAVE_KV5M_KEYTAB=no)]) + if test x"$samba_cv_HAVE_KV5M_KEYTAB" = x"yes"; then + AC_DEFINE(HAVE_KV5M_KEYTAB,1, + [Whether the KV5M_KEYTAB option is available]) + fi + + AC_CACHE_CHECK([for the krb5_princ_component macro], + samba_cv_HAVE_KRB5_PRINC_COMPONENT,[ + AC_TRY_LINK([#include ], + [const krb5_data *pkdata; krb5_context context; krb5_principal principal; + pkdata = krb5_princ_component(context, principal, 0);], + samba_cv_HAVE_KRB5_PRINC_COMPONENT=yes, + samba_cv_HAVE_KRB5_PRINC_COMPONENT=no)]) + if test x"$samba_cv_HAVE_KRB5_PRINC_COMPONENT" = x"yes"; then + AC_DEFINE(HAVE_KRB5_PRINC_COMPONENT,1, + [Whether krb5_princ_component is available]) + fi + + AC_CACHE_CHECK([for key in krb5_keytab_entry], + samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY,[ + AC_TRY_COMPILE([#include ], + [krb5_keytab_entry entry; krb5_keyblock e; entry.key = e;], + samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY=yes, + samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY=no)]) + if test x"$samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEY" = x"yes"; then + AC_DEFINE(HAVE_KRB5_KEYTAB_ENTRY_KEY,1, + [Whether krb5_keytab_entry has key member]) + fi + + AC_CACHE_CHECK([for keyblock in krb5_keytab_entry], + samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK,[ + AC_TRY_COMPILE([#include ], + [krb5_keytab_entry entry; entry.keyblock.keytype = 0;], + samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK=yes, + samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK=no)]) + if test x"$samba_cv_HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK" = x"yes"; then + AC_DEFINE(HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK,1, + [Whether krb5_keytab_entry has keyblock member]) + fi + + AC_CACHE_CHECK([for WRFILE: keytab support], + samba_cv_HAVE_WRFILE_KEYTAB,[ + AC_TRY_RUN([ + #include + main() + { + krb5_context context; + krb5_keytab keytab; + krb5_init_context(&context); + return krb5_kt_resolve(context, "WRFILE:api", &keytab); + }], + samba_cv_HAVE_WRFILE_KEYTAB=yes, + samba_cv_HAVE_WRFILE_KEYTAB=no)]) + if test x"$samba_cv_HAVE_WRFILE_KEYTAB" = x"yes"; then + AC_DEFINE(HAVE_WRFILE_KEYTAB,1, + [Whether the WRFILE:-keytab is supported]) + fi + + AC_CACHE_CHECK([for krb5_princ_realm returns krb5_realm or krb5_data], + samba_cv_KRB5_PRINC_REALM_RETURNS_REALM,[ + AC_TRY_COMPILE([#include ], + [krb5_context context;krb5_principal principal;krb5_realm realm; + realm = *krb5_princ_realm(context, principal);], + samba_cv_KRB5_PRINC_REALM_RETURNS_REALM=yes, + samba_cv_KRB5_PRINC_REALM_RETURNS_REALM=no)]) + if test x"$samba_cv_KRB5_PRINC_REALM_RETURNS_REALM" = x"yes"; then + AC_DEFINE(KRB5_PRINC_REALM_RETURNS_REALM,1, + [Whether krb5_princ_realm returns krb5_realm or krb5_data]) + fi + + # TODO: check all gssapi headers for this + AC_CACHE_CHECK([for GSS_C_DCE_STYLE in gssapi.h], + samba_cv_GSS_C_DCE_STYLE,[ + AC_TRY_COMPILE([#include ], + [int flags = GSS_C_DCE_STYLE;], + samba_cv_GSS_C_DCE_STYLE=yes, + samba_cv_GSS_C_DCE_STYLE=no)]) + + if test x"$ac_cv_lib_ext_krb5_krb5_mk_req_extended" = x"yes"; then + AC_DEFINE(HAVE_KRB5,1,[Whether to have KRB5 support]) + AC_MSG_CHECKING(whether KRB5 support is used) + SMB_EXT_LIB_ENABLE(KRB5,YES) + AC_MSG_RESULT(yes) + echo "KRB5_CFLAGS: ${KRB5_CFLAGS}" + echo "KRB5_CPPFLAGS: ${KRB5_CPPFLAGS}" + echo "KRB5_LDFLAGS: ${KRB5_LDFLAGS}" + echo "KRB5_LIBS: ${KRB5_LIBS}" + else + if test x"$with_krb5_support" = x"yes"; then + AC_MSG_ERROR(a working krb5 library is needed for KRB5 support) + else + AC_MSG_WARN(a working krb5 library is needed for KRB5 support) + fi + KRB5_CFLAGS="" + KRB5_CPPFLAGS="" + KRB5_LDFLAGS="" + KRB5_LIBS="" + with_krb5_support=no + fi + + CFLAGS=$ac_save_CFLAGS + CPPFLAGS=$ac_save_CPPFLAGS + LDFLAGS=$ac_save_LDFLAGS + LIBS="$ac_save_LIBS" + + # as a nasty hack add the krb5 stuff to the global vars, + # at some point this should not be needed anymore when the build system + # can handle that alone + CFLAGS="$CFLAGS $KRB5_CFLAGS" + CPPFLAGS="$CPPFLAGS $KRB5_CPPFLAGS" + LDFLAGS="$LDFLAGS $KRB5_LDFLAGS" +fi + +SMB_EXT_LIB(KRB5,[${KRB5_LIBS}],[${KRB5_CFLAGS}],[${KRB5_CPPFLAGS}],[${KRB5_LDFLAGS}]) + diff --git a/source4/auth/kerberos/kerberos.mk b/source4/auth/kerberos/kerberos.mk new file mode 100644 index 0000000000..a43e6bb517 --- /dev/null +++ b/source4/auth/kerberos/kerberos.mk @@ -0,0 +1,10 @@ +################################# +# Start SUBSYSTEM KERBEROS +[SUBSYSTEM::KERBEROS] +INIT_OBJ_FILES = auth/kerberos/kerberos.o +ADD_OBJ_FILES = \ + auth/kerberos/clikrb5.o \ + auth/kerberos/kerberos_verify.o \ + auth/kerberos/gssapi_parse.o +# End SUBSYSTEM KERBEROS +################################# diff --git a/source4/auth/kerberos/kerberos_verify.c b/source4/auth/kerberos/kerberos_verify.c new file mode 100644 index 0000000000..3188e603cd --- /dev/null +++ b/source4/auth/kerberos/kerberos_verify.c @@ -0,0 +1,486 @@ +/* + Unix SMB/CIFS implementation. + kerberos utility library + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Remus Koos 2001 + Copyright (C) Luke Howard 2003 + Copyright (C) Guenther Deschner 2003 + Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003 + Copyright (C) Andrew Bartlett 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "asn_1.h" +#include "lib/ldb/include/ldb.h" +#include "secrets.h" +#include "pstring.h" + +#ifdef HAVE_KRB5 + +#if !defined(HAVE_KRB5_PRINC_COMPONENT) +const krb5_data *krb5_princ_component(krb5_context, krb5_principal, int ); +#endif +static DATA_BLOB unwrap_pac(TALLOC_CTX *mem_ctx, DATA_BLOB *auth_data) +{ + DATA_BLOB out; + DATA_BLOB pac_contents = data_blob(NULL, 0); + struct asn1_data data; + int data_type; + if (!auth_data->length) { + return data_blob(NULL, 0); + } + + asn1_load(&data, *auth_data); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_CONTEXT(0)); + asn1_read_Integer(&data, &data_type); + asn1_end_tag(&data); + asn1_start_tag(&data, ASN1_CONTEXT(1)); + asn1_read_OctetString(&data, &pac_contents); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_free(&data); + + out = data_blob_talloc(mem_ctx, pac_contents.data, pac_contents.length); + + data_blob_free(&pac_contents); + + return out; +} + +/********************************************************************************** + Try to verify a ticket using the system keytab... the system keytab has kvno -1 entries, so + it's more like what microsoft does... see comment in utils/net_ads.c in the + ads_keytab_add_entry function for details. +***********************************************************************************/ + +static krb5_error_code ads_keytab_verify_ticket(TALLOC_CTX *mem_ctx, krb5_context context, + krb5_auth_context auth_context, + const char *service, + const DATA_BLOB *ticket, krb5_data *p_packet, + krb5_ticket **pp_tkt, + krb5_keyblock *keyblock) +{ + krb5_error_code ret = 0; + krb5_keytab keytab = NULL; + krb5_kt_cursor kt_cursor; + krb5_keytab_entry kt_entry; + char *valid_princ_formats[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + char *entry_princ_s = NULL; + const char *my_name, *my_fqdn; + int i; + int number_matched_principals = 0; + const char *last_error_message; + + /* Generate the list of principal names which we expect + * clients might want to use for authenticating to the file + * service. We allow name$,{host,cifs}/{name,fqdn,name.REALM}. */ + + my_name = lp_netbios_name(); + + my_fqdn = name_to_fqdn(mem_ctx, my_name); + + asprintf(&valid_princ_formats[0], "%s$@%s", my_name, lp_realm()); + asprintf(&valid_princ_formats[1], "host/%s@%s", my_name, lp_realm()); + asprintf(&valid_princ_formats[2], "host/%s@%s", my_fqdn, lp_realm()); + asprintf(&valid_princ_formats[3], "host/%s.%s@%s", my_name, lp_realm(), lp_realm()); + asprintf(&valid_princ_formats[4], "cifs/%s@%s", my_name, lp_realm()); + asprintf(&valid_princ_formats[5], "cifs/%s@%s", my_fqdn, lp_realm()); + asprintf(&valid_princ_formats[6], "cifs/%s.%s@%s", my_name, lp_realm(), lp_realm()); + + ZERO_STRUCT(kt_entry); + ZERO_STRUCT(kt_cursor); + + ret = krb5_kt_default(context, &keytab); + if (ret) { + DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_default failed (%s)\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + goto out; + } + + /* Iterate through the keytab. For each key, if the principal + * name case-insensitively matches one of the allowed formats, + * try verifying the ticket using that principal. */ + + ret = krb5_kt_start_seq_get(context, keytab, &kt_cursor); + if (ret) { + last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx); + DEBUG(1, ("ads_keytab_verify_ticket: krb5_kt_start_seq_get failed (%s)\n", + last_error_message)); + goto out; + } + + ret = krb5_kt_start_seq_get(context, keytab, &kt_cursor); + if (ret != KRB5_KT_END && ret != ENOENT ) { + ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; /* Pick an error... */ + while (ret && (krb5_kt_next_entry(context, keytab, &kt_entry, &kt_cursor) == 0)) { + krb5_error_code upn_ret; + upn_ret = krb5_unparse_name(context, kt_entry.principal, &entry_princ_s); + if (upn_ret) { + last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx); + DEBUG(1, ("ads_keytab_verify_ticket: krb5_unparse_name failed (%s)\n", + last_error_message)); + ret = upn_ret; + break; + } + for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) { + if (!strequal(entry_princ_s, valid_princ_formats[i])) { + continue; + } + + number_matched_principals++; + p_packet->length = ticket->length; + p_packet->data = (krb5_pointer)ticket->data; + *pp_tkt = NULL; + ret = krb5_rd_req(context, &auth_context, p_packet, kt_entry.principal, keytab, NULL, pp_tkt); + if (ret) { + last_error_message = smb_get_krb5_error_message(context, ret, mem_ctx); + DEBUG(10, ("ads_keytab_verify_ticket: krb5_rd_req(%s) failed: %s\n", + entry_princ_s, last_error_message)); + } else { + DEBUG(3,("ads_keytab_verify_ticket: krb5_rd_req succeeded for principal %s\n", + entry_princ_s)); + break; + } + } + + /* Free the name we parsed. */ + krb5_free_unparsed_name(context, entry_princ_s); + entry_princ_s = NULL; + + /* Free the entry we just read. */ + smb_krb5_kt_free_entry(context, &kt_entry); + ZERO_STRUCT(kt_entry); + } + krb5_kt_end_seq_get(context, keytab, &kt_cursor); + } + + ZERO_STRUCT(kt_cursor); + + out: + + if (ret) { + if (!number_matched_principals) { + DEBUG(3, ("ads_keytab_verify_ticket: no keytab principals matched expected file service name.\n")); + } else { + DEBUG(3, ("ads_keytab_verify_ticket: krb5_rd_req failed for all %d matched keytab principals\n", + number_matched_principals)); + } + DEBUG(3, ("ads_keytab_verify_ticket: last error: %s\n", last_error_message)); + } + + if (entry_princ_s) { + krb5_free_unparsed_name(context, entry_princ_s); + } + + { + krb5_keytab_entry zero_kt_entry; + ZERO_STRUCT(zero_kt_entry); + if (memcmp(&zero_kt_entry, &kt_entry, sizeof(krb5_keytab_entry))) { + smb_krb5_kt_free_entry(context, &kt_entry); + } + } + + { + krb5_kt_cursor zero_csr; + ZERO_STRUCT(zero_csr); + if ((memcmp(&kt_cursor, &zero_csr, sizeof(krb5_kt_cursor)) != 0) && keytab) { + krb5_kt_end_seq_get(context, keytab, &kt_cursor); + } + } + + if (keytab) { + krb5_kt_close(context, keytab); + } + + return ret; +} + +/********************************************************************************** + Try to verify a ticket using the secrets.tdb. +***********************************************************************************/ + +static krb5_error_code ads_secrets_verify_ticket(TALLOC_CTX *mem_ctx, krb5_context context, + krb5_auth_context auth_context, + krb5_principal host_princ, + const DATA_BLOB *ticket, krb5_data *p_packet, + krb5_ticket **pp_tkt, + krb5_keyblock *keyblock) +{ + krb5_error_code ret = 0; + krb5_error_code our_ret; + krb5_data password; + krb5_enctype *enctypes = NULL; + int i; + const struct ldb_val *password_v; + struct ldb_context *ldb; + int ldb_ret; + struct ldb_message **msgs; + const char *base_dn = SECRETS_PRIMARY_DOMAIN_DN; + const char *attrs[] = { + "secret", + NULL + }; + + ZERO_STRUCTP(keyblock); + + /* Local secrets are stored in secrets.ldb */ + ldb = secrets_db_connect(mem_ctx); + if (!ldb) { + return ENOENT; + } + + /* search for the secret record */ + ldb_ret = gendb_search(ldb, + mem_ctx, base_dn, &msgs, attrs, + SECRETS_PRIMARY_REALM_FILTER, + lp_realm()); + if (ldb_ret == 0) { + DEBUG(1, ("Could not find domain join record for %s\n", + lp_realm())); + return ENOENT; + } else if (ldb_ret != 1) { + DEBUG(1, ("Found %d records matching cn=%s under DN %s\n", ldb_ret, + lp_realm(), base_dn)); + return ENOENT; + } + + password_v = ldb_msg_find_ldb_val(msgs[0], "secret"); + + password.data = password_v->data; + password.length = password_v->length; + + /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */ + + if ((ret = get_kerberos_allowed_etypes(context, &enctypes))) { + DEBUG(1,("ads_secrets_verify_ticket: krb5_get_permitted_enctypes failed (%s)\n", + error_message(ret))); + return ret; + } + + p_packet->length = ticket->length; + p_packet->data = (krb5_pointer)ticket->data; + + /* We need to setup a auth context with each possible encoding type in turn. */ + + ret = KRB5_BAD_ENCTYPE; + for (i=0;enctypes[i];i++) { + krb5_keyblock *key = NULL; + + if (!(key = malloc_p(krb5_keyblock))) { + break; + } + + if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i])) { + SAFE_FREE(key); + continue; + } + + krb5_auth_con_setuseruserkey(context, auth_context, key); + + krb5_free_keyblock(context, key); + + our_ret = krb5_rd_req(context, &auth_context, p_packet, + NULL, + NULL, NULL, pp_tkt); + if (!our_ret) { + + DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n", + (unsigned int)enctypes[i] )); + ret = our_ret; + break; + } + + DEBUG((our_ret != KRB5_BAD_ENCTYPE) ? 3 : 10, + ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n", + (unsigned int)enctypes[i], smb_get_krb5_error_message(context, our_ret, mem_ctx))); + + if (our_ret != KRB5_BAD_ENCTYPE) { + ret = our_ret; + } + } + + free_kerberos_etypes(context, enctypes); + + return ret; +} + +/********************************************************************************** + Verify an incoming ticket and parse out the principal name and + authorization_data if available. +***********************************************************************************/ + + NTSTATUS ads_verify_ticket(TALLOC_CTX *mem_ctx, + krb5_context context, + krb5_auth_context auth_context, + const char *realm, const char *service, + const DATA_BLOB *ticket, + char **principal, DATA_BLOB *auth_data, + DATA_BLOB *ap_rep, + krb5_keyblock *keyblock) +{ + NTSTATUS sret = NT_STATUS_LOGON_FAILURE; + krb5_data packet; + krb5_ticket *tkt = NULL; + krb5_rcache rcache = NULL; + int ret; + + krb5_principal host_princ = NULL; + char *host_princ_s = NULL; + BOOL got_replay_mutex = False; + + char *malloc_principal; + + ZERO_STRUCT(packet); + ZERO_STRUCTP(auth_data); + ZERO_STRUCTP(ap_rep); + + /* This whole process is far more complex than I would + like. We have to go through all this to allow us to store + the secret internally, instead of using /etc/krb5.keytab */ + + asprintf(&host_princ_s, "%s$", lp_netbios_name()); + strlower_m(host_princ_s); + ret = krb5_parse_name(context, host_princ_s, &host_princ); + if (ret) { + DEBUG(1,("ads_verify_ticket: krb5_parse_name(%s) failed (%s)\n", + host_princ_s, error_message(ret))); + goto out; + } + + + /* Lock a mutex surrounding the replay as there is no locking in the MIT krb5 + * code surrounding the replay cache... */ + + if (!grab_server_mutex("replay cache mutex")) { + DEBUG(1,("ads_verify_ticket: unable to protect replay cache with mutex.\n")); + goto out; + } + + got_replay_mutex = True; + + /* + * JRA. We must set the rcache here. This will prevent replay attacks. + */ + + ret = krb5_get_server_rcache(context, krb5_princ_component(context, host_princ, 0), &rcache); + if (ret) { + DEBUG(1,("ads_verify_ticket: krb5_get_server_rcache failed (%s)\n", error_message(ret))); + goto out; + } + + ret = krb5_auth_con_setrcache(context, auth_context, rcache); + if (ret) { + DEBUG(1,("ads_verify_ticket: krb5_auth_con_setrcache failed (%s)\n", error_message(ret))); + goto out; + } + + ret = ads_keytab_verify_ticket(mem_ctx, context, auth_context, + service, ticket, &packet, &tkt, keyblock); + if (ret) { + DEBUG(10, ("ads_secrets_verify_ticket: using host principal: [%s]\n", host_princ_s)); + ret = ads_secrets_verify_ticket(mem_ctx, context, auth_context, + host_princ, ticket, + &packet, &tkt, keyblock); + } + + release_server_mutex(); + got_replay_mutex = False; + + if (ret) { + DEBUG(3,("ads_verify_ticket: krb5_rd_req with auth failed (%s)\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + goto out; + } + + ret = krb5_mk_rep(context, auth_context, &packet); + if (ret) { + DEBUG(3,("ads_verify_ticket: Failed to generate mutual authentication reply (%s)\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + goto out; + } + + *ap_rep = data_blob_talloc(mem_ctx, packet.data, packet.length); + SAFE_FREE(packet.data); + packet.length = 0; + +#if 0 + file_save("/tmp/ticket.dat", ticket->data, ticket->length); +#endif + + *auth_data = get_auth_data_from_tkt(mem_ctx, tkt); + + *auth_data = unwrap_pac(mem_ctx, auth_data); + +#if 0 + if (tkt->enc_part2) { + file_save("/tmp/authdata.dat", + tkt->enc_part2->authorization_data[0]->contents, + tkt->enc_part2->authorization_data[0]->length); + } +#endif + + if ((ret = krb5_unparse_name(context, get_principal_from_tkt(tkt), + &malloc_principal))) { + DEBUG(3,("ads_verify_ticket: krb5_unparse_name failed (%s)\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + sret = NT_STATUS_LOGON_FAILURE; + goto out; + } + + *principal = talloc_strdup(mem_ctx, malloc_principal); + SAFE_FREE(malloc_principal); + if (!principal) { + DEBUG(3,("ads_verify_ticket: talloc_strdup() failed\n")); + sret = NT_STATUS_NO_MEMORY; + goto out; + } + + sret = NT_STATUS_OK; + + out: + + if (got_replay_mutex) { + release_server_mutex(); + } + + if (!NT_STATUS_IS_OK(sret)) { + data_blob_free(auth_data); + } + + if (!NT_STATUS_IS_OK(sret)) { + data_blob_free(ap_rep); + } + + if (host_princ) { + krb5_free_principal(context, host_princ); + } + + if (tkt != NULL) { + krb5_free_ticket(context, tkt); + } + + SAFE_FREE(host_princ_s); + + return sret; +} + +#endif /* HAVE_KRB5 */ -- cgit