Imported Upstream version 1.15.1
[platform/upstream/krb5.git] / src / lib / krb5 / ccache / cc_kcm.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/cc_kcm.c - KCM cache type (client side) */
3 /*
4  * Copyright (C) 2014 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 /*
34  * This cache type contacts a daemon for each cache operation, using Heimdal's
35  * KCM protocol.  On OS X, the preferred transport is Mach RPC; on other
36  * Unix-like platforms or if the daemon is not available via RPC, Unix domain
37  * sockets are used instead.
38  */
39
40 #ifndef _WIN32
41 #include "k5-int.h"
42 #include "k5-input.h"
43 #include "cc-int.h"
44 #include "kcm.h"
45 #include <sys/socket.h>
46 #include <sys/un.h>
47 #ifdef __APPLE__
48 #include <mach/mach.h>
49 #include <servers/bootstrap.h>
50 #include "kcmrpc.h"
51 #endif
52
53 #define MAX_REPLY_SIZE (10 * 1024 * 1024)
54
55 const krb5_cc_ops krb5_kcm_ops;
56
57 struct uuid_list {
58     unsigned char *uuidbytes;   /* all of the uuids concatenated together */
59     size_t count;
60     size_t pos;
61 };
62
63 struct kcmio {
64     int fd;
65 #ifdef __APPLE__
66     mach_port_t mport;
67 #endif
68 };
69
70 /* This structure bundles together a KCM request and reply, to minimize how
71  * much we have to declare and clean up in each method. */
72 struct kcmreq {
73     struct k5buf reqbuf;
74     struct k5input reply;
75     void *reply_mem;
76 };
77 #define EMPTY_KCMREQ { EMPTY_K5BUF }
78
79 struct kcm_cache_data {
80     char *residual;             /* immutable; may be accessed without lock */
81     k5_cc_mutex lock;           /* protects io and changetime */
82     struct kcmio *io;
83     krb5_timestamp changetime;
84 };
85
86 struct kcm_ptcursor {
87     char *residual;             /* primary or singleton subsidiary */
88     struct uuid_list *uuids;    /* NULL for singleton subsidiary */
89     struct kcmio *io;
90     krb5_boolean first;
91 };
92
93 /* Map EINVAL or KRB5_CC_FORMAT to KRB5_KCM_MALFORMED_REPLY; pass through all
94  * other codes. */
95 static inline krb5_error_code
96 map_invalid(krb5_error_code code)
97 {
98     return (code == EINVAL || code == KRB5_CC_FORMAT) ?
99         KRB5_KCM_MALFORMED_REPLY : code;
100 }
101
102 /* Begin a request for the given opcode.  If cache is non-null, supply the
103  * cache name as a request parameter. */
104 static void
105 kcmreq_init(struct kcmreq *req, kcm_opcode opcode, krb5_ccache cache)
106 {
107     unsigned char bytes[4];
108     const char *name;
109
110     memset(req, 0, sizeof(*req));
111
112     bytes[0] = KCM_PROTOCOL_VERSION_MAJOR;
113     bytes[1] = KCM_PROTOCOL_VERSION_MINOR;
114     store_16_be(opcode, bytes + 2);
115
116     k5_buf_init_dynamic(&req->reqbuf);
117     k5_buf_add_len(&req->reqbuf, bytes, 4);
118     if (cache != NULL) {
119         name = ((struct kcm_cache_data *)cache->data)->residual;
120         k5_buf_add_len(&req->reqbuf, name, strlen(name) + 1);
121     }
122 }
123
124 /* Add a 32-bit value to the request in big-endian byte order. */
125 static void
126 kcmreq_put32(struct kcmreq *req, uint32_t val)
127 {
128     unsigned char bytes[4];
129
130     store_32_be(val, bytes);
131     k5_buf_add_len(&req->reqbuf, bytes, 4);
132 }
133
134 #ifdef __APPLE__
135
136 /* The maximum length of an in-band request or reply as defined by the RPC
137  * protocol. */
138 #define MAX_INBAND_SIZE 2048
139
140 /* Connect or reconnect to the KCM daemon via Mach RPC, if possible. */
141 static krb5_error_code
142 kcmio_mach_connect(krb5_context context, struct kcmio *io)
143 {
144     krb5_error_code ret;
145     kern_return_t st;
146     mach_port_t mport;
147     char *service;
148
149     ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
150                              KRB5_CONF_KCM_MACH_SERVICE, NULL,
151                              DEFAULT_KCM_MACH_SERVICE, &service);
152     if (ret)
153         return ret;
154     if (strcmp(service, "-") == 0) {
155         profile_release_string(service);
156         return KRB5_KCM_NO_SERVER;
157     }
158
159     st = bootstrap_look_up(bootstrap_port, service, &mport);
160     profile_release_string(service);
161     if (st)
162         return KRB5_KCM_NO_SERVER;
163     if (io->mport != MACH_PORT_NULL)
164         mach_port_deallocate(mach_task_self(), io->mport);
165     io->mport = mport;
166     return 0;
167 }
168
169 /* Invoke the Mach RPC to get a reply from the KCM daemon. */
170 static krb5_error_code
171 kcmio_mach_call(krb5_context context, struct kcmio *io, void *data,
172                 size_t len, void **reply_out, size_t *len_out)
173 {
174     krb5_error_code ret;
175     size_t inband_req_len = 0, outband_req_len = 0, reply_len;
176     char *inband_req = NULL, *outband_req = NULL, *outband_reply, *copy;
177     char inband_reply[MAX_INBAND_SIZE];
178     mach_msg_type_number_t inband_reply_len, outband_reply_len;
179     const void *reply;
180     kern_return_t st;
181     int code;
182
183     *reply_out = NULL;
184     *len_out = 0;
185
186     /* Use the in-band or out-of-band request buffer depending on len. */
187     if (len <= MAX_INBAND_SIZE) {
188         inband_req = data;
189         inband_req_len = len;
190     } else {
191         outband_req = data;
192         outband_req_len = len;
193     }
194
195     st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
196                         outband_req_len, &code, inband_reply,
197                         &inband_reply_len, &outband_reply, &outband_reply_len);
198     if (st == MACH_SEND_INVALID_DEST) {
199         /* Get a new port and try again. */
200         st = kcmio_mach_connect(context, io);
201         if (st)
202             return KRB5_KCM_RPC_ERROR;
203         st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
204                             outband_req_len, &code, inband_reply,
205                             &inband_reply_len, &outband_reply,
206                             &outband_reply_len);
207     }
208     if (st)
209         return KRB5_KCM_RPC_ERROR;
210
211     if (code) {
212         ret = code;
213         goto cleanup;
214     }
215
216     /* The reply could be in the in-band or out-of-band reply buffer. */
217     reply = outband_reply_len ? outband_reply : inband_reply;
218     reply_len = outband_reply_len ? outband_reply_len : inband_reply_len;
219     copy = k5memdup(reply, reply_len, &ret);
220     if (copy == NULL)
221         goto cleanup;
222
223     *reply_out = copy;
224     *len_out = reply_len;
225
226 cleanup:
227     if (outband_reply_len) {
228         vm_deallocate(mach_task_self(), (vm_address_t)outband_reply,
229                       outband_reply_len);
230     }
231     return ret;
232 }
233
234 /* Release any Mach RPC state within io. */
235 static void
236 kcmio_mach_close(struct kcmio *io)
237 {
238     if (io->mport != MACH_PORT_NULL)
239         mach_port_deallocate(mach_task_self(), io->mport);
240 }
241
242 #else /* __APPLE__ */
243
244 #define kcmio_mach_connect(context, io) EINVAL
245 #define kcmio_mach_call(context, io, data, len, reply_out, len_out) EINVAL
246 #define kcmio_mach_close(io)
247
248 #endif
249
250 /* Connect to the KCM daemon via a Unix domain socket. */
251 static krb5_error_code
252 kcmio_unix_socket_connect(krb5_context context, struct kcmio *io)
253 {
254     krb5_error_code ret;
255     int fd = -1;
256     struct sockaddr_un addr;
257     char *path = NULL;
258
259     ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
260                              KRB5_CONF_KCM_SOCKET, NULL,
261                              DEFAULT_KCM_SOCKET_PATH, &path);
262     if (ret)
263         goto cleanup;
264     if (strcmp(path, "-") == 0) {
265         ret = KRB5_KCM_NO_SERVER;
266         goto cleanup;
267     }
268
269     fd = socket(AF_UNIX, SOCK_STREAM, 0);
270     if (fd == -1) {
271         ret = errno;
272         goto cleanup;
273     }
274
275     memset(&addr, 0, sizeof(addr));
276     addr.sun_family = AF_UNIX;
277     strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
278     if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
279         ret = (errno == ENOENT) ? KRB5_KCM_NO_SERVER : errno;
280         goto cleanup;
281     }
282
283     io->fd = fd;
284     fd = -1;
285
286 cleanup:
287     if (fd != -1)
288         close(fd);
289     profile_release_string(path);
290     return ret;
291 }
292
293 /* Write a KCM request: 4-byte big-endian length, then the marshalled
294  * request. */
295 static krb5_error_code
296 kcmio_unix_socket_write(krb5_context context, struct kcmio *io, void *request,
297                         size_t len)
298 {
299     char lenbytes[4];
300
301     store_32_be(len, lenbytes);
302     if (krb5_net_write(context, io->fd, lenbytes, 4) < 0)
303         return errno;
304     if (krb5_net_write(context, io->fd, request, len) < 0)
305         return errno;
306     return 0;
307 }
308
309 /* Read a KCM reply: 4-byte big-endian length, 4-byte big-endian status code,
310  * then the marshalled reply. */
311 static krb5_error_code
312 kcmio_unix_socket_read(krb5_context context, struct kcmio *io,
313                        void **reply_out, size_t *len_out)
314 {
315     krb5_error_code code;
316     char lenbytes[4], codebytes[4], *reply;
317     size_t len;
318     int st;
319
320     *reply_out = NULL;
321     *len_out = 0;
322
323     st = krb5_net_read(context, io->fd, lenbytes, 4);
324     if (st != 4)
325         return (st == -1) ? errno : KRB5_CC_IO;
326     len = load_32_be(lenbytes);
327     if (len > MAX_REPLY_SIZE)
328         return KRB5_KCM_REPLY_TOO_BIG;
329
330     st = krb5_net_read(context, io->fd, codebytes, 4);
331     if (st != 4)
332         return (st == -1) ? errno : KRB5_CC_IO;
333     code = load_32_be(codebytes);
334     if (code != 0)
335         return code;
336
337     reply = malloc(len);
338     if (reply == NULL)
339         return ENOMEM;
340     st = krb5_net_read(context, io->fd, reply, len);
341     if (st == -1 || (size_t)st != len) {
342         free(reply);
343         return (st < 0) ? errno : KRB5_CC_IO;
344     }
345
346     *reply_out = reply;
347     *len_out = len;
348     return 0;
349 }
350
351 static krb5_error_code
352 kcmio_connect(krb5_context context, struct kcmio **io_out)
353 {
354     krb5_error_code ret;
355     struct kcmio *io;
356
357     *io_out = NULL;
358     io = calloc(1, sizeof(*io));
359     if (io == NULL)
360         return ENOMEM;
361     io->fd = -1;
362
363     /* Try Mach RPC (OS X only), then fall back to Unix domain sockets */
364     ret = kcmio_mach_connect(context, io);
365     if (ret)
366         ret = kcmio_unix_socket_connect(context, io);
367     if (ret) {
368         free(io);
369         return ret;
370     }
371
372     *io_out = io;
373     return 0;
374 }
375
376 /* Check req->reqbuf for an error condition and return it.  Otherwise, send the
377  * request to the KCM daemon and get a response. */
378 static krb5_error_code
379 kcmio_call(krb5_context context, struct kcmio *io, struct kcmreq *req)
380 {
381     krb5_error_code ret;
382     size_t reply_len = 0;
383
384     if (k5_buf_status(&req->reqbuf) != 0)
385         return ENOMEM;
386
387     if (io->fd != -1) {
388         ret = kcmio_unix_socket_write(context, io, req->reqbuf.data,
389                                       req->reqbuf.len);
390         if (ret)
391             return ret;
392         ret = kcmio_unix_socket_read(context, io, &req->reply_mem, &reply_len);
393         if (ret)
394             return ret;
395     } else {
396         /* We must be using Mach RPC. */
397         ret = kcmio_mach_call(context, io, req->reqbuf.data, req->reqbuf.len,
398                               &req->reply_mem, &reply_len);
399         if (ret)
400             return ret;
401     }
402
403     /* Read the status code from the marshalled reply. */
404     k5_input_init(&req->reply, req->reply_mem, reply_len);
405     ret = k5_input_get_uint32_be(&req->reply);
406     return req->reply.status ? KRB5_KCM_MALFORMED_REPLY : ret;
407 }
408
409 static void
410 kcmio_close(struct kcmio *io)
411 {
412     if (io != NULL) {
413         kcmio_mach_close(io);
414         if (io->fd != -1)
415             close(io->fd);
416         free(io);
417     }
418 }
419
420 /* Fetch a zero-terminated name string from req->reply.  The returned pointer
421  * is an alias and must not be freed by the caller. */
422 static krb5_error_code
423 kcmreq_get_name(struct kcmreq *req, const char **name_out)
424 {
425     const unsigned char *end;
426     struct k5input *in = &req->reply;
427
428     *name_out = NULL;
429     end = memchr(in->ptr, '\0', in->len);
430     if (end == NULL)
431         return KRB5_KCM_MALFORMED_REPLY;
432     *name_out = (const char *)in->ptr;
433     (void)k5_input_get_bytes(in, end + 1 - in->ptr);
434     return 0;
435 }
436
437 /* Fetch a UUID list from req->reply.  UUID lists are not delimited, so we
438  * consume the rest of the input. */
439 static krb5_error_code
440 kcmreq_get_uuid_list(struct kcmreq *req, struct uuid_list **uuids_out)
441 {
442     struct uuid_list *uuids;
443
444     *uuids_out = NULL;
445
446     if (req->reply.len % KCM_UUID_LEN != 0)
447         return KRB5_KCM_MALFORMED_REPLY;
448
449     uuids = malloc(sizeof(*uuids));
450     if (uuids == NULL)
451         return ENOMEM;
452     uuids->count = req->reply.len / KCM_UUID_LEN;
453     uuids->pos = 0;
454
455     if (req->reply.len > 0) {
456         uuids->uuidbytes = malloc(req->reply.len);
457         if (uuids->uuidbytes == NULL) {
458             free(uuids);
459             return ENOMEM;
460         }
461         memcpy(uuids->uuidbytes, req->reply.ptr, req->reply.len);
462         (void)k5_input_get_bytes(&req->reply, req->reply.len);
463     } else {
464         uuids->uuidbytes = NULL;
465     }
466
467     *uuids_out = uuids;
468     return 0;
469 }
470
471 static void
472 free_uuid_list(struct uuid_list *uuids)
473 {
474     if (uuids != NULL)
475         free(uuids->uuidbytes);
476     free(uuids);
477 }
478
479 static void
480 kcmreq_free(struct kcmreq *req)
481 {
482     k5_buf_free(&req->reqbuf);
483     free(req->reply_mem);
484 }
485
486 /* Create a krb5_ccache structure.  If io is NULL, make a new connection for
487  * the cache.  Otherwise, always take ownership of io. */
488 static krb5_error_code
489 make_cache(krb5_context context, const char *residual, struct kcmio *io,
490            krb5_ccache *cache_out)
491 {
492     krb5_error_code ret;
493     krb5_ccache cache = NULL;
494     struct kcm_cache_data *data = NULL;
495     char *residual_copy = NULL;
496
497     *cache_out = NULL;
498
499     if (io == NULL) {
500         ret = kcmio_connect(context, &io);
501         if (ret)
502             return ret;
503     }
504
505     cache = malloc(sizeof(*cache));
506     if (cache == NULL)
507         goto oom;
508     data = calloc(1, sizeof(*data));
509     if (data == NULL)
510         goto oom;
511     residual_copy = strdup(residual);
512     if (residual_copy == NULL)
513         goto oom;
514     if (k5_cc_mutex_init(&data->lock) != 0)
515         goto oom;
516
517     data->residual = residual_copy;
518     data->io = io;
519     data->changetime = 0;
520     cache->ops = &krb5_kcm_ops;
521     cache->data = data;
522     cache->magic = KV5M_CCACHE;
523     *cache_out = cache;
524     return 0;
525
526 oom:
527     free(cache);
528     free(data);
529     free(residual_copy);
530     kcmio_close(io);
531     return ENOMEM;
532 }
533
534 /* Lock cache's I/O structure and use it to call the KCM daemon.  If modify is
535  * true, update the last change time. */
536 static krb5_error_code
537 cache_call(krb5_context context, krb5_ccache cache, struct kcmreq *req,
538            krb5_boolean modify)
539 {
540     krb5_error_code ret;
541     struct kcm_cache_data *data = cache->data;
542
543     k5_cc_mutex_lock(context, &data->lock);
544     ret = kcmio_call(context, data->io, req);
545     if (modify && !ret)
546         data->changetime = time(NULL);
547     k5_cc_mutex_unlock(context, &data->lock);
548     return ret;
549 }
550
551 /* Try to propagate the KDC time offset from the cache to the krb5 context. */
552 static void
553 get_kdc_offset(krb5_context context, krb5_ccache cache)
554 {
555     struct kcmreq req = EMPTY_KCMREQ;
556     int32_t time_offset;
557
558     kcmreq_init(&req, KCM_OP_GET_KDC_OFFSET, cache);
559     if (cache_call(context, cache, &req, FALSE) != 0)
560         goto cleanup;
561     time_offset = k5_input_get_uint32_be(&req.reply);
562     if (!req.reply.status)
563         goto cleanup;
564     context->os_context.time_offset = time_offset;
565     context->os_context.usec_offset = 0;
566     context->os_context.os_flags &= ~KRB5_OS_TOFFSET_TIME;
567     context->os_context.os_flags |= KRB5_OS_TOFFSET_VALID;
568
569 cleanup:
570     kcmreq_free(&req);
571 }
572
573 /* Try to propagate the KDC offset from the krb5 context to the cache. */
574 static void
575 set_kdc_offset(krb5_context context, krb5_ccache cache)
576 {
577     struct kcmreq req;
578
579     if (context->os_context.os_flags & KRB5_OS_TOFFSET_VALID) {
580         kcmreq_init(&req, KCM_OP_SET_KDC_OFFSET, cache);
581         kcmreq_put32(&req, context->os_context.time_offset);
582         (void)cache_call(context, cache, &req, TRUE);
583         kcmreq_free(&req);
584     }
585 }
586
587 static const char * KRB5_CALLCONV
588 kcm_get_name(krb5_context context, krb5_ccache cache)
589 {
590     return ((struct kcm_cache_data *)cache->data)->residual;
591 }
592
593 static krb5_error_code KRB5_CALLCONV
594 kcm_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
595 {
596     krb5_error_code ret;
597     struct kcmreq req = EMPTY_KCMREQ;
598     struct kcmio *io = NULL;
599     const char *defname = NULL;
600
601     *cache_out = NULL;
602
603     ret = kcmio_connect(context, &io);
604     if (ret)
605         goto cleanup;
606
607     if (*residual == '\0') {
608         kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL);
609         ret = kcmio_call(context, io, &req);
610         if (ret)
611             goto cleanup;
612         ret = kcmreq_get_name(&req, &defname);
613         if (ret)
614             goto cleanup;
615         residual = defname;
616     }
617
618     ret = make_cache(context, residual, io, cache_out);
619     io = NULL;
620
621 cleanup:
622     kcmio_close(io);
623     kcmreq_free(&req);
624     return ret;
625 }
626
627 static krb5_error_code KRB5_CALLCONV
628 kcm_gen_new(krb5_context context, krb5_ccache *cache_out)
629 {
630     krb5_error_code ret;
631     struct kcmreq req = EMPTY_KCMREQ;
632     struct kcmio *io = NULL;
633     const char *name;
634
635     *cache_out = NULL;
636
637     ret = kcmio_connect(context, &io);
638     if (ret)
639         goto cleanup;
640     kcmreq_init(&req, KCM_OP_GEN_NEW, NULL);
641     ret = kcmio_call(context, io, &req);
642     if (ret)
643         goto cleanup;
644     ret = kcmreq_get_name(&req, &name);
645     if (ret)
646         goto cleanup;
647     ret = make_cache(context, name, io, cache_out);
648     io = NULL;
649
650 cleanup:
651     kcmreq_free(&req);
652     kcmio_close(io);
653     return ret;
654 }
655
656 static krb5_error_code KRB5_CALLCONV
657 kcm_initialize(krb5_context context, krb5_ccache cache, krb5_principal princ)
658 {
659     krb5_error_code ret;
660     struct kcmreq req;
661
662     kcmreq_init(&req, KCM_OP_INITIALIZE, cache);
663     k5_marshal_princ(&req.reqbuf, 4, princ);
664     ret = cache_call(context, cache, &req, TRUE);
665     kcmreq_free(&req);
666     set_kdc_offset(context, cache);
667     return ret;
668 }
669
670 static krb5_error_code KRB5_CALLCONV
671 kcm_close(krb5_context context, krb5_ccache cache)
672 {
673     struct kcm_cache_data *data = cache->data;
674
675     k5_cc_mutex_destroy(&data->lock);
676     kcmio_close(data->io);
677     free(data->residual);
678     free(data);
679     free(cache);
680     return 0;
681 }
682
683 static krb5_error_code KRB5_CALLCONV
684 kcm_destroy(krb5_context context, krb5_ccache cache)
685 {
686     krb5_error_code ret;
687     struct kcmreq req;
688
689     kcmreq_init(&req, KCM_OP_DESTROY, cache);
690     ret = cache_call(context, cache, &req, TRUE);
691     kcmreq_free(&req);
692     (void)kcm_close(context, cache);
693     return ret;
694 }
695
696 static krb5_error_code KRB5_CALLCONV
697 kcm_store(krb5_context context, krb5_ccache cache, krb5_creds *cred)
698 {
699     krb5_error_code ret;
700     struct kcmreq req;
701
702     kcmreq_init(&req, KCM_OP_STORE, cache);
703     k5_marshal_cred(&req.reqbuf, 4, cred);
704     ret = cache_call(context, cache, &req, TRUE);
705     kcmreq_free(&req);
706     return ret;
707 }
708
709 static krb5_error_code KRB5_CALLCONV
710 kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
711              krb5_creds *mcred, krb5_creds *cred_out)
712 {
713     /* There is a KCM opcode for retrieving creds, but Heimdal's client doesn't
714      * use it.  It causes the KCM daemon to actually make a TGS request. */
715     return k5_cc_retrieve_cred_default(context, cache, flags, mcred, cred_out);
716 }
717
718 static krb5_error_code KRB5_CALLCONV
719 kcm_get_princ(krb5_context context, krb5_ccache cache,
720               krb5_principal *princ_out)
721 {
722     krb5_error_code ret;
723     struct kcmreq req;
724
725     kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, cache);
726     ret = cache_call(context, cache, &req, FALSE);
727     /* Heimdal KCM can respond with code 0 and no principal. */
728     if (!ret && req.reply.len == 0)
729         ret = KRB5_FCC_NOFILE;
730     if (!ret)
731         ret = k5_unmarshal_princ(req.reply.ptr, req.reply.len, 4, princ_out);
732     kcmreq_free(&req);
733     return map_invalid(ret);
734 }
735
736 static krb5_error_code KRB5_CALLCONV
737 kcm_start_seq_get(krb5_context context, krb5_ccache cache,
738                   krb5_cc_cursor *cursor_out)
739 {
740     krb5_error_code ret;
741     struct kcmreq req = EMPTY_KCMREQ;
742     struct uuid_list *uuids;
743
744     *cursor_out = NULL;
745
746     get_kdc_offset(context, cache);
747
748     kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache);
749     ret = cache_call(context, cache, &req, FALSE);
750     if (ret)
751         goto cleanup;
752     ret = kcmreq_get_uuid_list(&req, &uuids);
753     if (ret)
754         goto cleanup;
755     *cursor_out = (krb5_cc_cursor)uuids;
756
757 cleanup:
758     kcmreq_free(&req);
759     return ret;
760 }
761
762 static krb5_error_code KRB5_CALLCONV
763 kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
764               krb5_creds *cred_out)
765 {
766     krb5_error_code ret;
767     struct kcmreq req;
768     struct uuid_list *uuids = (struct uuid_list *)*cursor;
769
770     memset(cred_out, 0, sizeof(*cred_out));
771
772     if (uuids->pos >= uuids->count)
773         return KRB5_CC_END;
774
775     kcmreq_init(&req, KCM_OP_GET_CRED_BY_UUID, cache);
776     k5_buf_add_len(&req.reqbuf, uuids->uuidbytes + (uuids->pos * KCM_UUID_LEN),
777                    KCM_UUID_LEN);
778     uuids->pos++;
779     ret = cache_call(context, cache, &req, FALSE);
780     if (!ret)
781         ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, cred_out);
782     kcmreq_free(&req);
783     return map_invalid(ret);
784 }
785
786 static krb5_error_code KRB5_CALLCONV
787 kcm_end_seq_get(krb5_context context, krb5_ccache cache,
788                 krb5_cc_cursor *cursor)
789 {
790     free_uuid_list((struct uuid_list *)*cursor);
791     *cursor = NULL;
792     return 0;
793 }
794
795 static krb5_error_code KRB5_CALLCONV
796 kcm_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
797                 krb5_creds *mcred)
798 {
799     krb5_error_code ret;
800     struct kcmreq req;
801
802     kcmreq_init(&req, KCM_OP_REMOVE_CRED, cache);
803     kcmreq_put32(&req, flags);
804     k5_marshal_mcred(&req.reqbuf, mcred);
805     ret = cache_call(context, cache, &req, TRUE);
806     kcmreq_free(&req);
807     return ret;
808 }
809
810 static krb5_error_code KRB5_CALLCONV
811 kcm_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
812 {
813     /* We don't currently care about any flags for this type. */
814     return 0;
815 }
816
817 static krb5_error_code KRB5_CALLCONV
818 kcm_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out)
819 {
820     /* We don't currently have any operational flags for this type. */
821     *flags_out = 0;
822     return 0;
823 }
824
825 /* Construct a per-type cursor, always taking ownership of io and uuids. */
826 static krb5_error_code
827 make_ptcursor(const char *residual, struct uuid_list *uuids, struct kcmio *io,
828               krb5_cc_ptcursor *cursor_out)
829 {
830     krb5_cc_ptcursor cursor = NULL;
831     struct kcm_ptcursor *data = NULL;
832     char *residual_copy = NULL;
833
834     *cursor_out = NULL;
835
836     if (residual != NULL) {
837         residual_copy = strdup(residual);
838         if (residual_copy == NULL)
839             goto oom;
840     }
841     cursor = malloc(sizeof(*cursor));
842     if (cursor == NULL)
843         goto oom;
844     data = malloc(sizeof(*data));
845     if (data == NULL)
846         goto oom;
847
848     data->residual = residual_copy;
849     data->uuids = uuids;
850     data->io = io;
851     data->first = TRUE;
852     cursor->ops = &krb5_kcm_ops;
853     cursor->data = data;
854     *cursor_out = cursor;
855     return 0;
856
857 oom:
858     kcmio_close(io);
859     free_uuid_list(uuids);
860     free(residual_copy);
861     free(data);
862     free(cursor);
863     return ENOMEM;
864 }
865
866 static krb5_error_code KRB5_CALLCONV
867 kcm_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
868 {
869     krb5_error_code ret;
870     struct kcmreq req = EMPTY_KCMREQ;
871     struct kcmio *io = NULL;
872     struct uuid_list *uuids = NULL;
873     const char *defname, *primary;
874
875     *cursor_out = NULL;
876
877     /* Don't try to use KCM for the cache collection unless the default cache
878      * name has the KCM type. */
879     defname = krb5_cc_default_name(context);
880     if (defname == NULL || strncmp(defname, "KCM:", 4) != 0)
881         return make_ptcursor(NULL, NULL, NULL, cursor_out);
882
883     ret = kcmio_connect(context, &io);
884     if (ret)
885         return ret;
886
887     /* If defname is a subsidiary cache, return a singleton cursor. */
888     if (strlen(defname) > 4)
889         return make_ptcursor(defname + 4, NULL, io, cursor_out);
890
891     kcmreq_init(&req, KCM_OP_GET_CACHE_UUID_LIST, NULL);
892     ret = kcmio_call(context, io, &req);
893     if (ret == KRB5_FCC_NOFILE) {
894         /* There are no accessible caches; return an empty cursor. */
895         ret = make_ptcursor(NULL, NULL, NULL, cursor_out);
896         goto cleanup;
897     }
898     if (ret)
899         goto cleanup;
900     ret = kcmreq_get_uuid_list(&req, &uuids);
901     if (ret)
902         goto cleanup;
903
904     kcmreq_free(&req);
905     kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL);
906     ret = kcmio_call(context, io, &req);
907     if (ret)
908         goto cleanup;
909     ret = kcmreq_get_name(&req, &primary);
910     if (ret)
911         goto cleanup;
912
913     ret = make_ptcursor(primary, uuids, io, cursor_out);
914     uuids = NULL;
915     io = NULL;
916
917 cleanup:
918     free_uuid_list(uuids);
919     kcmio_close(io);
920     kcmreq_free(&req);
921     return ret;
922 }
923
924 /* Return true if name is an initialized cache. */
925 static krb5_boolean
926 name_exists(krb5_context context, struct kcmio *io, const char *name)
927 {
928     krb5_error_code ret;
929     struct kcmreq req;
930
931     kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, NULL);
932     k5_buf_add_len(&req.reqbuf, name, strlen(name) + 1);
933     ret = kcmio_call(context, io, &req);
934     kcmreq_free(&req);
935     return ret == 0;
936 }
937
938 static krb5_error_code KRB5_CALLCONV
939 kcm_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
940                   krb5_ccache *cache_out)
941 {
942     krb5_error_code ret = 0;
943     struct kcmreq req = EMPTY_KCMREQ;
944     struct kcm_ptcursor *data = cursor->data;
945     struct uuid_list *uuids;
946     const unsigned char *id;
947     const char *name;
948
949     *cache_out = NULL;
950
951     /* Return the primary or specified subsidiary cache if we haven't yet. */
952     if (data->first && data->residual != NULL) {
953         data->first = FALSE;
954         if (name_exists(context, data->io, data->residual))
955             return make_cache(context, data->residual, NULL, cache_out);
956     }
957
958     uuids = data->uuids;
959     if (uuids == NULL)
960         return 0;
961
962     while (uuids->pos < uuids->count) {
963         /* Get the name of the next cache. */
964         id = &uuids->uuidbytes[KCM_UUID_LEN * uuids->pos++];
965         kcmreq_free(&req);
966         kcmreq_init(&req, KCM_OP_GET_CACHE_BY_UUID, NULL);
967         k5_buf_add_len(&req.reqbuf, id, KCM_UUID_LEN);
968         ret = kcmio_call(context, data->io, &req);
969         if (ret)
970             goto cleanup;
971         ret = kcmreq_get_name(&req, &name);
972         if (ret)
973             goto cleanup;
974
975         /* Don't yield the primary cache twice. */
976         if (strcmp(name, data->residual) == 0)
977             continue;
978
979         ret = make_cache(context, name, NULL, cache_out);
980         break;
981     }
982
983 cleanup:
984     kcmreq_free(&req);
985     return ret;
986 }
987
988 static krb5_error_code KRB5_CALLCONV
989 kcm_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
990 {
991     struct kcm_ptcursor *data = (*cursor)->data;
992
993     free(data->residual);
994     free_uuid_list(data->uuids);
995     kcmio_close(data->io);
996     free(data);
997     free(*cursor);
998     *cursor = NULL;
999     return 0;
1000 }
1001
1002 static krb5_error_code KRB5_CALLCONV
1003 kcm_lastchange(krb5_context context, krb5_ccache cache,
1004                krb5_timestamp *time_out)
1005 {
1006     struct kcm_cache_data *data = cache->data;
1007
1008     /*
1009      * KCM has no support for retrieving the last change time.  Return the time
1010      * of the last change made through this handle, which isn't very useful,
1011      * but is the best we can do for now.
1012      */
1013     k5_cc_mutex_lock(context, &data->lock);
1014     *time_out = data->changetime;
1015     k5_cc_mutex_unlock(context, &data->lock);
1016     return 0;
1017 }
1018
1019 static krb5_error_code KRB5_CALLCONV
1020 kcm_lock(krb5_context context, krb5_ccache cache)
1021 {
1022     k5_cc_mutex_lock(context, &((struct kcm_cache_data *)cache->data)->lock);
1023     return 0;
1024 }
1025
1026 static krb5_error_code KRB5_CALLCONV
1027 kcm_unlock(krb5_context context, krb5_ccache cache)
1028 {
1029     k5_cc_mutex_unlock(context, &((struct kcm_cache_data *)cache->data)->lock);
1030     return 0;
1031 }
1032
1033 static krb5_error_code KRB5_CALLCONV
1034 kcm_switch_to(krb5_context context, krb5_ccache cache)
1035 {
1036     krb5_error_code ret;
1037     struct kcmreq req;
1038
1039     kcmreq_init(&req, KCM_OP_SET_DEFAULT_CACHE, cache);
1040     ret = cache_call(context, cache, &req, FALSE);
1041     kcmreq_free(&req);
1042     return ret;
1043 }
1044
1045 const krb5_cc_ops krb5_kcm_ops = {
1046     0,
1047     "KCM",
1048     kcm_get_name,
1049     kcm_resolve,
1050     kcm_gen_new,
1051     kcm_initialize,
1052     kcm_destroy,
1053     kcm_close,
1054     kcm_store,
1055     kcm_retrieve,
1056     kcm_get_princ,
1057     kcm_start_seq_get,
1058     kcm_next_cred,
1059     kcm_end_seq_get,
1060     kcm_remove_cred,
1061     kcm_set_flags,
1062     kcm_get_flags,
1063     kcm_ptcursor_new,
1064     kcm_ptcursor_next,
1065     kcm_ptcursor_free,
1066     NULL, /* move */
1067     kcm_lastchange,
1068     NULL, /* wasdefault */
1069     kcm_lock,
1070     kcm_unlock,
1071     kcm_switch_to,
1072 };
1073
1074 #endif /* not _WIN32 */