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 
36 OM_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 
81 static krb5_error_code
82 get_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 
104 static 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 
243 end:
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 
255 static 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     }
308 end:
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 
319 OM_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 
367 OM_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