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
44static HEIMDAL_MUTEX acc_mutex = HEIMDAL_MUTEX_INITIALIZER;
45static cc_initialize_func init_func;
46static void (KRB5_CALLCONV *set_target_uid)(uid_t);
47static void (KRB5_CALLCONV *clear_target)(void);
48
49#ifdef HAVE_DLOPEN
50static void *cc_handle;
51#endif
52
53typedef struct krb5_acc {
54    char *cache_name;
55    cc_context_t context;
56    cc_ccache_t ccache;
57} krb5_acc;
58
59static krb5_error_code KRB5_CALLCONV acc_close(krb5_context, krb5_ccache);
60
61#define ACACHE(X) ((krb5_acc *)(X)->data.data)
62
63static 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
78static krb5_error_code
79translate_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
89static krb5_error_code
90init_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
171void
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
179void
180_heim_krb5_ipc_client_clear_target(void)
181{
182    init_ccapi(NULL);
183    if (clear_target != NULL)
184        (*clear_target)();
185}
186
187static krb5_error_code
188make_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
305nomem:
306    ret = ENOMEM;
307    krb5_set_error_message(context, ret, N_("malloc: out of memory", "malloc"));
308
309fail:
310    krb5_free_cred_contents(context, cred);
311    return ret;
312}
313
314static void
315free_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
334static krb5_error_code
335make_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
431fail:
432    free_ccred(cred);
433
434    krb5_clear_error_message(context);
435    return ret;
436}
437
438static cc_int32
439get_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
456static const char* KRB5_CALLCONV
457acc_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
493static krb5_error_code KRB5_CALLCONV
494acc_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
523static krb5_error_code KRB5_CALLCONV
524acc_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
563static krb5_error_code KRB5_CALLCONV
564acc_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
581static krb5_error_code KRB5_CALLCONV
582acc_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
635static krb5_error_code KRB5_CALLCONV
636acc_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
657static krb5_error_code KRB5_CALLCONV
658acc_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
675static krb5_error_code KRB5_CALLCONV
676acc_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
710static krb5_error_code KRB5_CALLCONV
711acc_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
738static krb5_error_code KRB5_CALLCONV
739acc_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
763static krb5_error_code KRB5_CALLCONV
764acc_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
790static krb5_error_code KRB5_CALLCONV
791acc_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
800static krb5_error_code KRB5_CALLCONV
801acc_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
876static krb5_error_code KRB5_CALLCONV
877acc_set_flags(krb5_context context,
878	      krb5_ccache id,
879	      krb5_flags flags)
880{
881    return 0;
882}
883
884static int KRB5_CALLCONV
885acc_get_version(krb5_context context,
886		krb5_ccache id)
887{
888    return 0;
889}
890
891struct cache_iter {
892    cc_context_t context;
893    cc_ccache_iterator_t iter;
894};
895
896static krb5_error_code KRB5_CALLCONV
897acc_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
930static krb5_error_code KRB5_CALLCONV
931acc_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
968static krb5_error_code KRB5_CALLCONV
969acc_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
981static krb5_error_code KRB5_CALLCONV
982acc_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
1013static krb5_error_code KRB5_CALLCONV
1014acc_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
1046static krb5_error_code KRB5_CALLCONV
1047acc_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
1065static krb5_error_code KRB5_CALLCONV
1066acc_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
1093KRB5_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