1 /* 2 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. 3 */ 4 /* 5 * lib/krb5/krb/gc_via_tgt.c 6 * 7 * Copyright 1990,1991 by the Massachusetts Institute of Technology. 8 * All Rights Reserved. 9 * 10 * Export of this software from the United States of America may 11 * require a specific license from the United States Government. 12 * It is the responsibility of any person or organization contemplating 13 * export to obtain such a license before exporting. 14 * 15 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 16 * distribute this software and its documentation for any purpose and 17 * without fee is hereby granted, provided that the above copyright 18 * notice appear in all copies and that both that copyright notice and 19 * this permission notice appear in supporting documentation, and that 20 * the name of M.I.T. not be used in advertising or publicity pertaining 21 * to distribution of the software without specific, written prior 22 * permission. Furthermore if you modify this software you must label 23 * your software as modified software and not distribute it in such a 24 * fashion that it might be confused with the original M.I.T. software. 25 * M.I.T. makes no representations about the suitability of 26 * this software for any purpose. It is provided "as is" without express 27 * or implied warranty. 28 * 29 * 30 * Given a tkt, and a target cred, get it. 31 * Assumes that the kdc_rep has been decrypted. 32 */ 33 34 #include "k5-int.h" 35 #include "int-proto.h" 36 #include <locale.h> 37 #include <ctype.h> 38 39 static krb5_error_code 40 krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address, krb5_data *psectkt, krb5_creds **ppcreds) 41 { 42 krb5_error_code retval; 43 krb5_data *pdata; 44 45 if ((*ppcreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) { 46 return ENOMEM; 47 } 48 49 memset(*ppcreds, 0, sizeof(krb5_creds)); 50 51 if ((retval = krb5_copy_principal(context, pkdcrep->client, 52 &(*ppcreds)->client))) 53 goto cleanup; 54 55 if ((retval = krb5_copy_principal(context, pkdcrep->enc_part2->server, 56 &(*ppcreds)->server))) 57 goto cleanup; 58 59 if ((retval = krb5_copy_keyblock_contents(context, 60 pkdcrep->enc_part2->session, 61 &(*ppcreds)->keyblock))) 62 goto cleanup; 63 64 if ((retval = krb5_copy_data(context, psectkt, &pdata))) 65 goto cleanup; 66 (*ppcreds)->second_ticket = *pdata; 67 krb5_xfree(pdata); 68 69 (*ppcreds)->ticket_flags = pkdcrep->enc_part2->flags; 70 (*ppcreds)->times = pkdcrep->enc_part2->times; 71 (*ppcreds)->magic = KV5M_CREDS; 72 73 (*ppcreds)->authdata = NULL; /* not used */ 74 (*ppcreds)->is_skey = psectkt->length != 0; 75 76 if (pkdcrep->enc_part2->caddrs) { 77 if ((retval = krb5_copy_addresses(context, pkdcrep->enc_part2->caddrs, 78 &(*ppcreds)->addresses))) 79 goto cleanup_keyblock; 80 } else { 81 /* no addresses in the list means we got what we had */ 82 if ((retval = krb5_copy_addresses(context, address, 83 &(*ppcreds)->addresses))) 84 goto cleanup_keyblock; 85 } 86 87 if ((retval = encode_krb5_ticket(pkdcrep->ticket, &pdata))) 88 goto cleanup_keyblock; 89 90 (*ppcreds)->ticket = *pdata; 91 free(pdata); 92 return 0; 93 94 cleanup_keyblock: 95 krb5_free_keyblock(context, &(*ppcreds)->keyblock); 96 97 cleanup: 98 free (*ppcreds); 99 return retval; 100 } 101 102 static krb5_error_code 103 check_reply_server(krb5_context context, krb5_flags kdcoptions, 104 krb5_creds *in_cred, krb5_kdc_rep *dec_rep) 105 { 106 107 if (!krb5_principal_compare(context, dec_rep->ticket->server, 108 dec_rep->enc_part2->server)) 109 return KRB5_KDCREP_MODIFIED; 110 111 /* Reply is self-consistent. */ 112 113 if (krb5_principal_compare(context, dec_rep->ticket->server, 114 in_cred->server)) 115 return 0; 116 117 /* Server in reply differs from what we requested. */ 118 119 if (kdcoptions & KDC_OPT_CANONICALIZE) { 120 /* in_cred server differs from ticket returned, but ticket 121 returned is consistent and we requested canonicalization. */ 122 #if 0 123 #ifdef DEBUG_REFERRALS 124 printf("gc_via_tkt: in_cred and encoding don't match but referrals requested\n"); 125 krb5int_dbgref_dump_principal("gc_via_tkt: in_cred",in_cred->server); 126 krb5int_dbgref_dump_principal("gc_via_tkt: encoded server",dec_rep->enc_part2->server); 127 #endif 128 #endif 129 return 0; 130 } 131 132 /* We didn't request canonicalization. */ 133 134 if (!IS_TGS_PRINC(context, in_cred->server) || 135 !IS_TGS_PRINC(context, dec_rep->ticket->server)) { 136 /* Canonicalization not requested, and not a TGS referral. */ 137 return KRB5_KDCREP_MODIFIED; 138 } 139 #if 0 140 /* 141 * Is this check needed? find_nxt_kdc() in gc_frm_kdc.c already 142 * effectively checks this. 143 */ 144 if (krb5_realm_compare(context, in_cred->client, in_cred->server) && 145 in_cred->server->data[1].length == in_cred->client->realm.length && 146 !memcmp(in_cred->client->realm.data, in_cred->server->data[1].data, 147 in_cred->client->realm.length)) { 148 /* Attempted to rewrite local TGS. */ 149 return KRB5_KDCREP_MODIFIED; 150 } 151 #endif 152 return 0; 153 } 154 155 krb5_error_code 156 krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, 157 krb5_flags kdcoptions, krb5_address *const *address, 158 krb5_creds *in_cred, krb5_creds **out_cred) 159 { 160 krb5_error_code retval; 161 krb5_kdc_rep *dec_rep; 162 krb5_error *err_reply; 163 krb5_response tgsrep; 164 krb5_enctype *enctypes = 0; 165 char *hostname_used = NULL; 166 167 #ifdef DEBUG_REFERRALS 168 printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off"); 169 krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt requested ticket", in_cred->server); 170 krb5int_dbgref_dump_principal("krb5_get_cred_via_tkt TGT in use", tkt->server); 171 #endif 172 173 /* tkt->client must be equal to in_cred->client */ 174 if (!krb5_principal_compare(context, tkt->client, in_cred->client)) { 175 /* Solaris Kerberos */ 176 char *r_name = NULL; 177 char *t_name = NULL; 178 krb5_error_code r_err, t_err; 179 t_err = krb5_unparse_name(context, tkt->client, &t_name); 180 r_err = krb5_unparse_name(context, in_cred->client, &r_name); 181 krb5_set_error_message(context, KRB5_PRINC_NOMATCH, 182 dgettext(TEXT_DOMAIN, 183 "Requested principal and ticket don't match: Requested principal is '%s' and ticket is '%s'"), 184 r_err ? "unknown" : r_name, 185 t_err ? "unknown" : t_name); 186 if (r_name) 187 krb5_free_unparsed_name(context, r_name); 188 if (t_name) 189 krb5_free_unparsed_name(context, t_name); 190 return KRB5_PRINC_NOMATCH; 191 } 192 193 if (!tkt->ticket.length) 194 return KRB5_NO_TKT_SUPPLIED; 195 196 if ((kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) && 197 (!in_cred->second_ticket.length)) 198 return(KRB5_NO_2ND_TKT); 199 200 201 /* check if we have the right TGT */ 202 /* tkt->server must be equal to */ 203 /* krbtgt/realmof(cred->server)@realmof(tgt->server) */ 204 /* 205 { 206 krb5_principal tempprinc; 207 if (retval = krb5_tgtname(context, 208 krb5_princ_realm(context, in_cred->server), 209 krb5_princ_realm(context, tkt->server), &tempprinc)) 210 return(retval); 211 212 if (!krb5_principal_compare(context, tempprinc, tkt->server)) { 213 krb5_free_principal(context, tempprinc); 214 return (KRB5_PRINC_NOMATCH); 215 } 216 krb5_free_principal(context, tempprinc); 217 } 218 */ 219 220 if (in_cred->keyblock.enctype) { 221 enctypes = (krb5_enctype *) malloc(sizeof(krb5_enctype)*2); 222 if (!enctypes) 223 return ENOMEM; 224 enctypes[0] = in_cred->keyblock.enctype; 225 enctypes[1] = 0; 226 } 227 228 retval = krb5_send_tgs2(context, kdcoptions, &in_cred->times, enctypes, 229 in_cred->server, address, in_cred->authdata, 230 0, /* no padata */ 231 (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) ? 232 &in_cred->second_ticket : NULL, 233 tkt, &tgsrep, &hostname_used); 234 if (enctypes) 235 free(enctypes); 236 if (retval) { 237 #ifdef DEBUG_REFERRALS 238 printf("krb5_get_cred_via_tkt ending early after send_tgs with: %s\n", 239 error_message(retval)); 240 #endif 241 return retval; 242 } 243 244 switch (tgsrep.message_type) { 245 case KRB5_TGS_REP: 246 break; 247 case KRB5_ERROR: 248 default: 249 if (krb5_is_krb_error(&tgsrep.response)) 250 retval = decode_krb5_error(&tgsrep.response, &err_reply); 251 else 252 retval = KRB5KRB_AP_ERR_MSG_TYPE; 253 254 if (retval) /* neither proper reply nor error! */ 255 goto error_4; 256 257 retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5; 258 if (err_reply->text.length > 0) { 259 #if 0 260 const char *m; 261 #endif 262 switch (err_reply->error) { 263 case KRB_ERR_GENERIC: 264 krb5_set_error_message(context, retval, 265 /* Solaris Kerberos - added dgettext */ 266 dgettext(TEXT_DOMAIN, 267 "KDC returned error string: %s"), 268 err_reply->text.data); 269 break; 270 case KDC_ERR_S_PRINCIPAL_UNKNOWN: 271 { 272 char *s_name; 273 if (krb5_unparse_name(context, in_cred->server, &s_name) == 274 0) { 275 /* Solaris Kerberos - added dgettext */ 276 krb5_set_error_message(context, retval, 277 dgettext(TEXT_DOMAIN, 278 "Server %s not found in Kerberos database"), 279 s_name); 280 krb5_free_unparsed_name(context, s_name); 281 } else 282 /* In case there's a stale S_PRINCIPAL_UNKNOWN 283 report already noted. */ 284 krb5_clear_error_message(context); 285 } 286 break; 287 288 case KRB_AP_ERR_SKEW: 289 /* Solaris Kerberos */ 290 { 291 char *s_name = NULL; 292 char *c_name = NULL; 293 char stimestring[17]; 294 char ctimestring[17]; 295 char fill = ' '; 296 int st_err, ct_err, serr, cerr; 297 298 st_err = krb5_timestamp_to_sfstring(err_reply->stime, 299 stimestring, 300 sizeof (stimestring), 301 &fill); 302 ct_err = krb5_timestamp_to_sfstring(err_reply->ctime, 303 ctimestring, 304 sizeof (ctimestring), 305 &fill); 306 serr = krb5_unparse_name(context, in_cred->server, &s_name); 307 cerr = krb5_unparse_name(context, in_cred->client, &c_name); 308 krb5_set_error_message(context, retval, 309 dgettext(TEXT_DOMAIN, 310 "Clock skew too great: '%s' requesting ticket '%s' from KDC '%s' (%s). Skew is %dm."), 311 cerr == 0 ? c_name : "unknown", 312 serr == 0 ? s_name : "unknown", 313 hostname_used ? 314 hostname_used : "host unknown", 315 st_err == 0 ? stimestring : "unknown", 316 (ct_err||st_err) ? 0 : 317 abs(err_reply->stime - 318 err_reply->ctime) / 60); 319 320 if (s_name) 321 krb5_free_unparsed_name(context, s_name); 322 if (c_name) 323 krb5_free_unparsed_name(context, c_name); 324 } 325 break; 326 case KRB_AP_ERR_TKT_NYV: 327 /* Solaris Kerberos */ 328 { 329 char *s_name = NULL; 330 char *c_name = NULL; 331 char timestring[17]; 332 char stimestring[17]; 333 char fill = ' '; 334 krb5_error_code t_err, st_err, cerr, serr; 335 336 t_err = krb5_timestamp_to_sfstring(tkt->times.starttime, 337 timestring, 338 sizeof (timestring), 339 &fill); 340 st_err = krb5_timestamp_to_sfstring(err_reply->stime, 341 stimestring, 342 sizeof (stimestring), 343 &fill); 344 serr = krb5_unparse_name(context, in_cred->server, &s_name); 345 cerr = krb5_unparse_name(context, in_cred->client, &c_name); 346 krb5_set_error_message(context, retval, 347 dgettext(TEXT_DOMAIN, 348 "Ticket not yet valid: '%s' requesting ticket '%s' from '%s' (%s). TGT start time is %s"), 349 cerr ? "unknown" : c_name, 350 serr ? "unknown" : s_name, 351 hostname_used ? hostname_used : "host unknown", 352 st_err ? "unknown" : stimestring, 353 t_err ? "unknown" : timestring); 354 if (s_name) 355 krb5_free_unparsed_name(context, s_name); 356 if (c_name) 357 krb5_free_unparsed_name(context, c_name); 358 } 359 break; 360 default: 361 #if 0 /* We should stop the KDC from sending back this text, because 362 if the local language doesn't match the KDC's language, we'd 363 just wind up printing out the error message in two languages. 364 Well, when we get some localization. Which is already 365 happening in KfM. */ 366 m = error_message(retval); 367 /* Special case: MIT KDC may return this same string 368 in the e-text field. */ 369 if (strlen (m) == err_reply->text.length-1 370 && !strcmp(m, err_reply->text.data)) 371 break; 372 /* Solaris Kerberos - added dgettext */ 373 krb5_set_error_message(context, retval, 374 dgettext(TEXT_DOMAIN, 375 "%s (KDC supplied additional data: %s)"), 376 m, err_reply->text.data); 377 #endif 378 break; 379 } 380 } 381 382 krb5_free_error(context, err_reply); 383 goto error_4; 384 } 385 386 if ((retval = krb5_decode_kdc_rep(context, &tgsrep.response, 387 &tkt->keyblock, &dec_rep))) 388 goto error_4; 389 390 if (dec_rep->msg_type != KRB5_TGS_REP) { 391 retval = KRB5KRB_AP_ERR_MSG_TYPE; 392 goto error_3; 393 } 394 395 /* make sure the response hasn't been tampered with..... */ 396 retval = 0; 397 398 if (!krb5_principal_compare(context, dec_rep->client, tkt->client)) 399 retval = KRB5_KDCREP_MODIFIED; 400 401 if (retval == 0) 402 retval = check_reply_server(context, kdcoptions, in_cred, dec_rep); 403 404 if (dec_rep->enc_part2->nonce != tgsrep.expected_nonce) 405 retval = KRB5_KDCREP_MODIFIED; 406 407 if ((kdcoptions & KDC_OPT_POSTDATED) && 408 (in_cred->times.starttime != 0) && 409 (in_cred->times.starttime != dec_rep->enc_part2->times.starttime)) 410 retval = KRB5_KDCREP_MODIFIED; 411 412 if ((in_cred->times.endtime != 0) && 413 (dec_rep->enc_part2->times.endtime > in_cred->times.endtime)) 414 retval = KRB5_KDCREP_MODIFIED; 415 416 if ((kdcoptions & KDC_OPT_RENEWABLE) && 417 (in_cred->times.renew_till != 0) && 418 (dec_rep->enc_part2->times.renew_till > in_cred->times.renew_till)) 419 retval = KRB5_KDCREP_MODIFIED; 420 421 if ((kdcoptions & KDC_OPT_RENEWABLE_OK) && 422 (dec_rep->enc_part2->flags & KDC_OPT_RENEWABLE) && 423 (in_cred->times.endtime != 0) && 424 (dec_rep->enc_part2->times.renew_till > in_cred->times.endtime)) 425 retval = KRB5_KDCREP_MODIFIED; 426 427 if (retval != 0) 428 goto error_3; 429 430 if (!in_cred->times.starttime && 431 !in_clock_skew(dec_rep->enc_part2->times.starttime, 432 tgsrep.request_time)) { 433 retval = KRB5_KDCREP_SKEW; 434 goto error_3; 435 } 436 437 retval = krb5_kdcrep2creds(context, dec_rep, address, 438 &in_cred->second_ticket, out_cred); 439 440 error_3:; 441 memset(dec_rep->enc_part2->session->contents, 0, 442 dec_rep->enc_part2->session->length); 443 krb5_free_kdc_rep(context, dec_rep); 444 445 error_4:; 446 if (hostname_used) 447 free(hostname_used); 448 free(tgsrep.response.data); 449 #ifdef DEBUG_REFERRALS 450 printf("krb5_get_cred_via_tkt ending; %s\n", retval?error_message(retval):"no error"); 451 #endif 452 return retval; 453 } 454