diff options
Diffstat (limited to 'source4/auth/kerberos')
-rw-r--r-- | source4/auth/kerberos/clikrb5.c | 113 | ||||
-rw-r--r-- | source4/auth/kerberos/config.m4 | 540 | ||||
-rw-r--r-- | source4/auth/kerberos/config.mk | 18 | ||||
-rw-r--r-- | source4/auth/kerberos/gssapi_parse.c | 123 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos-notes.txt | 466 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos.c | 122 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos.h | 153 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos_heimdal.c | 101 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos_pac.c | 777 | ||||
-rw-r--r-- | source4/auth/kerberos/kerberos_util.c | 681 | ||||
-rw-r--r-- | source4/auth/kerberos/krb5_init_context.c | 482 | ||||
-rw-r--r-- | source4/auth/kerberos/krb5_init_context.h | 37 |
12 files changed, 3613 insertions, 0 deletions
diff --git a/source4/auth/kerberos/clikrb5.c b/source4/auth/kerberos/clikrb5.c new file mode 100644 index 0000000000..cf87d13cf2 --- /dev/null +++ b/source4/auth/kerberos/clikrb5.c @@ -0,0 +1,113 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5 routines for active directory + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "system/kerberos.h" +#include "system/time.h" +#include "auth/kerberos/kerberos.h" + +#ifdef HAVE_KRB5 + +#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(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(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; + } + ret = krb5_string_to_key_salt(context, enctype, password->data, + salt, key); + krb5_free_salt(context, salt); + return ret; +} +#else +#error UNKNOWN_CREATE_KEY_FUNCTIONS +#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 +} + + 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); + if (context_error) { + ret = talloc_asprintf(mem_ctx, "%s: %s", error_message(code), context_error); + krb5_free_error_string(context, context_error); + return ret; + } +#endif + ret = talloc_strdup(mem_ctx, error_message(code)); + return ret; +} + +#endif diff --git a/source4/auth/kerberos/config.m4 b/source4/auth/kerberos/config.m4 new file mode 100644 index 0000000000..bf14ca0ee4 --- /dev/null +++ b/source4/auth/kerberos/config.m4 @@ -0,0 +1,540 @@ +# NOTE! this whole m4 file is disabled in configure.in for now + +################################################# +# 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_krb5.h gssapi/gssapi.h gssapi/gssapi_generic.h gssapi/gssapi_krb5.h com_err.h) + + + # 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 + ################################################################## + # 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) + + 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) + AC_CHECK_FUNC_EXT(krb5_initlog, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_addlog_func, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(krb5_set_warn_dest, $KRB5_LIBS) + + LIBS="$LIBS $KRB5_LIBS" + + AC_CACHE_CHECK([for krb5_log_facility type], + samba_cv_HAVE_KRB5_LOG_FACILITY,[ + AC_TRY_COMPILE([#include <krb5.h>], + [krb5_log_facility block;], + samba_cv_HAVE_KRB5_LOG_FACILITY=yes, + samba_cv_HAVE_KRB5_LOG_FACILITY=no)]) + + if test x"$samba_cv_HAVE_KRB5_LOG_FACILITY" = x"yes"; then + AC_DEFINE(HAVE_KRB5_LOG_FACILITY,1, + [Whether the type krb5_log_facility exists]) + fi + + AC_CACHE_CHECK([for krb5_encrypt_block type], + samba_cv_HAVE_KRB5_ENCRYPT_BLOCK,[ + AC_TRY_COMPILE([#include <krb5.h>], + [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.h>], + [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.h>], + [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.h>], + [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.h>], + [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.h>], + [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.h>], + [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.h>], + [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.h>], + [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.h>], + [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.h>], + [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 <krb5.h>], + [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.h>], + [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.h>], + [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<krb5.h> + 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.h>], + [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 <gssapi.h>], + [int flags = GSS_C_DCE_STYLE;], + samba_cv_GSS_C_DCE_STYLE=yes, + samba_cv_GSS_C_DCE_STYLE=no)]) + + AC_CHECK_FUNC_EXT(gsskrb5_get_initiator_subkey, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(gsskrb5_extract_authz_data_from_sec_context, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(gsskrb5_register_acceptor_identity, $KRB5_LIBS) + AC_CHECK_FUNC_EXT(gss_krb5_ccache_name, $KRB5_LIBS) + 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_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 + + # checks if we have access to a libkdc + # and can use it for our builtin kdc server_service + KDC_CFLAGS="" + KDC_CPPFLAGS="" + KDC_DLFLAGS="" + KDC_LIBS="" + AC_CHECK_HEADERS(kdc.h) + AC_CHECK_LIB_EXT(kdc, KDC_LIBS, krb5_kdc_default_config) + AC_CHECK_LIB_EXT(hdb, KDC_LIBS, hdb_generate_key_set_password) + + AC_MSG_CHECKING(whether libkdc is used) + if test x"$ac_cv_header_kdc_h" = x"yes"; then + if test x"$ac_cv_lib_ext_kdc_krb5_kdc_default_config" = x"yes"; then + if test x"$ac_cv_lib_ext_hdb_hdb_generate_key_set_password" = x"yes"; then + SMB_ENABLE(KDC,YES) + AC_MSG_RESULT(yes) + echo "KDC_LIBS: ${KDC_LIBS}" + else + AC_MSG_RESULT(no) + fi + else + AC_MSG_RESULT(no) + fi + else + AC_MSG_RESULT(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}]) +SMB_EXT_LIB(KDC,[${KDC_LIBS}],[${KDC_CFLAGS}],[${KDC_CPPFLAGS}],[${KDC_LDFLAGS}]) diff --git a/source4/auth/kerberos/config.mk b/source4/auth/kerberos/config.mk new file mode 100644 index 0000000000..951e247258 --- /dev/null +++ b/source4/auth/kerberos/config.mk @@ -0,0 +1,18 @@ +################################# +# Start SUBSYSTEM KERBEROS +[SUBSYSTEM::KERBEROS] +PUBLIC_DEPENDENCIES = HEIMDAL_KRB5 NDR_KRB5PAC samba-socket LIBCLI_RESOLVE +PRIVATE_DEPENDENCIES = ASN1_UTIL auth_sam_reply LIBPACKET LIBNDR +# End SUBSYSTEM KERBEROS +################################# + +KERBEROS_OBJ_FILES = $(addprefix $(authsrcdir)/kerberos/, \ + kerberos.o \ + clikrb5.o \ + kerberos_heimdal.o \ + kerberos_pac.o \ + gssapi_parse.o \ + krb5_init_context.o) + +$(eval $(call proto_header_template,$(authsrcdir)/kerberos/proto.h,$(KERBEROS_OBJ_FILES:.o=.c))) + diff --git a/source4/auth/kerberos/gssapi_parse.c b/source4/auth/kerberos/gssapi_parse.c new file mode 100644 index 0000000000..77e907d3fa --- /dev/null +++ b/source4/auth/kerberos/gssapi_parse.c @@ -0,0 +1,123 @@ +/* + Unix SMB/CIFS implementation. + + simple GSSAPI wrappers + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/util/asn1.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; + + if (!data || !ticket->data) { + return data_blob(NULL,0); + } + + data = asn1_init(mem_ctx); + if (data == NULL) { + return data_blob(NULL,0); + } + + 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); + return data_blob(NULL,0); + } + + 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 = asn1_init(mem_ctx); + int data_remaining; + + if (!data) { + return false; + } + + 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; +} + + +/* + check a GSS-API wrapper packet givin an expected OID +*/ +bool gensec_gssapi_check_oid(const DATA_BLOB *blob, const char *oid) +{ + bool ret; + struct asn1_data *data = asn1_init(NULL); + + if (!data) return false; + + asn1_load(data, *blob); + asn1_start_tag(data, ASN1_APPLICATION(0)); + asn1_check_OID(data, oid); + + ret = !data->has_error; + + asn1_free(data); + + return ret; +} + + diff --git a/source4/auth/kerberos/kerberos-notes.txt b/source4/auth/kerberos/kerberos-notes.txt new file mode 100644 index 0000000000..43881a20d3 --- /dev/null +++ b/source4/auth/kerberos/kerberos-notes.txt @@ -0,0 +1,466 @@ +AllowedWorkstationNames and Krb5 +-------------------------------- + +Microsoft uses the clientAddresses *multiple value* field in the krb5 +protocol (particularly the AS_REQ) to communicate it's netbios name. +This is (my guess) to permit the userWorkstations field to work. + +The KDC I imagine checks the netbios address against this value, in +the same way that the Samba server does this. + +The checking of this implies a little of the next question: + +Is a DAL the layer we need? +--------------------------- + +Looking at what we need to pass around, I start to seriously wonder if +the DAL is even the right layer - we seem to want to create an account +authorization abstraction layer - is this account permitted to login to +this computer, at this time? + +This information in AD is much richer than the Heimdal HDB, and it +seems to make sense to do AD-specific access control checks in an +AD-specific layer, not in the back-end agnostic server. + +Because the DAL only reads in the principalName as the key, it has +trouble performing access control decisions on things other than the +name. + +I'll be very interested if the DAL really works for eDirectory too. +Perhaps all we need to do is add in the same kludges as we have in +Samba 3.0 for eDirectory. Hmm... + +That said, the current layer provides us with a very good start, and +any redefinition would occour from that basis. + + +GSSAPI layer requirements +------------------------- + +Welcome to the wonderful world of canonicalisation + +The MIT GSSAPI libs do not support kinit returning a different +realm to what the client asked for, even just in case differences. + +Heimdal has the same problem, and this applies to the krb5 layer, not +just gssapi. + +We need to test if the canonicalisation is controlled by the KDCOption +flags, windows always sends the Canonicalize flags + +Old Clients (samba3 and HPUX clients) uses 'selfmade' gssapi/krb5 +for using it in the CIFS session setup. Because they use krb5_mk_req() +they get a chksum field depending on the encryption type, but that's wrong +for GSSAPI (see rfc 1964 section 1.1.1). The Cheksum type 8003 +should be used in the Authenticator of the AP-REQ! That allows the channel bindings, +the GCC_C_* req_flags and optional delegation tickets to be passed from the client to the server. +Hower windows doesn't seems to care about if the checksum is of the wrong type, +for CIFS SessionSetups, it seems that the req_flags are just set to 0. +So this can't work for LDAP connections with sign or seal, or for any DCERPC +connection. + +So we need to also support old clients! + +Principal Names, long and short names +------------------------------------- + +As far as servicePrincipalNames are concerned, these are not +canonicalised, except as regards the realm in the reply. That is, the +client gets back the principal it asked for, with the realm portion +'fixed' to uppercase, long form. + +The short name of the realm seems to be accepted for at least AS_REQ +operations, but because the server performs canonicalisation, this +causes pain for current client libraries. + +The canonicalisation of names matters not only for the KDC, but also +for code that has to deal with keytabs. + +We also need to handle type 10 names (NT-ENTERPRISE), which are a full +principal name in the principal field, unrelated to the realm. + +HOST/ Aliases +------------- + +There is another post somewhere (ref lost for the moment) that details +where in active directory the list of stored aliases for HOST/ is. +This should be read, parsed and used to allow any of these requests to +use the HOST/ key. + +For example, this is how HTTP/, DNS/ and CIFS/ can use HOST/ without +any explicit entry. + + +Jean-Baptiste.Marchand@hsc.fr reminds me: + +> This is the SPNMappings attribute in Active Directory: + +> http://msdn.microsoft.com/library/en-us/adschema/adschema/a_spnmappings.asp + +We implement this in hdb-ldb. + +Implicit names for Win2000 Accounts +----------------------------------- + +Despite not having a DNS name, nor a servicePrincipalName on accounts +created by computers running win2000, it appears we are expected to +have an implicit mapping from host/computer.full.name and +host/computer to it's entry. + +Returned Salt for PreAuthentication +----------------------------------- + +When the server replies for pre-authentication, it returns the Salt, +which may be in the form of a principalName that is in no way +connected with the current names. (ie, even if the userPrincipalName +and samAccountName are renamed, the old salt is returned). + +This is probably the kerberos standard salt, kept in the 'Key'. The +standard generation rules are found in a Mail from Luke Howard dated +10 Nov 2004: + + +From: Luke Howard <lukeh@padl.com> +Message-Id: <200411100231.iAA2VLUW006101@au.padl.com> +MIME-Version: 1.0 +Content-Type: text/plain; charset=US-ASCII +Organization: PADL Software Pty Ltd +To: lukeh@padl.com +Date: Wed, 10 Nov 2004 13:31:21 +1100 +Versions: dmail (bsd44) 2.6d/makemail 2.10 +Cc: huaraz@moeller.plus.com, samba-technical@lists.samba.org +Subject: Re: Samba-3.0.7-1.3E Active Directory Issues +X-BeenThere: samba-technical@lists.samba.org +X-Mailman-Version: 2.1.4 +Precedence: list +Reply-To: lukeh@padl.com + +Did some more testing, it appears the behaviour has another +explanation. It appears that the standard Kerberos password salt +algorithm is applied in Windows 2003, just that the source principal +name is different. + +Here is what I've been able to deduce from creating a bunch of +different accounts: + +Type of account Principal for Salting +======================================================================== +Computer Account host/<SAM-Name-Without-$>.realm@REALM +User Account Without UPN <SAM-Name>@REALM +User Account With UPN <LHS-Of-UPN>@REALM + +Note that if the computer account's SAM account name does not include +the trailing '$', then the entire SAM account name is used as input to +the salting principal. Setting a UPN for a computer account has no +effect. + +It seems to me odd that the RHS of the UPN is not used in the salting +principal. For example, a user with UPN foo@mydomain.com in the realm +MYREALM.COM would have a salt of MYREALM.COMfoo. Perhaps this is to +allow a user's UPN suffix to be changed without changing the salt. And +perhaps using the UPN for salting signifies a move away SAM names and +their associated constraints. + +For more information on how UPNs relate to the Kerberos protocol, +see: + +http://www.ietf.org/proceedings/01dec/I-D/draft-ietf-krb-wg-kerberos-referrals-02.txt + +-- Luke + +-- + + + + +Heimdal oddities +---------------- + +Heimdal is built such that it should be able to serve multiple realms +at the same time. This isn't relevant for Samba's use, but it shows +up in a lot of generalisations throughout the code. + +Other odd things: + - Support for multiple passwords on a client account: we seem to + call hdb_next_enctype2key() in the pre-authentication routines to + allow multiple passwords per account in krb5. (I think this was + intened to allow multiple salts) + +State Machine safety +-------------------- + +Samba is a giant state machine, and as such have very different +requirements to those traditionally expressed for kerberos and GSSAPI +libraries. + +Samba requires all of the libraries it uses to be state machine safe in +their use of internal data. This does not mean thread safe, and an +application could be thread safe, but not state machine safe (if it +instead used thread-local variables). + +So, what does it mean for a library to be state machine safe? This is +mostly a question of context, and how the library manages whatever +internal state machines it has. If the library uses a context +variable, passed in by the caller, which contains all the information +about the current state of the library, then it is safe. An example +of this state is the sequence number and session keys for an ongoing +encrypted session). + +The other issue affecting state machines is 'blocking' (waiting for a +read on a network socket). + +Heimdal has this 'state machine safety' in parts, and we have modified +the lorikeet branch to improve this behviour, when using a new, +non-standard API. + +Heimdal uses a per-context variable for the 'krb5_auth_context', which +controls the ongoing encrypted connection, but does use global +variables for the ubiquitous krb5_context parameter. + +The modification that has added most to 'state machine safety' of +GSSAPI is the addition of the gsskrb5_acquire_creds function. This +allows the caller to specify a keytab and ccache, for use by the +GSSAPI code. Therefore there is no need to use global variables to +communicate this information. + +At a more theoritical level (simply counting static and global +variables) Heimdal is not state machine safe for the GSSAPI layer. +The Krb5 layer alone is much closer, as far as I can tell, blocking +excepted. . + +To deal with blocking, we could have a fork()ed child per context, +using the 'GSSAPI export context' function to transfer +the GSSAPI state back into the main code for the wrap()/unwrap() part +of the operation. This will still hit issues of static storage (one +gss_krb5_context per process, and multiple GSSAPI encrypted sessions +at a time) but these may not matter in practice. + +In the short-term, we deal with blocking by taking over the network +send() and recv() functions, therefore making them 'semi-async'. This +doens't apply to DNS yet. + +GSSAPI and Kerberos extensions +------------------------------ + +This is a general list of the other extensions we have made to / need from +the kerberos libraries + + - DCE_STYLE + + - gsskrb5_get_initiator_subkey() (return the exact key that Samba3 + has always asked for. gsskrb5_get_subkey() might do what we need + anyway) + + - gsskrb5_acquire_creds() (takes keytab and/or ccache as input + parameters, see keytab and state machine discussion) + + - gss_krb5_copy_service_keyblock() (get the key used to actually + encrypt the ticket to the server, because the same key is used for + the PAC validation). + - gsskrb5_extract_authtime_from_sec_context (get authtime from + kerberos ticket) + - gsskrb5_extract_authz_data_from_sec_context (get authdata from + ticket, ie the PAC. Must unwrap the data if in an AD-IFRELEVENT) + - gsskrb5_wrap_size (find out how big the wrapped packet will be, + given input length). + +Keytab requirements +------------------- + +Because windows machine account handling is very different to the +tranditional 'MIT' keytab operation. This starts when we look at the +basis of the secrets handling: + +Traditional 'MIT' behaviour is to use a keytab, continaing salted key +data, extracted from the KDC. (In this modal, there is no 'service +password', instead the keys are often simply application of random +bytes). Heimdal also implements this behaviour. + +The windows modal is very different - instead of sharing a keytab with +each member server, a password is stored for the whole machine. The +password is set with non-kerberos mechanisms (particularly SAMR, a +DCE-RPC service) and when interacting on a kerberos basis, the +password is salted by the client. (That is, no salt infromation +appears to be convayed from the KDC to the member). + +In dealing with this modal, we leverage both the traditional file +keytab and in-MEMORY keytabs. + +When dealing with a windows KDC, the behaviour regarding case +sensitivity and canonacolisation must be accomidated. This means that +an incoming request to a member server may have a wide variety of +service principal names. These include: + +machine$@REALM (samba clients) +HOST/foo.bar@realm (win2k clients) +HOST/foo@realm (win2k clients, using netbios) +cifs/foo.bar@realm (winxp clients) +cifs/foo@realm (winxp clients, using netbios) + +as well as all case variations on the above. + +Because that all got 'too hard' to put into a keytab in the +traditional way (with the client to specify the name), we either +pre-compute the keys into a traditional keytab or make an in-MEMORY +keytab at run time. In both cases we specifiy the principal name to +GSSAPI, which avoids the need to store duplicate principals. + +We use a 'private' keytab in our private dir, referenced from the +secrets.ldb by default. + +Extra Heimdal functions used +---------------------------- +(an attempt to list some of the Heimdal-specific functions I know we use) + +krb5_free_keyblock_contents() + +also a raft of prinicpal manipulation functions: + +Prncipal Manipulation +--------------------- + +Samba makes extensive use of the principal manipulation functions in +Heimdal, including the known structure behind krb_principal and +krb5_realm (a char *). + +Authz data extraction +--------------------- + +We use krb5_ticket_get_authorization_data_type(), and expect it to +return the correct authz data, even if wrapped in an AD-IFRELEVENT container. + + +KDC/hdb Extensions +-------------- + +We have modified Heimdal's 'hdb' interface to specify the 'type' of +Principal being requested. This allows us to correctly behave with +the different 'classes' of Principal name. + +We currently define 2 classes: + - client (kinit) + - server (tgt) + +I also now specify the kerberos principal as an explict parameter, not +an in/out value on the entry itself. + +Inside hdb-ldb, we add krbtgt as a special class of principal, because +of particular special-case backend requirements. + +Callbacks: + In addition, I have added a new interface hdb_fetch_ex(), which + returns a structure including callbacks, which provide the hook for + the PAC, as well as a callback into the main access control routines. + + A new callback should be added to increment the bad password counter + on failure. + + Another possability for a callback is to obtain the keys. This would + allow the plaintext password to only be hashed into the encryption + types we need. This idea from the eDirectory/MIT DAL work. + + This probably should be combined with storing the hashed passwords in + the supplementalCredentials attribute. If combined with a kvno + parameter, this could also allow changing of the krbtgt password + (valuable for security). + +libkdc +------ + +Samba4 needs to be built as a single binary (design requirement), and +this should include the KDC. Samba also (and perhaps more +importantly) needs to control the configuration environment of the +KDC. + +The interface we have defined for libkdc allow for packet injection +into the post-socket layer, with a defined krb5_context and +kdb5_kdc_configuration structure. These effectively redirect the +kerberos warnings, logging and database calls as we require. + +Using our socket lib +-------------------- + +An important detail in the use of libkdc is that we use our own socket +lib. This allows the KDC code to be as portable as the rest of samba +(this cuts both ways), but far more importantly it ensures a +consistancy in the handling of requests, binding to sockets etc. + +To handle TCP, we use of our socket layer in much the same way as +we deal with TCP for CIFS. Tridge created a generic packet handling +layer for this. + +For the client, we likewise must take over the socket functions, so +that our single thread smbd will not lock up talking to itself. (We +allow processing while waiting for packets in our socket routines). + +Kerberos logging support +------------------------ + +Samba now (optionally in the main code, required for the KDC) uses the +krb5_log_facility from Heimdal. This allows us to redirect the +warnings and status from the KDC (and client/server kerberos code) to +Samba's DEBUG() system. + +Similarly important is the Heimdal-specific krb5_get_error_string() +function, which does a lot to reduce the 'administrator pain' level, +by providing specific, english text-string error messages instead of +just error code translations. + + +Short name rules +---------------- + +Samba is highly likely to be misconfigured, in many weird and +interesting ways. As such, we have a patch for Heimdal that avoids +DNS lookups on names without a . in them. This should avoid some +delay and root server load. + +PAC Correctness +--------------- + +We now put the PAC into the TGT, not just the service ticket. + +Forwarded tickets +----------------- + +We extract forwarded tickets from the GSSAPI layer, and put +them into the credentials. We can then use them for proxy work. + + +Kerberos TODO +============= + +(Feel free to contribute to any of these tasks, or ask +abartlet@samba.org about them). + +Lockout Control +-------------- + +We need to get (either if PADL publishes their patch, or write our +own) access control hooks in the Heimdal KDC. We need to lockout +accounts, and perform other controls. + +Gssmonger +--------- + +Microsoft has released a testsuite called gssmonger, which tests +interop. We should compile it against lorikeet-heimdal, MIT and see +if we can build a 'Samba4' server for it. + +Kpasswd server +-------------- + +I have a partial kpasswd server which needs finishing, and a we need a +client testsuite written, either via the krb5 API or directly against +GENSEC and the ASN.1 routines. + +Currently it only works for Heimdal, not MIT clients. This may be due +to call ordering constraints. + + +Correct TCP support +------------------- + +Our current TCP support does not send back 'too large' error messages +if the high bit is set. This is needed for a proposed extension +mechanism, but is likewise unsupported in both current Heimdal and MIT. diff --git a/source4/auth/kerberos/kerberos.c b/source4/auth/kerberos/kerberos.c new file mode 100644 index 0000000000..2579ab20cc --- /dev/null +++ b/source4/auth/kerberos/kerberos.c @@ -0,0 +1,122 @@ +/* + 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 <abartlet@samba.org> 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" + +#ifdef HAVE_KRB5 + +/* + simulate a kinit, putting the tgt in the given credentials cache. + Orignally by remus@snapserver.com + + This version is built to use a keyblock, rather than needing the + original password. +*/ + int kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc, + krb5_principal principal, krb5_keyblock *keyblock, + time_t *expire_time, time_t *kdc_time) +{ + krb5_error_code code = 0; + krb5_creds my_creds; + krb5_get_init_creds_opt options; + + krb5_get_init_creds_opt_init(&options); + + krb5_get_init_creds_opt_set_default_flags(ctx, NULL, NULL, &options); + + if ((code = krb5_get_init_creds_keyblock(ctx, &my_creds, principal, keyblock, + 0, NULL, &options))) { + return code; + } + + if ((code = krb5_cc_initialize(ctx, cc, principal))) { + krb5_free_cred_contents(ctx, &my_creds); + return code; + } + + if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) { + krb5_free_cred_contents(ctx, &my_creds); + 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); + + 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, + krb5_principal principal, const char *password, + time_t *expire_time, time_t *kdc_time) +{ + krb5_error_code code = 0; + krb5_creds my_creds; + krb5_get_init_creds_opt options; + + krb5_get_init_creds_opt_init(&options); + + krb5_get_init_creds_opt_set_default_flags(ctx, NULL, NULL, &options); + + if ((code = krb5_get_init_creds_password(ctx, &my_creds, principal, password, + NULL, + NULL, 0, NULL, &options))) { + return code; + } + + if ((code = krb5_cc_initialize(ctx, cc, principal))) { + krb5_free_cred_contents(ctx, &my_creds); + return code; + } + + if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) { + krb5_free_cred_contents(ctx, &my_creds); + 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); + + return 0; +} + + +#endif diff --git a/source4/auth/kerberos/kerberos.h b/source4/auth/kerberos/kerberos.h new file mode 100644 index 0000000000..8585aa321b --- /dev/null +++ b/source4/auth/kerberos/kerberos.h @@ -0,0 +1,153 @@ +/* + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#if defined(HAVE_KRB5) + +#include "auth/kerberos/krb5_init_context.h" +#include "librpc/gen_ndr/krb5pac.h" + +struct auth_serversupplied_info; +struct cli_credentials; + +struct ccache_container { + struct smb_krb5_context *smb_krb5_context; + krb5_ccache ccache; +}; + +struct keytab_container { + struct smb_krb5_context *smb_krb5_context; + krb5_keytab keytab; +}; + +/* not really ASN.1, but RFC 1964 */ +#define TOK_ID_KRB_AP_REQ ((const uint8_t *)"\x01\x00") +#define TOK_ID_KRB_AP_REP ((const uint8_t *)"\x02\x00") +#define TOK_ID_KRB_ERROR ((const uint8_t *)"\x03\x00") +#define TOK_ID_GSS_GETMIC ((const uint8_t *)"\x01\x01") +#define TOK_ID_GSS_WRAP ((const uint8_t *)"\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 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); +bool get_auth_data_from_tkt(TALLOC_CTX *mem_ctx, DATA_BLOB *auth_data, krb5_ticket *tkt); +int kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc, + krb5_principal principal, const char *password, + time_t *expire_time, time_t *kdc_time); +int kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc, + krb5_principal principal, krb5_keyblock *keyblock, + 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); + krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + krb5_ccache ccache); +krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + krb5_principal *princ); +NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct PAC_DATA **pac_data_out, + DATA_BLOB blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + krb5_error_code *k5ret); + NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct PAC_LOGON_INFO **logon_info, + DATA_BLOB blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + krb5_error_code *k5ret); + krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct PAC_DATA *pac_data, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + DATA_BLOB *pac); + krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct auth_serversupplied_info *server_info, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_principal client_principal, + time_t tgs_authtime, + DATA_BLOB *pac); +struct loadparm_context; + +#include "auth/kerberos/proto.h" + +#endif /* HAVE_KRB5 */ diff --git a/source4/auth/kerberos/kerberos_heimdal.c b/source4/auth/kerberos/kerberos_heimdal.c new file mode 100644 index 0000000000..f669d0f2f4 --- /dev/null +++ b/source4/auth/kerberos/kerberos_heimdal.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * 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. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS 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 INSTITUTE OR 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 file for code taken from the Heimdal code, to preserve licence */ +/* Modified by Andrew Bartlett <abartlet@samba.org> */ + +#include "includes.h" +#include "system/kerberos.h" + +/* Taken from accept_sec_context.c,v 1.65 */ +krb5_error_code smb_rd_req_return_stuff(krb5_context context, + krb5_auth_context *auth_context, + const krb5_data *inbuf, + krb5_keytab keytab, + krb5_principal acceptor_principal, + krb5_data *outbuf, + krb5_ticket **ticket, + krb5_keyblock **keyblock) +{ + krb5_rd_req_in_ctx in = NULL; + krb5_rd_req_out_ctx out = NULL; + krb5_error_code kret; + + *keyblock = NULL; + *ticket = NULL; + outbuf->length = 0; + outbuf->data = NULL; + + kret = krb5_rd_req_in_ctx_alloc(context, &in); + if (kret == 0) + kret = krb5_rd_req_in_set_keytab(context, in, keytab); + if (kret) { + if (in) + krb5_rd_req_in_ctx_free(context, in); + return kret; + } + + kret = krb5_rd_req_ctx(context, + auth_context, + inbuf, + acceptor_principal, + in, &out); + krb5_rd_req_in_ctx_free(context, in); + if (kret) { + return kret; + } + + /* + * We need to remember some data on the context_handle. + */ + kret = krb5_rd_req_out_get_ticket(context, out, + ticket); + if (kret == 0) { + kret = krb5_rd_req_out_get_keyblock(context, out, + keyblock); + } + krb5_rd_req_out_ctx_free(context, out); + + if (kret == 0) { + kret = krb5_mk_rep(context, *auth_context, outbuf); + } + + if (kret) { + krb5_free_ticket(context, *ticket); + krb5_free_keyblock(context, *keyblock); + krb5_data_free(outbuf); + } + + return kret; +} + diff --git a/source4/auth/kerberos/kerberos_pac.c b/source4/auth/kerberos/kerberos_pac.c new file mode 100644 index 0000000000..2943e05b18 --- /dev/null +++ b/source4/auth/kerberos/kerberos_pac.c @@ -0,0 +1,777 @@ +/* + Unix SMB/CIFS implementation. + + Create and parse the krb5 PAC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005,2008 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/auth.h" +#include "auth/kerberos/kerberos.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "lib/ldb/include/ldb.h" +#include "auth/auth_sam_reply.h" +#include "param/param.h" + +krb5_error_code check_pac_checksum(TALLOC_CTX *mem_ctx, + DATA_BLOB pac_data, + struct PAC_SIGNATURE_DATA *sig, + krb5_context context, + const krb5_keyblock *keyblock) +{ + krb5_error_code ret; + krb5_crypto crypto; + Checksum cksum; + + cksum.cksumtype = (CKSUMTYPE)sig->type; + cksum.checksum.length = sig->signature.length; + cksum.checksum.data = sig->signature.data; + + ret = krb5_crypto_init(context, + keyblock, + 0, + &crypto); + if (ret) { + DEBUG(0,("krb5_crypto_init() failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + return ret; + } + ret = krb5_verify_checksum(context, + crypto, + KRB5_KU_OTHER_CKSUM, + pac_data.data, + pac_data.length, + &cksum); + krb5_crypto_destroy(context, crypto); + + return ret; +} + + NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct PAC_DATA **pac_data_out, + DATA_BLOB blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + krb5_error_code *k5ret) +{ + krb5_error_code ret; + NTSTATUS status; + enum ndr_err_code ndr_err; + struct PAC_SIGNATURE_DATA *srv_sig_ptr = NULL; + struct PAC_SIGNATURE_DATA *kdc_sig_ptr = NULL; + struct PAC_SIGNATURE_DATA *srv_sig_wipe = NULL; + struct PAC_SIGNATURE_DATA *kdc_sig_wipe = NULL; + struct PAC_LOGON_INFO *logon_info = NULL; + struct PAC_LOGON_NAME *logon_name = NULL; + struct PAC_DATA *pac_data; + struct PAC_DATA_RAW *pac_data_raw; + + DATA_BLOB *srv_sig_blob = NULL; + DATA_BLOB *kdc_sig_blob = NULL; + + DATA_BLOB modified_pac_blob; + NTTIME tgs_authtime_nttime; + krb5_principal client_principal_pac; + int i; + + krb5_clear_error_string(context); + + if (k5ret) { + *k5ret = KRB5_PARSE_MALFORMED; + } + + pac_data = talloc(mem_ctx, struct PAC_DATA); + pac_data_raw = talloc(mem_ctx, struct PAC_DATA_RAW); + kdc_sig_wipe = talloc(mem_ctx, struct PAC_SIGNATURE_DATA); + srv_sig_wipe = talloc(mem_ctx, struct PAC_SIGNATURE_DATA); + if (!pac_data_raw || !pac_data || !kdc_sig_wipe || !srv_sig_wipe) { + if (k5ret) { + *k5ret = ENOMEM; + } + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_pull_struct_blob(&blob, pac_data, + iconv_convenience, pac_data, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC: %s\n", + nt_errstr(status))); + return status; + } + + if (pac_data->num_buffers < 4) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + ndr_err = ndr_pull_struct_blob(&blob, pac_data_raw, + iconv_convenience, pac_data_raw, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA_RAW); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC: %s\n", + nt_errstr(status))); + return status; + } + + if (pac_data_raw->num_buffers < 4) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (pac_data->num_buffers != pac_data_raw->num_buffers) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("misparse! PAC_DATA has %d buffers while PAC_DATA_RAW has %d\n", + pac_data->num_buffers, pac_data_raw->num_buffers)); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != pac_data_raw->buffers[i].type) { + DEBUG(0,("misparse! PAC_DATA buffer %d has type %d while PAC_DATA_RAW has %d\n", + i, pac_data->buffers[i].type, pac_data->buffers[i].type)); + return NT_STATUS_INVALID_PARAMETER; + } + switch (pac_data->buffers[i].type) { + case PAC_TYPE_LOGON_INFO: + if (!pac_data->buffers[i].info) { + break; + } + logon_info = pac_data->buffers[i].info->logon_info.info; + break; + case PAC_TYPE_SRV_CHECKSUM: + if (!pac_data->buffers[i].info) { + break; + } + srv_sig_ptr = &pac_data->buffers[i].info->srv_cksum; + srv_sig_blob = &pac_data_raw->buffers[i].info->remaining; + break; + case PAC_TYPE_KDC_CHECKSUM: + if (!pac_data->buffers[i].info) { + break; + } + kdc_sig_ptr = &pac_data->buffers[i].info->kdc_cksum; + kdc_sig_blob = &pac_data_raw->buffers[i].info->remaining; + break; + case PAC_TYPE_LOGON_NAME: + logon_name = &pac_data->buffers[i].info->logon_name; + break; + default: + break; + } + } + + if (!logon_info) { + DEBUG(0,("PAC no logon_info\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!logon_name) { + DEBUG(0,("PAC no logon_name\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!srv_sig_ptr || !srv_sig_blob) { + DEBUG(0,("PAC no srv_key\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!kdc_sig_ptr || !kdc_sig_blob) { + DEBUG(0,("PAC no kdc_key\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Find and zero out the signatures, as required by the signing algorithm */ + + /* We find the data blobs above, now we parse them to get at the exact portion we should zero */ + ndr_err = ndr_pull_struct_blob(kdc_sig_blob, kdc_sig_wipe, + iconv_convenience, kdc_sig_wipe, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the KDC signature: %s\n", + nt_errstr(status))); + return status; + } + + ndr_err = ndr_pull_struct_blob(srv_sig_blob, srv_sig_wipe, + iconv_convenience, srv_sig_wipe, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the SRV signature: %s\n", + nt_errstr(status))); + return status; + } + + /* Now zero the decoded structure */ + memset(kdc_sig_wipe->signature.data, '\0', kdc_sig_wipe->signature.length); + memset(srv_sig_wipe->signature.data, '\0', srv_sig_wipe->signature.length); + + /* and reencode, back into the same place it came from */ + ndr_err = ndr_push_struct_blob(kdc_sig_blob, pac_data_raw, + iconv_convenience, + kdc_sig_wipe, + (ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the KDC signature: %s\n", + nt_errstr(status))); + return status; + } + ndr_err = ndr_push_struct_blob(srv_sig_blob, pac_data_raw, + iconv_convenience, + srv_sig_wipe, + (ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the SRV signature: %s\n", + nt_errstr(status))); + return status; + } + + /* push out the whole structure, but now with zero'ed signatures */ + ndr_err = ndr_push_struct_blob(&modified_pac_blob, pac_data_raw, + iconv_convenience, + pac_data_raw, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA_RAW); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the RAW PAC: %s\n", + nt_errstr(status))); + return status; + } + + /* verify by service_key */ + ret = check_pac_checksum(mem_ctx, + modified_pac_blob, srv_sig_ptr, + context, + service_keyblock); + if (ret) { + DEBUG(1, ("PAC Decode: Failed to verify the service signature: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + if (k5ret) { + *k5ret = ret; + } + return NT_STATUS_ACCESS_DENIED; + } + + if (krbtgt_keyblock) { + ret = check_pac_checksum(mem_ctx, + srv_sig_ptr->signature, kdc_sig_ptr, + context, krbtgt_keyblock); + if (ret) { + DEBUG(1, ("PAC Decode: Failed to verify the KDC signature: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + if (k5ret) { + *k5ret = ret; + } + return NT_STATUS_ACCESS_DENIED; + } + } + + /* Convert to NT time, so as not to loose accuracy in comparison */ + unix_to_nt_time(&tgs_authtime_nttime, tgs_authtime); + + if (tgs_authtime_nttime != logon_name->logon_time) { + DEBUG(2, ("PAC Decode: Logon time mismatch between ticket and PAC!\n")); + DEBUG(2, ("PAC Decode: PAC: %s\n", nt_time_string(mem_ctx, logon_name->logon_time))); + DEBUG(2, ("PAC Decode: Ticket: %s\n", nt_time_string(mem_ctx, tgs_authtime_nttime))); + return NT_STATUS_ACCESS_DENIED; + } + + ret = krb5_parse_name_flags(context, logon_name->account_name, KRB5_PRINCIPAL_PARSE_NO_REALM, + &client_principal_pac); + if (ret) { + DEBUG(2, ("Could not parse name from incoming PAC: [%s]: %s\n", + logon_name->account_name, + smb_get_krb5_error_message(context, ret, mem_ctx))); + if (k5ret) { + *k5ret = ret; + } + return NT_STATUS_INVALID_PARAMETER; + } + + if (!krb5_principal_compare_any_realm(context, client_principal, client_principal_pac)) { + DEBUG(2, ("Name in PAC [%s] does not match principal name in ticket\n", + logon_name->account_name)); + return NT_STATUS_ACCESS_DENIED; + } + +#if 0 + if (strcasecmp(logon_info->info3.base.account_name.string, + "Administrator")== 0) { + file_save("tmp_pac_data-admin.dat",blob.data,blob.length); + } +#endif + + DEBUG(3,("Found account name from PAC: %s [%s]\n", + logon_info->info3.base.account_name.string, + logon_info->info3.base.full_name.string)); + *pac_data_out = pac_data; + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct PAC_LOGON_INFO **logon_info, + DATA_BLOB blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + krb5_error_code *k5ret) +{ + NTSTATUS nt_status; + struct PAC_DATA *pac_data; + int i; + nt_status = kerberos_decode_pac(mem_ctx, + iconv_convenience, + &pac_data, + blob, + context, + krbtgt_keyblock, + service_keyblock, + client_principal, + tgs_authtime, + k5ret); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + *logon_info = NULL; + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != PAC_TYPE_LOGON_INFO) { + continue; + } + *logon_info = pac_data->buffers[i].info->logon_info.info; + } + if (!*logon_info) { + return NT_STATUS_INVALID_PARAMETER; + } + return NT_STATUS_OK; +} + +static krb5_error_code make_pac_checksum(TALLOC_CTX *mem_ctx, + DATA_BLOB *pac_data, + struct PAC_SIGNATURE_DATA *sig, + krb5_context context, + const krb5_keyblock *keyblock) +{ + krb5_error_code ret; + krb5_crypto crypto; + Checksum cksum; + + + ret = krb5_crypto_init(context, + keyblock, + 0, + &crypto); + if (ret) { + DEBUG(0,("krb5_crypto_init() failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + return ret; + } + ret = krb5_create_checksum(context, + crypto, + KRB5_KU_OTHER_CKSUM, + 0, + pac_data->data, + pac_data->length, + &cksum); + if (ret) { + DEBUG(2, ("PAC Verification failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + } + + krb5_crypto_destroy(context, crypto); + + if (ret) { + return ret; + } + + sig->type = cksum.cksumtype; + sig->signature = data_blob_talloc(mem_ctx, cksum.checksum.data, cksum.checksum.length); + free_Checksum(&cksum); + + return 0; +} + + krb5_error_code kerberos_encode_pac(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct PAC_DATA *pac_data, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + DATA_BLOB *pac) +{ + NTSTATUS nt_status; + krb5_error_code ret; + enum ndr_err_code ndr_err; + DATA_BLOB zero_blob = data_blob(NULL, 0); + DATA_BLOB tmp_blob = data_blob(NULL, 0); + struct PAC_SIGNATURE_DATA *kdc_checksum = NULL; + struct PAC_SIGNATURE_DATA *srv_checksum = NULL; + int i; + + /* First, just get the keytypes filled in (and lengths right, eventually) */ + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != PAC_TYPE_KDC_CHECKSUM) { + continue; + } + kdc_checksum = &pac_data->buffers[i].info->kdc_cksum, + ret = make_pac_checksum(mem_ctx, &zero_blob, + kdc_checksum, + context, krbtgt_keyblock); + if (ret) { + DEBUG(2, ("making krbtgt PAC checksum failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + talloc_free(pac_data); + return ret; + } + } + + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != PAC_TYPE_SRV_CHECKSUM) { + continue; + } + srv_checksum = &pac_data->buffers[i].info->srv_cksum; + ret = make_pac_checksum(mem_ctx, &zero_blob, + srv_checksum, + context, service_keyblock); + if (ret) { + DEBUG(2, ("making service PAC checksum failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + talloc_free(pac_data); + return ret; + } + } + + if (!kdc_checksum) { + DEBUG(2, ("Invalid PAC constructed for signing, no KDC checksum present!")); + return EINVAL; + } + if (!srv_checksum) { + DEBUG(2, ("Invalid PAC constructed for signing, no SRV checksum present!")); + return EINVAL; + } + + /* But wipe out the actual signatures */ + memset(kdc_checksum->signature.data, '\0', kdc_checksum->signature.length); + memset(srv_checksum->signature.data, '\0', srv_checksum->signature.length); + + ndr_err = ndr_push_struct_blob(&tmp_blob, mem_ctx, + iconv_convenience, + pac_data, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC (presig) push failed: %s\n", nt_errstr(nt_status))); + talloc_free(pac_data); + return EINVAL; + } + + /* Then sign the result of the previous push, where the sig was zero'ed out */ + ret = make_pac_checksum(mem_ctx, &tmp_blob, srv_checksum, + context, service_keyblock); + + /* Then sign Server checksum */ + ret = make_pac_checksum(mem_ctx, &srv_checksum->signature, kdc_checksum, context, krbtgt_keyblock); + if (ret) { + DEBUG(2, ("making krbtgt PAC checksum failed: %s\n", + smb_get_krb5_error_message(context, ret, mem_ctx))); + talloc_free(pac_data); + return ret; + } + + /* And push it out again, this time to the world. This relies on determanistic pointer values */ + ndr_err = ndr_push_struct_blob(&tmp_blob, mem_ctx, + iconv_convenience, + pac_data, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(1, ("PAC (final) push failed: %s\n", nt_errstr(nt_status))); + talloc_free(pac_data); + return EINVAL; + } + + *pac = tmp_blob; + + return ret; +} + + + krb5_error_code kerberos_create_pac(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + struct auth_serversupplied_info *server_info, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_principal client_principal, + time_t tgs_authtime, + DATA_BLOB *pac) +{ + NTSTATUS nt_status; + krb5_error_code ret; + struct PAC_DATA *pac_data = talloc(mem_ctx, struct PAC_DATA); + struct netr_SamInfo3 *sam3; + union PAC_INFO *u_LOGON_INFO; + struct PAC_LOGON_INFO *LOGON_INFO; + union PAC_INFO *u_LOGON_NAME; + struct PAC_LOGON_NAME *LOGON_NAME; + union PAC_INFO *u_KDC_CHECKSUM; + union PAC_INFO *u_SRV_CHECKSUM; + + char *name; + + enum { + PAC_BUF_LOGON_INFO = 0, + PAC_BUF_LOGON_NAME = 1, + PAC_BUF_SRV_CHECKSUM = 2, + PAC_BUF_KDC_CHECKSUM = 3, + PAC_BUF_NUM_BUFFERS = 4 + }; + + if (!pac_data) { + return ENOMEM; + } + + pac_data->num_buffers = PAC_BUF_NUM_BUFFERS; + pac_data->version = 0; + + pac_data->buffers = talloc_array(pac_data, + struct PAC_BUFFER, + pac_data->num_buffers); + if (!pac_data->buffers) { + talloc_free(pac_data); + return ENOMEM; + } + + /* LOGON_INFO */ + u_LOGON_INFO = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_LOGON_INFO) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_LOGON_INFO].type = PAC_TYPE_LOGON_INFO; + pac_data->buffers[PAC_BUF_LOGON_INFO].info = u_LOGON_INFO; + + /* LOGON_NAME */ + u_LOGON_NAME = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_LOGON_NAME) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_LOGON_NAME].type = PAC_TYPE_LOGON_NAME; + pac_data->buffers[PAC_BUF_LOGON_NAME].info = u_LOGON_NAME; + LOGON_NAME = &u_LOGON_NAME->logon_name; + + /* SRV_CHECKSUM */ + u_SRV_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_SRV_CHECKSUM) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_SRV_CHECKSUM].type = PAC_TYPE_SRV_CHECKSUM; + pac_data->buffers[PAC_BUF_SRV_CHECKSUM].info = u_SRV_CHECKSUM; + + /* KDC_CHECKSUM */ + u_KDC_CHECKSUM = talloc_zero(pac_data->buffers, union PAC_INFO); + if (!u_KDC_CHECKSUM) { + talloc_free(pac_data); + return ENOMEM; + } + pac_data->buffers[PAC_BUF_KDC_CHECKSUM].type = PAC_TYPE_KDC_CHECKSUM; + pac_data->buffers[PAC_BUF_KDC_CHECKSUM].info = u_KDC_CHECKSUM; + + /* now the real work begins... */ + + LOGON_INFO = talloc_zero(u_LOGON_INFO, struct PAC_LOGON_INFO); + if (!LOGON_INFO) { + talloc_free(pac_data); + return ENOMEM; + } + nt_status = auth_convert_server_info_saminfo3(LOGON_INFO, server_info, &sam3); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("Getting Samba info failed: %s\n", nt_errstr(nt_status))); + talloc_free(pac_data); + return EINVAL; + } + + u_LOGON_INFO->logon_info.info = LOGON_INFO; + LOGON_INFO->info3 = *sam3; + + ret = krb5_unparse_name_flags(context, client_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &name); + if (ret) { + return ret; + } + LOGON_NAME->account_name = talloc_strdup(LOGON_NAME, name); + free(name); + /* + this logon_time field is absolutely critical. This is what + caused all our PAC troubles :-) + */ + unix_to_nt_time(&LOGON_NAME->logon_time, tgs_authtime); + + ret = kerberos_encode_pac(mem_ctx, + iconv_convenience, + pac_data, + context, + krbtgt_keyblock, + service_keyblock, + pac); + talloc_free(pac_data); + return ret; +} + +krb5_error_code kerberos_pac_to_server_info(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + krb5_pac pac, + krb5_context context, + struct auth_serversupplied_info **server_info) +{ + NTSTATUS nt_status; + enum ndr_err_code ndr_err; + krb5_error_code ret; + + DATA_BLOB pac_logon_info_in, pac_srv_checksum_in, pac_kdc_checksum_in; + krb5_data k5pac_logon_info_in, k5pac_srv_checksum_in, k5pac_kdc_checksum_in; + + union PAC_INFO info; + union netr_Validation validation; + struct auth_serversupplied_info *server_info_out; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + if (!tmp_ctx) { + return ENOMEM; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_LOGON_INFO, &k5pac_logon_info_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return EINVAL; + } + + pac_logon_info_in = data_blob_const(k5pac_logon_info_in.data, k5pac_logon_info_in.length); + + ndr_err = ndr_pull_union_blob(&pac_logon_info_in, tmp_ctx, iconv_convenience, &info, + PAC_TYPE_LOGON_INFO, + (ndr_pull_flags_fn_t)ndr_pull_PAC_INFO); + krb5_data_free(&k5pac_logon_info_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err) || !info.logon_info.info) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC LOGON_INFO: %s\n", nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return EINVAL; + } + + /* Pull this right into the normal auth sysstem structures */ + validation.sam3 = &info.logon_info.info->info3; + nt_status = make_server_info_netlogon_validation(mem_ctx, + "", + 3, &validation, + &server_info_out); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(tmp_ctx); + return EINVAL; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_SRV_CHECKSUM, &k5pac_srv_checksum_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return ret; + } + + pac_srv_checksum_in = data_blob_const(k5pac_srv_checksum_in.data, k5pac_srv_checksum_in.length); + + ndr_err = ndr_pull_struct_blob(&pac_srv_checksum_in, server_info_out, + iconv_convenience, &server_info_out->pac_srv_sig, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + krb5_data_free(&k5pac_srv_checksum_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the KDC signature: %s\n", + nt_errstr(nt_status))); + return EINVAL; + } + + ret = krb5_pac_get_buffer(context, pac, PAC_TYPE_KDC_CHECKSUM, &k5pac_kdc_checksum_in); + if (ret != 0) { + talloc_free(tmp_ctx); + return ret; + } + + pac_kdc_checksum_in = data_blob_const(k5pac_kdc_checksum_in.data, k5pac_kdc_checksum_in.length); + + ndr_err = ndr_pull_struct_blob(&pac_kdc_checksum_in, server_info_out, + iconv_convenience, &server_info_out->pac_kdc_sig, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + krb5_data_free(&k5pac_kdc_checksum_in); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + nt_status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the KDC signature: %s\n", + nt_errstr(nt_status))); + return EINVAL; + } + + *server_info = server_info_out; + + return 0; +} + + +NTSTATUS kerberos_pac_blob_to_server_info(TALLOC_CTX *mem_ctx, + struct smb_iconv_convenience *iconv_convenience, + DATA_BLOB pac_blob, + krb5_context context, + struct auth_serversupplied_info **server_info) +{ + krb5_error_code ret; + krb5_pac pac; + ret = krb5_pac_parse(context, + pac_blob.data, pac_blob.length, + &pac); + if (ret) { + return map_nt_error_from_unix(ret); + } + + + ret = kerberos_pac_to_server_info(mem_ctx, iconv_convenience, pac, context, server_info); + krb5_pac_free(context, pac); + if (ret) { + return map_nt_error_from_unix(ret); + } + return NT_STATUS_OK; +} diff --git a/source4/auth/kerberos/kerberos_util.c b/source4/auth/kerberos/kerberos_util.c new file mode 100644 index 0000000000..9002715065 --- /dev/null +++ b/source4/auth/kerberos/kerberos_util.c @@ -0,0 +1,681 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos utility functions for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_proto.h" +#include "auth/credentials/credentials_krb5.h" + +struct principal_container { + struct smb_krb5_context *smb_krb5_context; + krb5_principal principal; +}; + +static int free_principal(struct principal_container *pc) +{ + /* current heimdal - 0.6.3, which we need anyway, fixes segfaults here */ + krb5_free_principal(pc->smb_krb5_context->krb5_context, pc->principal); + + return 0; +} + +static krb5_error_code salt_principal_from_credentials(TALLOC_CTX *parent_ctx, + struct cli_credentials *machine_account, + struct smb_krb5_context *smb_krb5_context, + krb5_principal *salt_princ) +{ + krb5_error_code ret; + char *machine_username; + char *salt_body; + char *lower_realm; + const char *salt_principal; + struct principal_container *mem_ctx = talloc(parent_ctx, struct principal_container); + if (!mem_ctx) { + return ENOMEM; + } + + salt_principal = cli_credentials_get_salt_principal(machine_account); + if (salt_principal) { + ret = krb5_parse_name(smb_krb5_context->krb5_context, salt_principal, salt_princ); + } else { + machine_username = talloc_strdup(mem_ctx, cli_credentials_get_username(machine_account)); + + if (!machine_username) { + talloc_free(mem_ctx); + return ENOMEM; + } + + if (machine_username[strlen(machine_username)-1] == '$') { + machine_username[strlen(machine_username)-1] = '\0'; + } + lower_realm = strlower_talloc(mem_ctx, cli_credentials_get_realm(machine_account)); + if (!lower_realm) { + talloc_free(mem_ctx); + return ENOMEM; + } + + salt_body = talloc_asprintf(mem_ctx, "%s.%s", machine_username, + lower_realm); + if (!salt_body) { + talloc_free(mem_ctx); + return ENOMEM; + } + + ret = krb5_make_principal(smb_krb5_context->krb5_context, salt_princ, + cli_credentials_get_realm(machine_account), + "host", salt_body, NULL); + } + + if (ret == 0) { + /* This song-and-dance effectivly puts the principal + * into talloc, so we can't loose it. */ + mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, smb_krb5_context); + mem_ctx->principal = *salt_princ; + talloc_set_destructor(mem_ctx, free_principal); + } + return ret; +} + +/* Obtain the principal set on this context. Requires a + * smb_krb5_context because we are doing krb5 principal parsing with + * the library routines. The returned princ is placed in the talloc + * system by means of a destructor (do *not* free). */ + + krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + krb5_principal *princ) +{ + krb5_error_code ret; + const char *princ_string; + struct principal_container *mem_ctx = talloc(parent_ctx, struct principal_container); + if (!mem_ctx) { + return ENOMEM; + } + + princ_string = cli_credentials_get_principal(credentials, mem_ctx); + + /* A NULL here has meaning, as the gssapi server case will + * then use the principal from the client */ + if (!princ_string) { + talloc_free(mem_ctx); + princ = NULL; + return 0; + } + + ret = krb5_parse_name(smb_krb5_context->krb5_context, + princ_string, princ); + + if (ret == 0) { + /* This song-and-dance effectivly puts the principal + * into talloc, so we can't loose it. */ + mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, smb_krb5_context); + mem_ctx->principal = *princ; + talloc_set_destructor(mem_ctx, free_principal); + } + return ret; +} + +/** + * Return a freshly allocated ccache (destroyed by destructor on child + * of parent_ctx), for a given set of client credentials + */ + + krb5_error_code kinit_to_ccache(TALLOC_CTX *parent_ctx, + struct cli_credentials *credentials, + struct smb_krb5_context *smb_krb5_context, + krb5_ccache ccache) +{ + krb5_error_code ret; + const char *password; + time_t kdc_time = 0; + krb5_principal princ; + int tries; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + + if (!mem_ctx) { + return ENOMEM; + } + + ret = principal_from_credentials(mem_ctx, credentials, smb_krb5_context, &princ); + if (ret) { + talloc_free(mem_ctx); + return ret; + } + + password = cli_credentials_get_password(credentials); + + tries = 2; + while (tries--) { + if (password) { + ret = kerberos_kinit_password_cc(smb_krb5_context->krb5_context, ccache, + princ, + password, NULL, &kdc_time); + } else { + /* No password available, try to use a keyblock instead */ + + krb5_keyblock keyblock; + const struct samr_Password *mach_pwd; + mach_pwd = cli_credentials_get_nt_hash(credentials, mem_ctx); + if (!mach_pwd) { + talloc_free(mem_ctx); + DEBUG(1, ("kinit_to_ccache: No password available for kinit\n")); + return EINVAL; + } + ret = krb5_keyblock_init(smb_krb5_context->krb5_context, + ETYPE_ARCFOUR_HMAC_MD5, + mach_pwd->hash, sizeof(mach_pwd->hash), + &keyblock); + + if (ret == 0) { + ret = kerberos_kinit_keyblock_cc(smb_krb5_context->krb5_context, ccache, + princ, + &keyblock, NULL, &kdc_time); + krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &keyblock); + } + } + + if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { + /* Perhaps we have been given an invalid skew, so try again without it */ + time_t t = time(NULL); + krb5_set_real_time(smb_krb5_context->krb5_context, t, 0); + } else { + /* not a skew problem */ + break; + } + } + + if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { + DEBUG(1,("kinit for %s failed (%s)\n", + cli_credentials_get_principal(credentials, mem_ctx), + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + + /* cope with ticket being in the future due to clock skew */ + if ((unsigned)kdc_time > time(NULL)) { + time_t t = time(NULL); + int time_offset =(unsigned)kdc_time-t; + DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset)); + krb5_set_real_time(smb_krb5_context->krb5_context, t + time_offset + 1, 0); + } + + if (ret == KRB5KDC_ERR_PREAUTH_FAILED && cli_credentials_wrong_password(credentials)) { + ret = kinit_to_ccache(parent_ctx, + credentials, + smb_krb5_context, + ccache); + } + if (ret) { + DEBUG(1,("kinit for %s failed (%s)\n", + cli_credentials_get_principal(credentials, mem_ctx), + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + talloc_free(mem_ctx); + return 0; +} + +static int free_keytab(struct keytab_container *ktc) +{ + krb5_kt_close(ktc->smb_krb5_context->krb5_context, ktc->keytab); + + return 0; +} + +int smb_krb5_open_keytab(TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + const char *keytab_name, struct keytab_container **ktc) +{ + krb5_keytab keytab; + int ret; + ret = krb5_kt_resolve(smb_krb5_context->krb5_context, keytab_name, &keytab); + if (ret) { + DEBUG(1,("failed to open krb5 keytab: %s\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + return ret; + } + + *ktc = talloc(mem_ctx, struct keytab_container); + if (!*ktc) { + return ENOMEM; + } + + (*ktc)->smb_krb5_context = talloc_reference(*ktc, smb_krb5_context); + (*ktc)->keytab = keytab; + talloc_set_destructor(*ktc, free_keytab); + + return 0; +} + +static krb5_error_code keytab_add_keys(TALLOC_CTX *parent_ctx, + const char *princ_string, + krb5_principal princ, + krb5_principal salt_princ, + int kvno, + const char *password_s, + struct smb_krb5_context *smb_krb5_context, + const char **enctype_strings, + krb5_keytab keytab) +{ + int i; + krb5_error_code ret; + krb5_data password; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + if (!mem_ctx) { + return ENOMEM; + } + + password.data = discard_const_p(char *, password_s); + password.length = strlen(password_s); + + for (i=0; enctype_strings[i]; i++) { + krb5_keytab_entry entry; + krb5_enctype enctype; + ret = krb5_string_to_enctype(smb_krb5_context->krb5_context, enctype_strings[i], &enctype); + if (ret != 0) { + DEBUG(1, ("Failed to interpret %s as a krb5 encryption type: %s\n", + enctype_strings[i], + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + ret = create_kerberos_key_from_string(smb_krb5_context->krb5_context, + salt_princ, &password, &entry.keyblock, enctype); + if (ret != 0) { + talloc_free(mem_ctx); + return ret; + } + + entry.principal = princ; + entry.vno = kvno; + ret = krb5_kt_add_entry(smb_krb5_context->krb5_context, keytab, &entry); + if (ret != 0) { + DEBUG(1, ("Failed to add %s entry for %s(kvno %d) to keytab: %s\n", + enctype_strings[i], + princ_string, + kvno, + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock); + return ret; + } + + DEBUG(5, ("Added %s(kvno %d) to keytab (%s)\n", + princ_string, kvno, + enctype_strings[i])); + + krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock); + } + talloc_free(mem_ctx); + return 0; +} + +static int create_keytab(TALLOC_CTX *parent_ctx, + struct cli_credentials *machine_account, + struct smb_krb5_context *smb_krb5_context, + const char **enctype_strings, + krb5_keytab keytab, + bool add_old) +{ + krb5_error_code ret; + const char *password_s; + const char *old_secret; + int kvno; + krb5_principal salt_princ; + krb5_principal princ; + const char *princ_string; + + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + if (!mem_ctx) { + return ENOMEM; + } + + princ_string = cli_credentials_get_principal(machine_account, mem_ctx); + /* Get the principal we will store the new keytab entries under */ + ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, &princ); + if (ret) { + DEBUG(1,("create_keytab: makeing krb5 principal failed (%s)\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + + /* The salt used to generate these entries may be different however, fetch that */ + ret = salt_principal_from_credentials(mem_ctx, machine_account, + smb_krb5_context, + &salt_princ); + if (ret) { + DEBUG(1,("create_keytab: makeing salt principal failed (%s)\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + + /* Finally, do the dance to get the password to put in the entry */ + password_s = cli_credentials_get_password(machine_account); + if (!password_s) { + krb5_keytab_entry entry; + const struct samr_Password *mach_pwd; + + if (!str_list_check(enctype_strings, "arcfour-hmac-md5")) { + DEBUG(1, ("Asked to create keytab, but with only an NT hash supplied, " + "but not listing arcfour-hmac-md5 as an enc type to include in the keytab!\n")); + talloc_free(mem_ctx); + return EINVAL; + } + + /* If we don't have the plaintext password, try for + * the MD4 password hash */ + mach_pwd = cli_credentials_get_nt_hash(machine_account, mem_ctx); + if (!mach_pwd) { + /* OK, nothing to do here */ + talloc_free(mem_ctx); + return 0; + } + ret = krb5_keyblock_init(smb_krb5_context->krb5_context, + ETYPE_ARCFOUR_HMAC_MD5, + mach_pwd->hash, sizeof(mach_pwd->hash), + &entry.keyblock); + if (ret) { + DEBUG(1, ("create_keytab: krb5_keyblock_init failed: %s\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + + entry.principal = princ; + entry.vno = cli_credentials_get_kvno(machine_account); + ret = krb5_kt_add_entry(smb_krb5_context->krb5_context, keytab, &entry); + if (ret) { + DEBUG(1, ("Failed to add ARCFOUR_HMAC (only) entry for %s to keytab: %s", + cli_credentials_get_principal(machine_account, mem_ctx), + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock); + return ret; + } + + DEBUG(5, ("Added %s(kvno %d) to keytab (arcfour-hmac-md5)\n", + cli_credentials_get_principal(machine_account, mem_ctx), + cli_credentials_get_kvno(machine_account))); + + krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &entry.keyblock); + + /* Can't go any further, we only have this one key */ + talloc_free(mem_ctx); + return 0; + } + + kvno = cli_credentials_get_kvno(machine_account); + /* good, we actually have the real plaintext */ + ret = keytab_add_keys(mem_ctx, princ_string, princ, salt_princ, + kvno, password_s, smb_krb5_context, + enctype_strings, keytab); + if (!ret) { + talloc_free(mem_ctx); + return ret; + } + + if (!add_old || kvno == 0) { + talloc_free(mem_ctx); + return 0; + } + + old_secret = cli_credentials_get_old_password(machine_account); + if (!old_secret) { + talloc_free(mem_ctx); + return 0; + } + + ret = keytab_add_keys(mem_ctx, princ_string, princ, salt_princ, + kvno - 1, old_secret, smb_krb5_context, + enctype_strings, keytab); + if (!ret) { + talloc_free(mem_ctx); + return ret; + } + + talloc_free(mem_ctx); + return 0; +} + + +/* + * Walk the keytab, looking for entries of this principal name, with KVNO other than current kvno -1. + * + * These entries are now stale, we only keep the current, and previous entries around. + * + * Inspired by the code in Samba3 for 'use kerberos keytab'. + * + */ + +static krb5_error_code remove_old_entries(TALLOC_CTX *parent_ctx, + struct cli_credentials *machine_account, + struct smb_krb5_context *smb_krb5_context, + krb5_keytab keytab, bool *found_previous) +{ + krb5_error_code ret, ret2; + krb5_kt_cursor cursor; + krb5_principal princ; + int kvno; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + const char *princ_string; + if (!mem_ctx) { + return ENOMEM; + } + + *found_previous = false; + princ_string = cli_credentials_get_principal(machine_account, mem_ctx); + + /* Get the principal we will store the new keytab entries under */ + ret = principal_from_credentials(mem_ctx, machine_account, smb_krb5_context, &princ); + if (ret) { + DEBUG(1,("update_keytab: makeing krb5 principal failed (%s)\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + + kvno = cli_credentials_get_kvno(machine_account); + + /* for each entry in the keytab */ + ret = krb5_kt_start_seq_get(smb_krb5_context->krb5_context, keytab, &cursor); + switch (ret) { + case 0: + break; + case HEIM_ERR_OPNOTSUPP: + case ENOENT: + case KRB5_KT_END: + /* no point enumerating if there isn't anything here */ + talloc_free(mem_ctx); + return 0; + default: + DEBUG(1,("failed to open keytab for read of old entries: %s\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + talloc_free(mem_ctx); + return ret; + } + + while (!ret) { + krb5_keytab_entry entry; + ret = krb5_kt_next_entry(smb_krb5_context->krb5_context, keytab, &entry, &cursor); + if (ret) { + break; + } + /* if it matches our principal */ + if (!krb5_kt_compare(smb_krb5_context->krb5_context, &entry, princ, 0, 0)) { + /* Free the entry, it wasn't the one we were looking for anyway */ + krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry); + continue; + } + + /* delete it, if it is not kvno -1 */ + if (entry.vno != (kvno - 1 )) { + /* Release the enumeration. We are going to + * have to start this from the top again, + * because deletes during enumeration may not + * always be consistant. + * + * Also, the enumeration locks a FILE: keytab + */ + + krb5_kt_end_seq_get(smb_krb5_context->krb5_context, keytab, &cursor); + + ret = krb5_kt_remove_entry(smb_krb5_context->krb5_context, keytab, &entry); + krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry); + + /* Deleted: Restart from the top */ + ret2 = krb5_kt_start_seq_get(smb_krb5_context->krb5_context, keytab, &cursor); + if (ret2) { + krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry); + DEBUG(1,("failed to restart enumeration of keytab: %s\n", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + + talloc_free(mem_ctx); + return ret2; + } + + if (ret) { + break; + } + + } else { + *found_previous = true; + } + + /* Free the entry, we don't need it any more */ + krb5_kt_free_entry(smb_krb5_context->krb5_context, &entry); + + + } + krb5_kt_end_seq_get(smb_krb5_context->krb5_context, keytab, &cursor); + + switch (ret) { + case 0: + break; + case ENOENT: + case KRB5_KT_END: + ret = 0; + break; + default: + DEBUG(1,("failed in deleting old entries for principal: %s: %s\n", + princ_string, + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + ret, mem_ctx))); + } + talloc_free(mem_ctx); + return ret; +} + +int smb_krb5_update_keytab(TALLOC_CTX *parent_ctx, + struct cli_credentials *machine_account, + struct smb_krb5_context *smb_krb5_context, + const char **enctype_strings, + struct keytab_container *keytab_container) +{ + krb5_error_code ret; + bool found_previous; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + if (!mem_ctx) { + return ENOMEM; + } + + ret = remove_old_entries(mem_ctx, machine_account, + smb_krb5_context, keytab_container->keytab, &found_previous); + if (ret != 0) { + talloc_free(mem_ctx); + return ret; + } + + /* Create a new keytab. If during the cleanout we found + * entires for kvno -1, then don't try and duplicate them. + * Otherwise, add kvno, and kvno -1 */ + + ret = create_keytab(mem_ctx, machine_account, smb_krb5_context, + enctype_strings, + keytab_container->keytab, + found_previous ? false : true); + talloc_free(mem_ctx); + return ret; +} + +int smb_krb5_create_memory_keytab(TALLOC_CTX *parent_ctx, + struct cli_credentials *machine_account, + struct smb_krb5_context *smb_krb5_context, + const char **enctype_strings, + struct keytab_container **keytab_container) +{ + krb5_error_code ret; + TALLOC_CTX *mem_ctx = talloc_new(parent_ctx); + const char *rand_string; + const char *keytab_name; + if (!mem_ctx) { + return ENOMEM; + } + + *keytab_container = talloc(mem_ctx, struct keytab_container); + + rand_string = generate_random_str(mem_ctx, 16); + if (!rand_string) { + talloc_free(mem_ctx); + return ENOMEM; + } + + keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s", + rand_string); + if (!keytab_name) { + talloc_free(mem_ctx); + return ENOMEM; + } + + ret = smb_krb5_open_keytab(mem_ctx, smb_krb5_context, keytab_name, keytab_container); + if (ret) { + return ret; + } + + ret = smb_krb5_update_keytab(mem_ctx, machine_account, smb_krb5_context, enctype_strings, *keytab_container); + if (ret == 0) { + talloc_steal(parent_ctx, *keytab_container); + } else { + *keytab_container = NULL; + } + talloc_free(mem_ctx); + return ret; +} + diff --git a/source4/auth/kerberos/krb5_init_context.c b/source4/auth/kerberos/krb5_init_context.c new file mode 100644 index 0000000000..82e42a4560 --- /dev/null +++ b/source4/auth/kerberos/krb5_init_context.c @@ -0,0 +1,482 @@ +/* + Unix SMB/CIFS implementation. + Wrapper for krb5_init_context + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Stefan Metzmacher 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "lib/socket/socket.h" +#include "lib/stream/packet.h" +#include "system/network.h" +#include "lib/events/events.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" + +/* + context structure for operations on cldap packets +*/ +struct smb_krb5_socket { + struct socket_context *sock; + + /* the fd event */ + struct fd_event *fde; + + NTSTATUS status; + DATA_BLOB request, reply; + + struct packet_context *packet; + + size_t partial_read; + + krb5_krbhst_info *hi; +}; + +static int smb_krb5_context_destroy_1(struct smb_krb5_context *ctx) +{ + krb5_free_context(ctx->krb5_context); + return 0; +} + +static int smb_krb5_context_destroy_2(struct smb_krb5_context *ctx) +{ + /* Otherwise krb5_free_context will try and close what we have already free()ed */ + krb5_set_warn_dest(ctx->krb5_context, NULL); + krb5_closelog(ctx->krb5_context, ctx->logf); + smb_krb5_context_destroy_1(ctx); + return 0; +} + +/* We never close down the DEBUG system, and no need to unreference the use */ +static void smb_krb5_debug_close(void *private) { + return; +} + +static void smb_krb5_debug_wrapper(const char *timestr, const char *msg, void *private) +{ + DEBUG(2, ("Kerberos: %s\n", msg)); +} + +/* + handle recv events on a smb_krb5 socket +*/ +static void smb_krb5_socket_recv(struct smb_krb5_socket *smb_krb5) +{ + TALLOC_CTX *tmp_ctx = talloc_new(smb_krb5); + DATA_BLOB blob; + size_t nread, dsize; + + smb_krb5->status = socket_pending(smb_krb5->sock, &dsize); + if (!NT_STATUS_IS_OK(smb_krb5->status)) { + talloc_free(tmp_ctx); + return; + } + + blob = data_blob_talloc(tmp_ctx, NULL, dsize); + if (blob.data == NULL && dsize != 0) { + smb_krb5->status = NT_STATUS_NO_MEMORY; + talloc_free(tmp_ctx); + return; + } + + smb_krb5->status = socket_recv(smb_krb5->sock, blob.data, blob.length, &nread); + if (!NT_STATUS_IS_OK(smb_krb5->status)) { + talloc_free(tmp_ctx); + return; + } + blob.length = nread; + + if (nread == 0) { + smb_krb5->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; + talloc_free(tmp_ctx); + return; + } + + DEBUG(2,("Received smb_krb5 packet of length %d\n", + (int)blob.length)); + + talloc_steal(smb_krb5, blob.data); + smb_krb5->reply = blob; + talloc_free(tmp_ctx); +} + +static NTSTATUS smb_krb5_full_packet(void *private, DATA_BLOB data) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private, struct smb_krb5_socket); + talloc_steal(smb_krb5, data.data); + smb_krb5->reply = data; + smb_krb5->reply.length -= 4; + smb_krb5->reply.data += 4; + return NT_STATUS_OK; +} + +/* + handle request timeouts +*/ +static void smb_krb5_request_timeout(struct event_context *event_ctx, + struct timed_event *te, struct timeval t, + void *private) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private, struct smb_krb5_socket); + DEBUG(5,("Timed out smb_krb5 packet\n")); + smb_krb5->status = NT_STATUS_IO_TIMEOUT; +} + +static void smb_krb5_error_handler(void *private, NTSTATUS status) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private, struct smb_krb5_socket); + smb_krb5->status = status; +} + +/* + handle send events on a smb_krb5 socket +*/ +static void smb_krb5_socket_send(struct smb_krb5_socket *smb_krb5) +{ + NTSTATUS status; + + size_t len; + + len = smb_krb5->request.length; + status = socket_send(smb_krb5->sock, &smb_krb5->request, &len); + + if (!NT_STATUS_IS_OK(status)) return; + + EVENT_FD_READABLE(smb_krb5->fde); + + EVENT_FD_NOT_WRITEABLE(smb_krb5->fde); + return; +} + + +/* + handle fd events on a smb_krb5_socket +*/ +static void smb_krb5_socket_handler(struct event_context *ev, struct fd_event *fde, + uint16_t flags, void *private) +{ + struct smb_krb5_socket *smb_krb5 = talloc_get_type(private, struct smb_krb5_socket); + switch (smb_krb5->hi->proto) { + case KRB5_KRBHST_UDP: + if (flags & EVENT_FD_READ) { + smb_krb5_socket_recv(smb_krb5); + return; + } + if (flags & EVENT_FD_WRITE) { + smb_krb5_socket_send(smb_krb5); + return; + } + /* not reached */ + return; + case KRB5_KRBHST_TCP: + if (flags & EVENT_FD_READ) { + packet_recv(smb_krb5->packet); + return; + } + if (flags & EVENT_FD_WRITE) { + packet_queue_run(smb_krb5->packet); + return; + } + /* not reached */ + return; + case KRB5_KRBHST_HTTP: + /* can't happen */ + break; + } +} + + +krb5_error_code smb_krb5_send_and_recv_func(krb5_context context, + void *data, + krb5_krbhst_info *hi, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf) +{ + krb5_error_code ret; + NTSTATUS status; + struct socket_address *remote_addr; + const char *name; + struct addrinfo *ai, *a; + struct smb_krb5_socket *smb_krb5; + + struct event_context *ev = talloc_get_type(data, struct event_context); + + DATA_BLOB send_blob = data_blob_const(send_buf->data, send_buf->length); + + ret = krb5_krbhst_get_addrinfo(context, hi, &ai); + if (ret) { + return ret; + } + + for (a = ai; a; a = ai->ai_next) { + smb_krb5 = talloc(NULL, struct smb_krb5_socket); + if (!smb_krb5) { + return ENOMEM; + } + smb_krb5->hi = hi; + + switch (a->ai_family) { + case PF_INET: + name = "ipv4"; + break; +#ifdef HAVE_IPV6 + case PF_INET6: + name = "ipv6"; + break; +#endif + default: + talloc_free(smb_krb5); + return EINVAL; + } + + status = NT_STATUS_INVALID_PARAMETER; + switch (hi->proto) { + case KRB5_KRBHST_UDP: + if (lp_parm_bool(global_loadparm, NULL, "krb5", "udp", true)) { + status = socket_create(name, SOCKET_TYPE_DGRAM, &smb_krb5->sock, 0); + } + break; + case KRB5_KRBHST_TCP: + if (lp_parm_bool(global_loadparm, NULL, "krb5", "tcp", true)) { + status = socket_create(name, SOCKET_TYPE_STREAM, &smb_krb5->sock, 0); + } + break; + case KRB5_KRBHST_HTTP: + talloc_free(smb_krb5); + return EINVAL; + } + if (!NT_STATUS_IS_OK(status)) { + talloc_free(smb_krb5); + continue; + } + + talloc_steal(smb_krb5, smb_krb5->sock); + + remote_addr = socket_address_from_sockaddr(smb_krb5, a->ai_addr, a->ai_addrlen); + if (!remote_addr) { + talloc_free(smb_krb5); + continue; + } + + status = socket_connect_ev(smb_krb5->sock, NULL, remote_addr, 0, + NULL, ev); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(smb_krb5); + continue; + } + talloc_free(remote_addr); + + /* Setup the FDE, start listening for read events + * from the start (otherwise we may miss a socket + * drop) and mark as AUTOCLOSE along with the fde */ + + /* Ths is equivilant to EVENT_FD_READABLE(smb_krb5->fde) */ + smb_krb5->fde = event_add_fd(ev, smb_krb5->sock, + socket_get_fd(smb_krb5->sock), + EVENT_FD_READ|EVENT_FD_AUTOCLOSE, + smb_krb5_socket_handler, smb_krb5); + /* its now the job of the event layer to close the socket */ + socket_set_flags(smb_krb5->sock, SOCKET_FLAG_NOCLOSE); + + event_add_timed(ev, smb_krb5, + timeval_current_ofs(timeout, 0), + smb_krb5_request_timeout, smb_krb5); + + + smb_krb5->status = NT_STATUS_OK; + smb_krb5->reply = data_blob(NULL, 0); + + switch (hi->proto) { + case KRB5_KRBHST_UDP: + EVENT_FD_WRITEABLE(smb_krb5->fde); + smb_krb5->request = send_blob; + break; + case KRB5_KRBHST_TCP: + + smb_krb5->packet = packet_init(smb_krb5); + if (smb_krb5->packet == NULL) { + talloc_free(smb_krb5); + return ENOMEM; + } + packet_set_private(smb_krb5->packet, smb_krb5); + packet_set_socket(smb_krb5->packet, smb_krb5->sock); + packet_set_callback(smb_krb5->packet, smb_krb5_full_packet); + packet_set_full_request(smb_krb5->packet, packet_full_request_u32); + packet_set_error_handler(smb_krb5->packet, smb_krb5_error_handler); + packet_set_event_context(smb_krb5->packet, ev); + packet_set_fde(smb_krb5->packet, smb_krb5->fde); + + smb_krb5->request = data_blob_talloc(smb_krb5, NULL, send_blob.length + 4); + RSIVAL(smb_krb5->request.data, 0, send_blob.length); + memcpy(smb_krb5->request.data+4, send_blob.data, send_blob.length); + packet_send(smb_krb5->packet, smb_krb5->request); + break; + case KRB5_KRBHST_HTTP: + talloc_free(smb_krb5); + return EINVAL; + } + while ((NT_STATUS_IS_OK(smb_krb5->status)) && !smb_krb5->reply.length) { + if (event_loop_once(ev) != 0) { + talloc_free(smb_krb5); + return EINVAL; + } + } + if (NT_STATUS_EQUAL(smb_krb5->status, NT_STATUS_IO_TIMEOUT)) { + talloc_free(smb_krb5); + continue; + } + + if (!NT_STATUS_IS_OK(smb_krb5->status)) { + DEBUG(2,("Error reading smb_krb5 reply packet: %s\n", nt_errstr(smb_krb5->status))); + talloc_free(smb_krb5); + continue; + } + + ret = krb5_data_copy(recv_buf, smb_krb5->reply.data, smb_krb5->reply.length); + if (ret) { + talloc_free(smb_krb5); + return ret; + } + talloc_free(smb_krb5); + + break; + } + if (a) { + return 0; + } + return KRB5_KDC_UNREACH; +} + +krb5_error_code smb_krb5_init_context(void *parent_ctx, + struct event_context *ev, + struct loadparm_context *lp_ctx, + struct smb_krb5_context **smb_krb5_context) +{ + krb5_error_code ret; + TALLOC_CTX *tmp_ctx; + char **config_files; + const char *config_file; + + initialize_krb5_error_table(); + + tmp_ctx = talloc_new(parent_ctx); + *smb_krb5_context = talloc(tmp_ctx, struct smb_krb5_context); + + if (!*smb_krb5_context || !tmp_ctx) { + talloc_free(tmp_ctx); + return ENOMEM; + } + + ret = krb5_init_context(&(*smb_krb5_context)->krb5_context); + if (ret) { + DEBUG(1,("krb5_init_context failed (%s)\n", + error_message(ret))); + talloc_free(tmp_ctx); + return ret; + } + + talloc_set_destructor(*smb_krb5_context, smb_krb5_context_destroy_1); + + config_file = config_path(tmp_ctx, lp_ctx, "krb5.conf"); + if (!config_file) { + talloc_free(tmp_ctx); + return ENOMEM; + } + + /* Use our local krb5.conf file by default */ + ret = krb5_prepend_config_files_default(config_file == NULL?"":config_file, &config_files); + if (ret) { + DEBUG(1,("krb5_prepend_config_files_default failed (%s)\n", + smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + + ret = krb5_set_config_files((*smb_krb5_context)->krb5_context, + config_files); + krb5_free_config_files(config_files); + if (ret) { + DEBUG(1,("krb5_set_config_files failed (%s)\n", + smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + + if (lp_realm(lp_ctx) && *lp_realm(lp_ctx)) { + char *upper_realm = strupper_talloc(tmp_ctx, lp_realm(lp_ctx)); + if (!upper_realm) { + DEBUG(1,("gensec_krb5_start: could not uppercase realm: %s\n", lp_realm(lp_ctx))); + talloc_free(tmp_ctx); + return ENOMEM; + } + ret = krb5_set_default_realm((*smb_krb5_context)->krb5_context, upper_realm); + if (ret) { + DEBUG(1,("krb5_set_default_realm failed (%s)\n", + smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + } + + /* TODO: Should we have a different name here? */ + ret = krb5_initlog((*smb_krb5_context)->krb5_context, "Samba", &(*smb_krb5_context)->logf); + + if (ret) { + DEBUG(1,("krb5_initlog failed (%s)\n", + smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + + talloc_set_destructor(*smb_krb5_context, smb_krb5_context_destroy_2); + + ret = krb5_addlog_func((*smb_krb5_context)->krb5_context, (*smb_krb5_context)->logf, 0 /* min */, -1 /* max */, + smb_krb5_debug_wrapper, smb_krb5_debug_close, NULL); + if (ret) { + DEBUG(1,("krb5_addlog_func failed (%s)\n", + smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + krb5_set_warn_dest((*smb_krb5_context)->krb5_context, (*smb_krb5_context)->logf); + + /* Set use of our socket lib */ + ret = krb5_set_send_to_kdc_func((*smb_krb5_context)->krb5_context, + smb_krb5_send_and_recv_func, + ev); + if (ret) { + DEBUG(1,("krb5_set_send_recv_func failed (%s)\n", + smb_get_krb5_error_message((*smb_krb5_context)->krb5_context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return ret; + } + + talloc_steal(parent_ctx, *smb_krb5_context); + talloc_free(tmp_ctx); + + /* Set options in kerberos */ + + krb5_set_dns_canonicalize_hostname((*smb_krb5_context)->krb5_context, + lp_parm_bool(lp_ctx, NULL, "krb5", "set_dns_canonicalize", false)); + + return 0; +} + diff --git a/source4/auth/kerberos/krb5_init_context.h b/source4/auth/kerberos/krb5_init_context.h new file mode 100644 index 0000000000..162a19a4ab --- /dev/null +++ b/source4/auth/kerberos/krb5_init_context.h @@ -0,0 +1,37 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5 routines for active directory + Copyright (C) Andrew Bartlett 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +struct smb_krb5_context { + krb5_context krb5_context; + krb5_log_facility *logf; +}; + +struct event_context; +struct loadparm_context; +krb5_error_code smb_krb5_init_context(void *parent_ctx, struct event_context *ev, + struct loadparm_context *lp_ctx, + struct smb_krb5_context **smb_krb5_context); +void smb_krb5_free_context(struct smb_krb5_context *smb_krb5_context); + +krb5_error_code smb_krb5_send_and_recv_func(krb5_context context, + void *data, + krb5_krbhst_info *hi, + time_t timeout, + const krb5_data *send_buf, + krb5_data *recv_buf); |