1 /* 2 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 7 /* 8 * lib/krb5/krb/get_creds.c 9 * 10 * Copyright 1990 by the Massachusetts Institute of Technology. 11 * All Rights Reserved. 12 * 13 * Export of this software from the United States of America may 14 * require a specific license from the United States Government. 15 * It is the responsibility of any person or organization contemplating 16 * export to obtain such a license before exporting. 17 * 18 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and 19 * distribute this software and its documentation for any purpose and 20 * without fee is hereby granted, provided that the above copyright 21 * notice appear in all copies and that both that copyright notice and 22 * this permission notice appear in supporting documentation, and that 23 * the name of M.I.T. not be used in advertising or publicity pertaining 24 * to distribution of the software without specific, written prior 25 * permission. Furthermore if you modify this software you must label 26 * your software as modified software and not distribute it in such a 27 * fashion that it might be confused with the original M.I.T. software. 28 * M.I.T. makes no representations about the suitability of 29 * this software for any purpose. It is provided "as is" without express 30 * or implied warranty. 31 * 32 * 33 * krb5_get_credentials() 34 */ 35 36 37 38 /* 39 Attempts to use the credentials cache or TGS exchange to get an additional 40 ticket for the 41 client identified by in_creds->client, the server identified by 42 in_creds->server, with options options, expiration date specified in 43 in_creds->times.endtime (0 means as long as possible), session key type 44 specified in in_creds->keyblock.enctype (if non-zero) 45 46 Any returned ticket and intermediate ticket-granting tickets are 47 stored in ccache. 48 49 returns errors from encryption routines, system errors 50 */ 51 52 #include "k5-int.h" 53 54 /*ARGSUSED*/ 55 static krb5_error_code 56 krb5_get_credentials_core(krb5_context context, krb5_flags options, 57 krb5_creds *in_creds, krb5_creds *mcreds, 58 krb5_flags *fields) 59 { 60 /* Solaris Kerberos */ 61 krb5_error_code ret = 0; 62 63 if (!in_creds || !in_creds->server || !in_creds->client) 64 return EINVAL; 65 66 memset((char *)mcreds, 0, sizeof(krb5_creds)); 67 mcreds->magic = KV5M_CREDS; 68 /* 69 * Solaris Kerberos: 70 * Set endtime appropriately to make sure we do not rope in 71 * expired creds. If endtime is set to 0 (which it almost always 72 * is, courtesy memset/calloc) the krb5_cc_retrieve_cred() call in 73 * krb5_get_credentials() with KRB5_TC_MATCH_TIMES will 74 * succeed and return the expired cred. 75 * 76 * Hence, endtime below is set to "now" if in_creds->times.endtime 77 * is 0, so that krb5_cc_retrieve_cred fails and we get fresh creds, 78 * if necessary. But, if in_creds has a non-zero endtime, we honor it. 79 */ 80 if (in_creds->times.endtime != 0) 81 mcreds->times.endtime = in_creds->times.endtime; 82 else 83 if ((ret = krb5_timeofday(context, &mcreds->times.endtime)) != 0) 84 return (ret); 85 86 ret = krb5_copy_keyblock_contents(context, &in_creds->keyblock, 87 &mcreds->keyblock); 88 if (ret) 89 return (ret); 90 91 mcreds->authdata = in_creds->authdata; 92 mcreds->server = in_creds->server; 93 mcreds->client = in_creds->client; 94 95 *fields = KRB5_TC_MATCH_TIMES /*XXX |KRB5_TC_MATCH_SKEY_TYPE */ 96 | KRB5_TC_MATCH_AUTHDATA 97 | KRB5_TC_SUPPORTED_KTYPES; 98 if (mcreds->keyblock.enctype) { 99 krb5_enctype *ktypes; 100 int i; 101 102 *fields |= KRB5_TC_MATCH_KTYPE; 103 ret = krb5_get_tgs_ktypes (context, mcreds->server, &ktypes); 104 for (i = 0; ktypes[i]; i++) 105 if (ktypes[i] == mcreds->keyblock.enctype) 106 break; 107 if (ktypes[i] == 0) 108 ret = KRB5_CC_NOT_KTYPE; 109 free (ktypes); 110 if (ret) { 111 krb5_free_keyblock_contents(context, &mcreds->keyblock); 112 return ret; 113 } 114 } 115 if (options & KRB5_GC_USER_USER) { 116 /* also match on identical 2nd tkt and tkt encrypted in a 117 session key */ 118 *fields |= KRB5_TC_MATCH_2ND_TKT|KRB5_TC_MATCH_IS_SKEY; 119 mcreds->is_skey = TRUE; 120 mcreds->second_ticket = in_creds->second_ticket; 121 if (!in_creds->second_ticket.length) { 122 krb5_free_keyblock_contents(context, &mcreds->keyblock); 123 return KRB5_NO_2ND_TKT; 124 } 125 } 126 127 return 0; 128 } 129 130 krb5_error_code KRB5_CALLCONV 131 krb5_get_credentials(krb5_context context, krb5_flags options, 132 krb5_ccache ccache, krb5_creds *in_creds, 133 krb5_creds **out_creds) 134 { 135 krb5_error_code retval; 136 krb5_creds mcreds; 137 krb5_creds *ncreds; 138 krb5_creds **tgts; 139 krb5_flags fields; 140 int not_ktype; 141 142 retval = krb5_get_credentials_core(context, options, 143 in_creds, 144 &mcreds, &fields); 145 146 if (retval) return retval; 147 148 if ((ncreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) { 149 krb5_free_keyblock_contents(context, &mcreds.keyblock); 150 return ENOMEM; 151 } 152 153 memset((char *)ncreds, 0, sizeof(krb5_creds)); 154 ncreds->magic = KV5M_CREDS; 155 156 /* The caller is now responsible for cleaning up in_creds */ 157 /* Solaris Kerberos */ 158 if ((retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds, 159 ncreds)) !=0) { 160 krb5_xfree(ncreds); 161 ncreds = in_creds; 162 } else { 163 *out_creds = ncreds; 164 } 165 166 if ((retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) 167 || options & KRB5_GC_CACHED) { 168 krb5_free_keyblock_contents(context, &mcreds.keyblock); 169 return retval; 170 } 171 172 if (retval == KRB5_CC_NOT_KTYPE) 173 not_ktype = 1; 174 else 175 not_ktype = 0; 176 177 retval = krb5_get_cred_from_kdc(context, ccache, ncreds, out_creds, &tgts); 178 if (tgts) { 179 register int i = 0; 180 krb5_error_code rv2; 181 while (tgts[i]) { 182 /* Solaris Kerberos */ 183 if ((rv2 = krb5_cc_store_cred(context, ccache, tgts[i])) != 0) { 184 retval = rv2; 185 break; 186 } 187 i++; 188 } 189 krb5_free_tgt_creds(context, tgts); 190 } 191 /* 192 * Translate KRB5_CC_NOTFOUND if we previously got 193 * KRB5_CC_NOT_KTYPE from krb5_cc_retrieve_cred(), in order to 194 * handle the case where there is no TGT in the ccache and the 195 * input enctype didn't match. This handling is necessary because 196 * some callers, such as GSSAPI, iterate through enctypes and 197 * KRB5_CC_NOTFOUND passed through from the 198 * krb5_get_cred_from_kdc() is semantically incorrect, since the 199 * actual failure was the non-existence of a ticket of the correct 200 * enctype rather than the missing TGT. 201 */ 202 if ((retval == KRB5_CC_NOTFOUND || retval == KRB5_CC_NOT_KTYPE) 203 && not_ktype) 204 retval = KRB5_CC_NOT_KTYPE; 205 206 if (!retval) { 207 /* the purpose of the krb5_get_credentials call is to 208 * obtain a set of credentials for the caller. the 209 * krb5_cc_store_cred() call is to optimize performance 210 * for future calls. Ignore any errors, since the credentials 211 * are still valid even if we fail to store them in the cache. 212 */ 213 /* Solaris Kerberos */ 214 retval = krb5_cc_store_cred(context, ccache, *out_creds); 215 } 216 217 krb5_free_keyblock_contents(context, &mcreds.keyblock); 218 return retval; 219 } 220 221 #define INT_GC_VALIDATE 1 222 #define INT_GC_RENEW 2 223 224 /*ARGSUSED*/ 225 static krb5_error_code 226 krb5_get_credentials_val_renew_core(krb5_context context, krb5_flags options, 227 krb5_ccache ccache, krb5_creds *in_creds, 228 krb5_creds **out_creds, int which) 229 { 230 krb5_error_code retval; 231 krb5_principal tmp; 232 krb5_creds **tgts = 0; 233 234 switch(which) { 235 case INT_GC_VALIDATE: 236 retval = krb5_get_cred_from_kdc_validate(context, ccache, 237 in_creds, out_creds, &tgts); 238 break; 239 case INT_GC_RENEW: 240 retval = krb5_get_cred_from_kdc_renew(context, ccache, 241 in_creds, out_creds, &tgts); 242 break; 243 default: 244 /* Should never happen */ 245 retval = 255; 246 break; 247 } 248 if (retval) return retval; 249 if (tgts) krb5_free_tgt_creds(context, tgts); 250 251 retval = krb5_cc_get_principal(context, ccache, &tmp); 252 if (retval) return retval; 253 254 retval = krb5_cc_initialize(context, ccache, tmp); 255 /* Solaris Kerberos */ 256 if (retval) { 257 krb5_free_principal(context, tmp); 258 return retval; 259 } 260 261 retval = krb5_cc_store_cred(context, ccache, *out_creds); 262 krb5_free_principal(context, tmp); 263 return retval; 264 } 265 266 krb5_error_code KRB5_CALLCONV 267 krb5_get_credentials_validate(krb5_context context, krb5_flags options, 268 krb5_ccache ccache, krb5_creds *in_creds, 269 krb5_creds **out_creds) 270 { 271 return(krb5_get_credentials_val_renew_core(context, options, ccache, 272 in_creds, out_creds, 273 INT_GC_VALIDATE)); 274 } 275 276 krb5_error_code KRB5_CALLCONV 277 krb5_get_credentials_renew(krb5_context context, krb5_flags options, 278 krb5_ccache ccache, krb5_creds *in_creds, 279 krb5_creds **out_creds) 280 { 281 282 return(krb5_get_credentials_val_renew_core(context, options, ccache, 283 in_creds, out_creds, 284 INT_GC_RENEW)); 285 } 286 287 static krb5_error_code 288 krb5_validate_or_renew_creds(krb5_context context, krb5_creds *creds, 289 krb5_principal client, krb5_ccache ccache, 290 char *in_tkt_service, int validate) 291 { 292 krb5_error_code ret; 293 krb5_creds in_creds; /* only client and server need to be filled in */ 294 krb5_creds *out_creds = 0; /* for check before dereferencing below */ 295 krb5_creds **tgts; 296 297 memset((char *)&in_creds, 0, sizeof(krb5_creds)); 298 299 in_creds.server = NULL; 300 tgts = NULL; 301 302 in_creds.client = client; 303 304 if (in_tkt_service) { 305 /* this is ugly, because so are the data structures involved. I'm 306 in the library, so I'm going to manipulate the data structures 307 directly, otherwise, it will be worse. */ 308 309 if ((ret = krb5_parse_name(context, in_tkt_service, &in_creds.server))) 310 goto cleanup; 311 312 /* stuff the client realm into the server principal. 313 realloc if necessary */ 314 if (in_creds.server->realm.length < in_creds.client->realm.length) 315 if ((in_creds.server->realm.data = 316 (char *) realloc(in_creds.server->realm.data, 317 in_creds.client->realm.length)) == NULL) { 318 ret = ENOMEM; 319 goto cleanup; 320 } 321 322 in_creds.server->realm.length = in_creds.client->realm.length; 323 memcpy(in_creds.server->realm.data, in_creds.client->realm.data, 324 in_creds.client->realm.length); 325 } else { 326 if ((ret = krb5_build_principal_ext(context, &in_creds.server, 327 in_creds.client->realm.length, 328 in_creds.client->realm.data, 329 KRB5_TGS_NAME_SIZE, 330 KRB5_TGS_NAME, 331 in_creds.client->realm.length, 332 in_creds.client->realm.data, 333 0))) 334 goto cleanup; 335 } 336 337 if (validate) 338 ret = krb5_get_cred_from_kdc_validate(context, ccache, 339 &in_creds, &out_creds, &tgts); 340 else 341 ret = krb5_get_cred_from_kdc_renew(context, ccache, 342 &in_creds, &out_creds, &tgts); 343 344 /* ick. copy the struct contents, free the container */ 345 if (out_creds) { 346 *creds = *out_creds; 347 krb5_xfree(out_creds); 348 } 349 350 cleanup: 351 352 if (in_creds.server) 353 krb5_free_principal(context, in_creds.server); 354 if (tgts) 355 krb5_free_tgt_creds(context, tgts); 356 357 return(ret); 358 } 359 360 krb5_error_code KRB5_CALLCONV 361 krb5_get_validated_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service) 362 { 363 return(krb5_validate_or_renew_creds(context, creds, client, ccache, 364 in_tkt_service, 1)); 365 } 366 367 krb5_error_code KRB5_CALLCONV 368 krb5_get_renewed_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service) 369 { 370 return(krb5_validate_or_renew_creds(context, creds, client, ccache, 371 in_tkt_service, 0)); 372 } 373