1/*
2 * Copyright (c) 2010 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Portions Copyright (c) 2010 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
38#ifdef __APPLE__
39
40#include <CoreFoundation/CoreFoundation.h>
41#include <Security/Security.h>
42#include <uuid/uuid.h>
43
44/*
45 *  kSecAttrDescription
46 *          'Kerberos ticket for %@'
47 *  kSecAttrServer
48 *          the name of the credentials cache
49 *  kSecAttrCreator
50 *       'GSSL'
51 *  kSecAttrType
52 *       'iTGT'
53 *       'oTGT'
54 *  kSecAttrLabel
55 *       'Kerberos ticket for %@'
56 *  kSecAttrIsInvisible
57 *       'Yes'
58 *  kSecAttrIsNegative
59 *       set on the keychain that is default
60 *  kSecAttrAccount
61 *       The service principal name
62 *  kSecAttrService
63 *       'GSS-API'
64 *  kSecAttrGeneric
65 *       iTGT
66 *          kdc offset: int32_t
67 *       oTGT
68 *       'The kerberos ticket'
69 */
70
71
72typedef struct krb5_kcache{
73    char *name;
74    CFStringRef cname;
75    int version;
76}krb5_kcache;
77
78#define KCACHE(X) ((krb5_kcache*)(X)->data.data)
79
80struct kcc_cursor {
81    CFArrayRef array;
82    CFIndex offset;
83};
84
85static CFTypeRef kGSSiTGT;
86static CFTypeRef kGSSoTGT;
87static CFTypeRef kGSSCreator;
88#ifndef __APPLE_TARGET_EMBEDDED__
89static SecAccessRef kGSSAnyAccess;
90#endif
91
92static void
93create_constants(void)
94{
95    static dispatch_once_t once;
96    dispatch_once(&once, ^{
97	    int32_t num;
98
99	    num = 'iTGT';
100	    kGSSiTGT = CFNumberCreate(NULL, kCFNumberSInt32Type, &num);
101	    num = 'oTGT';
102	    kGSSoTGT = CFNumberCreate(NULL, kCFNumberSInt32Type, &num);
103	    num = 'GSSL';
104	    kGSSCreator = CFNumberCreate(NULL, kCFNumberSInt32Type, &num);
105
106#ifndef __APPLE_TARGET_EMBEDDED__
107	    OSStatus ret;
108
109	    ret = SecAccessCreate(CFSTR("GSS-credential"), NULL, &kGSSAnyAccess);
110	    if (ret)
111		return;
112
113	    SecKeychainPromptSelector promptSelector;
114	    CFStringRef promptDescription = NULL;
115	    CFArrayRef appList = NULL, aclList = NULL;
116	    SecACLRef acl;
117
118	    aclList = SecAccessCopyMatchingACLList(kGSSAnyAccess, kSecACLAuthorizationDecrypt);
119	    if (aclList == NULL)
120		return;
121
122	    if (CFArrayGetCount(aclList) < 1) {
123		CFRelease(aclList);
124		return;
125	    }
126
127	    acl = (SecACLRef)CFArrayGetValueAtIndex(aclList, 0);
128
129	    ret = SecACLCopyContents(acl, &appList, &promptDescription, &promptSelector);
130	    if (ret == 0) {
131		promptSelector &= ~kSecKeychainPromptRequirePassphase;
132		(void)SecACLSetContents(acl, NULL, promptDescription, promptSelector);
133	    }
134
135	    CFRelease(promptDescription);
136	    CFRelease(appList);
137	    CFRelease(aclList);
138#endif
139	});
140}
141
142static void
143delete_type(krb5_context context, krb5_kcache *k)
144{
145#ifndef __APPLE_TARGET_EMBEDDED__
146#define USE_DELETE 1 /* rdar://8736785 */
147#endif
148
149    const void *keys[] = {
150#ifdef USE_DELETE
151	kSecReturnRef,
152#endif
153	kSecClass,
154	kSecAttrCreator,
155	kSecAttrServer,
156    };
157    const void *values[] = {
158#ifdef USE_DELETE
159	kCFBooleanTrue,
160#endif
161	kSecClassInternetPassword,
162	kGSSCreator,
163	k->cname,
164    };
165    CFDictionaryRef query;
166    OSStatus ret;
167
168    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]), NULL, NULL);
169    heim_assert(query != NULL, "out of memory");
170
171#ifdef USE_DELETE
172    CFArrayRef array;
173
174    ret = SecItemCopyMatching(query, (CFTypeRef *)&array);
175    if (ret == 0) {
176	CFIndex n, count = CFArrayGetCount(array);
177
178	for (n = 0; n < count; n++)
179	    SecKeychainItemDelete((SecKeychainItemRef)CFArrayGetValueAtIndex(array, n));
180	CFRelease(array);
181    }
182
183#else
184    ret = SecItemDelete(query);
185    if (ret)
186	_krb5_debugx(context, 5, "failed to delete credential %s: %d\n", k->name, (int)ret);
187#endif
188    CFRelease(query);
189}
190
191static CFDictionaryRef
192CreateSecItemFromCache(krb5_kcache *k)
193{
194    CFDictionaryRef result = NULL, query = NULL;
195    OSStatus ret;
196
197    create_constants();
198
199    const void *keys[] = {
200	kSecClass,
201	kSecReturnAttributes,
202	kSecAttrType,
203	kSecAttrServer
204    };
205    const void *values[] = {
206	kSecClassInternetPassword,
207	kCFBooleanTrue,
208	kGSSiTGT,
209	k->cname
210    };
211
212    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
213			       &kCFTypeDictionaryKeyCallBacks,
214			       &kCFTypeDictionaryValueCallBacks);
215    heim_assert(query != NULL, "out of memory");
216
217    ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
218    CFRelease(query);
219    if (ret)
220	return NULL;
221
222    return result;
223}
224
225static const char*
226kcc_get_name(krb5_context context,
227	     krb5_ccache id)
228{
229    krb5_kcache *k = KCACHE(id);
230    return k->name;
231}
232
233static krb5_error_code
234kcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
235{
236    krb5_kcache *k;
237
238    k = malloc(sizeof(*k));
239    if (k == NULL) {
240	krb5_set_error_message(context, KRB5_CC_NOMEM,
241			       N_("malloc: out of memory", ""));
242	return KRB5_CC_NOMEM;
243    }
244    k->name = strdup(res);
245    if (k->name == NULL){
246	free(k);
247	krb5_set_error_message(context, KRB5_CC_NOMEM,
248			       N_("malloc: out of memory", ""));
249	return KRB5_CC_NOMEM;
250    }
251    k->cname = CFStringCreateWithCString(NULL, k->name, kCFStringEncodingUTF8);
252    if (k->cname == NULL) {
253	free(k->name);
254	free(k);
255	krb5_set_error_message(context, KRB5_CC_NOMEM,
256			       N_("malloc: out of memory", ""));
257	return KRB5_CC_NOMEM;
258
259    }
260    (*id)->data.data = k;
261    (*id)->data.length = sizeof(*k);
262    return 0;
263}
264
265static krb5_error_code
266kcc_gen_new(krb5_context context, krb5_ccache *id)
267{
268    krb5_kcache *k;
269    uuid_t uuid;
270    uuid_string_t uuidstr;
271
272    k = malloc(sizeof(*k));
273    if (k == NULL) {
274	krb5_set_error_message(context, KRB5_CC_NOMEM,
275			       N_("malloc: out of memory", ""));
276	return KRB5_CC_NOMEM;
277    }
278
279    uuid_generate_random(uuid);
280    uuid_unparse(uuid, uuidstr);
281
282    k->name = strdup(uuidstr);
283    if (k->name == NULL) {
284	free(k);
285	krb5_set_error_message(context, KRB5_CC_NOMEM,
286			       N_("malloc: out of memory", ""));
287	return KRB5_CC_NOMEM;
288    }
289
290    k->cname = CFStringCreateWithCString(NULL, k->name, kCFStringEncodingUTF8);
291    if (k->cname == NULL) {
292	free(k->name);
293	free(k);
294	krb5_set_error_message(context, KRB5_CC_NOMEM,
295			       N_("malloc: out of memory", ""));
296	return KRB5_CC_NOMEM;
297
298    }
299
300    (*id)->data.data = k;
301    (*id)->data.length = sizeof(*k);
302    return 0;
303}
304
305static CFStringRef
306CFStringCreateFromPrincipal(CFAllocatorRef alloc, krb5_context context, krb5_principal principal)
307{
308    CFStringRef str;
309    char *p;
310
311    if (krb5_unparse_name(context, principal, &p) != 0)
312	return NULL;
313
314    str = CFStringCreateWithCString(alloc, p, kCFStringEncodingUTF8);
315    krb5_xfree(p);
316
317    return str;
318}
319
320
321static krb5_error_code
322kcc_initialize(krb5_context context,
323	       krb5_ccache id,
324	       krb5_principal primary_principal)
325{
326    krb5_error_code ret = 0;
327    krb5_kcache *k = KCACHE(id);
328    CFDictionaryRef query;
329    CFTypeRef item = NULL;
330    CFStringRef principal = NULL;
331    CFStringRef label = NULL;
332    OSStatus osret;
333
334    create_constants();
335
336    /*
337       delete all entries for
338
339       kSecAttrServer == k->name
340    */
341
342    delete_type(context, k);
343
344    /*
345      add entry with
346
347      kSecAttrServer, k->name
348      kSecAttrLabel, "credential for %@", primary_principal
349      kSecAttrType, ITGT
350      kSecAttrAccount, primary_principal
351    */
352
353    principal = CFStringCreateFromPrincipal(NULL, context, primary_principal);
354    if (principal == NULL) {
355	ret = krb5_enomem(context);
356	goto out;
357    }
358
359    label = CFStringCreateWithFormat(NULL, NULL, CFSTR("Kerberos credentials for %@"), principal);
360    if (label == NULL) {
361	ret = krb5_enomem(context);
362	goto out;
363    }
364
365    const void *add_keys[] = {
366	kSecAttrCreator,
367#ifdef __APPLE_TARGET_EMBEDDED__
368	kSecAttrAccessible,
369#else
370	kSecAttrAccess,
371#endif
372	kSecClass,
373	kSecAttrType,
374	kSecAttrServer,
375	kSecAttrAccount,
376	kSecAttrLabel,
377	kSecAttrIsInvisible
378    };
379    const void *add_values[] = {
380	kGSSCreator,
381#ifdef __APPLE_TARGET_EMBEDDED__
382	kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
383#else
384	kGSSAnyAccess,
385#endif
386	kSecClassInternetPassword,
387	kGSSiTGT,
388	k->cname,
389	principal,
390	label,
391	kCFBooleanTrue
392    };
393
394    query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL);
395    heim_assert(query != NULL, "out of memory");
396
397
398    osret = SecItemAdd(query, &item);
399    CFRelease(query);
400    if (osret) {
401	_krb5_debugx(context, 5, "failed to store credential: %d\n", (int)osret);
402	ret = EINVAL;
403	krb5_set_error_message(context, ret, "failed to store credential: %d", (int)osret);
404	goto out;
405    }
406
407 out:
408    if (label)
409	CFRelease(label);
410    if (item)
411	CFRelease(item);
412    if (principal)
413	CFRelease(principal);
414
415    return ret;
416}
417
418static krb5_error_code
419kcc_close(krb5_context context,
420	  krb5_ccache id)
421{
422    krb5_kcache *k = KCACHE(id);
423
424    if (k->cname)
425	CFRelease(k->cname);
426    free(k->name);
427    krb5_data_free(&id->data);
428    return 0;
429}
430
431static krb5_error_code
432kcc_destroy(krb5_context context,
433	    krb5_ccache id)
434{
435    krb5_kcache *k = KCACHE(id);
436
437    create_constants();
438
439    delete_type(context, k);
440
441    return 0;
442}
443
444static krb5_error_code
445kcc_store_cred(krb5_context context,
446	       krb5_ccache id,
447	       krb5_creds *creds)
448{
449    krb5_kcache *k = KCACHE(id);
450    krb5_storage *sp = NULL;
451    CFDataRef dref = NULL;
452    krb5_data data;
453    CFStringRef principal = NULL;
454    CFStringRef label = NULL;
455    CFDictionaryRef query = NULL;
456    krb5_error_code ret;
457    CFTypeRef item = NULL;
458
459    create_constants();
460
461    krb5_data_zero(&data);
462
463    sp = krb5_storage_emem();
464    if (sp == NULL) {
465	ret = krb5_enomem(context);
466	goto out;
467    }
468
469    ret = krb5_store_creds(sp, creds);
470    if (ret)
471	goto out;
472
473    krb5_storage_to_data(sp, &data);
474
475    dref = CFDataCreateWithBytesNoCopy(NULL, data.data, data.length, kCFAllocatorNull);
476    if (dref == NULL) {
477	ret = krb5_enomem(context);
478	goto out;
479    }
480
481    principal = CFStringCreateFromPrincipal(NULL, context, creds->server);
482    if (principal == NULL) {
483	ret = krb5_enomem(context);
484	goto out;
485    }
486
487    label = CFStringCreateWithFormat(NULL, NULL, CFSTR("kerberos credentials for %@"), principal);
488    if (label == NULL) {
489	ret = krb5_enomem(context);
490	goto out;
491    }
492
493    /*
494      store
495
496      kSecAttrServer, k->name
497      kSecAttrLabel, "credential for %@", cred.server
498      kSecAttrType, ITGT
499      kSecAttrAccount, cred.server
500      kSecAttrGeneric, data
501    */
502
503    const void *add_keys[] = {
504	kSecAttrCreator,
505#ifdef __APPLE_TARGET_EMBEDDED__
506	kSecAttrAccessible,
507#else
508	kSecAttrAccess,
509#endif
510	kSecClass,
511	kSecAttrType,
512	kSecAttrServer,
513	kSecAttrAccount,
514	kSecAttrLabel,
515	kSecValueData,
516	kSecAttrIsInvisible
517    };
518    const void *add_values[] = {
519	kGSSCreator,
520#ifdef __APPLE_TARGET_EMBEDDED__
521	kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
522#else
523	kGSSAnyAccess,
524#endif
525	kSecClassInternetPassword,
526	kGSSoTGT,
527	k->cname,
528	principal,
529	label,
530	dref,
531	kCFBooleanTrue
532    };
533
534    query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL);
535    heim_assert(query != NULL, "out of memory");
536
537    ret = SecItemAdd(query, &item);
538    CFRelease(query);
539    if (ret) {
540	_krb5_debugx(context, 5, "failed to add credential to %s: %d\n", k->name, (int)ret);
541	ret = EINVAL;
542	krb5_set_error_message(context, ret, "failed to store credential to %s", k->name);
543	goto out;
544    }
545
546 out:
547    if (item)
548	CFRelease(item);
549    if (sp)
550	krb5_storage_free(sp);
551    if (dref)
552	CFRelease(dref);
553    if (principal)
554	CFRelease(principal);
555    if (label)
556	CFRelease(label);
557    krb5_data_free(&data);
558
559    return ret;
560}
561
562static krb5_error_code
563kcc_get_principal(krb5_context context,
564		  krb5_ccache id,
565		  krb5_principal *principal)
566{
567    krb5_error_code ret = 0;
568    krb5_kcache *k = KCACHE(id);
569    CFDictionaryRef result;
570
571    /*
572      lookup:
573      kSecAttrServer, k->name
574      kSecAttrType, ITGT
575
576      to get:
577
578      kSecAttrAccount, primary_principal
579    */
580
581
582    result = CreateSecItemFromCache(k);
583    if (result == NULL) {
584	krb5_set_error_message(context, KRB5_CC_END,
585			       "failed finding cache: %s", k->name);
586	ret = KRB5_CC_END;
587	goto out;
588    }
589
590    CFStringRef p = CFDictionaryGetValue(result, kSecAttrAccount);
591    if (p == NULL) {
592	ret = KRB5_CC_END;
593	goto out;
594    }
595
596    char *str = rk_cfstring2cstring(p);
597    if (str == NULL) {
598	ret = krb5_enomem(context);
599	goto out;
600    }
601
602    krb5_parse_name(context, str, principal);
603    free(str);
604
605 out:
606    if (result)
607	CFRelease(result);
608
609    return ret;
610}
611
612static void
613free_cursor(struct kcc_cursor *c)
614{
615    if (c->array)
616	CFRelease(c->array);
617    free(c);
618}
619
620static krb5_error_code
621kcc_get_first (krb5_context context,
622	       krb5_ccache id,
623	       krb5_cc_cursor *cursor)
624{
625    krb5_error_code ret;
626    krb5_kcache *k = KCACHE(id);
627    CFDictionaryRef query = NULL;
628    CFArrayRef result = NULL;
629    struct kcc_cursor *c;
630
631    create_constants();
632
633    c = calloc(1, sizeof(*c));
634    if (c == NULL)
635	return krb5_enomem(context);
636
637    /*
638      lookup:
639      kSecAttrServer, k->name
640      kSecAttrType, OTGT
641    */
642
643    const void *keys[] = {
644	kSecClass,
645	kSecReturnData,
646	kSecMatchLimit,
647	kSecAttrType,
648	kSecAttrServer
649    };
650    const void *values[] = {
651	kSecClassInternetPassword,
652	kCFBooleanTrue,
653	kSecMatchLimitAll,
654	kGSSoTGT,
655	k->cname
656    };
657
658    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
659			       &kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
660    heim_assert(query != NULL, "out of memory");
661
662    ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
663    if (ret) {
664	result = NULL;
665	ret = 0;
666	goto out;
667    }
668    if (CFGetTypeID(result) != CFArrayGetTypeID()) {
669	CFRelease(result);
670	ret = 0;
671	result = NULL;
672    }
673
674    /* sort by creation date */
675
676    c->array = result;
677    c->offset = 0;
678
679 out:
680    if (query)
681	CFRelease(query);
682    if (ret) {
683	free_cursor(c);
684    } else {
685	*cursor = c;
686    }
687
688    return ret;
689}
690
691static krb5_error_code
692kcc_get_next (krb5_context context,
693	      krb5_ccache id,
694	      krb5_cc_cursor *cursor,
695	      krb5_creds *creds)
696{
697    struct kcc_cursor *c = *cursor;
698    krb5_error_code ret;
699    krb5_storage *sp;
700    CFDataRef data;
701
702    if (c->array == NULL)
703	return KRB5_CC_END;
704
705    if (c->offset >= CFArrayGetCount(c->array))
706	return KRB5_CC_END;
707
708    data = CFArrayGetValueAtIndex(c->array, c->offset++);
709    if (data == NULL)
710	return KRB5_CC_END;
711
712    sp = krb5_storage_from_readonly_mem(CFDataGetBytePtr(data), CFDataGetLength(data));
713    if (sp == NULL)
714	return KRB5_CC_END;
715
716    ret = krb5_ret_creds(sp, creds);
717    krb5_storage_free(sp);
718
719    return ret;
720}
721
722static krb5_error_code
723kcc_end_get (krb5_context context,
724	     krb5_ccache id,
725	     krb5_cc_cursor *cursor)
726{
727    free_cursor((struct kcc_cursor *)*cursor);
728    *cursor = NULL;
729
730    return 0;
731}
732
733static krb5_error_code
734kcc_remove_cred(krb5_context context,
735		krb5_ccache id,
736		krb5_flags which,
737		krb5_creds *cred)
738{
739    /* lookup cred and delete */
740
741    return 0;
742}
743
744static krb5_error_code
745kcc_set_flags(krb5_context context,
746	      krb5_ccache id,
747	      krb5_flags flags)
748{
749    return 0; /* XXX */
750}
751
752static int
753kcc_get_version(krb5_context context,
754		krb5_ccache id)
755{
756    return 0;
757}
758
759static krb5_error_code
760kcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
761{
762    struct kcc_cursor *c;
763    CFArrayRef result;
764    CFDictionaryRef query;
765    OSStatus osret;
766
767    create_constants();
768
769    c = calloc(1, sizeof(*c));
770    if (c == NULL)
771	return krb5_enomem(context);
772
773    const void *keys[] = {
774	kSecClass,
775	kSecReturnAttributes,
776	kSecMatchLimit,
777	kSecAttrType,
778    };
779    const void *values[] = {
780	kSecClassInternetPassword,
781	kCFBooleanTrue,
782	kSecMatchLimitAll,
783	kGSSiTGT,
784    };
785
786    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
787			       &kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
788    heim_assert(query != NULL, "out of memory");
789
790    osret = SecItemCopyMatching(query, (CFTypeRef *)&result);
791    CFRelease(query);
792    if (osret)
793	result = NULL;
794
795    /* sort by creation date */
796
797    c->array = result;
798    c->offset = 0;
799
800    *cursor = c;
801    return 0;
802}
803
804static krb5_error_code
805kcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
806{
807    struct kcc_cursor *c = cursor;
808    CFDictionaryRef item;
809    krb5_error_code ret;
810    CFStringRef sref;
811    char *name;
812
813    if (c->array == NULL)
814	return KRB5_CC_END;
815
816    if (c->offset >= CFArrayGetCount(c->array))
817	return KRB5_CC_END;
818
819    item = CFArrayGetValueAtIndex(c->array, c->offset++);
820    if (item == NULL)
821	return KRB5_CC_END;
822
823    sref = CFDictionaryGetValue(item, kSecAttrServer);
824    if (sref == NULL)
825	return KRB5_CC_END;
826
827    name = rk_cfstring2cstring(sref);
828    if (name == NULL)
829	return KRB5_CC_NOMEM;
830
831    ret = _krb5_cc_allocate(context, &krb5_kcc_ops, id);
832    if (ret) {
833	free(name);
834	return ret;
835    }
836
837    ret = kcc_resolve(context, id, name);
838    free(name);
839    if (ret)
840	krb5_cc_close(context, *id);
841
842    return ret;
843}
844
845static krb5_error_code
846kcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
847{
848    free_cursor((struct kcc_cursor *)cursor);
849    return 0;
850}
851
852static krb5_error_code
853kcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
854{
855    krb5_kcache *kfrom = KCACHE(from), *kto = KCACHE(to);
856    CFDictionaryRef query = NULL, update = NULL;
857    krb5_error_code ret = 0;
858    OSStatus osret;
859
860    /* if we are ask to overwrite ourself, we are done */
861    if (strcmp(kfrom->name, kto->name) == 0)
862	return 0;
863
864    create_constants();
865
866    delete_type(context, kto);
867
868    /* lookup
869       kSecAttrServer, k->name
870
871       and rename
872    */
873
874    const void *keys[] = {
875	kSecClass,
876	kSecReturnRef,
877	kSecMatchLimit,
878	kSecAttrServer
879    };
880    const void *values[] = {
881	kSecClassInternetPassword,
882	kCFBooleanTrue,
883	kSecMatchLimitAll,
884	kfrom->cname
885    };
886
887    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
888			       &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
889    heim_assert(query != NULL, "out of memory");
890
891
892    const void *update_keys[] = {
893	kSecAttrServer
894    };
895    const void *update_values[] = {
896	kto->cname
897    };
898
899    update = CFDictionaryCreate(NULL, update_keys, update_values, sizeof(update_keys) / sizeof(update_keys[0]),
900				&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
901    heim_assert(query != NULL, "out of memory");
902
903    osret = SecItemUpdate(query, update);
904    if (osret)
905	krb5_set_error_message(context, (ret = KRB5_CC_END), "Failed to rename credential: %s with error %d", kfrom->name, (int)osret);
906
907    CFRelease(query);
908    CFRelease(update);
909
910    return ret;
911}
912
913static void
914set_default(CFStringRef name, bool clear_first)
915{
916    CFDictionaryRef update = NULL, query = NULL;
917
918    /* search for all
919       kSecAttrType, ITGT
920       kSecAttrIsNegative, True
921
922       and change to
923       to -> kSecAttrIsNegative, false
924    */
925
926    /* change kSecAttrIsNegative -> true for id */
927
928    const void *keys[] = {
929	kSecClass,
930	kSecMatchLimit,
931	kSecAttrCreator,
932#ifdef __APPLE_TARGET_EMBEDDED__
933	kSecAttrAccessible,
934#endif
935	kSecAttrType,
936	kSecAttrServer
937    };
938    const void *values[] = {
939	kSecClassInternetPassword,
940	kSecMatchLimitAll,
941	kGSSCreator,
942#ifdef __APPLE_TARGET_EMBEDDED__
943	kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
944#endif
945	kGSSiTGT,
946	name
947    };
948
949    const void *update_keys[] = {
950	kSecAttrIsNegative
951    };
952    const void *update_values_false[] = {
953	kCFBooleanFalse
954    };
955    const void *update_values_true[] = {
956	kCFBooleanTrue
957    };
958
959    /*
960     * Clear all default flags
961     */
962    if (clear_first) {
963
964	query = CFDictionaryCreate(NULL, keys, values, (sizeof(keys) / sizeof(keys[0])) - 1,
965				   &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
966	heim_assert(query != NULL, "out of memory");
967
968
969	update = CFDictionaryCreate(NULL, update_keys, update_values_false, sizeof(update_keys) / sizeof(update_keys[0]),
970				    &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
971	heim_assert(update != NULL, "out of memory");
972
973	(void)SecItemUpdate(query, update);
974
975	CFRelease(query);
976	CFRelease(update);
977    }
978
979    /*
980     * Set default flag
981     */
982
983    query = CFDictionaryCreate(NULL, keys, values, (sizeof(keys) / sizeof(keys[0])),
984			       &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
985    heim_assert(query != NULL, "out of memory");
986
987    update = CFDictionaryCreate(NULL, update_keys, update_values_true, sizeof(update_keys) / sizeof(update_keys[0]),
988				&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
989    heim_assert(update != NULL, "out of memory");
990
991    (void)SecItemUpdate(query, update);
992    CFRelease(query);
993    CFRelease(update);
994}
995
996
997static krb5_error_code
998kcc_get_default_name(krb5_context context, char **str)
999{
1000    CFDictionaryRef result = NULL, query = NULL;
1001    OSStatus ret;
1002
1003    *str = NULL;
1004
1005    create_constants();
1006
1007    const void *keys[] = {
1008	kSecClass,
1009	kSecReturnAttributes,
1010#ifdef __APPLE_TARGET_EMBEDDED__
1011	kSecAttrAccessible,
1012#endif
1013	kSecAttrType,
1014	kSecAttrIsNegative
1015    };
1016    const void *values[] = {
1017	kSecClassInternetPassword,
1018	kCFBooleanTrue,
1019#ifdef __APPLE_TARGET_EMBEDDED__
1020	kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
1021#endif
1022	kGSSiTGT,
1023	kCFBooleanTrue
1024    };
1025
1026    /* lookup
1027       kSecAttrType, ITGT
1028       kSecAttrIsNegative, True
1029    */
1030
1031    query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]),
1032			       &kCFTypeDictionaryKeyCallBacks,
1033			       &kCFTypeDictionaryValueCallBacks);
1034    heim_assert(query != NULL, "out of memory");
1035
1036    ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
1037    CFRelease(query);
1038
1039    if (ret) {
1040	/*
1041	 * Didn't find one, just pick one
1042	 */
1043	query = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(keys[0]) - 1,
1044				   &kCFTypeDictionaryKeyCallBacks,
1045				   &kCFTypeDictionaryValueCallBacks);
1046	heim_assert(query != NULL, "out of memory");
1047
1048	ret = SecItemCopyMatching(query, (CFTypeRef *)&result);
1049	CFRelease(query);
1050
1051	if (ret == 0) {
1052	    CFStringRef name = CFDictionaryGetValue(result, kSecAttrServer);
1053	    set_default(name, false);
1054	}
1055    }
1056
1057    if (ret == 0) {
1058	CFStringRef name = CFDictionaryGetValue(result, kSecAttrServer);
1059	char *n;
1060
1061	if (name == NULL)
1062	    return krb5_enomem(context);
1063
1064	n = rk_cfstring2cstring(name);
1065	if (n == NULL)
1066	    return ENOMEM;
1067
1068	asprintf(str, "KCC:%s", n);
1069	free(n);
1070	if (*str == NULL)
1071	    return krb5_enomem(context);
1072	return 0;
1073    } else {
1074
1075	*str = strdup("KCC:default-kcache");
1076	if (*str == NULL)
1077	    return krb5_enomem(context);
1078    }
1079    return 0;
1080}
1081
1082static krb5_error_code
1083kcc_set_default(krb5_context context, krb5_ccache id)
1084{
1085    krb5_kcache *k = KCACHE(id);
1086
1087    create_constants();
1088
1089    set_default(k->cname, true);
1090
1091    return 0;
1092}
1093
1094static krb5_error_code
1095kcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1096{
1097    /* lookup modification date */
1098    *mtime = 0;
1099    return 0;
1100}
1101
1102static krb5_error_code
1103kcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1104{
1105    return 0;
1106}
1107
1108static krb5_error_code
1109kcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1110{
1111    *kdc_offset = 0;
1112    return 0;
1113}
1114
1115static krb5_error_code
1116kcc_get_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid)
1117{
1118    krb5_kcache *k = KCACHE(id);
1119    uuid_t kuuid;
1120    heim_assert(sizeof(uuid_t) == sizeof(krb5_uuid), "uuid size different");
1121    uuid_parse(k->name, kuuid);
1122    memcpy(uuid, kuuid, sizeof(krb5_uuid));
1123    return 0;
1124}
1125
1126static krb5_error_code
1127kcc_resolve_by_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid)
1128{
1129    uuid_string_t uuidstr;
1130    uuid_unparse(uuid, uuidstr);
1131    return kcc_resolve(context, &id, uuidstr);
1132}
1133
1134
1135
1136/**
1137 * Variable containing the KEYCHAIN based credential cache implemention.
1138 *
1139 * @ingroup krb5_ccache
1140 */
1141
1142KRB5_LIB_VARIABLE const krb5_cc_ops krb5_kcc_ops = {
1143    KRB5_CC_OPS_VERSION,
1144    "KCC",
1145    kcc_get_name,
1146    kcc_resolve,
1147    kcc_gen_new,
1148    kcc_initialize,
1149    kcc_destroy,
1150    kcc_close,
1151    kcc_store_cred,
1152    NULL,
1153    kcc_get_principal,
1154    kcc_get_first,
1155    kcc_get_next,
1156    kcc_end_get,
1157    kcc_remove_cred,
1158    kcc_set_flags,
1159    kcc_get_version,
1160    kcc_get_cache_first,
1161    kcc_get_cache_next,
1162    kcc_end_cache_get,
1163    kcc_move,
1164    kcc_get_default_name,
1165    kcc_set_default,
1166    kcc_lastchange,
1167    kcc_set_kdc_offset,
1168    kcc_get_kdc_offset,
1169    NULL,
1170    NULL,
1171    kcc_get_uuid,
1172    kcc_resolve_by_uuid
1173};
1174
1175#endif /* __APPLE__ */
1176