1 /* 2 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 #pragma ident "%Z%%M% %I% %E% SMI" 7 8 /* 9 * Copyright (c) 1994 by the Massachusetts Institute of Technology. 10 * Copyright (c) 1994 CyberSAFE Corporation 11 * Copyright (c) 1993 Open Computing Security Group 12 * Copyright (c) 1990,1991 by the Massachusetts Institute of Technology. 13 * All Rights Reserved. 14 * 15 * Export of this software from the United States of America may 16 * require a specific license from the United States Government. 17 * It is the responsibility of any person or organization contemplating 18 * export to obtain such a license before exporting. 19 * 20 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 21 * distribute this software and its documentation for any purpose and 22 * without fee is hereby granted, provided that the above copyright 23 * notice appear in all copies and that both that copyright notice and 24 * this permission notice appear in supporting documentation, and that 25 * the name of M.I.T. not be used in advertising or publicity pertaining 26 * to distribution of the software without specific, written prior 27 * permission. Furthermore if you modify this software you must label 28 * your software as modified software and not distribute it in such a 29 * fashion that it might be confused with the original M.I.T. software. 30 * Neither M.I.T., the Open Computing Security Group, nor 31 * CyberSAFE Corporation make any representations about the suitability of 32 * this software for any purpose. It is provided "as is" without express 33 * or implied warranty. 34 * 35 * krb5_get_cred_from_kdc() 36 * Get credentials from some KDC somewhere, possibly accumulating tgts 37 * along the way. 38 */ 39 40 #include <k5-int.h> 41 #include <stdio.h> 42 #include "int-proto.h" 43 44 /* 45 * Retrieve credentials for principal in_cred->client, 46 * server in_cred->server, ticket flags creds->ticket_flags, possibly 47 * second_ticket if needed by ticket_flags. 48 * 49 * Credentials are requested from the KDC for the server's realm. Any 50 * TGT credentials obtained in the process of contacting the KDC are 51 * returned in an array of credentials; tgts is filled in to point to an 52 * array of pointers to credential structures (if no TGT's were used, the 53 * pointer is zeroed). TGT's may be returned even if no useful end ticket 54 * was obtained. 55 * 56 * The returned credentials are NOT cached. 57 * 58 * This routine should not be called if the credentials are already in 59 * the cache. 60 * 61 * If credentials are obtained, creds is filled in with the results; 62 * creds->ticket and creds->keyblock->key are set to allocated storage, 63 * which should be freed by the caller when finished. 64 * 65 * returns errors, system errors. 66 */ 67 68 /* helper macro: convert flags to necessary KDC options */ 69 70 #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK) 71 72 static krb5_error_code 73 krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, kdcopt) 74 krb5_context context; 75 krb5_ccache ccache; 76 krb5_creds *in_cred; 77 krb5_creds **out_cred; 78 krb5_creds ***tgts; 79 int kdcopt; 80 { 81 krb5_creds **ret_tgts = NULL; 82 int ntgts = 0; 83 84 krb5_creds tgt, tgtq, *tgtr = NULL; 85 krb5_error_code retval; 86 krb5_principal int_server = NULL; /* Intermediate server for request */ 87 88 krb5_principal *tgs_list = NULL; 89 krb5_principal *top_server = NULL; 90 krb5_principal *next_server = NULL; 91 int nservers = 0; 92 krb5_boolean old_use_conf_ktypes = context->use_conf_ktypes; 93 94 /* in case we never get a TGT, zero the return */ 95 96 *tgts = NULL; 97 98 memset((char *)&tgtq, 0, sizeof(tgtq)); 99 memset((char *)&tgt, 0, sizeof(tgt)); 100 101 /* 102 * we know that the desired credentials aren't in the cache yet. 103 * 104 * To get them, we first need a tgt for the realm of the server. 105 * first, we see if we have such a TGT in cache. if not, then 106 * we ask the kdc to give us one. if that doesn't work, then 107 * we try to get a tgt for a realm that is closest to the target. 108 * once we have that, then we ask that realm if it can give us 109 * tgt for the target. if not, we do the process over with this 110 * new tgt. 111 */ 112 113 /* 114 * (the ticket may be issued by some other intermediate 115 * realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY) 116 */ 117 if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) 118 goto cleanup; 119 120 /* get target tgt from cache */ 121 if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server), 122 krb5_princ_realm(context, in_cred->client), 123 &int_server))) { 124 goto cleanup; 125 } 126 127 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) { 128 goto cleanup; 129 } 130 131 /* set endtime to now so krb5_cc_retrieve_cred won't return an expired tik */ 132 if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { 133 goto cleanup; 134 } 135 136 context->use_conf_ktypes = 1; 137 if ((retval = krb5_cc_retrieve_cred(context, ccache, 138 KRB5_TC_MATCH_SRV_NAMEONLY | 139 KRB5_TC_SUPPORTED_KTYPES | 140 KRB5_TC_MATCH_TIMES, 141 &tgtq, &tgt)) != 0) { 142 143 if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { 144 goto cleanup; 145 } 146 147 /* 148 * Note that we want to request a TGT from our local KDC, even 149 * if we already have a TGT for some intermediate realm. The 150 * reason is that our local KDC may have a shortcut to the 151 * destination realm, and if it does we want to use the 152 * shortcut because it will provide greater security. - bcn 153 */ 154 155 /* 156 * didn't find it in the cache so it is time to get a local 157 * tgt and walk the realms tree. 158 */ 159 krb5_free_principal(context, int_server); 160 int_server = NULL; 161 if ((retval = krb5_tgtname(context, 162 krb5_princ_realm(context, in_cred->client), 163 krb5_princ_realm(context, in_cred->client), 164 &int_server))) { 165 goto cleanup; 166 } 167 168 krb5_free_cred_contents(context, &tgtq); 169 memset((char *)&tgtq, 0, sizeof(tgtq)); 170 if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) 171 goto cleanup; 172 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) 173 goto cleanup; 174 175 if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { 176 goto cleanup; 177 } 178 179 if ((retval = krb5_cc_retrieve_cred(context, ccache, 180 KRB5_TC_MATCH_SRV_NAMEONLY | 181 KRB5_TC_SUPPORTED_KTYPES | 182 KRB5_TC_MATCH_TIMES, 183 &tgtq, &tgt)) != 0) { 184 goto cleanup; 185 } 186 187 /* get a list of realms to consult */ 188 189 if ((retval = krb5_walk_realm_tree(context, 190 krb5_princ_realm(context,in_cred->client), 191 krb5_princ_realm(context,in_cred->server), 192 &tgs_list, 193 KRB5_REALM_BRANCH_CHAR))) { 194 goto cleanup; 195 } 196 197 for (nservers = 0; tgs_list[nservers]; nservers++) 198 ; 199 200 /* allocate storage for TGT pointers. */ 201 202 if (!(ret_tgts = (krb5_creds **) calloc(nservers+1, sizeof(krb5_creds)))) { 203 retval = ENOMEM; 204 goto cleanup; 205 } 206 *tgts = ret_tgts; 207 208 /* 209 * step one is to take the current tgt and see if there is a tgt for 210 * krbtgt/realmof(target)@realmof(tgt). if not, try to get one with 211 * the tgt. 212 * 213 * if we don't get a tgt for the target, then try to find a tgt as 214 * close to the target realm as possible. at each step if there isn't 215 * a tgt in the cache we have to try and get one with our latest tgt. 216 * once we have a tgt for a closer realm, we go back to step one. 217 * 218 * once we have a tgt for the target, we go try and get credentials. 219 */ 220 221 for (top_server = tgs_list; 222 top_server < tgs_list + nservers; 223 top_server = next_server) { 224 225 /* look in cache for a tgt for the destination */ 226 227 krb5_free_cred_contents(context, &tgtq); 228 memset(&tgtq, 0, sizeof(tgtq)); 229 if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) 230 goto cleanup; 231 232 krb5_free_principal(context, int_server); 233 int_server = NULL; 234 if ((retval = krb5_tgtname(context, 235 krb5_princ_realm(context, in_cred->server), 236 krb5_princ_realm(context, *top_server), 237 &int_server))) { 238 goto cleanup; 239 } 240 241 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) 242 goto cleanup; 243 244 if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { 245 goto cleanup; 246 } 247 248 if ((retval = krb5_cc_retrieve_cred(context, ccache, 249 KRB5_TC_MATCH_SRV_NAMEONLY | 250 KRB5_TC_SUPPORTED_KTYPES | 251 KRB5_TC_MATCH_TIMES, 252 &tgtq, &tgt)) != 0) { 253 254 if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { 255 goto cleanup; 256 } 257 258 /* didn't find it in the cache so try and get one */ 259 /* with current tgt. */ 260 261 if (!valid_enctype(tgt.keyblock.enctype)) { 262 retval = KRB5_PROG_ETYPE_NOSUPP; 263 goto cleanup; 264 } 265 266 krb5_free_cred_contents(context, &tgtq); 267 memset(&tgtq, 0, sizeof(tgtq)); 268 #ifdef HAVE_C_STRUCTURE_ASSIGNMENT 269 tgtq.times = tgt.times; 270 #else 271 memcpy(&tgtq.times, &tgt.times, sizeof(krb5_ticket_times)); 272 #endif 273 274 if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) 275 goto cleanup; 276 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) 277 goto cleanup; 278 tgtq.is_skey = FALSE; 279 tgtq.ticket_flags = tgt.ticket_flags; 280 if ((retval = krb5_get_cred_via_tkt(context, &tgt, 281 FLAGS2OPTS(tgtq.ticket_flags), 282 tgt.addresses, &tgtq, &tgtr))) { 283 284 /* 285 * couldn't get one so now loop backwards through the realms 286 * list and try and get a tgt for a realm as close to the 287 * target as possible. the kdc should give us a tgt for the 288 * closest one it knows about, but not all kdc's do this yet. 289 */ 290 291 for (next_server = tgs_list + nservers - 1; 292 next_server > top_server; 293 next_server--) { 294 krb5_free_cred_contents(context, &tgtq); 295 memset(&tgtq, 0, sizeof(tgtq)); 296 if ((retval = krb5_copy_principal(context, tgt.client, 297 &tgtq.client))) 298 goto cleanup; 299 300 krb5_free_principal(context, int_server); 301 int_server = NULL; 302 if ((retval = krb5_tgtname(context, 303 krb5_princ_realm(context, *next_server), 304 krb5_princ_realm(context, *top_server), 305 &int_server))) { 306 goto cleanup; 307 } 308 309 if ((retval = krb5_copy_principal(context, int_server, 310 &tgtq.server))) 311 goto cleanup; 312 313 if ((retval = krb5_timeofday(context, 314 &(tgtq.times.endtime))) != 0) { 315 goto cleanup; 316 } 317 318 if ((retval = krb5_cc_retrieve_cred(context, ccache, 319 KRB5_TC_MATCH_SRV_NAMEONLY | 320 KRB5_TC_SUPPORTED_KTYPES | 321 KRB5_TC_MATCH_TIMES, 322 &tgtq, &tgt)) != 0) { 323 if (retval != KRB5_CC_NOTFOUND) { 324 goto cleanup; 325 } 326 327 /* not in the cache so try and get one with our current tgt. */ 328 329 if (!valid_enctype(tgt.keyblock.enctype)) { 330 retval = KRB5_PROG_ETYPE_NOSUPP; 331 goto cleanup; 332 } 333 334 krb5_free_cred_contents(context, &tgtq); 335 memset(&tgtq, 0, sizeof(tgtq)); 336 tgtq.times = tgt.times; 337 if ((retval = krb5_copy_principal(context, tgt.client, 338 &tgtq.client))) 339 goto cleanup; 340 if ((retval = krb5_copy_principal(context, int_server, 341 &tgtq.server))) 342 goto cleanup; 343 tgtq.is_skey = FALSE; 344 tgtq.ticket_flags = tgt.ticket_flags; 345 if ((retval = krb5_get_cred_via_tkt(context, &tgt, 346 FLAGS2OPTS(tgtq.ticket_flags), 347 tgt.addresses, 348 &tgtq, &tgtr))) { 349 continue; 350 } 351 352 /* save tgt in return array */ 353 if ((retval = krb5_copy_creds(context, tgtr, 354 &ret_tgts[ntgts]))) { 355 goto cleanup; 356 } 357 krb5_free_creds(context, tgtr); 358 tgtr = NULL; 359 360 tgt = *ret_tgts[ntgts++]; 361 } 362 363 /* got one as close as possible, now start all over */ 364 365 break; 366 } 367 368 if (next_server == top_server) { 369 goto cleanup; 370 } 371 continue; 372 } 373 374 /* 375 * Got a tgt. If it is for the target realm we can go try for the 376 * credentials. If it is not for the target realm, then make sure it 377 * is in the realms hierarchy and if so, save it and start the loop 378 * over from there. Note that we only need to compare the instance 379 * names since that is the target realm of the tgt. 380 */ 381 382 for (next_server = top_server; *next_server; next_server++) { 383 krb5_data *realm_1 = krb5_princ_component(context, next_server[0], 1); 384 krb5_data *realm_2 = krb5_princ_component(context, tgtr->server, 1); 385 if (realm_1->length == realm_2->length && 386 !memcmp(realm_1->data, realm_2->data, realm_1->length)) { 387 break; 388 } 389 } 390 391 if (!next_server) { 392 retval = KRB5_KDCREP_MODIFIED; 393 goto cleanup; 394 } 395 396 if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) { 397 goto cleanup; 398 } 399 krb5_free_creds(context, tgtr); 400 tgtr = NULL; 401 402 tgt = *ret_tgts[ntgts++]; 403 404 /* we're done if it is the target */ 405 406 if (!*next_server++) break; 407 } 408 } 409 } 410 411 /* got/finally have tgt! try for the creds */ 412 413 if (!valid_enctype(tgt.keyblock.enctype)) { 414 retval = KRB5_PROG_ETYPE_NOSUPP; 415 goto cleanup; 416 } 417 418 context->use_conf_ktypes = old_use_conf_ktypes; 419 retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgt.ticket_flags) | 420 kdcopt | 421 (in_cred->second_ticket.length ? 422 KDC_OPT_ENC_TKT_IN_SKEY : 0), 423 tgt.addresses, in_cred, out_cred); 424 425 /* cleanup and return */ 426 427 cleanup: 428 429 if (tgtr) krb5_free_creds(context, tgtr); 430 if(tgs_list) krb5_free_realm_tree(context, tgs_list); 431 krb5_free_cred_contents(context, &tgtq); 432 if (int_server) krb5_free_principal(context, int_server); 433 if (ntgts == 0) { 434 *tgts = NULL; 435 if (ret_tgts) free(ret_tgts); 436 krb5_free_cred_contents(context, &tgt); 437 } 438 context->use_conf_ktypes = old_use_conf_ktypes; 439 return(retval); 440 } 441 442 krb5_error_code 443 krb5_get_cred_from_kdc(context, ccache, in_cred, out_cred, tgts) 444 krb5_context context; 445 krb5_ccache ccache; 446 krb5_creds *in_cred; 447 krb5_creds **out_cred; 448 krb5_creds ***tgts; 449 { 450 451 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, 452 0); 453 } 454 455 krb5_error_code 456 krb5_get_cred_from_kdc_validate(context, ccache, in_cred, out_cred, tgts) 457 krb5_context context; 458 krb5_ccache ccache; 459 krb5_creds *in_cred; 460 krb5_creds **out_cred; 461 krb5_creds ***tgts; 462 { 463 464 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, 465 KDC_OPT_VALIDATE); 466 } 467 468 krb5_error_code 469 krb5_get_cred_from_kdc_renew(context, ccache, in_cred, out_cred, tgts) 470 krb5_context context; 471 krb5_ccache ccache; 472 krb5_creds *in_cred; 473 krb5_creds **out_cred; 474 krb5_creds ***tgts; 475 { 476 477 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, 478 KDC_OPT_RENEW); 479 } 480