1/*
2 * Copyright (c) 2013 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2013 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 "heimcred.h"
38
39#ifdef HAVE_XCC
40
41#define CFRELEASE_NULL(x) do { if (x) { CFRelease(x); x = NULL; } } while(0)
42
43typedef struct krb5_xcc {
44    CFUUIDRef uuid;
45    HeimCredRef cred;
46    CFStringRef clientName;
47    krb5_principal primary_principal;
48    char *cache_name;
49} krb5_xcc;
50
51struct xcc_cursor {
52    CFArrayRef array;
53    CFIndex offset;
54};
55
56#define XCACHE(X) ((krb5_xcc *)(X)->data.data)
57
58static void
59free_cursor(struct xcc_cursor *c)
60{
61    if (c->array)
62	CFRelease(c->array);
63    free(c);
64}
65
66#if 0
67static krb5_error_code
68make_cred(krb5_context context,
69	  HeimCredRef xcred,
70	  krb5_creds *cred)
71{
72    memset(cred, 0, sizeof(*cred));
73    return 0;
74}
75#endif
76
77static CFStringRef
78CFStringCreateFromPrincipal(krb5_context context, krb5_principal principal)
79{
80    CFStringRef str;
81    char *p;
82
83    if (krb5_unparse_name(context, principal, &p) != 0)
84	return NULL;
85
86    str = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8);
87    krb5_xfree(p);
88
89    return str;
90}
91
92static krb5_principal
93PrincipalFromCFString(krb5_context context, CFStringRef string)
94{
95    krb5_principal principal = NULL;
96    char *p = rk_cfstring2cstring(string);
97    if (p == NULL)
98	return NULL;
99
100    (void)krb5_parse_name(context, p, &principal);
101    free(p);
102    return principal;
103}
104
105
106static const char* KRB5_CALLCONV
107xcc_get_name(krb5_context context,
108	     krb5_ccache id)
109{
110    krb5_xcc *a = XCACHE(id);
111    return a->cache_name;
112}
113
114static krb5_error_code KRB5_CALLCONV
115xcc_alloc(krb5_context context, krb5_ccache *id)
116{
117    (*id)->data.data = calloc(1, sizeof(krb5_xcc));
118    (*id)->data.length = sizeof(krb5_xcc);
119
120    return 0;
121}
122
123static void
124genName(krb5_xcc *x)
125{
126    if (x->cache_name)
127	return;
128    CFUUIDBytes bytes = CFUUIDGetUUIDBytes(x->uuid);
129    x->cache_name = malloc(37);
130    uuid_unparse((void *)&bytes, x->cache_name);
131}
132
133static krb5_error_code KRB5_CALLCONV
134xcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
135{
136    krb5_error_code ret;
137    CFUUIDBytes bytes;
138    krb5_xcc *x;
139
140    if (uuid_parse(res, (void *)&bytes) != 0) {
141	krb5_set_error_message(context, KRB5_CC_END, "failed to parse uuid: %s", res);
142	return KRB5_CC_END;
143    }
144
145    CFUUIDRef uuidref = CFUUIDCreateFromUUIDBytes(NULL, bytes);
146    if (uuidref == NULL) {
147	krb5_set_error_message(context, KRB5_CC_END, "failed to create uuid from: %s", res);
148	return KRB5_CC_END;
149    }
150
151    ret = xcc_alloc(context, id);
152    if (ret) {
153	CFRELEASE_NULL(uuidref);
154	return ret;
155    }
156
157    x = XCACHE((*id));
158
159    x->uuid = uuidref;
160    genName(x);
161
162    return 0;
163}
164
165static krb5_error_code
166xcc_create(krb5_context context, krb5_xcc *x, CFUUIDRef uuid)
167{
168    const void *keys[] = {
169    	(void *)kHEIMAttrCredentialGroupLead,
170	(void *)kHEIMAttrType,
171	(void *)kHEIMAttrUUID
172    };
173    const void *values[] = {
174	(void *)kCFBooleanTrue,
175	(void *)kHEIMTypeKerberos,
176	(void *)uuid
177    };
178    CFDictionaryRef attrs;
179    krb5_error_code ret;
180
181    CFIndex num_keys = sizeof(keys)/sizeof(keys[0]);
182    if (uuid == NULL)
183	num_keys -= 1;
184
185    attrs = CFDictionaryCreate(NULL, keys, values, num_keys, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
186    if (attrs == NULL) abort();
187
188    /*
189     * Contract with HeimCredCreate is that it set parent and group uuid's when they are not set on the credential
190     */
191    x->cred = HeimCredCreate(attrs, NULL);
192    CFRelease(attrs);
193    if (x->cred) {
194	if (x->uuid) abort();
195	x->uuid = HeimCredGetUUID(x->cred);
196	if (x->uuid == NULL) abort();
197	CFRetain(x->uuid);
198
199	ret = 0;
200	genName(x);
201    } else
202	ret = ENOMEM;
203
204    return ret;
205}
206
207static krb5_error_code KRB5_CALLCONV
208xcc_gen_new(krb5_context context, krb5_ccache *id)
209{
210    krb5_error_code ret;
211    krb5_xcc *x;
212
213    ret = xcc_alloc(context, id);
214    if (ret)
215	return ret;
216
217    x = XCACHE(*id);
218
219    ret = xcc_create(context, x, NULL);
220
221    return ret;
222}
223
224static krb5_error_code KRB5_CALLCONV
225xcc_initialize(krb5_context context,
226	       krb5_ccache id,
227	       krb5_principal primary_principal)
228{
229    krb5_xcc *x = XCACHE(id);
230    krb5_error_code ret;
231
232    if (x->primary_principal)
233	krb5_free_principal(context, x->primary_principal);
234
235    ret = krb5_copy_principal(context, primary_principal, &x->primary_principal);
236    if (ret)
237	return ret;
238
239    CFRELEASE_NULL(x->clientName);
240
241    x->clientName = CFStringCreateFromPrincipal(context, primary_principal);
242    if (x->clientName == NULL)
243	return krb5_enomem(context);
244
245    if (x->cred == NULL) {
246	ret = xcc_create(context, x, x->uuid);
247	if (ret)
248	    return ret;
249    }
250    if (!HeimCredSetAttribute(x->cred, kHEIMAttrClientName, x->clientName, NULL)) {\
251	ret = EINVAL;
252	krb5_set_error_message(context, ret, "failed to store credential to %s", x->cache_name);
253    }
254
255
256    return ret;
257}
258
259static krb5_error_code KRB5_CALLCONV
260xcc_close(krb5_context context,
261	  krb5_ccache id)
262{
263    krb5_xcc *x = XCACHE(id);
264    krb5_free_principal(context, x->primary_principal);
265    CFRELEASE_NULL(x->uuid);
266    CFRELEASE_NULL(x->cred);
267    free(x->cache_name);
268    return 0;
269}
270
271static krb5_error_code KRB5_CALLCONV
272xcc_destroy(krb5_context context,
273	    krb5_ccache id)
274{
275    krb5_xcc *x = XCACHE(id);
276    if (x->uuid)
277	HeimCredDeleteByUUID(x->uuid);
278    CFRELEASE_NULL(x->cred);
279
280    return 0;
281}
282
283static krb5_error_code KRB5_CALLCONV
284xcc_store_cred(krb5_context context,
285	       krb5_ccache id,
286	       krb5_creds *creds)
287{
288    krb5_xcc *x = XCACHE(id);
289    krb5_storage *sp = NULL;
290    CFDataRef dref = NULL;
291    krb5_data data;
292    CFStringRef principal = NULL;
293    CFDictionaryRef query = NULL;
294    krb5_error_code ret;
295
296    krb5_data_zero(&data);
297
298    sp = krb5_storage_emem();
299    if (sp == NULL) {
300	ret = krb5_enomem(context);
301	goto out;
302    }
303
304    ret = krb5_store_creds(sp, creds);
305    if (ret)
306	goto out;
307
308    krb5_storage_to_data(sp, &data);
309
310    dref = CFDataCreateWithBytesNoCopy(NULL, data.data, data.length, kCFAllocatorNull);
311    if (dref == NULL) {
312	ret = krb5_enomem(context);
313	goto out;
314    }
315
316    principal = CFStringCreateFromPrincipal(context, creds->server);
317    if (principal == NULL) {
318	ret = krb5_enomem(context);
319	goto out;
320    }
321
322    const void *add_keys[] = {
323	kHEIMAttrType,
324	kHEIMAttrClientName,
325	kHEIMAttrServerName,
326	kHEIMAttrData,
327	kHEIMAttrCredentialGroup,
328	kHEIMAttrParentCredential,
329    };
330    const void *add_values[] = {
331	kHEIMTypeKerberos,
332	x->clientName,
333	principal,
334	dref,
335	x->uuid,
336	x->uuid,
337    };
338
339    query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL);
340    heim_assert(query != NULL, "out of memory");
341
342    HeimCredRef ccred = HeimCredCreate(query, NULL);
343    if (ccred) {
344	CFRelease(ccred);
345    } else {
346	_krb5_debugx(context, 5, "failed to add credential to %s\n", x->cache_name);
347	ret = EINVAL;
348	krb5_set_error_message(context, ret, "failed to store credential to %s", x->cache_name);
349	goto out;
350    }
351
352out:
353    if (sp)
354	krb5_storage_free(sp);
355    CFRELEASE_NULL(dref);
356    CFRELEASE_NULL(principal);
357    krb5_data_free(&data);
358
359    return ret;
360}
361
362static krb5_error_code KRB5_CALLCONV
363xcc_get_principal(krb5_context context,
364		  krb5_ccache id,
365		  krb5_principal *principal)
366{
367    krb5_xcc *x = XCACHE(id);
368    if (x->cred == NULL) {
369	x->cred = HeimCredCopyFromUUID(x->uuid);
370	if (x->cred == NULL) {
371	    krb5_set_error_message(context, KRB5_CC_END, "no credential for %s", x->cache_name);
372	    return KRB5_CC_END;
373	}
374    }
375    if (x->clientName == NULL) {
376	x->clientName = HeimCredCopyAttribute(x->cred, kHEIMAttrClientName);
377	if (x->clientName == NULL) {
378	    krb5_set_error_message(context, KRB5_CC_END, "no principal name for %s", x->cache_name);
379	    return KRB5_CC_END;
380	}
381    }
382    if (x->primary_principal == NULL) {
383	x->primary_principal = PrincipalFromCFString(context, x->clientName);
384	if (x->primary_principal == NULL) {
385	    krb5_set_error_message(context, KRB5_CC_END, "no principal for %s", x->cache_name);
386	    return KRB5_CC_END;
387	}
388    }
389
390    return krb5_copy_principal(context, x->primary_principal, principal);
391}
392
393static krb5_error_code KRB5_CALLCONV
394xcc_get_first (krb5_context context,
395	       krb5_ccache id,
396	       krb5_cc_cursor *cursor)
397{
398    CFDictionaryRef query;
399    krb5_xcc *x = XCACHE(id);
400    CFUUIDRef uuid = HeimCredGetUUID(x->cred);
401    struct xcc_cursor *c;
402
403    c = calloc(1, sizeof(*c));
404    if (c == NULL)
405	return krb5_enomem(context);
406
407    const void *keys[] = { (void *)kHEIMAttrCredentialGroup, kHEIMAttrType };
408    const void *values[] = { (void *)uuid, kHEIMTypeKerberos };
409
410    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
411    if (query == NULL) abort();
412
413    c->array = HeimCredCopyQuery(query);
414    CFRELEASE_NULL(query);
415    if (c->array == NULL) {
416	free_cursor(c);
417	return KRB5_CC_END;
418    }
419
420    *cursor = c;
421
422    return 0;
423}
424
425
426static krb5_error_code KRB5_CALLCONV
427xcc_get_next (krb5_context context,
428	      krb5_ccache id,
429	      krb5_cc_cursor *cursor,
430	      krb5_creds *creds)
431{
432    struct xcc_cursor *c = *cursor;
433    krb5_error_code ret;
434    krb5_storage *sp;
435    HeimCredRef cred;
436    CFDataRef data;
437
438    if (c->array == NULL)
439	return KRB5_CC_END;
440
441next:
442    if (c->offset >= CFArrayGetCount(c->array))
443	return KRB5_CC_END;
444
445    cred = (HeimCredRef)CFArrayGetValueAtIndex(c->array, c->offset++);
446    if (cred == NULL)
447	return KRB5_CC_END;
448
449    data = HeimCredCopyAttribute(cred, kHEIMAttrData);
450    if (data == NULL)
451	goto next;
452
453    sp = krb5_storage_from_readonly_mem(CFDataGetBytePtr(data), CFDataGetLength(data));
454    if (sp == NULL) {
455	CFRELEASE_NULL(data);
456	return KRB5_CC_END;
457    }
458
459    ret = krb5_ret_creds(sp, creds);
460    krb5_storage_free(sp);
461    CFRELEASE_NULL(data);
462
463    return ret;
464}
465
466static krb5_error_code KRB5_CALLCONV
467xcc_end_get (krb5_context context,
468	     krb5_ccache id,
469	     krb5_cc_cursor *cursor)
470{
471    free_cursor((struct xcc_cursor *)*cursor);
472    *cursor = NULL;
473
474    return 0;
475}
476
477static krb5_error_code KRB5_CALLCONV
478xcc_remove_cred(krb5_context context,
479		krb5_ccache id,
480		krb5_flags which,
481		krb5_creds *cred)
482{
483    CFDictionaryRef query;
484    krb5_xcc *x = XCACHE(id);
485
486    CFStringRef servername = CFStringCreateFromPrincipal(context, cred->server);
487    if (servername == NULL)
488	return KRB5_CC_END;
489
490    const void *keys[] = { (void *)kHEIMAttrCredentialGroup, kHEIMAttrType, kHEIMAttrServerName };
491    const void *values[] = { (void *)x->uuid, kHEIMTypeKerberos, servername };
492
493    /* XXX match enctype */
494
495    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
496    if (query == NULL) abort();
497
498    CFRELEASE_NULL(servername);
499
500    bool res = HeimCredDeleteQuery(query, NULL);
501    CFRELEASE_NULL(query);
502
503    if (!res)
504	return KRB5_CC_END;
505    return 0;
506}
507
508static krb5_error_code KRB5_CALLCONV
509xcc_set_flags(krb5_context context,
510	      krb5_ccache id,
511	      krb5_flags flags)
512{
513    return 0;
514}
515
516static int KRB5_CALLCONV
517xcc_get_version(krb5_context context,
518		krb5_ccache id)
519{
520    return 0;
521}
522
523static krb5_error_code KRB5_CALLCONV
524xcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
525{
526    CFDictionaryRef query;
527    struct xcc_cursor *c;
528
529    const void *keys[] = {
530    	(void *)kHEIMAttrCredentialGroupLead,
531	(void *)kHEIMAttrType,
532    };
533    const void *values[] = {
534	(void *)kCFBooleanTrue,
535	(void *)kHEIMTypeKerberos,
536    };
537
538    c = calloc(1, sizeof(*c));
539    if (c == NULL)
540	return krb5_enomem(context);
541
542    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
543    if (query == NULL) abort();
544
545    c->array = HeimCredCopyQuery(query);
546    CFRELEASE_NULL(query);
547    if (c->array == NULL) {
548	free_cursor(c);
549	return KRB5_CC_END;
550    }
551    *cursor = c;
552    return 0;
553}
554
555static krb5_error_code KRB5_CALLCONV
556xcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
557{
558    struct xcc_cursor *c = cursor;
559    krb5_error_code ret;
560    HeimCredRef cred;
561    krb5_xcc *x;
562
563    if (c->array == NULL)
564	return KRB5_CC_END;
565
566    if (c->offset >= CFArrayGetCount(c->array))
567	return KRB5_CC_END;
568
569    cred = (HeimCredRef)CFArrayGetValueAtIndex(c->array, c->offset++);
570    if (cred == NULL)
571	return KRB5_CC_END;
572
573    ret = _krb5_cc_allocate(context, &krb5_xcc_ops, id);
574    if (ret)
575	return ret;
576
577    xcc_alloc(context, id);
578    x = XCACHE((*id));
579
580    x->uuid = HeimCredGetUUID(cred);
581    CFRetain(x->uuid);
582    x->cred = cred;
583    CFRetain(cred);
584    genName(x);
585
586    return ret;
587}
588
589static krb5_error_code KRB5_CALLCONV
590xcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
591{
592    free_cursor((struct xcc_cursor *)cursor);
593    return 0;
594}
595
596static krb5_error_code KRB5_CALLCONV
597xcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
598{
599    krb5_xcc *xfrom = XCACHE(from);
600    krb5_xcc *xto = XCACHE(to);
601
602    if (!HeimCredMove(xfrom->uuid, xto->uuid))
603	return KRB5_CC_END;
604
605    CFRELEASE_NULL(xto->cred);
606    CFRELEASE_NULL(xfrom->cred);
607
608    free(xto->cache_name);
609    xto->cache_name = NULL;
610    genName(xto);
611
612    CFRELEASE_NULL(xto->clientName);
613    xto->clientName = xfrom->clientName;
614    xfrom->clientName = NULL;
615
616    if (xto->primary_principal)
617	krb5_free_principal(context, xto->primary_principal);
618    xto->primary_principal = xfrom->primary_principal;
619    xfrom->primary_principal = NULL;
620
621    return 0;
622}
623
624static krb5_error_code KRB5_CALLCONV
625xcc_get_default_name(krb5_context context, char **str)
626{
627    krb5_set_error_message(context, EINVAL, "XCACHE doesn't have a default name");
628    return EINVAL;
629}
630
631static krb5_error_code KRB5_CALLCONV
632xcc_set_default(krb5_context context, krb5_ccache id)
633{
634    krb5_set_error_message(context, EINVAL, "XCACHE doesn't have a default name");
635    return EINVAL;
636}
637
638static krb5_error_code KRB5_CALLCONV
639xcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
640{
641    *mtime = 0;
642    return 0;
643}
644
645static krb5_error_code
646xcc_get_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid)
647{
648    krb5_xcc *x = XCACHE(id);
649    CFUUIDBytes bytes = CFUUIDGetUUIDBytes(x->uuid);
650    memcpy(uuid, &bytes, sizeof(krb5_uuid));
651    return 0;
652}
653
654static krb5_error_code
655xcc_resolve_by_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid)
656{
657    krb5_error_code ret;
658    CFUUIDBytes bytes;
659    krb5_xcc *x;
660
661    memcpy(&bytes, uuid, sizeof(bytes));
662
663    CFUUIDRef uuidref = CFUUIDCreateFromUUIDBytes(NULL, bytes);
664    if (uuidref == NULL) {
665	krb5_set_error_message(context, KRB5_CC_END, "failed to create uuid");
666	return KRB5_CC_END;
667    }
668
669    ret = xcc_alloc(context, &id);
670    if (ret) {
671	CFRELEASE_NULL(uuidref);
672	return ret;
673    }
674
675    x = XCACHE(id);
676
677    x->uuid = uuidref;
678    genName(x);
679
680    return 0;
681}
682
683static krb5_error_code
684xcc_set_acl(krb5_context context, krb5_ccache id, const char *type, /* heim_object_t */ void *obj)
685{
686    krb5_xcc *x = XCACHE(id);
687    bool res;
688
689    CFStringRef t = CFStringCreateWithCString(NULL, type, kCFStringEncodingUTF8);
690    if (t == NULL)
691	return krb5_enomem(context);
692
693    res = HeimCredSetAttribute(x->cred, t, obj, NULL);
694    CFRELEASE_NULL(t);
695
696    if (!res)
697	return KRB5_CC_END;
698
699    return 0;
700}
701
702/**
703 * Variable containing the XCACHE based credential cache implemention.
704 *
705 * @ingroup krb5_ccache
706 */
707
708KRB5_LIB_VARIABLE const krb5_cc_ops krb5_xcc_ops = {
709    KRB5_CC_OPS_VERSION,
710    "XCACHE",
711    xcc_get_name,
712    xcc_resolve,
713    xcc_gen_new,
714    xcc_initialize,
715    xcc_destroy,
716    xcc_close,
717    xcc_store_cred,
718    NULL, /* acc_retrieve */
719    xcc_get_principal,
720    xcc_get_first,
721    xcc_get_next,
722    xcc_end_get,
723    xcc_remove_cred,
724    xcc_set_flags,
725    xcc_get_version,
726    xcc_get_cache_first,
727    xcc_get_cache_next,
728    xcc_end_cache_get,
729    xcc_move,
730    xcc_get_default_name,
731    xcc_set_default,
732    xcc_lastchange,
733    NULL, /* set_kdc_offset */
734    NULL, /* get_kdc_offset */
735    NULL, /* hold */
736    NULL, /* unhold */
737    xcc_get_uuid,
738    xcc_resolve_by_uuid,
739    NULL,
740    NULL,
741    xcc_set_acl
742};
743
744#endif /* HAVE_XCC */
745