xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/get_creds.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * Copyright 2008 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_data(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 	    return ret;
112     }
113     if (options & KRB5_GC_USER_USER) {
114 	/* also match on identical 2nd tkt and tkt encrypted in a
115 	   session key */
116 	*fields |= KRB5_TC_MATCH_2ND_TKT|KRB5_TC_MATCH_IS_SKEY;
117 	mcreds->is_skey = TRUE;
118 	mcreds->second_ticket = in_creds->second_ticket;
119 	if (!in_creds->second_ticket.length)
120 	    return KRB5_NO_2ND_TKT;
121     }
122 
123     return 0;
124 }
125 
126 krb5_error_code KRB5_CALLCONV
127 krb5_get_credentials(krb5_context context, krb5_flags options,
128 		     krb5_ccache ccache, krb5_creds *in_creds,
129 		     krb5_creds **out_creds)
130 {
131     krb5_error_code retval;
132     krb5_creds mcreds;
133     krb5_creds *ncreds;
134     krb5_creds **tgts;
135     krb5_flags fields;
136     int not_ktype;
137 
138     retval = krb5_get_credentials_core(context, options,
139 				       in_creds,
140 				       &mcreds, &fields);
141 
142     if (retval) return retval;
143 
144     if ((ncreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL)
145 	return ENOMEM;
146 
147     memset((char *)ncreds, 0, sizeof(krb5_creds));
148     ncreds->magic = KV5M_CREDS;
149 
150     /* The caller is now responsible for cleaning up in_creds */
151     /* Solaris Kerberos */
152     if ((retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds,
153 					ncreds)) !=0) {
154 	krb5_xfree(ncreds);
155 	ncreds = in_creds;
156     } else {
157 	*out_creds = ncreds;
158     }
159 
160     if ((retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE)
161 	|| options & KRB5_GC_CACHED)
162 	return retval;
163 
164     if (retval == KRB5_CC_NOT_KTYPE)
165 	not_ktype = 1;
166     else
167 	not_ktype = 0;
168 
169     retval = krb5_get_cred_from_kdc(context, ccache, ncreds, out_creds, &tgts);
170     if (tgts) {
171 	register int i = 0;
172 	krb5_error_code rv2;
173 	while (tgts[i]) {
174 	    /* Solaris Kerberos */
175 	    if ((rv2 = krb5_cc_store_cred(context, ccache, tgts[i])) != 0) {
176 		retval = rv2;
177 		break;
178 	    }
179 	    i++;
180 	}
181 	krb5_free_tgt_creds(context, tgts);
182     }
183     /*
184      * Translate KRB5_CC_NOTFOUND if we previously got
185      * KRB5_CC_NOT_KTYPE from krb5_cc_retrieve_cred(), in order to
186      * handle the case where there is no TGT in the ccache and the
187      * input enctype didn't match.  This handling is necessary because
188      * some callers, such as GSSAPI, iterate through enctypes and
189      * KRB5_CC_NOTFOUND passed through from the
190      * krb5_get_cred_from_kdc() is semantically incorrect, since the
191      * actual failure was the non-existence of a ticket of the correct
192      * enctype rather than the missing TGT.
193      */
194     if ((retval == KRB5_CC_NOTFOUND || retval == KRB5_CC_NOT_KTYPE)
195 	&& not_ktype)
196 	retval = KRB5_CC_NOT_KTYPE;
197 
198     if (!retval) {
199         /* the purpose of the krb5_get_credentials call is to
200          * obtain a set of credentials for the caller.  the
201          * krb5_cc_store_cred() call is to optimize performance
202          * for future calls.  Ignore any errors, since the credentials
203          * are still valid even if we fail to store them in the cache.
204          */
205 	/* Solaris Kerberos */
206 	retval = krb5_cc_store_cred(context, ccache, *out_creds);
207     }
208     return retval;
209 }
210 
211 #define INT_GC_VALIDATE 1
212 #define INT_GC_RENEW 2
213 
214 /*ARGSUSED*/
215 static krb5_error_code
216 krb5_get_credentials_val_renew_core(krb5_context context, krb5_flags options,
217 				    krb5_ccache ccache, krb5_creds *in_creds,
218 				    krb5_creds **out_creds, int which)
219 {
220     krb5_error_code retval;
221     krb5_principal tmp;
222     krb5_creds **tgts = 0;
223 
224     switch(which) {
225     case INT_GC_VALIDATE:
226 	    retval = krb5_get_cred_from_kdc_validate(context, ccache,
227 					     in_creds, out_creds, &tgts);
228 	    break;
229     case INT_GC_RENEW:
230 	    retval = krb5_get_cred_from_kdc_renew(context, ccache,
231 					     in_creds, out_creds, &tgts);
232 	    break;
233     default:
234 	    /* Should never happen */
235 	    retval = 255;
236 	    break;
237     }
238     if (retval) return retval;
239     if (tgts) krb5_free_tgt_creds(context, tgts);
240 
241     retval = krb5_cc_get_principal(context, ccache, &tmp);
242     if (retval) return retval;
243 
244     retval = krb5_cc_initialize(context, ccache, tmp);
245     /* Solaris Kerberos */
246     if (retval) {
247 	krb5_free_principal(context, tmp);
248 	return retval;
249     }
250 
251     retval = krb5_cc_store_cred(context, ccache, *out_creds);
252     krb5_free_principal(context, tmp);
253     return retval;
254 }
255 
256 krb5_error_code KRB5_CALLCONV
257 krb5_get_credentials_validate(krb5_context context, krb5_flags options,
258 			      krb5_ccache ccache, krb5_creds *in_creds,
259 			      krb5_creds **out_creds)
260 {
261     return(krb5_get_credentials_val_renew_core(context, options, ccache,
262 					       in_creds, out_creds,
263 					       INT_GC_VALIDATE));
264 }
265 
266 krb5_error_code KRB5_CALLCONV
267 krb5_get_credentials_renew(krb5_context context, krb5_flags options,
268 			   krb5_ccache ccache, krb5_creds *in_creds,
269 			   krb5_creds **out_creds)
270 {
271 
272     return(krb5_get_credentials_val_renew_core(context, options, ccache,
273 					       in_creds, out_creds,
274 					       INT_GC_RENEW));
275 }
276 
277 static krb5_error_code
278 krb5_validate_or_renew_creds(krb5_context context, krb5_creds *creds,
279 			     krb5_principal client, krb5_ccache ccache,
280 			     char *in_tkt_service, int validate)
281 {
282     krb5_error_code ret;
283     krb5_creds in_creds; /* only client and server need to be filled in */
284     krb5_creds *out_creds = 0; /* for check before dereferencing below */
285     krb5_creds **tgts;
286 
287     memset((char *)&in_creds, 0, sizeof(krb5_creds));
288 
289     in_creds.server = NULL;
290     tgts = NULL;
291 
292     in_creds.client = client;
293 
294     if (in_tkt_service) {
295 	/* this is ugly, because so are the data structures involved.  I'm
296 	   in the library, so I'm going to manipulate the data structures
297 	   directly, otherwise, it will be worse. */
298 
299         if ((ret = krb5_parse_name(context, in_tkt_service, &in_creds.server)))
300 	    goto cleanup;
301 
302 	/* stuff the client realm into the server principal.
303 	   realloc if necessary */
304 	if (in_creds.server->realm.length < in_creds.client->realm.length)
305 	    if ((in_creds.server->realm.data =
306 		 (char *) realloc(in_creds.server->realm.data,
307 				  in_creds.client->realm.length)) == NULL) {
308 		ret = ENOMEM;
309 		goto cleanup;
310 	    }
311 
312 	in_creds.server->realm.length = in_creds.client->realm.length;
313 	memcpy(in_creds.server->realm.data, in_creds.client->realm.data,
314 	       in_creds.client->realm.length);
315     } else {
316 	if ((ret = krb5_build_principal_ext(context, &in_creds.server,
317 					   in_creds.client->realm.length,
318 					   in_creds.client->realm.data,
319 					   KRB5_TGS_NAME_SIZE,
320 					   KRB5_TGS_NAME,
321 					   in_creds.client->realm.length,
322 					   in_creds.client->realm.data,
323 					    0)))
324 	    goto cleanup;
325     }
326 
327     if (validate)
328 	ret = krb5_get_cred_from_kdc_validate(context, ccache,
329 					      &in_creds, &out_creds, &tgts);
330     else
331 	ret = krb5_get_cred_from_kdc_renew(context, ccache,
332 					   &in_creds, &out_creds, &tgts);
333 
334     /* ick.  copy the struct contents, free the container */
335     if (out_creds) {
336 	*creds = *out_creds;
337 	krb5_xfree(out_creds);
338     }
339 
340 cleanup:
341 
342     if (in_creds.server)
343 	krb5_free_principal(context, in_creds.server);
344     if (tgts)
345 	krb5_free_tgt_creds(context, tgts);
346 
347     return(ret);
348 }
349 
350 krb5_error_code KRB5_CALLCONV
351 krb5_get_validated_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service)
352 {
353     return(krb5_validate_or_renew_creds(context, creds, client, ccache,
354 					in_tkt_service, 1));
355 }
356 
357 krb5_error_code KRB5_CALLCONV
358 krb5_get_renewed_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service)
359 {
360     return(krb5_validate_or_renew_creds(context, creds, client, ccache,
361 					in_tkt_service, 0));
362 }
363