1/*
2 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "gsskrb5_locl.h"
35
36OM_uint32
37__gsskrb5_ccache_lifetime(OM_uint32 *minor_status,
38			  krb5_context context,
39			  krb5_ccache id,
40			  krb5_principal principal,
41			  OM_uint32 *lifetime)
42{
43    krb5_creds in_cred, out_cred;
44    krb5_const_realm realm;
45    krb5_error_code kret;
46
47    memset(&in_cred, 0, sizeof(in_cred));
48    in_cred.client = principal;
49
50    realm = krb5_principal_get_realm(context,  principal);
51    if (realm == NULL) {
52	_gsskrb5_clear_status ();
53	*minor_status = KRB5_PRINC_NOMATCH; /* XXX */
54	return GSS_S_FAILURE;
55    }
56
57    kret = krb5_make_principal(context, &in_cred.server,
58			       realm, KRB5_TGS_NAME, realm, NULL);
59    if (kret) {
60	*minor_status = kret;
61	return GSS_S_FAILURE;
62    }
63
64    kret = krb5_cc_retrieve_cred(context, id, 0, &in_cred, &out_cred);
65    krb5_free_principal(context, in_cred.server);
66    if (kret) {
67	*minor_status = 0;
68	*lifetime = 0;
69	return GSS_S_COMPLETE;
70    }
71
72    *lifetime = out_cred.times.endtime;
73    krb5_free_cred_contents(context, &out_cred);
74
75    return GSS_S_COMPLETE;
76}
77
78
79
80
81static krb5_error_code
82get_keytab(krb5_context context, krb5_keytab *keytab)
83{
84    krb5_error_code kret;
85
86    HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex);
87
88    if (_gsskrb5_keytab != NULL) {
89	char *name = NULL;
90
91	kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name);
92	if (kret == 0) {
93	    kret = krb5_kt_resolve(context, name, keytab);
94	    krb5_xfree(name);
95	}
96    } else
97	kret = krb5_kt_default(context, keytab);
98
99    HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex);
100
101    return (kret);
102}
103
104static OM_uint32 acquire_initiator_cred
105		  (OM_uint32 * minor_status,
106		   krb5_context context,
107		   gss_const_OID credential_type,
108		   const void *credential_data,
109		   const gss_name_t desired_name,
110		   OM_uint32 time_req,
111		   gss_const_OID desired_mech,
112		   gss_cred_usage_t cred_usage,
113		   gsskrb5_cred handle
114		  )
115{
116    OM_uint32 ret;
117    krb5_creds cred;
118    krb5_principal def_princ;
119    krb5_get_init_creds_opt *opt;
120    krb5_ccache ccache;
121    krb5_keytab keytab;
122    krb5_error_code kret;
123
124    keytab = NULL;
125    ccache = NULL;
126    def_princ = NULL;
127    ret = GSS_S_FAILURE;
128    memset(&cred, 0, sizeof(cred));
129
130    /*
131     * If we have a preferred principal, lets try to find it in all
132     * caches, otherwise, fall back to default cache, ignore all
133     * errors while searching.
134     */
135
136    if (credential_type != GSS_C_NO_OID &&
137	!gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) {
138	kret = KRB5_NOCREDS_SUPPLIED; /* XXX */
139	goto end;
140    }
141
142    if (handle->principal) {
143	kret = krb5_cc_cache_match (context,
144				    handle->principal,
145				    &ccache);
146	if (kret == 0) {
147	    ret = GSS_S_COMPLETE;
148	    goto found;
149	}
150    }
151
152    if (ccache == NULL) {
153	kret = krb5_cc_default(context, &ccache);
154	if (kret)
155	    goto end;
156    }
157    kret = krb5_cc_get_principal(context, ccache, &def_princ);
158    if (kret != 0) {
159	/* we'll try to use a keytab below */
160	krb5_cc_close(context, ccache);
161	def_princ = NULL;
162	kret = 0;
163    } else if (handle->principal == NULL)  {
164	kret = krb5_copy_principal(context, def_princ, &handle->principal);
165	if (kret)
166	    goto end;
167    } else if (handle->principal != NULL &&
168	       krb5_principal_compare(context, handle->principal,
169				      def_princ) == FALSE) {
170	krb5_free_principal(context, def_princ);
171	def_princ = NULL;
172	krb5_cc_close(context, ccache);
173	ccache = NULL;
174    }
175    if (def_princ == NULL) {
176	/* We have no existing credentials cache,
177	 * so attempt to get a TGT using a keytab.
178	 */
179	if (handle->principal == NULL) {
180	    kret = krb5_get_default_principal(context, &handle->principal);
181	    if (kret)
182		goto end;
183	}
184	kret = krb5_get_init_creds_opt_alloc(context, &opt);
185	if (kret)
186	    goto end;
187	if (credential_type != GSS_C_NO_OID &&
188	    gss_oid_equal(credential_type, GSS_C_CRED_PASSWORD)) {
189	    gss_buffer_t password = (gss_buffer_t)credential_data;
190
191	    /* XXX are we requiring password to be NUL terminated? */
192
193	    kret = krb5_get_init_creds_password(context, &cred,
194						handle->principal,
195						password->value,
196						NULL, NULL, 0, NULL, opt);
197	} else {
198	    kret = get_keytab(context, &keytab);
199	    if (kret) {
200		krb5_get_init_creds_opt_free(context, opt);
201		goto end;
202	    }
203	    kret = krb5_get_init_creds_keytab(context, &cred,
204					      handle->principal, keytab,
205					      0, NULL, opt);
206	}
207	krb5_get_init_creds_opt_free(context, opt);
208	if (kret)
209	    goto end;
210	kret = krb5_cc_new_unique(context, krb5_cc_type_memory,
211				  NULL, &ccache);
212	if (kret)
213	    goto end;
214	kret = krb5_cc_initialize(context, ccache, cred.client);
215	if (kret) {
216	    krb5_cc_destroy(context, ccache);
217	    goto end;
218	}
219	kret = krb5_cc_store_cred(context, ccache, &cred);
220	if (kret) {
221	    krb5_cc_destroy(context, ccache);
222	    goto end;
223	}
224	handle->lifetime = cred.times.endtime;
225	handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
226    } else {
227
228	ret = __gsskrb5_ccache_lifetime(minor_status,
229					context,
230					ccache,
231					handle->principal,
232					&handle->lifetime);
233	if (ret != GSS_S_COMPLETE) {
234	    krb5_cc_close(context, ccache);
235	    goto end;
236	}
237	kret = 0;
238    }
239 found:
240    handle->ccache = ccache;
241    ret = GSS_S_COMPLETE;
242
243end:
244    if (cred.client != NULL)
245	krb5_free_cred_contents(context, &cred);
246    if (def_princ != NULL)
247	krb5_free_principal(context, def_princ);
248    if (keytab != NULL)
249	krb5_kt_close(context, keytab);
250    if (ret != GSS_S_COMPLETE && kret != 0)
251	*minor_status = kret;
252    return (ret);
253}
254
255static OM_uint32 acquire_acceptor_cred
256		  (OM_uint32 * minor_status,
257		   krb5_context context,
258		   gss_const_OID credential_type,
259		   const void *credential_data,
260		   const gss_name_t desired_name,
261		   OM_uint32 time_req,
262		   gss_const_OID desired_mech,
263		   gss_cred_usage_t cred_usage,
264		   gsskrb5_cred handle
265		  )
266{
267    OM_uint32 ret;
268    krb5_error_code kret;
269
270    ret = GSS_S_FAILURE;
271
272    if (credential_type != GSS_C_NO_OID) {
273	kret = EINVAL;
274	goto end;
275    }
276
277    kret = get_keytab(context, &handle->keytab);
278    if (kret)
279	goto end;
280
281    /* check that the requested principal exists in the keytab */
282    if (handle->principal) {
283	krb5_keytab_entry entry;
284
285	kret = krb5_kt_get_entry(context, handle->keytab,
286				 handle->principal, 0, 0, &entry);
287	if (kret)
288	    goto end;
289	krb5_kt_free_entry(context, &entry);
290	ret = GSS_S_COMPLETE;
291    } else {
292	/*
293	 * Check if there is at least one entry in the keytab before
294	 * declaring it as an useful keytab.
295	 */
296	krb5_keytab_entry tmp;
297	krb5_kt_cursor c;
298
299	kret = krb5_kt_start_seq_get (context, handle->keytab, &c);
300	if (kret)
301	    goto end;
302	if (krb5_kt_next_entry(context, handle->keytab, &tmp, &c) == 0) {
303	    krb5_kt_free_entry(context, &tmp);
304	    ret = GSS_S_COMPLETE; /* ok found one entry */
305	}
306	krb5_kt_end_seq_get (context, handle->keytab, &c);
307    }
308end:
309    if (ret != GSS_S_COMPLETE) {
310	if (handle->keytab != NULL)
311	    krb5_kt_close(context, handle->keytab);
312	if (kret != 0) {
313	    *minor_status = kret;
314	}
315    }
316    return (ret);
317}
318
319OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred
320(OM_uint32 * minor_status,
321 const gss_name_t desired_name,
322 OM_uint32 time_req,
323 const gss_OID_set desired_mechs,
324 gss_cred_usage_t cred_usage,
325 gss_cred_id_t * output_cred_handle,
326 gss_OID_set * actual_mechs,
327 OM_uint32 * time_rec
328    )
329{
330    OM_uint32 ret;
331
332    if (desired_mechs) {
333	int present = 0;
334
335	ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
336				      desired_mechs, &present);
337	if (ret)
338	    return ret;
339	if (!present) {
340	    *minor_status = 0;
341	    return GSS_S_BAD_MECH;
342	}
343    }
344
345    ret = _gsskrb5_acquire_cred_ext(minor_status,
346				    desired_name,
347				    GSS_C_NO_OID,
348				    NULL,
349				    time_req,
350				    GSS_KRB5_MECHANISM,
351				    cred_usage,
352				    output_cred_handle);
353    if (ret)
354	return ret;
355
356
357    ret = _gsskrb5_inquire_cred(minor_status, *output_cred_handle,
358				NULL, time_rec, NULL, actual_mechs);
359    if (ret) {
360	OM_uint32 tmp;
361	_gsskrb5_release_cred(&tmp, output_cred_handle);
362    }
363
364    return ret;
365}
366
367OM_uint32 GSSAPI_CALLCONV _gsskrb5_acquire_cred_ext
368(OM_uint32 * minor_status,
369 const gss_name_t desired_name,
370 gss_const_OID credential_type,
371 const void *credential_data,
372 OM_uint32 time_req,
373 gss_const_OID desired_mech,
374 gss_cred_usage_t cred_usage,
375 gss_cred_id_t * output_cred_handle
376    )
377{
378    krb5_context context;
379    gsskrb5_cred handle;
380    OM_uint32 ret;
381
382    cred_usage &= GSS_C_OPTION_MASK;
383
384    if (cred_usage != GSS_C_ACCEPT && cred_usage != GSS_C_INITIATE && cred_usage != GSS_C_BOTH) {
385	*minor_status = GSS_KRB5_S_G_BAD_USAGE;
386	return GSS_S_FAILURE;
387    }
388
389    GSSAPI_KRB5_INIT(&context);
390
391    *output_cred_handle = NULL;
392
393    handle = calloc(1, sizeof(*handle));
394    if (handle == NULL) {
395	*minor_status = ENOMEM;
396        return (GSS_S_FAILURE);
397    }
398
399    HEIMDAL_MUTEX_init(&handle->cred_id_mutex);
400
401    if (desired_name != GSS_C_NO_NAME) {
402	ret = _gsskrb5_canon_name(minor_status, context, 1, NULL,
403				  desired_name, &handle->principal);
404	if (ret) {
405	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
406	    free(handle);
407	    return ret;
408	}
409    }
410    if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) {
411	ret = acquire_initiator_cred(minor_status, context,
412				     credential_type, credential_data,
413				     desired_name, time_req,
414				     desired_mech, cred_usage, handle);
415    	if (ret != GSS_S_COMPLETE) {
416	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
417	    krb5_free_principal(context, handle->principal);
418	    free(handle);
419	    return (ret);
420	}
421    }
422    if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) {
423	ret = acquire_acceptor_cred(minor_status, context,
424				    credential_type, credential_data,
425				    desired_name, time_req,
426				    desired_mech, cred_usage, handle);
427	if (ret != GSS_S_COMPLETE) {
428	    HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
429	    krb5_free_principal(context, handle->principal);
430	    free(handle);
431	    return (ret);
432	}
433    }
434    ret = gss_create_empty_oid_set(minor_status, &handle->mechanisms);
435    if (ret == GSS_S_COMPLETE)
436    	ret = gss_add_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
437				     &handle->mechanisms);
438    if (ret != GSS_S_COMPLETE) {
439	if (handle->mechanisms != NULL)
440	    gss_release_oid_set(NULL, &handle->mechanisms);
441	HEIMDAL_MUTEX_destroy(&handle->cred_id_mutex);
442	krb5_free_principal(context, handle->principal);
443	free(handle);
444	return (ret);
445    }
446    handle->usage = cred_usage;
447    *minor_status = 0;
448    *output_cred_handle = (gss_cred_id_t)handle;
449    return (GSS_S_COMPLETE);
450}
451