1/* 2 Unix SMB/CIFS implementation. 3 simple kerberos5 routines for active directory 4 Copyright (C) Andrew Tridgell 2001 5 Copyright (C) Luke Howard 2002-2003 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2 of the License, or 10 (at your option) any later version. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this program; if not, write to the Free Software 19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20*/ 21 22#include "includes.h" 23 24#ifdef HAVE_KRB5 25 26#ifdef HAVE_KRB5_KEYBLOCK_KEYVALUE 27#define KRB5_KEY_TYPE(k) ((k)->keytype) 28#define KRB5_KEY_LENGTH(k) ((k)->keyvalue.length) 29#define KRB5_KEY_DATA(k) ((k)->keyvalue.data) 30#else 31#define KRB5_KEY_TYPE(k) ((k)->enctype) 32#define KRB5_KEY_LENGTH(k) ((k)->length) 33#define KRB5_KEY_DATA(k) ((k)->contents) 34#endif /* HAVE_KRB5_KEYBLOCK_KEYVALUE */ 35 36#ifndef HAVE_KRB5_SET_REAL_TIME 37/* 38 * This function is not in the Heimdal mainline. 39 */ 40 krb5_error_code krb5_set_real_time(krb5_context context, int32_t seconds, int32_t microseconds) 41{ 42 krb5_error_code ret; 43 int32_t sec, usec; 44 45 ret = krb5_us_timeofday(context, &sec, &usec); 46 if (ret) 47 return ret; 48 49 context->kdc_sec_offset = seconds - sec; 50 context->kdc_usec_offset = microseconds - usec; 51 52 return 0; 53} 54#endif 55 56#if defined(HAVE_KRB5_SET_DEFAULT_IN_TKT_ETYPES) && !defined(HAVE_KRB5_SET_DEFAULT_TGS_KTYPES) 57 krb5_error_code krb5_set_default_tgs_ktypes(krb5_context ctx, const krb5_enctype *enc) 58{ 59 return krb5_set_default_in_tkt_etypes(ctx, enc); 60} 61#endif 62 63#if defined(HAVE_ADDR_TYPE_IN_KRB5_ADDRESS) 64/* HEIMDAL */ 65 void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr) 66{ 67 pkaddr->addr_type = KRB5_ADDRESS_INET; 68 pkaddr->address.length = sizeof(((struct sockaddr_in *)paddr)->sin_addr); 69 pkaddr->address.data = (char *)&(((struct sockaddr_in *)paddr)->sin_addr); 70} 71#elif defined(HAVE_ADDRTYPE_IN_KRB5_ADDRESS) 72/* MIT */ 73 void setup_kaddr( krb5_address *pkaddr, struct sockaddr *paddr) 74{ 75 pkaddr->addrtype = ADDRTYPE_INET; 76 pkaddr->length = sizeof(((struct sockaddr_in *)paddr)->sin_addr); 77 pkaddr->contents = (krb5_octet *)&(((struct sockaddr_in *)paddr)->sin_addr); 78} 79#else 80#error UNKNOWN_ADDRTYPE 81#endif 82 83#if defined(HAVE_KRB5_PRINCIPAL2SALT) && defined(HAVE_KRB5_USE_ENCTYPE) && defined(HAVE_KRB5_STRING_TO_KEY) && defined(HAVE_KRB5_ENCRYPT_BLOCK) 84 int create_kerberos_key_from_string_direct(krb5_context context, 85 krb5_principal host_princ, 86 krb5_data *password, 87 krb5_keyblock *key, 88 krb5_enctype enctype) 89{ 90 int ret; 91 krb5_data salt; 92 krb5_encrypt_block eblock; 93 94 ret = krb5_principal2salt(context, host_princ, &salt); 95 if (ret) { 96 DEBUG(1,("krb5_principal2salt failed (%s)\n", error_message(ret))); 97 return ret; 98 } 99 krb5_use_enctype(context, &eblock, enctype); 100 ret = krb5_string_to_key(context, &eblock, key, password, &salt); 101 SAFE_FREE(salt.data); 102 return ret; 103} 104#elif defined(HAVE_KRB5_GET_PW_SALT) && defined(HAVE_KRB5_STRING_TO_KEY_SALT) 105 int create_kerberos_key_from_string_direct(krb5_context context, 106 krb5_principal host_princ, 107 krb5_data *password, 108 krb5_keyblock *key, 109 krb5_enctype enctype) 110{ 111 int ret; 112 krb5_salt salt; 113 114 ret = krb5_get_pw_salt(context, host_princ, &salt); 115 if (ret) { 116 DEBUG(1,("krb5_get_pw_salt failed (%s)\n", error_message(ret))); 117 return ret; 118 } 119 return krb5_string_to_key_salt(context, enctype, password->data, 120 salt, key); 121} 122#else 123#error UNKNOWN_CREATE_KEY_FUNCTIONS 124#endif 125 126 int create_kerberos_key_from_string(krb5_context context, 127 krb5_principal host_princ, 128 krb5_data *password, 129 krb5_keyblock *key, 130 krb5_enctype enctype) 131{ 132 krb5_principal salt_princ = NULL; 133 int ret; 134 /* 135 * Check if we've determined that the KDC is salting keys for this 136 * principal/enctype in a non-obvious way. If it is, try to match 137 * its behavior. 138 */ 139 salt_princ = kerberos_fetch_salt_princ_for_host_princ(context, host_princ, enctype); 140 ret = create_kerberos_key_from_string_direct(context, salt_princ ? salt_princ : host_princ, password, key, enctype); 141 if (salt_princ) { 142 krb5_free_principal(context, salt_princ); 143 } 144 return ret; 145} 146 147#if defined(HAVE_KRB5_GET_PERMITTED_ENCTYPES) 148 krb5_error_code get_kerberos_allowed_etypes(krb5_context context, 149 krb5_enctype **enctypes) 150{ 151 return krb5_get_permitted_enctypes(context, enctypes); 152} 153#elif defined(HAVE_KRB5_GET_DEFAULT_IN_TKT_ETYPES) 154 krb5_error_code get_kerberos_allowed_etypes(krb5_context context, 155 krb5_enctype **enctypes) 156{ 157 return krb5_get_default_in_tkt_etypes(context, enctypes); 158} 159#else 160#error UNKNOWN_GET_ENCTYPES_FUNCTIONS 161#endif 162 163 void free_kerberos_etypes(krb5_context context, 164 krb5_enctype *enctypes) 165{ 166#if defined(HAVE_KRB5_FREE_KTYPES) 167 krb5_free_ktypes(context, enctypes); 168 return; 169#else 170 SAFE_FREE(enctypes); 171 return; 172#endif 173} 174 175#if defined(HAVE_KRB5_AUTH_CON_SETKEY) && !defined(HAVE_KRB5_AUTH_CON_SETUSERUSERKEY) 176 krb5_error_code krb5_auth_con_setuseruserkey(krb5_context context, 177 krb5_auth_context auth_context, 178 krb5_keyblock *keyblock) 179{ 180 return krb5_auth_con_setkey(context, auth_context, keyblock); 181} 182#endif 183 184 void get_auth_data_from_tkt(DATA_BLOB *auth_data, krb5_ticket *tkt) 185{ 186#if defined(HAVE_KRB5_TKT_ENC_PART2) 187 if (tkt->enc_part2 && tkt->enc_part2->authorization_data && tkt->enc_part2->authorization_data[0] && tkt->enc_part2->authorization_data[0]->length) 188 *auth_data = data_blob(tkt->enc_part2->authorization_data[0]->contents, 189 tkt->enc_part2->authorization_data[0]->length); 190#else 191 if (tkt->ticket.authorization_data && tkt->ticket.authorization_data->len) 192 *auth_data = data_blob(tkt->ticket.authorization_data->val->ad_data.data, 193 tkt->ticket.authorization_data->val->ad_data.length); 194#endif 195} 196 197 krb5_const_principal get_principal_from_tkt(krb5_ticket *tkt) 198{ 199#if defined(HAVE_KRB5_TKT_ENC_PART2) 200 return tkt->enc_part2->client; 201#else 202 return tkt->client; 203#endif 204} 205 206#if !defined(HAVE_KRB5_LOCATE_KDC) 207 krb5_error_code krb5_locate_kdc(krb5_context ctx, const krb5_data *realm, struct sockaddr **addr_pp, int *naddrs, int get_masters) 208{ 209 krb5_krbhst_handle hnd; 210 krb5_krbhst_info *hinfo; 211 krb5_error_code rc; 212 int num_kdcs, i; 213 struct sockaddr *sa; 214 struct addrinfo *ai; 215 216 *addr_pp = NULL; 217 *naddrs = 0; 218 219 rc = krb5_krbhst_init(ctx, realm->data, KRB5_KRBHST_KDC, &hnd); 220 if (rc) { 221 DEBUG(0, ("krb5_locate_kdc: krb5_krbhst_init failed (%s)\n", error_message(rc))); 222 return rc; 223 } 224 225 for ( num_kdcs = 0; (rc = krb5_krbhst_next(ctx, hnd, &hinfo) == 0); num_kdcs++) 226 ; 227 228 krb5_krbhst_reset(ctx, hnd); 229 230 if (!num_kdcs) { 231 DEBUG(0, ("krb5_locate_kdc: zero kdcs found !\n")); 232 krb5_krbhst_free(ctx, hnd); 233 return -1; 234 } 235 236 sa = SMB_MALLOC_ARRAY( struct sockaddr, num_kdcs ); 237 if (!sa) { 238 DEBUG(0, ("krb5_locate_kdc: malloc failed\n")); 239 krb5_krbhst_free(ctx, hnd); 240 naddrs = 0; 241 return -1; 242 } 243 244 memset(sa, '\0', sizeof(struct sockaddr) * num_kdcs ); 245 246 for (i = 0; i < num_kdcs && (rc = krb5_krbhst_next(ctx, hnd, &hinfo) == 0); i++) { 247 248#if defined(HAVE_KRB5_KRBHST_GET_ADDRINFO) 249 rc = krb5_krbhst_get_addrinfo(ctx, hinfo, &ai); 250 if (rc) { 251 DEBUG(0,("krb5_krbhst_get_addrinfo failed: %s\n", error_message(rc))); 252 continue; 253 } 254#endif 255 if (hinfo->ai && hinfo->ai->ai_family == AF_INET) 256 memcpy(&sa[i], hinfo->ai->ai_addr, sizeof(struct sockaddr)); 257 } 258 259 krb5_krbhst_free(ctx, hnd); 260 261 *naddrs = num_kdcs; 262 *addr_pp = sa; 263 return 0; 264} 265#endif 266 267#if !defined(HAVE_KRB5_FREE_UNPARSED_NAME) 268 void krb5_free_unparsed_name(krb5_context context, char *val) 269{ 270 SAFE_FREE(val); 271} 272#endif 273 274 void kerberos_free_data_contents(krb5_context context, krb5_data *pdata) 275{ 276#if defined(HAVE_KRB5_FREE_DATA_CONTENTS) 277 if (pdata->data) { 278 krb5_free_data_contents(context, pdata); 279 } 280#else 281 SAFE_FREE(pdata->data); 282#endif 283} 284 285 void kerberos_set_creds_enctype(krb5_creds *pcreds, int enctype) 286{ 287#if defined(HAVE_KRB5_KEYBLOCK_IN_CREDS) 288 KRB5_KEY_TYPE((&pcreds->keyblock)) = enctype; 289#elif defined(HAVE_KRB5_SESSION_IN_CREDS) 290 KRB5_KEY_TYPE((&pcreds->session)) = enctype; 291#else 292#error UNKNOWN_KEYBLOCK_MEMBER_IN_KRB5_CREDS_STRUCT 293#endif 294} 295 296 BOOL kerberos_compatible_enctypes(krb5_context context, 297 krb5_enctype enctype1, 298 krb5_enctype enctype2) 299{ 300#if defined(HAVE_KRB5_C_ENCTYPE_COMPARE) 301 krb5_boolean similar = 0; 302 303 krb5_c_enctype_compare(context, enctype1, enctype2, &similar); 304 return similar ? True : False; 305#elif defined(HAVE_KRB5_ENCTYPES_COMPATIBLE_KEYS) 306 return krb5_enctypes_compatible_keys(context, enctype1, enctype2) ? True : False; 307#endif 308} 309 310static BOOL ads_cleanup_expired_creds(krb5_context context, 311 krb5_ccache ccache, 312 krb5_creds *credsp) 313{ 314 krb5_error_code retval; 315 316 DEBUG(3, ("Ticket in ccache[%s] expiration %s\n", 317 krb5_cc_default_name(context), 318 http_timestring(credsp->times.endtime))); 319 320 /* we will probably need new tickets if the current ones 321 will expire within 10 seconds. 322 */ 323 if (credsp->times.endtime >= (time(NULL) + 10)) 324 return False; 325 326 /* heimdal won't remove creds from a file ccache, and 327 perhaps we shouldn't anyway, since internally we 328 use memory ccaches, and a FILE one probably means that 329 we're using creds obtained outside of our exectuable 330 */ 331 if (StrCaseCmp(krb5_cc_get_type(context, ccache), "FILE") == 0) { 332 DEBUG(5, ("ads_cleanup_expired_creds: We do not remove creds from a FILE ccache\n")); 333 return False; 334 } 335 336 retval = krb5_cc_remove_cred(context, ccache, 0, credsp); 337 if (retval) { 338 DEBUG(1, ("ads_cleanup_expired_creds: krb5_cc_remove_cred failed, err %s\n", 339 error_message(retval))); 340 /* If we have an error in this, we want to display it, 341 but continue as though we deleted it */ 342 } 343 return True; 344} 345 346/* 347 we can't use krb5_mk_req because w2k wants the service to be in a particular format 348*/ 349static krb5_error_code ads_krb5_mk_req(krb5_context context, 350 krb5_auth_context *auth_context, 351 const krb5_flags ap_req_options, 352 const char *principal, 353 krb5_ccache ccache, 354 krb5_data *outbuf) 355{ 356 krb5_error_code retval; 357 krb5_principal server; 358 krb5_creds * credsp; 359 krb5_creds creds; 360 krb5_data in_data; 361 BOOL creds_ready = False; 362 363 retval = krb5_parse_name(context, principal, &server); 364 if (retval) { 365 DEBUG(1,("ads_krb5_mk_req: Failed to parse principal %s\n", principal)); 366 return retval; 367 } 368 369 /* obtain ticket & session key */ 370 ZERO_STRUCT(creds); 371 if ((retval = krb5_copy_principal(context, server, &creds.server))) { 372 DEBUG(1,("krb5_copy_principal failed (%s)\n", 373 error_message(retval))); 374 goto cleanup_princ; 375 } 376 377 if ((retval = krb5_cc_get_principal(context, ccache, &creds.client))) { 378 /* This can commonly fail on smbd startup with no ticket in the cache. 379 * Report at higher level than 1. */ 380 DEBUG(3,("ads_krb5_mk_req: krb5_cc_get_principal failed (%s)\n", 381 error_message(retval))); 382 goto cleanup_creds; 383 } 384 385 while(!creds_ready) { 386 if ((retval = krb5_get_credentials(context, 0, ccache, 387 &creds, &credsp))) { 388 DEBUG(1,("ads_krb5_mk_req: krb5_get_credentials failed for %s (%s)\n", 389 principal, error_message(retval))); 390 goto cleanup_creds; 391 } 392 393 /* cope with ticket being in the future due to clock skew */ 394 if ((unsigned)credsp->times.starttime > time(NULL)) { 395 time_t t = time(NULL); 396 int time_offset =(unsigned)credsp->times.starttime-t; 397 DEBUG(4,("ads_krb5_mk_req: Advancing clock by %d seconds to cope with clock skew\n", time_offset)); 398 krb5_set_real_time(context, t + time_offset + 1, 0); 399 } 400 401 if (!ads_cleanup_expired_creds(context, ccache, credsp)) 402 creds_ready = True; 403 } 404 405 DEBUG(10,("ads_krb5_mk_req: Ticket (%s) in ccache (%s) is valid until: (%s - %d)\n", 406 principal, krb5_cc_default_name(context), 407 http_timestring((unsigned)credsp->times.endtime), 408 (unsigned)credsp->times.endtime)); 409 410 in_data.length = 0; 411 retval = krb5_mk_req_extended(context, auth_context, ap_req_options, 412 &in_data, credsp, outbuf); 413 if (retval) { 414 DEBUG(1,("ads_krb5_mk_req: krb5_mk_req_extended failed (%s)\n", 415 error_message(retval))); 416 } 417 418 krb5_free_creds(context, credsp); 419 420cleanup_creds: 421 krb5_free_cred_contents(context, &creds); 422 423cleanup_princ: 424 krb5_free_principal(context, server); 425 426 return retval; 427} 428 429/* 430 get a kerberos5 ticket for the given service 431*/ 432int cli_krb5_get_ticket(const char *principal, time_t time_offset, 433 DATA_BLOB *ticket, DATA_BLOB *session_key_krb5) 434{ 435 krb5_error_code retval; 436 krb5_data packet; 437 krb5_context context = NULL; 438 krb5_ccache ccdef = NULL; 439 krb5_auth_context auth_context = NULL; 440 krb5_enctype enc_types[] = { 441#ifdef ENCTYPE_ARCFOUR_HMAC 442 ENCTYPE_ARCFOUR_HMAC, 443#endif 444 ENCTYPE_DES_CBC_MD5, 445 ENCTYPE_DES_CBC_CRC, 446 ENCTYPE_NULL}; 447 448 retval = krb5_init_context(&context); 449 if (retval) { 450 DEBUG(1,("cli_krb5_get_ticket: krb5_init_context failed (%s)\n", 451 error_message(retval))); 452 goto failed; 453 } 454 455 if (time_offset != 0) { 456 krb5_set_real_time(context, time(NULL) + time_offset, 0); 457 } 458 459 if ((retval = krb5_cc_default(context, &ccdef))) { 460 DEBUG(1,("cli_krb5_get_ticket: krb5_cc_default failed (%s)\n", 461 error_message(retval))); 462 goto failed; 463 } 464 465 if ((retval = krb5_set_default_tgs_ktypes(context, enc_types))) { 466 DEBUG(1,("cli_krb5_get_ticket: krb5_set_default_tgs_ktypes failed (%s)\n", 467 error_message(retval))); 468 goto failed; 469 } 470 471 if ((retval = ads_krb5_mk_req(context, 472 &auth_context, 473 AP_OPTS_USE_SUBKEY, 474 principal, 475 ccdef, &packet))) { 476 goto failed; 477 } 478 479 get_krb5_smb_session_key(context, auth_context, session_key_krb5, False); 480 481 *ticket = data_blob(packet.data, packet.length); 482 483 kerberos_free_data_contents(context, &packet); 484 485failed: 486 487 if ( context ) { 488 if (ccdef) 489 krb5_cc_close(context, ccdef); 490 if (auth_context) 491 krb5_auth_con_free(context, auth_context); 492 krb5_free_context(context); 493 } 494 495 return retval; 496} 497 498 BOOL get_krb5_smb_session_key(krb5_context context, krb5_auth_context auth_context, DATA_BLOB *session_key, BOOL remote) 499 { 500 krb5_keyblock *skey; 501 krb5_error_code err; 502 BOOL ret = False; 503 504 if (remote) 505 err = krb5_auth_con_getremotesubkey(context, auth_context, &skey); 506 else 507 err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey); 508 if (err == 0 && skey != NULL) { 509 DEBUG(10, ("Got KRB5 session key of length %d\n", KRB5_KEY_LENGTH(skey))); 510 *session_key = data_blob(KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey)); 511 dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length); 512 513 ret = True; 514 515 krb5_free_keyblock(context, skey); 516 } else { 517 DEBUG(10, ("KRB5 error getting session key %d\n", err)); 518 } 519 520 return ret; 521 } 522 523 524#if defined(HAVE_KRB5_PRINCIPAL_GET_COMP_STRING) && !defined(HAVE_KRB5_PRINC_COMPONENT) 525 const krb5_data *krb5_princ_component(krb5_context context, krb5_principal principal, int i ) 526{ 527 static krb5_data kdata; 528 529 kdata.data = krb5_principal_get_comp_string(context, principal, i); 530 kdata.length = strlen(kdata.data); 531 return &kdata; 532} 533#endif 534 535 krb5_error_code smb_krb5_kt_free_entry(krb5_context context, krb5_keytab_entry *kt_entry) 536{ 537#if defined(HAVE_KRB5_KT_FREE_ENTRY) 538 return krb5_kt_free_entry(context, kt_entry); 539#elif defined(HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS) 540 return krb5_free_keytab_entry_contents(context, kt_entry); 541#else 542#error UNKNOWN_KT_FREE_FUNCTION 543#endif 544} 545 546#else /* HAVE_KRB5 */ 547 /* this saves a few linking headaches */ 548int cli_krb5_get_ticket(const char *principal, time_t time_offset, 549 DATA_BLOB *ticket, DATA_BLOB *session_key_krb5) 550{ 551 DEBUG(0,("NO KERBEROS SUPPORT\n")); 552 return 1; 553} 554 555#endif 556