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