1 /* 2 * Copyright 2005 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,2003 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(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts, int kdcopt) 74 { 75 krb5_creds **ret_tgts = NULL; 76 int ntgts = 0; 77 78 krb5_creds tgt, tgtq, *tgtr = NULL; 79 krb5_error_code retval; 80 krb5_principal int_server = NULL; /* Intermediate server for request */ 81 82 krb5_principal *tgs_list = NULL; 83 krb5_principal *top_server = NULL; 84 krb5_principal *next_server = NULL; 85 unsigned int nservers = 0; 86 krb5_boolean old_use_conf_ktypes = context->use_conf_ktypes; 87 88 /* in case we never get a TGT, zero the return */ 89 90 *tgts = NULL; 91 92 memset((char *)&tgtq, 0, sizeof(tgtq)); 93 memset((char *)&tgt, 0, sizeof(tgt)); 94 95 /* 96 * we know that the desired credentials aren't in the cache yet. 97 * 98 * To get them, we first need a tgt for the realm of the server. 99 * first, we see if we have such a TGT in cache. if not, then 100 * we ask the kdc to give us one. if that doesn't work, then 101 * we try to get a tgt for a realm that is closest to the target. 102 * once we have that, then we ask that realm if it can give us 103 * tgt for the target. if not, we do the process over with this 104 * new tgt. 105 */ 106 107 /* 108 * (the ticket may be issued by some other intermediate 109 * realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY) 110 */ 111 if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) 112 goto cleanup; 113 114 /* get target tgt from cache */ 115 if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server), 116 krb5_princ_realm(context, in_cred->client), 117 &int_server))) { 118 goto cleanup; 119 } 120 121 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) { 122 goto cleanup; 123 } 124 125 /* set endtime to now so krb5_cc_retrieve_cred won't return an expired tik */ 126 if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { 127 goto cleanup; 128 } 129 130 context->use_conf_ktypes = 1; 131 if ((retval = krb5_cc_retrieve_cred(context, ccache, 132 KRB5_TC_MATCH_SRV_NAMEONLY | 133 KRB5_TC_SUPPORTED_KTYPES | 134 KRB5_TC_MATCH_TIMES, 135 &tgtq, &tgt)) != 0) { 136 137 if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { 138 goto cleanup; 139 } 140 141 /* 142 * Note that we want to request a TGT from our local KDC, even 143 * if we already have a TGT for some intermediate realm. The 144 * reason is that our local KDC may have a shortcut to the 145 * destination realm, and if it does we want to use the 146 * shortcut because it will provide greater security. - bcn 147 */ 148 149 /* 150 * didn't find it in the cache so it is time to get a local 151 * tgt and walk the realms tree. 152 */ 153 krb5_free_principal(context, int_server); 154 int_server = NULL; 155 if ((retval = krb5_tgtname(context, 156 krb5_princ_realm(context, in_cred->client), 157 krb5_princ_realm(context, in_cred->client), 158 &int_server))) { 159 goto cleanup; 160 } 161 162 krb5_free_cred_contents(context, &tgtq); 163 memset((char *)&tgtq, 0, sizeof(tgtq)); 164 if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) 165 goto cleanup; 166 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) 167 goto cleanup; 168 169 if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { 170 goto cleanup; 171 } 172 173 if ((retval = krb5_cc_retrieve_cred(context, ccache, 174 KRB5_TC_MATCH_SRV_NAMEONLY | 175 KRB5_TC_SUPPORTED_KTYPES | 176 KRB5_TC_MATCH_TIMES, 177 &tgtq, &tgt)) != 0) { 178 goto cleanup; 179 } 180 181 /* get a list of realms to consult */ 182 183 if ((retval = krb5_walk_realm_tree(context, 184 krb5_princ_realm(context,in_cred->client), 185 krb5_princ_realm(context,in_cred->server), 186 &tgs_list, 187 KRB5_REALM_BRANCH_CHAR))) { 188 goto cleanup; 189 } 190 191 for (nservers = 0; tgs_list[nservers]; nservers++) 192 ; 193 194 /* allocate storage for TGT pointers. */ 195 196 if (!(ret_tgts = (krb5_creds **) calloc(nservers+1, sizeof(krb5_creds)))) { 197 retval = ENOMEM; 198 goto cleanup; 199 } 200 *tgts = ret_tgts; 201 202 /* 203 * step one is to take the current tgt and see if there is a tgt for 204 * krbtgt/realmof(target)@realmof(tgt). if not, try to get one with 205 * the tgt. 206 * 207 * if we don't get a tgt for the target, then try to find a tgt as 208 * close to the target realm as possible. at each step if there isn't 209 * a tgt in the cache we have to try and get one with our latest tgt. 210 * once we have a tgt for a closer realm, we go back to step one. 211 * 212 * once we have a tgt for the target, we go try and get credentials. 213 */ 214 215 for (top_server = tgs_list; 216 top_server < tgs_list + nservers; 217 top_server = next_server) { 218 219 /* look in cache for a tgt for the destination */ 220 221 krb5_free_cred_contents(context, &tgtq); 222 memset(&tgtq, 0, sizeof(tgtq)); 223 if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) 224 goto cleanup; 225 226 krb5_free_principal(context, int_server); 227 int_server = NULL; 228 if ((retval = krb5_tgtname(context, 229 krb5_princ_realm(context, in_cred->server), 230 krb5_princ_realm(context, *top_server), 231 &int_server))) { 232 goto cleanup; 233 } 234 235 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) 236 goto cleanup; 237 238 if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { 239 goto cleanup; 240 } 241 242 if ((retval = krb5_cc_retrieve_cred(context, ccache, 243 KRB5_TC_MATCH_SRV_NAMEONLY | 244 KRB5_TC_SUPPORTED_KTYPES | 245 KRB5_TC_MATCH_TIMES, 246 &tgtq, &tgt)) != 0) { 247 248 if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { 249 goto cleanup; 250 } 251 252 /* didn't find it in the cache so try and get one */ 253 /* with current tgt. */ 254 255 if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { 256 retval = KRB5_PROG_ETYPE_NOSUPP; 257 goto cleanup; 258 } 259 260 krb5_free_cred_contents(context, &tgtq); 261 memset(&tgtq, 0, sizeof(tgtq)); 262 tgtq.times = tgt.times; 263 264 if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) 265 goto cleanup; 266 if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) 267 goto cleanup; 268 tgtq.is_skey = FALSE; 269 tgtq.ticket_flags = tgt.ticket_flags; 270 retval = krb5_get_cred_via_tkt(context, &tgt, 271 FLAGS2OPTS(tgtq.ticket_flags), 272 tgt.addresses, &tgtq, &tgtr); 273 if (retval) { 274 275 /* 276 * couldn't get one so now loop backwards through the realms 277 * list and try and get a tgt for a realm as close to the 278 * target as possible. the kdc should give us a tgt for the 279 * closest one it knows about, but not all kdc's do this yet. 280 */ 281 282 for (next_server = tgs_list + nservers - 1; 283 next_server > top_server; 284 next_server--) { 285 krb5_free_cred_contents(context, &tgtq); 286 memset(&tgtq, 0, sizeof(tgtq)); 287 if ((retval = krb5_copy_principal(context, tgt.client, 288 &tgtq.client))) 289 goto cleanup; 290 291 krb5_free_principal(context, int_server); 292 int_server = NULL; 293 if ((retval = krb5_tgtname(context, 294 krb5_princ_realm(context, *next_server), 295 krb5_princ_realm(context, *top_server), 296 &int_server))) { 297 goto cleanup; 298 } 299 300 if ((retval = krb5_copy_principal(context, int_server, 301 &tgtq.server))) 302 goto cleanup; 303 304 if ((retval = krb5_timeofday(context, 305 &(tgtq.times.endtime))) != 0) { 306 goto cleanup; 307 } 308 309 if ((retval = krb5_cc_retrieve_cred(context, ccache, 310 KRB5_TC_MATCH_SRV_NAMEONLY | 311 KRB5_TC_SUPPORTED_KTYPES | 312 KRB5_TC_MATCH_TIMES, 313 &tgtq, &tgt)) != 0) { 314 if (retval != KRB5_CC_NOTFOUND) { 315 goto cleanup; 316 } 317 318 /* not in the cache so try and get one with our current tgt. */ 319 320 if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { 321 retval = KRB5_PROG_ETYPE_NOSUPP; 322 goto cleanup; 323 } 324 325 krb5_free_cred_contents(context, &tgtq); 326 memset(&tgtq, 0, sizeof(tgtq)); 327 tgtq.times = tgt.times; 328 if ((retval = krb5_copy_principal(context, tgt.client, 329 &tgtq.client))) 330 goto cleanup; 331 if ((retval = krb5_copy_principal(context, int_server, 332 &tgtq.server))) 333 goto cleanup; 334 tgtq.is_skey = FALSE; 335 tgtq.ticket_flags = tgt.ticket_flags; 336 retval = krb5_get_cred_via_tkt(context, &tgt, 337 FLAGS2OPTS(tgtq.ticket_flags), 338 tgt.addresses, 339 &tgtq, &tgtr); 340 if (retval) 341 continue; 342 343 /* save tgt in return array */ 344 if ((retval = krb5_copy_creds(context, tgtr, 345 &ret_tgts[ntgts]))) { 346 goto cleanup; 347 } 348 krb5_free_creds(context, tgtr); 349 tgtr = NULL; 350 351 tgt = *ret_tgts[ntgts++]; 352 } 353 354 /* got one as close as possible, now start all over */ 355 356 break; 357 } 358 359 if (next_server == top_server) { 360 goto cleanup; 361 } 362 continue; 363 } 364 365 /* 366 * Got a tgt. If it is for the target realm we can go try for the 367 * credentials. If it is not for the target realm, then make sure it 368 * is in the realms hierarchy and if so, save it and start the loop 369 * over from there. Note that we only need to compare the instance 370 * names since that is the target realm of the tgt. 371 */ 372 373 for (next_server = top_server; *next_server; next_server++) { 374 krb5_data *realm_1 = krb5_princ_component(context, next_server[0], 1); 375 krb5_data *realm_2 = krb5_princ_component(context, tgtr->server, 1); 376 if (realm_1 != NULL && 377 realm_2 != NULL && 378 realm_1->length == realm_2->length && 379 !memcmp(realm_1->data, realm_2->data, realm_1->length)) { 380 break; 381 } 382 } 383 384 if (!next_server) { 385 retval = KRB5_KDCREP_MODIFIED; 386 goto cleanup; 387 } 388 389 if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) { 390 goto cleanup; 391 } 392 krb5_free_creds(context, tgtr); 393 tgtr = NULL; 394 395 tgt = *ret_tgts[ntgts++]; 396 397 /* we're done if it is the target */ 398 399 if (!*next_server++) break; 400 } 401 } 402 } 403 404 /* got/finally have tgt! try for the creds */ 405 406 if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { 407 retval = KRB5_PROG_ETYPE_NOSUPP; 408 goto cleanup; 409 } 410 411 context->use_conf_ktypes = old_use_conf_ktypes; 412 retval = krb5_get_cred_via_tkt(context, &tgt, 413 FLAGS2OPTS(tgt.ticket_flags) | 414 kdcopt | 415 (in_cred->second_ticket.length ? 416 KDC_OPT_ENC_TKT_IN_SKEY : 0), 417 tgt.addresses, in_cred, out_cred); 418 419 /* cleanup and return */ 420 421 cleanup: 422 423 if (tgtr) krb5_free_creds(context, tgtr); 424 if(tgs_list) krb5_free_realm_tree(context, tgs_list); 425 krb5_free_cred_contents(context, &tgtq); 426 if (int_server) krb5_free_principal(context, int_server); 427 if (ntgts == 0) { 428 *tgts = NULL; 429 if (ret_tgts) free(ret_tgts); 430 krb5_free_cred_contents(context, &tgt); 431 } 432 context->use_conf_ktypes = old_use_conf_ktypes; 433 return(retval); 434 } 435 436 krb5_error_code 437 krb5_get_cred_from_kdc(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts) 438 { 439 440 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, 441 0); 442 } 443 444 krb5_error_code 445 krb5_get_cred_from_kdc_validate(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts) 446 { 447 448 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, 449 KDC_OPT_VALIDATE); 450 } 451 452 krb5_error_code 453 krb5_get_cred_from_kdc_renew(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts) 454 { 455 456 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, 457 KDC_OPT_RENEW); 458 } 459