xref: /freebsd/crypto/krb5/src/lib/krb5/ccache/cc_api_macos.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/cc_api_macos.c - Native MacOS X ccache code */
3 /*
4  * Copyright (C) 2022 United States Government as represented by the
5  * Secretary of the Navy.
6  * 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  * * Redistributions of source code must retain the above copyright
13  *   notice, this list of conditions and the following disclaimer.
14  *
15  * * Redistributions in binary form must reproduce the above copyright
16  *   notice, this list of conditions and the following disclaimer in
17  *   the documentation and/or other materials provided with the
18  *   distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 /*
35  * This ccache module provides compatibility with the default native ccache
36  * type for macOS, by linking against the native Kerberos framework and calling
37  * the CCAPI stubs.  Due to workarounds for specific behaviors of the CCAPI
38  * stubs, this implementation is separate from the API ccache implementation
39  * used on Windows.
40  */
41 
42 #include "k5-int.h"
43 #include "cc-int.h"
44 #include "ccapi_util.h"
45 #include <CredentialsCache.h>
46 
47 #ifdef USE_CCAPI_MACOS
48 
49 #include <sys/utsname.h>
50 #include <xpc/xpc.h>
51 
52 const krb5_cc_ops krb5_api_macos_ops;
53 
54 struct api_macos_cache_data {
55     char *residual;
56     cc_context_t cc_context;
57     cc_ccache_t cache;
58 };
59 
60 struct api_macos_ptcursor {
61     krb5_boolean first;
62     char *primary;
63     cc_context_t cc_context;
64     cc_ccache_iterator_t iter;
65 };
66 
67 /* Map a CCAPI error code to a com_err code. */
68 static krb5_error_code
ccerr2mit(uint32_t err)69 ccerr2mit(uint32_t err)
70 {
71     switch (err) {
72     case ccNoError:
73         return 0;
74     case ccIteratorEnd:
75         return KRB5_CC_END;
76     case ccErrNoMem:
77         return ENOMEM;
78     case ccErrCCacheNotFound:
79         return KRB5_FCC_NOFILE;
80     default:
81         return KRB5_FCC_INTERNAL;
82     }
83 }
84 
85 /* Construct a ccache handle for residual.  Use cc_context if it is not null,
86  * or initialize a new one if it is. */
87 static krb5_error_code
make_cache(const char * residual,cc_context_t cc_context,krb5_ccache * ccache_out)88 make_cache(const char *residual, cc_context_t cc_context,
89            krb5_ccache *ccache_out)
90 {
91     krb5_ccache cache = NULL;
92     char *residual_copy = NULL;
93     struct api_macos_cache_data *data = NULL;
94     uint32_t err;
95 
96     *ccache_out = NULL;
97 
98     if (cc_context == NULL) {
99         err = cc_initialize(&cc_context, ccapi_version_max, NULL, NULL);
100         if (err != ccNoError)
101             return KRB5_FCC_INTERNAL;
102     }
103 
104     cache = malloc(sizeof(*cache));
105     if (cache == NULL)
106         goto oom;
107 
108     data = calloc(1, sizeof(*data));
109     if (data == NULL)
110         goto oom;
111 
112     residual_copy = strdup(residual);
113     if (residual_copy == NULL)
114         goto oom;
115 
116     data->residual = residual_copy;
117     data->cc_context = cc_context;
118     cache->ops = &krb5_api_macos_ops;
119     cache->data = data;
120     cache->magic = KV5M_CCACHE;
121     *ccache_out = cache;
122     return 0;
123 
124 oom:
125     free(cache);
126     free(data);
127     free(residual_copy);
128     if (cc_context)
129         cc_context_release(cc_context);
130     return ENOMEM;
131 }
132 
133 static uint32_t
open_cache(struct api_macos_cache_data * data)134 open_cache(struct api_macos_cache_data *data)
135 {
136     if (data->cache != NULL)
137         return ccNoError;
138     return cc_context_open_ccache(data->cc_context, data->residual,
139                                   &data->cache);
140 }
141 
142 static const char *
api_macos_get_name(krb5_context context,krb5_ccache ccache)143 api_macos_get_name(krb5_context context, krb5_ccache ccache)
144 {
145     struct api_macos_cache_data *data = ccache->data;
146 
147     return data->residual;
148 }
149 
150 /*
151  * We would like to use cc_context_get_default_ccache_name() for this, but that
152  * doesn't work on macOS if the default cache name is set by the environment or
153  * configuration.  So we have to do what the underlying macOS Heimdal API cache
154  * type does to fetch the primary name.
155  *
156  * For macOS 11 (Darwin 20) and later, implement just enough of the XCACHE
157  * protocol to fetch the primary UUID.  For earlier versions, query the KCM
158  * daemon.
159  */
160 static krb5_error_code
get_primary_name(krb5_context context,char ** name_out)161 get_primary_name(krb5_context context, char **name_out)
162 {
163     krb5_error_code ret;
164     xpc_connection_t conn = NULL;
165     xpc_object_t request = NULL, reply = NULL;
166     const uint8_t *uuid;
167     uint64_t flags = XPC_CONNECTION_MACH_SERVICE_PRIVILEGED;
168     char uuidstr[37], *end;
169     struct utsname un;
170     long release;
171 
172     *name_out = NULL;
173 
174     if (uname(&un) == 0) {
175         release = strtol(un.release, &end, 10);
176         if (end != un.release && release < 20) {
177             /* Query the KCM daemon for macOS 10 and earlier. */
178             ret = k5_kcm_primary_name(context, name_out);
179             goto cleanup;
180         }
181     }
182 
183     conn = xpc_connection_create_mach_service("com.apple.GSSCred", NULL,
184                                               flags);
185     if (conn == NULL) {
186         ret = ENOMEM;
187         goto cleanup;
188     }
189     xpc_connection_set_event_handler(conn, ^(xpc_object_t o){ ; });
190     xpc_connection_resume(conn);
191 
192     request = xpc_dictionary_create(NULL, NULL, 0);
193     if (request == NULL) {
194         ret = ENOMEM;
195         goto cleanup;
196     }
197     xpc_dictionary_set_string(request, "command", "default");
198     xpc_dictionary_set_string(request, "mech", "kHEIMTypeKerberos");
199 
200     reply = xpc_connection_send_message_with_reply_sync(conn, request);
201     if (reply == NULL || xpc_get_type(reply) == XPC_TYPE_ERROR) {
202         ret = KRB5_CC_IO;
203         goto cleanup;
204     }
205 
206     uuid = xpc_dictionary_get_uuid(reply, "default");
207     if (uuid == NULL) {
208         ret = KRB5_CC_IO;
209         goto cleanup;
210     }
211     uuid_unparse(uuid, uuidstr);
212 
213     *name_out = strdup(uuidstr);
214     ret = (*name_out == NULL) ? ENOMEM : 0;
215 
216 cleanup:
217     if (request != NULL)
218         xpc_release(request);
219     if (reply != NULL)
220         xpc_release(reply);
221     if (conn != NULL)
222         xpc_release(conn);
223     return ret;
224 }
225 
226 static krb5_error_code
api_macos_resolve(krb5_context context,krb5_ccache * cache_out,const char * residual)227 api_macos_resolve(krb5_context context, krb5_ccache *cache_out,
228                   const char *residual)
229 {
230     krb5_error_code ret;
231     char *primary = NULL;
232 
233     if (*residual == '\0') {
234         ret = get_primary_name(context, &primary);
235         if (ret)
236             return ret;
237         residual = primary;
238     }
239     ret = make_cache(residual, NULL, cache_out);
240     free(primary);
241     return ret;
242 }
243 
244 static krb5_error_code
api_macos_gen_new(krb5_context context,krb5_ccache * cache_out)245 api_macos_gen_new(krb5_context context, krb5_ccache *cache_out)
246 {
247     krb5_error_code ret;
248     uint32_t err;
249     cc_context_t cc_context = NULL;
250     cc_ccache_t cc_ccache = NULL;
251     cc_string_t cachename = NULL;
252     struct api_macos_cache_data *data;
253 
254     *cache_out = NULL;
255 
256     err = cc_initialize(&cc_context, ccapi_version_max, NULL, NULL);
257     if (err)
258         goto cleanup;
259 
260     err = cc_context_create_new_ccache(cc_context, cc_credentials_v5, "",
261                                        &cc_ccache);
262     if (err)
263         goto cleanup;
264 
265     err = cc_ccache_get_name(cc_ccache, &cachename);
266     if (err)
267         goto cleanup;
268 
269     ret = make_cache(cachename->data, cc_context, cache_out);
270     cc_context = NULL;
271     if (!ret) {
272         data = (*cache_out)->data;
273         data->cache = cc_ccache;
274         cc_ccache = NULL;
275     }
276 
277 cleanup:
278     if (cc_context != NULL)
279         cc_context_release(cc_context);
280     if (cc_ccache != NULL)
281         cc_ccache_release(cc_ccache);
282     return err ? KRB5_FCC_INTERNAL : 0;
283 }
284 
285 static krb5_error_code
api_macos_initialize(krb5_context context,krb5_ccache cache,krb5_principal princ)286 api_macos_initialize(krb5_context context, krb5_ccache cache,
287                      krb5_principal princ)
288 {
289     krb5_error_code ret;
290     struct api_macos_cache_data *data = cache->data;
291     uint32_t err;
292     char *princstr = NULL, *prefix_name = NULL;
293 
294     /* Apple's cc_context_create_ccache() requires a name with type prefix. */
295     if (asprintf(&prefix_name, "API:%s", data->residual) < 0)
296         return ENOMEM;
297 
298     ret = krb5_unparse_name(context, princ, &princstr);
299     if (ret) {
300         free(prefix_name);
301         return ret;
302     }
303 
304     if (data->cache != NULL) {
305         cc_ccache_release(data->cache);
306         data->cache = NULL;
307     }
308 
309     err = cc_context_create_ccache(data->cc_context, prefix_name,
310                                    cc_credentials_v5, princstr,
311                                    &data->cache);
312     krb5_free_unparsed_name(context, princstr);
313     free(prefix_name);
314     return ccerr2mit(err);
315 }
316 
317 static krb5_error_code
api_macos_close(krb5_context context,krb5_ccache cache)318 api_macos_close(krb5_context context, krb5_ccache cache)
319 {
320     struct api_macos_cache_data *data = cache->data;
321 
322     if (data->cache != NULL)
323         cc_ccache_release(data->cache);
324     cc_context_release(data->cc_context);
325     free(data->residual);
326     free(data);
327     free(cache);
328     return 0;
329 }
330 
331 static krb5_error_code
api_macos_destroy(krb5_context context,krb5_ccache cache)332 api_macos_destroy(krb5_context context, krb5_ccache cache)
333 {
334     struct api_macos_cache_data *data = cache->data;
335 
336     open_cache(data);
337     if (data->cache != NULL) {
338         cc_ccache_destroy(data->cache);
339         data->cache = NULL;
340     }
341     return api_macos_close(context, cache);
342 }
343 
344 static krb5_error_code
api_macos_store(krb5_context context,krb5_ccache cache,krb5_creds * creds)345 api_macos_store(krb5_context context, krb5_ccache cache, krb5_creds *creds)
346 {
347     struct api_macos_cache_data *data = cache->data;
348     cc_credentials_union *c_un = NULL;
349     krb5_error_code ret;
350     uint32_t err;
351 
352     err = open_cache(data);
353     if (err)
354         return ccerr2mit(err);
355 
356     ret = k5_krb5_to_ccapi_creds(context, creds, &c_un);
357     if (ret)
358         return ret;
359     err = cc_ccache_store_credentials(data->cache, c_un);
360     k5_release_ccapi_cred(c_un);
361     return ccerr2mit(err);
362 }
363 
364 static krb5_error_code
api_macos_retrieve(krb5_context context,krb5_ccache cache,krb5_flags whichfields,krb5_creds * mcreds,krb5_creds * creds)365 api_macos_retrieve(krb5_context context, krb5_ccache cache,
366                    krb5_flags whichfields, krb5_creds *mcreds,
367                    krb5_creds *creds)
368 {
369     return k5_cc_retrieve_cred_default(context, cache, whichfields,
370                                        mcreds, creds);
371 }
372 
373 static krb5_error_code
api_macos_get_princ(krb5_context context,krb5_ccache cache,krb5_principal * princ)374 api_macos_get_princ(krb5_context context, krb5_ccache cache,
375                     krb5_principal *princ)
376 {
377     struct api_macos_cache_data *data = cache->data;
378     krb5_error_code ret;
379     uint32_t err;
380     cc_string_t outprinc;
381 
382     err = open_cache(data);
383     if (err)
384         return ccerr2mit(err);
385 
386     err = cc_ccache_get_principal(data->cache, cc_credentials_v5, &outprinc);
387     if (err)
388         return ccerr2mit(err);
389     ret = krb5_parse_name(context, outprinc->data, princ);
390     cc_string_release(outprinc);
391     return ret;
392 }
393 
394 static krb5_error_code
api_macos_start_seq_get(krb5_context context,krb5_ccache cache,krb5_cc_cursor * cursor)395 api_macos_start_seq_get(krb5_context context, krb5_ccache cache,
396                         krb5_cc_cursor *cursor)
397 {
398     struct api_macos_cache_data *data = cache->data;
399     uint32_t err;
400     cc_credentials_iterator_t iter;
401 
402     err = open_cache(data);
403     if (err)
404         return ccerr2mit(err);
405 
406     err = cc_ccache_new_credentials_iterator(data->cache, &iter);
407     if (err)
408         return ccerr2mit(err);
409 
410     *cursor = (krb5_cc_cursor)iter;
411     return 0;
412 }
413 
414 static krb5_error_code
api_macos_next_cred(krb5_context context,krb5_ccache cache,krb5_cc_cursor * cursor,krb5_creds * creds)415 api_macos_next_cred(krb5_context context, krb5_ccache cache,
416                     krb5_cc_cursor *cursor, krb5_creds *creds)
417 {
418     struct api_macos_cache_data *data = cache->data;
419     uint32_t err;
420     krb5_error_code ret;
421     cc_credentials_iterator_t iter = (cc_credentials_iterator_t) *cursor;
422     cc_credentials_t acreds;
423 
424     err = open_cache(data);
425     if (err)
426         return ccerr2mit(err);
427 
428     err = cc_credentials_iterator_next(iter, &acreds);
429     if (!err) {
430         ret = k5_ccapi_to_krb5_creds(context, acreds->data, creds);
431         cc_credentials_release(acreds);
432     } else {
433         ret = ccerr2mit(err);
434     }
435     return ret;
436 }
437 
438 static krb5_error_code
api_macos_end_seq_get(krb5_context context,krb5_ccache cache,krb5_cc_cursor * cursor)439 api_macos_end_seq_get(krb5_context context, krb5_ccache cache,
440                       krb5_cc_cursor *cursor)
441 {
442     cc_credentials_iterator_t iter = *cursor;
443 
444     cc_credentials_iterator_release(iter);
445     *cursor = NULL;
446     return 0;
447 }
448 
449 static krb5_error_code
api_macos_remove_cred(krb5_context context,krb5_ccache cache,krb5_flags flags,krb5_creds * creds)450 api_macos_remove_cred(krb5_context context, krb5_ccache cache,
451                       krb5_flags flags, krb5_creds *creds)
452 {
453     struct api_macos_cache_data *data = cache->data;
454     uint32_t err;
455     krb5_error_code ret = 0;
456     cc_credentials_iterator_t iter = NULL;
457     cc_credentials_t acreds;
458     krb5_creds mcreds;
459     krb5_boolean match;
460 
461     err = open_cache(data);
462     if (err)
463         return ccerr2mit(err);
464 
465     err = cc_ccache_new_credentials_iterator(data->cache, &iter);
466     if (err)
467         return ccerr2mit(err);
468 
469     for (;;) {
470         err = cc_credentials_iterator_next(iter, &acreds);
471         if (err)
472             break;
473 
474         ret = k5_ccapi_to_krb5_creds(context, acreds->data, &mcreds);
475         if (ret) {
476             cc_credentials_release(acreds);
477             break;
478         }
479 
480         match = krb5int_cc_creds_match_request(context, flags, creds, &mcreds);
481         krb5_free_cred_contents(context, &mcreds);
482         if (match)
483             err = cc_ccache_remove_credentials(data->cache, acreds);
484         cc_credentials_release(acreds);
485         if (err)
486             break;
487     }
488 
489     cc_credentials_iterator_release(iter);
490 
491     if (ret)
492         return ret;
493     if (err != ccIteratorEnd)
494         return ccerr2mit(err);
495     return 0;
496 }
497 
498 static krb5_error_code
api_macos_set_flags(krb5_context context,krb5_ccache cache,krb5_flags flags)499 api_macos_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
500 {
501     return 0;
502 }
503 
504 static krb5_error_code
api_macos_get_flags(krb5_context context,krb5_ccache cache,krb5_flags * flags)505 api_macos_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags)
506 {
507     *flags = 0;
508     return 0;
509 }
510 
511 static krb5_error_code
api_macos_ptcursor_new(krb5_context context,krb5_cc_ptcursor * ptcursor_out)512 api_macos_ptcursor_new(krb5_context context, krb5_cc_ptcursor *ptcursor_out)
513 {
514     krb5_cc_ptcursor ptcursor = NULL;
515     struct api_macos_ptcursor *apt = NULL;
516 
517     apt = malloc(sizeof(*apt));
518     if (apt == NULL)
519         return ENOMEM;
520     apt->first = TRUE;
521     apt->primary = NULL;
522     apt->cc_context = NULL;
523     apt->iter = NULL;
524 
525     ptcursor = malloc(sizeof(*ptcursor));
526     if (ptcursor == NULL) {
527         free(apt);
528         return ENOMEM;
529     }
530 
531     ptcursor->ops = &krb5_api_macos_ops;
532     ptcursor->data = apt;
533     *ptcursor_out = ptcursor;
534     return 0;
535 }
536 
537 /* Create a cache object and open it to ensure that it exists in the
538  * collection.  If it does not, return success but set *cache_out to NULL. */
539 static krb5_error_code
make_open_cache(const char * residual,krb5_ccache * cache_out)540 make_open_cache(const char *residual, krb5_ccache *cache_out)
541 {
542     krb5_error_code ret;
543     krb5_ccache cache;
544     uint32_t err;
545 
546     *cache_out = NULL;
547 
548     ret = make_cache(residual, NULL, &cache);
549     if (ret)
550         return ret;
551 
552     err = open_cache(cache->data);
553     if (err) {
554         api_macos_close(NULL, cache);
555         return (err == ccErrCCacheNotFound) ? 0 : ccerr2mit(err);
556     }
557 
558     *cache_out = cache;
559     return 0;
560 }
561 
562 static krb5_error_code
api_macos_ptcursor_next(krb5_context context,krb5_cc_ptcursor ptcursor,krb5_ccache * cache_out)563 api_macos_ptcursor_next(krb5_context context, krb5_cc_ptcursor ptcursor,
564                         krb5_ccache *cache_out)
565 {
566     krb5_error_code ret;
567     uint32_t err;
568     struct api_macos_ptcursor *apt = ptcursor->data;
569     const char *defname, *defresidual;
570     cc_ccache_t cache;
571     cc_string_t residual;
572     struct api_macos_cache_data *data;
573 
574     *cache_out = NULL;
575 
576     defname = krb5_cc_default_name(context);
577     if (defname == NULL || strncmp(defname, "API:", 4) != 0)
578         return 0;
579     defresidual = defname + 4;
580 
581     /* If the default cache name is a subsidiary cache, yield that cache if it
582      * exists and stop. */
583     if (*defresidual != '\0') {
584         if (!apt->first)
585             return 0;
586         apt->first = FALSE;
587         return make_open_cache(defresidual, cache_out);
588     }
589 
590     if (apt->first) {
591         apt->first = FALSE;
592 
593         /* Prepare to iterate over the collection. */
594         err = cc_initialize(&apt->cc_context, ccapi_version_max, NULL, NULL);
595         if (err)
596             return KRB5_FCC_INTERNAL;
597         err = cc_context_new_ccache_iterator(apt->cc_context, &apt->iter);
598         if (err)
599             return KRB5_FCC_INTERNAL;
600 
601         /* Yield the primary cache first if it exists. */
602         ret = get_primary_name(context, &apt->primary);
603         if (ret)
604             return ret;
605         ret = make_open_cache(apt->primary, cache_out);
606         if (ret || *cache_out != NULL)
607             return ret;
608     }
609 
610     for (;;) {
611         err = cc_ccache_iterator_next(apt->iter, &cache);
612         if (err)
613             return (err == ccIteratorEnd) ? 0 : ccerr2mit(err);
614 
615         err = cc_ccache_get_name(cache, &residual);
616         if (err) {
617             cc_ccache_release(cache);
618             return ccerr2mit(err);
619         }
620 
621         /* Skip the primary cache since we yielded it first. */
622         if (strcmp(residual->data, apt->primary) != 0)
623             break;
624     }
625 
626     ret = make_cache(residual->data, NULL, cache_out);
627     cc_string_release(residual);
628     if (ret) {
629         cc_ccache_release(cache);
630         return ret;
631     }
632     data = (*cache_out)->data;
633     data->cache = cache;
634     return 0;
635 }
636 
637 static krb5_error_code
api_macos_ptcursor_free(krb5_context context,krb5_cc_ptcursor * ptcursor)638 api_macos_ptcursor_free(krb5_context context, krb5_cc_ptcursor *ptcursor)
639 {
640     struct api_macos_ptcursor *apt = (*ptcursor)->data;
641 
642     if (apt != NULL) {
643         if (apt->iter != NULL)
644             cc_ccache_iterator_release(apt->iter);
645         if (apt->cc_context != NULL)
646             cc_context_release(apt->cc_context);
647         free(apt->primary);
648         free(apt);
649     }
650 
651     free(*ptcursor);
652     *ptcursor = NULL;
653 
654     return 0;
655 }
656 
657 static krb5_error_code
api_macos_lock(krb5_context context,krb5_ccache cache)658 api_macos_lock(krb5_context context, krb5_ccache cache)
659 {
660     struct api_macos_cache_data *data = cache->data;
661     uint32_t err;
662 
663     err = open_cache(data);
664     if (err)
665         return ccerr2mit(err);
666 
667     err = cc_ccache_lock(data->cache, cc_lock_write, cc_lock_block);
668     return ccerr2mit(err);
669 }
670 
671 static krb5_error_code
api_macos_unlock(krb5_context context,krb5_ccache cache)672 api_macos_unlock(krb5_context context, krb5_ccache cache)
673 {
674     struct api_macos_cache_data *data = cache->data;
675     uint32_t err;
676 
677     err = open_cache(data);
678     if (err)
679         return ccerr2mit(err);
680 
681     err = cc_ccache_unlock(data->cache);
682     return ccerr2mit(err);
683 }
684 
685 static krb5_error_code
api_macos_switch_to(krb5_context context,krb5_ccache cache)686 api_macos_switch_to(krb5_context context, krb5_ccache cache)
687 {
688     struct api_macos_cache_data *data = cache->data;
689     uint32_t err;
690 
691     err = open_cache(data);
692     if (err)
693         return ccerr2mit(err);
694 
695     err = cc_ccache_set_default(data->cache);
696     return ccerr2mit(err);
697 }
698 
699 const krb5_cc_ops krb5_api_macos_ops = {
700     0,
701     "API",
702     api_macos_get_name,
703     api_macos_resolve,
704     api_macos_gen_new,
705     api_macos_initialize,
706     api_macos_destroy,
707     api_macos_close,
708     api_macos_store,
709     api_macos_retrieve,
710     api_macos_get_princ,
711     api_macos_start_seq_get,
712     api_macos_next_cred,
713     api_macos_end_seq_get,
714     api_macos_remove_cred,
715     api_macos_set_flags,
716     api_macos_get_flags,
717     api_macos_ptcursor_new,
718     api_macos_ptcursor_next,
719     api_macos_ptcursor_free,
720     NULL, /* move */
721     NULL, /* wasdefault */
722     api_macos_lock,
723     api_macos_unlock,
724     api_macos_switch_to,
725 };
726 
727 #endif /* TARGET_OS_MAC */
728