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