xref: /freebsd/crypto/heimdal/lib/krb5/acache.c (revision 62cfcf62f627e5093fb37026a6d8c98e4d2ef04c)
1 /*
2  * Copyright (c) 2004 - 2007 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include "krb5_locl.h"
37 #include <krb5_ccapi.h>
38 #ifdef HAVE_DLFCN_H
39 #include <dlfcn.h>
40 #endif
41 
42 #ifndef KCM_IS_API_CACHE
43 
44 static HEIMDAL_MUTEX acc_mutex = HEIMDAL_MUTEX_INITIALIZER;
45 static cc_initialize_func init_func;
46 static void (KRB5_CALLCONV *set_target_uid)(uid_t);
47 static void (KRB5_CALLCONV *clear_target)(void);
48 
49 #ifdef HAVE_DLOPEN
50 static void *cc_handle;
51 #endif
52 
53 typedef struct krb5_acc {
54     char *cache_name;
55     cc_context_t context;
56     cc_ccache_t ccache;
57 } krb5_acc;
58 
59 static krb5_error_code KRB5_CALLCONV acc_close(krb5_context, krb5_ccache);
60 
61 #define ACACHE(X) ((krb5_acc *)(X)->data.data)
62 
63 static const struct {
64     cc_int32 error;
65     krb5_error_code ret;
66 } cc_errors[] = {
67     { ccErrBadName,		KRB5_CC_BADNAME },
68     { ccErrCredentialsNotFound,	KRB5_CC_NOTFOUND },
69     { ccErrCCacheNotFound,	KRB5_FCC_NOFILE },
70     { ccErrContextNotFound,	KRB5_CC_NOTFOUND },
71     { ccIteratorEnd,		KRB5_CC_END },
72     { ccErrNoMem,		KRB5_CC_NOMEM },
73     { ccErrServerUnavailable,	KRB5_CC_NOSUPP },
74     { ccErrInvalidCCache,	KRB5_CC_BADNAME },
75     { ccNoError,		0 }
76 };
77 
78 static krb5_error_code
79 translate_cc_error(krb5_context context, cc_int32 error)
80 {
81     size_t i;
82     krb5_clear_error_message(context);
83     for(i = 0; i < sizeof(cc_errors)/sizeof(cc_errors[0]); i++)
84 	if (cc_errors[i].error == error)
85 	    return cc_errors[i].ret;
86     return KRB5_FCC_INTERNAL;
87 }
88 
89 static krb5_error_code
90 init_ccapi(krb5_context context)
91 {
92     const char *lib = NULL;
93 
94     HEIMDAL_MUTEX_lock(&acc_mutex);
95     if (init_func) {
96 	HEIMDAL_MUTEX_unlock(&acc_mutex);
97 	if (context)
98 	    krb5_clear_error_message(context);
99 	return 0;
100     }
101 
102     if (context)
103 	lib = krb5_config_get_string(context, NULL,
104 				     "libdefaults", "ccapi_library",
105 				     NULL);
106     if (lib == NULL) {
107 #ifdef __APPLE__
108 	lib = "/System/Library/Frameworks/Kerberos.framework/Kerberos";
109 #elif defined(KRB5_USE_PATH_TOKENS) && defined(_WIN32)
110 	lib = "%{LIBDIR}/libkrb5_cc.dll";
111 #else
112 	lib = "/usr/lib/libkrb5_cc.so";
113 #endif
114     }
115 
116 #ifdef HAVE_DLOPEN
117 
118 #ifndef RTLD_LAZY
119 #define RTLD_LAZY 0
120 #endif
121 #ifndef RTLD_LOCAL
122 #define RTLD_LOCAL 0
123 #endif
124 
125 #ifdef KRB5_USE_PATH_TOKENS
126     {
127       char * explib = NULL;
128       if (_krb5_expand_path_tokens(context, lib, &explib) == 0) {
129 	cc_handle = dlopen(explib, RTLD_LAZY|RTLD_LOCAL);
130 	free(explib);
131       }
132     }
133 #else
134     cc_handle = dlopen(lib, RTLD_LAZY|RTLD_LOCAL);
135 #endif
136 
137     if (cc_handle == NULL) {
138 	HEIMDAL_MUTEX_unlock(&acc_mutex);
139 	if (context)
140 	    krb5_set_error_message(context, KRB5_CC_NOSUPP,
141 				   N_("Failed to load API cache module %s", "file"),
142 				   lib);
143 	return KRB5_CC_NOSUPP;
144     }
145 
146     init_func = (cc_initialize_func)dlsym(cc_handle, "cc_initialize");
147     set_target_uid = (void (KRB5_CALLCONV *)(uid_t))
148 	dlsym(cc_handle, "krb5_ipc_client_set_target_uid");
149     clear_target = (void (KRB5_CALLCONV *)(void))
150 	dlsym(cc_handle, "krb5_ipc_client_clear_target");
151     HEIMDAL_MUTEX_unlock(&acc_mutex);
152     if (init_func == NULL) {
153 	if (context)
154 	    krb5_set_error_message(context, KRB5_CC_NOSUPP,
155 				   N_("Failed to find cc_initialize"
156 				      "in %s: %s", "file, error"), lib, dlerror());
157 	dlclose(cc_handle);
158 	return KRB5_CC_NOSUPP;
159     }
160 
161     return 0;
162 #else
163     HEIMDAL_MUTEX_unlock(&acc_mutex);
164     if (context)
165 	krb5_set_error_message(context, KRB5_CC_NOSUPP,
166 			       N_("no support for shared object", ""));
167     return KRB5_CC_NOSUPP;
168 #endif
169 }
170 
171 void
172 _heim_krb5_ipc_client_set_target_uid(uid_t uid)
173 {
174     init_ccapi(NULL);
175     if (set_target_uid != NULL)
176         (*set_target_uid)(uid);
177 }
178 
179 void
180 _heim_krb5_ipc_client_clear_target(void)
181 {
182     init_ccapi(NULL);
183     if (clear_target != NULL)
184         (*clear_target)();
185 }
186 
187 static krb5_error_code
188 make_cred_from_ccred(krb5_context context,
189 		     const cc_credentials_v5_t *incred,
190 		     krb5_creds *cred)
191 {
192     krb5_error_code ret;
193     unsigned int i;
194 
195     memset(cred, 0, sizeof(*cred));
196 
197     ret = krb5_parse_name(context, incred->client, &cred->client);
198     if (ret)
199 	goto fail;
200 
201     ret = krb5_parse_name(context, incred->server, &cred->server);
202     if (ret)
203 	goto fail;
204 
205     cred->session.keytype = incred->keyblock.type;
206     cred->session.keyvalue.length = incred->keyblock.length;
207     cred->session.keyvalue.data = malloc(incred->keyblock.length);
208     if (cred->session.keyvalue.data == NULL)
209 	goto nomem;
210     memcpy(cred->session.keyvalue.data, incred->keyblock.data,
211 	   incred->keyblock.length);
212 
213     cred->times.authtime = incred->authtime;
214     cred->times.starttime = incred->starttime;
215     cred->times.endtime = incred->endtime;
216     cred->times.renew_till = incred->renew_till;
217 
218     ret = krb5_data_copy(&cred->ticket,
219 			 incred->ticket.data,
220 			 incred->ticket.length);
221     if (ret)
222 	goto nomem;
223 
224     ret = krb5_data_copy(&cred->second_ticket,
225 			 incred->second_ticket.data,
226 			 incred->second_ticket.length);
227     if (ret)
228 	goto nomem;
229 
230     cred->authdata.val = NULL;
231     cred->authdata.len = 0;
232 
233     cred->addresses.val = NULL;
234     cred->addresses.len = 0;
235 
236     for (i = 0; incred->authdata && incred->authdata[i]; i++)
237 	;
238 
239     if (i) {
240 	cred->authdata.val = calloc(i, sizeof(cred->authdata.val[0]));
241 	if (cred->authdata.val == NULL)
242 	    goto nomem;
243 	cred->authdata.len = i;
244 	for (i = 0; i < cred->authdata.len; i++) {
245 	    cred->authdata.val[i].ad_type = incred->authdata[i]->type;
246 	    ret = krb5_data_copy(&cred->authdata.val[i].ad_data,
247 				 incred->authdata[i]->data,
248 				 incred->authdata[i]->length);
249 	    if (ret)
250 		goto nomem;
251 	}
252     }
253 
254     for (i = 0; incred->addresses && incred->addresses[i]; i++)
255 	;
256 
257     if (i) {
258 	cred->addresses.val = calloc(i, sizeof(cred->addresses.val[0]));
259 	if (cred->addresses.val == NULL)
260 	    goto nomem;
261 	cred->addresses.len = i;
262 
263 	for (i = 0; i < cred->addresses.len; i++) {
264 	    cred->addresses.val[i].addr_type = incred->addresses[i]->type;
265 	    ret = krb5_data_copy(&cred->addresses.val[i].address,
266 				 incred->addresses[i]->data,
267 				 incred->addresses[i]->length);
268 	    if (ret)
269 		goto nomem;
270 	}
271     }
272 
273     cred->flags.i = 0;
274     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_FORWARDABLE)
275 	cred->flags.b.forwardable = 1;
276     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_FORWARDED)
277 	cred->flags.b.forwarded = 1;
278     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PROXIABLE)
279 	cred->flags.b.proxiable = 1;
280     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PROXY)
281 	cred->flags.b.proxy = 1;
282     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_MAY_POSTDATE)
283 	cred->flags.b.may_postdate = 1;
284     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_POSTDATED)
285 	cred->flags.b.postdated = 1;
286     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_INVALID)
287 	cred->flags.b.invalid = 1;
288     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_RENEWABLE)
289 	cred->flags.b.renewable = 1;
290     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_INITIAL)
291 	cred->flags.b.initial = 1;
292     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_PRE_AUTH)
293 	cred->flags.b.pre_authent = 1;
294     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_HW_AUTH)
295 	cred->flags.b.hw_authent = 1;
296     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED)
297 	cred->flags.b.transited_policy_checked = 1;
298     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE)
299 	cred->flags.b.ok_as_delegate = 1;
300     if (incred->ticket_flags & KRB5_CCAPI_TKT_FLG_ANONYMOUS)
301 	cred->flags.b.anonymous = 1;
302 
303     return 0;
304 
305 nomem:
306     ret = ENOMEM;
307     krb5_set_error_message(context, ret, N_("malloc: out of memory", "malloc"));
308 
309 fail:
310     krb5_free_cred_contents(context, cred);
311     return ret;
312 }
313 
314 static void
315 free_ccred(cc_credentials_v5_t *cred)
316 {
317     int i;
318 
319     if (cred->addresses) {
320 	for (i = 0; cred->addresses[i] != 0; i++) {
321 	    if (cred->addresses[i]->data)
322 		free(cred->addresses[i]->data);
323 	    free(cred->addresses[i]);
324 	}
325 	free(cred->addresses);
326     }
327     if (cred->server)
328 	free(cred->server);
329     if (cred->client)
330 	free(cred->client);
331     memset(cred, 0, sizeof(*cred));
332 }
333 
334 static krb5_error_code
335 make_ccred_from_cred(krb5_context context,
336 		     const krb5_creds *incred,
337 		     cc_credentials_v5_t *cred)
338 {
339     krb5_error_code ret;
340     size_t i;
341 
342     memset(cred, 0, sizeof(*cred));
343 
344     ret = krb5_unparse_name(context, incred->client, &cred->client);
345     if (ret)
346 	goto fail;
347 
348     ret = krb5_unparse_name(context, incred->server, &cred->server);
349     if (ret)
350 	goto fail;
351 
352     cred->keyblock.type = incred->session.keytype;
353     cred->keyblock.length = incred->session.keyvalue.length;
354     cred->keyblock.data = incred->session.keyvalue.data;
355 
356     cred->authtime = incred->times.authtime;
357     cred->starttime = incred->times.starttime;
358     cred->endtime = incred->times.endtime;
359     cred->renew_till = incred->times.renew_till;
360 
361     cred->ticket.length = incred->ticket.length;
362     cred->ticket.data = incred->ticket.data;
363 
364     cred->second_ticket.length = incred->second_ticket.length;
365     cred->second_ticket.data = incred->second_ticket.data;
366 
367     /* XXX this one should also be filled in */
368     cred->authdata = NULL;
369 
370     cred->addresses = calloc(incred->addresses.len + 1,
371 			     sizeof(cred->addresses[0]));
372     if (cred->addresses == NULL) {
373 
374 	ret = ENOMEM;
375 	goto fail;
376     }
377 
378     for (i = 0; i < incred->addresses.len; i++) {
379 	cc_data *addr;
380 	addr = malloc(sizeof(*addr));
381 	if (addr == NULL) {
382 	    ret = ENOMEM;
383 	    goto fail;
384 	}
385 	addr->type = incred->addresses.val[i].addr_type;
386 	addr->length = incred->addresses.val[i].address.length;
387 	addr->data = malloc(addr->length);
388 	if (addr->data == NULL) {
389 	    free(addr);
390 	    ret = ENOMEM;
391 	    goto fail;
392 	}
393 	memcpy(addr->data, incred->addresses.val[i].address.data,
394 	       addr->length);
395 	cred->addresses[i] = addr;
396     }
397     cred->addresses[i] = NULL;
398 
399     cred->ticket_flags = 0;
400     if (incred->flags.b.forwardable)
401 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_FORWARDABLE;
402     if (incred->flags.b.forwarded)
403 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_FORWARDED;
404     if (incred->flags.b.proxiable)
405 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PROXIABLE;
406     if (incred->flags.b.proxy)
407 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PROXY;
408     if (incred->flags.b.may_postdate)
409 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_MAY_POSTDATE;
410     if (incred->flags.b.postdated)
411 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_POSTDATED;
412     if (incred->flags.b.invalid)
413 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_INVALID;
414     if (incred->flags.b.renewable)
415 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_RENEWABLE;
416     if (incred->flags.b.initial)
417 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_INITIAL;
418     if (incred->flags.b.pre_authent)
419 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_PRE_AUTH;
420     if (incred->flags.b.hw_authent)
421 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_HW_AUTH;
422     if (incred->flags.b.transited_policy_checked)
423 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_TRANSIT_POLICY_CHECKED;
424     if (incred->flags.b.ok_as_delegate)
425 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_OK_AS_DELEGATE;
426     if (incred->flags.b.anonymous)
427 	cred->ticket_flags |= KRB5_CCAPI_TKT_FLG_ANONYMOUS;
428 
429     return 0;
430 
431 fail:
432     free_ccred(cred);
433 
434     krb5_clear_error_message(context);
435     return ret;
436 }
437 
438 static cc_int32
439 get_cc_name(krb5_acc *a)
440 {
441     cc_string_t name;
442     cc_int32 error;
443 
444     error = (*a->ccache->func->get_name)(a->ccache, &name);
445     if (error)
446 	return error;
447 
448     a->cache_name = strdup(name->data);
449     (*name->func->release)(name);
450     if (a->cache_name == NULL)
451 	return ccErrNoMem;
452     return ccNoError;
453 }
454 
455 
456 static const char* KRB5_CALLCONV
457 acc_get_name(krb5_context context,
458 	     krb5_ccache id)
459 {
460     krb5_acc *a = ACACHE(id);
461     int32_t error;
462 
463     if (a->cache_name == NULL) {
464 	krb5_error_code ret;
465 	krb5_principal principal;
466 	char *name;
467 
468 	ret = _krb5_get_default_principal_local(context, &principal);
469 	if (ret)
470 	    return NULL;
471 
472 	ret = krb5_unparse_name(context, principal, &name);
473 	krb5_free_principal(context, principal);
474 	if (ret)
475 	    return NULL;
476 
477 	error = (*a->context->func->create_new_ccache)(a->context,
478 						       cc_credentials_v5,
479 						       name,
480 						       &a->ccache);
481 	krb5_xfree(name);
482 	if (error)
483 	    return NULL;
484 
485 	error = get_cc_name(a);
486 	if (error)
487 	    return NULL;
488     }
489 
490     return a->cache_name;
491 }
492 
493 static krb5_error_code KRB5_CALLCONV
494 acc_alloc(krb5_context context, krb5_ccache *id)
495 {
496     krb5_error_code ret;
497     cc_int32 error;
498     krb5_acc *a;
499 
500     ret = init_ccapi(context);
501     if (ret)
502 	return ret;
503 
504     ret = krb5_data_alloc(&(*id)->data, sizeof(*a));
505     if (ret) {
506 	krb5_clear_error_message(context);
507 	return ret;
508     }
509 
510     a = ACACHE(*id);
511 
512     error = (*init_func)(&a->context, ccapi_version_3, NULL, NULL);
513     if (error) {
514 	krb5_data_free(&(*id)->data);
515 	return translate_cc_error(context, error);
516     }
517 
518     a->cache_name = NULL;
519 
520     return 0;
521 }
522 
523 static krb5_error_code KRB5_CALLCONV
524 acc_resolve(krb5_context context, krb5_ccache *id, const char *res)
525 {
526     krb5_error_code ret;
527     cc_int32 error;
528     krb5_acc *a;
529 
530     ret = acc_alloc(context, id);
531     if (ret)
532 	return ret;
533 
534     a = ACACHE(*id);
535 
536     error = (*a->context->func->open_ccache)(a->context, res, &a->ccache);
537     if (error == ccNoError) {
538 	cc_time_t offset;
539 	error = get_cc_name(a);
540 	if (error != ccNoError) {
541 	    acc_close(context, *id);
542 	    *id = NULL;
543 	    return translate_cc_error(context, error);
544 	}
545 
546 	error = (*a->ccache->func->get_kdc_time_offset)(a->ccache,
547 							cc_credentials_v5,
548 							&offset);
549 	if (error == 0)
550 	    context->kdc_sec_offset = offset;
551 
552     } else if (error == ccErrCCacheNotFound) {
553 	a->ccache = NULL;
554 	a->cache_name = NULL;
555     } else {
556 	*id = NULL;
557 	return translate_cc_error(context, error);
558     }
559 
560     return 0;
561 }
562 
563 static krb5_error_code KRB5_CALLCONV
564 acc_gen_new(krb5_context context, krb5_ccache *id)
565 {
566     krb5_error_code ret;
567     krb5_acc *a;
568 
569     ret = acc_alloc(context, id);
570     if (ret)
571 	return ret;
572 
573     a = ACACHE(*id);
574 
575     a->ccache = NULL;
576     a->cache_name = NULL;
577 
578     return 0;
579 }
580 
581 static krb5_error_code KRB5_CALLCONV
582 acc_initialize(krb5_context context,
583 	       krb5_ccache id,
584 	       krb5_principal primary_principal)
585 {
586     krb5_acc *a = ACACHE(id);
587     krb5_error_code ret;
588     int32_t error;
589     char *name;
590 
591     ret = krb5_unparse_name(context, primary_principal, &name);
592     if (ret)
593 	return ret;
594 
595     if (a->cache_name == NULL) {
596 	error = (*a->context->func->create_new_ccache)(a->context,
597 						       cc_credentials_v5,
598 						       name,
599 						       &a->ccache);
600 	free(name);
601 	if (error == ccNoError)
602 	    error = get_cc_name(a);
603     } else {
604 	cc_credentials_iterator_t iter;
605 	cc_credentials_t ccred;
606 
607 	error = (*a->ccache->func->new_credentials_iterator)(a->ccache, &iter);
608 	if (error) {
609 	    free(name);
610 	    return translate_cc_error(context, error);
611 	}
612 
613 	while (1) {
614 	    error = (*iter->func->next)(iter, &ccred);
615 	    if (error)
616 		break;
617 	    (*a->ccache->func->remove_credentials)(a->ccache, ccred);
618 	    (*ccred->func->release)(ccred);
619 	}
620 	(*iter->func->release)(iter);
621 
622 	error = (*a->ccache->func->set_principal)(a->ccache,
623 						  cc_credentials_v5,
624 						  name);
625     }
626 
627     if (error == 0 && context->kdc_sec_offset)
628 	error = (*a->ccache->func->set_kdc_time_offset)(a->ccache,
629 							cc_credentials_v5,
630 							context->kdc_sec_offset);
631 
632     return translate_cc_error(context, error);
633 }
634 
635 static krb5_error_code KRB5_CALLCONV
636 acc_close(krb5_context context,
637 	  krb5_ccache id)
638 {
639     krb5_acc *a = ACACHE(id);
640 
641     if (a->ccache) {
642 	(*a->ccache->func->release)(a->ccache);
643 	a->ccache = NULL;
644     }
645     if (a->cache_name) {
646 	free(a->cache_name);
647 	a->cache_name = NULL;
648     }
649     if (a->context) {
650 	(*a->context->func->release)(a->context);
651 	a->context = NULL;
652     }
653     krb5_data_free(&id->data);
654     return 0;
655 }
656 
657 static krb5_error_code KRB5_CALLCONV
658 acc_destroy(krb5_context context,
659 	    krb5_ccache id)
660 {
661     krb5_acc *a = ACACHE(id);
662     cc_int32 error = 0;
663 
664     if (a->ccache) {
665 	error = (*a->ccache->func->destroy)(a->ccache);
666 	a->ccache = NULL;
667     }
668     if (a->context) {
669 	error = (a->context->func->release)(a->context);
670 	a->context = NULL;
671     }
672     return translate_cc_error(context, error);
673 }
674 
675 static krb5_error_code KRB5_CALLCONV
676 acc_store_cred(krb5_context context,
677 	       krb5_ccache id,
678 	       krb5_creds *creds)
679 {
680     krb5_acc *a = ACACHE(id);
681     cc_credentials_union cred;
682     cc_credentials_v5_t v5cred;
683     krb5_error_code ret;
684     cc_int32 error;
685 
686     if (a->ccache == NULL) {
687 	krb5_set_error_message(context, KRB5_CC_NOTFOUND,
688 			       N_("No API credential found", ""));
689 	return KRB5_CC_NOTFOUND;
690     }
691 
692     cred.version = cc_credentials_v5;
693     cred.credentials.credentials_v5 = &v5cred;
694 
695     ret = make_ccred_from_cred(context,
696 			       creds,
697 			       &v5cred);
698     if (ret)
699 	return ret;
700 
701     error = (*a->ccache->func->store_credentials)(a->ccache, &cred);
702     if (error)
703 	ret = translate_cc_error(context, error);
704 
705     free_ccred(&v5cred);
706 
707     return ret;
708 }
709 
710 static krb5_error_code KRB5_CALLCONV
711 acc_get_principal(krb5_context context,
712 		  krb5_ccache id,
713 		  krb5_principal *principal)
714 {
715     krb5_acc *a = ACACHE(id);
716     krb5_error_code ret;
717     int32_t error;
718     cc_string_t name;
719 
720     if (a->ccache == NULL) {
721 	krb5_set_error_message(context, KRB5_CC_NOTFOUND,
722 			       N_("No API credential found", ""));
723 	return KRB5_CC_NOTFOUND;
724     }
725 
726     error = (*a->ccache->func->get_principal)(a->ccache,
727 					      cc_credentials_v5,
728 					      &name);
729     if (error)
730 	return translate_cc_error(context, error);
731 
732     ret = krb5_parse_name(context, name->data, principal);
733 
734     (*name->func->release)(name);
735     return ret;
736 }
737 
738 static krb5_error_code KRB5_CALLCONV
739 acc_get_first (krb5_context context,
740 	       krb5_ccache id,
741 	       krb5_cc_cursor *cursor)
742 {
743     cc_credentials_iterator_t iter;
744     krb5_acc *a = ACACHE(id);
745     int32_t error;
746 
747     if (a->ccache == NULL) {
748 	krb5_set_error_message(context, KRB5_CC_NOTFOUND,
749 			       N_("No API credential found", ""));
750 	return KRB5_CC_NOTFOUND;
751     }
752 
753     error = (*a->ccache->func->new_credentials_iterator)(a->ccache, &iter);
754     if (error) {
755 	krb5_clear_error_message(context);
756 	return ENOENT;
757     }
758     *cursor = iter;
759     return 0;
760 }
761 
762 
763 static krb5_error_code KRB5_CALLCONV
764 acc_get_next (krb5_context context,
765 	      krb5_ccache id,
766 	      krb5_cc_cursor *cursor,
767 	      krb5_creds *creds)
768 {
769     cc_credentials_iterator_t iter = *cursor;
770     cc_credentials_t cred;
771     krb5_error_code ret;
772     int32_t error;
773 
774     while (1) {
775 	error = (*iter->func->next)(iter, &cred);
776 	if (error)
777 	    return translate_cc_error(context, error);
778 	if (cred->data->version == cc_credentials_v5)
779 	    break;
780 	(*cred->func->release)(cred);
781     }
782 
783     ret = make_cred_from_ccred(context,
784 			       cred->data->credentials.credentials_v5,
785 			       creds);
786     (*cred->func->release)(cred);
787     return ret;
788 }
789 
790 static krb5_error_code KRB5_CALLCONV
791 acc_end_get (krb5_context context,
792 	     krb5_ccache id,
793 	     krb5_cc_cursor *cursor)
794 {
795     cc_credentials_iterator_t iter = *cursor;
796     (*iter->func->release)(iter);
797     return 0;
798 }
799 
800 static krb5_error_code KRB5_CALLCONV
801 acc_remove_cred(krb5_context context,
802 		krb5_ccache id,
803 		krb5_flags which,
804 		krb5_creds *cred)
805 {
806     cc_credentials_iterator_t iter;
807     krb5_acc *a = ACACHE(id);
808     cc_credentials_t ccred;
809     krb5_error_code ret;
810     cc_int32 error;
811     char *client, *server;
812 
813     if (a->ccache == NULL) {
814 	krb5_set_error_message(context, KRB5_CC_NOTFOUND,
815 			       N_("No API credential found", ""));
816 	return KRB5_CC_NOTFOUND;
817     }
818 
819     if (cred->client) {
820 	ret = krb5_unparse_name(context, cred->client, &client);
821 	if (ret)
822 	    return ret;
823     } else
824 	client = NULL;
825 
826     ret = krb5_unparse_name(context, cred->server, &server);
827     if (ret) {
828 	free(client);
829 	return ret;
830     }
831 
832     error = (*a->ccache->func->new_credentials_iterator)(a->ccache, &iter);
833     if (error) {
834 	free(server);
835 	free(client);
836 	return translate_cc_error(context, error);
837     }
838 
839     ret = KRB5_CC_NOTFOUND;
840     while (1) {
841 	cc_credentials_v5_t *v5cred;
842 
843 	error = (*iter->func->next)(iter, &ccred);
844 	if (error)
845 	    break;
846 
847 	if (ccred->data->version != cc_credentials_v5)
848 	    goto next;
849 
850 	v5cred = ccred->data->credentials.credentials_v5;
851 
852 	if (client && strcmp(v5cred->client, client) != 0)
853 	    goto next;
854 
855 	if (strcmp(v5cred->server, server) != 0)
856 	    goto next;
857 
858 	(*a->ccache->func->remove_credentials)(a->ccache, ccred);
859 	ret = 0;
860     next:
861 	(*ccred->func->release)(ccred);
862     }
863 
864     (*iter->func->release)(iter);
865 
866     if (ret)
867 	krb5_set_error_message(context, ret,
868 			       N_("Can't find credential %s in cache",
869 				 "principal"), server);
870     free(server);
871     free(client);
872 
873     return ret;
874 }
875 
876 static krb5_error_code KRB5_CALLCONV
877 acc_set_flags(krb5_context context,
878 	      krb5_ccache id,
879 	      krb5_flags flags)
880 {
881     return 0;
882 }
883 
884 static int KRB5_CALLCONV
885 acc_get_version(krb5_context context,
886 		krb5_ccache id)
887 {
888     return 0;
889 }
890 
891 struct cache_iter {
892     cc_context_t context;
893     cc_ccache_iterator_t iter;
894 };
895 
896 static krb5_error_code KRB5_CALLCONV
897 acc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
898 {
899     struct cache_iter *iter;
900     krb5_error_code ret;
901     cc_int32 error;
902 
903     ret = init_ccapi(context);
904     if (ret)
905 	return ret;
906 
907     iter = calloc(1, sizeof(*iter));
908     if (iter == NULL) {
909 	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
910 	return ENOMEM;
911     }
912 
913     error = (*init_func)(&iter->context, ccapi_version_3, NULL, NULL);
914     if (error) {
915 	free(iter);
916 	return translate_cc_error(context, error);
917     }
918 
919     error = (*iter->context->func->new_ccache_iterator)(iter->context,
920 							&iter->iter);
921     if (error) {
922 	free(iter);
923 	krb5_clear_error_message(context);
924 	return ENOENT;
925     }
926     *cursor = iter;
927     return 0;
928 }
929 
930 static krb5_error_code KRB5_CALLCONV
931 acc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
932 {
933     struct cache_iter *iter = cursor;
934     cc_ccache_t cache;
935     krb5_acc *a;
936     krb5_error_code ret;
937     int32_t error;
938 
939     error = (*iter->iter->func->next)(iter->iter, &cache);
940     if (error)
941 	return translate_cc_error(context, error);
942 
943     ret = _krb5_cc_allocate(context, &krb5_acc_ops, id);
944     if (ret) {
945 	(*cache->func->release)(cache);
946 	return ret;
947     }
948 
949     ret = acc_alloc(context, id);
950     if (ret) {
951 	(*cache->func->release)(cache);
952 	free(*id);
953 	return ret;
954     }
955 
956     a = ACACHE(*id);
957     a->ccache = cache;
958 
959     error = get_cc_name(a);
960     if (error) {
961 	acc_close(context, *id);
962 	*id = NULL;
963 	return translate_cc_error(context, error);
964     }
965     return 0;
966 }
967 
968 static krb5_error_code KRB5_CALLCONV
969 acc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
970 {
971     struct cache_iter *iter = cursor;
972 
973     (*iter->iter->func->release)(iter->iter);
974     iter->iter = NULL;
975     (*iter->context->func->release)(iter->context);
976     iter->context = NULL;
977     free(iter);
978     return 0;
979 }
980 
981 static krb5_error_code KRB5_CALLCONV
982 acc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
983 {
984     krb5_acc *afrom = ACACHE(from);
985     krb5_acc *ato = ACACHE(to);
986     int32_t error;
987 
988     if (ato->ccache == NULL) {
989 	cc_string_t name;
990 
991 	error = (*afrom->ccache->func->get_principal)(afrom->ccache,
992 						      cc_credentials_v5,
993 						      &name);
994 	if (error)
995 	    return translate_cc_error(context, error);
996 
997 	error = (*ato->context->func->create_new_ccache)(ato->context,
998 							 cc_credentials_v5,
999 							 name->data,
1000 							 &ato->ccache);
1001 	(*name->func->release)(name);
1002 	if (error)
1003 	    return translate_cc_error(context, error);
1004     }
1005 
1006     error = (*ato->ccache->func->move)(afrom->ccache, ato->ccache);
1007 
1008     acc_destroy(context, from);
1009 
1010     return translate_cc_error(context, error);
1011 }
1012 
1013 static krb5_error_code KRB5_CALLCONV
1014 acc_get_default_name(krb5_context context, char **str)
1015 {
1016     krb5_error_code ret;
1017     cc_context_t cc;
1018     cc_string_t name;
1019     int32_t error;
1020 
1021     ret = init_ccapi(context);
1022     if (ret)
1023 	return ret;
1024 
1025     error = (*init_func)(&cc, ccapi_version_3, NULL, NULL);
1026     if (error)
1027 	return translate_cc_error(context, error);
1028 
1029     error = (*cc->func->get_default_ccache_name)(cc, &name);
1030     if (error) {
1031 	(*cc->func->release)(cc);
1032 	return translate_cc_error(context, error);
1033     }
1034 
1035     error = asprintf(str, "API:%s", name->data);
1036     (*name->func->release)(name);
1037     (*cc->func->release)(cc);
1038 
1039     if (error < 0 || *str == NULL) {
1040 	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
1041 	return ENOMEM;
1042     }
1043     return 0;
1044 }
1045 
1046 static krb5_error_code KRB5_CALLCONV
1047 acc_set_default(krb5_context context, krb5_ccache id)
1048 {
1049     krb5_acc *a = ACACHE(id);
1050     cc_int32 error;
1051 
1052     if (a->ccache == NULL) {
1053 	krb5_set_error_message(context, KRB5_CC_NOTFOUND,
1054 			       N_("No API credential found", ""));
1055 	return KRB5_CC_NOTFOUND;
1056     }
1057 
1058     error = (*a->ccache->func->set_default)(a->ccache);
1059     if (error)
1060 	return translate_cc_error(context, error);
1061 
1062     return 0;
1063 }
1064 
1065 static krb5_error_code KRB5_CALLCONV
1066 acc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1067 {
1068     krb5_acc *a = ACACHE(id);
1069     cc_int32 error;
1070     cc_time_t t;
1071 
1072     if (a->ccache == NULL) {
1073 	krb5_set_error_message(context, KRB5_CC_NOTFOUND,
1074 			       N_("No API credential found", ""));
1075 	return KRB5_CC_NOTFOUND;
1076     }
1077 
1078     error = (*a->ccache->func->get_change_time)(a->ccache, &t);
1079     if (error)
1080 	return translate_cc_error(context, error);
1081 
1082     *mtime = t;
1083 
1084     return 0;
1085 }
1086 
1087 /**
1088  * Variable containing the API based credential cache implemention.
1089  *
1090  * @ingroup krb5_ccache
1091  */
1092 
1093 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_acc_ops = {
1094     KRB5_CC_OPS_VERSION,
1095     "API",
1096     acc_get_name,
1097     acc_resolve,
1098     acc_gen_new,
1099     acc_initialize,
1100     acc_destroy,
1101     acc_close,
1102     acc_store_cred,
1103     NULL, /* acc_retrieve */
1104     acc_get_principal,
1105     acc_get_first,
1106     acc_get_next,
1107     acc_end_get,
1108     acc_remove_cred,
1109     acc_set_flags,
1110     acc_get_version,
1111     acc_get_cache_first,
1112     acc_get_cache_next,
1113     acc_end_cache_get,
1114     acc_move,
1115     acc_get_default_name,
1116     acc_set_default,
1117     acc_lastchange,
1118     NULL,
1119     NULL,
1120 };
1121 
1122 #endif
1123