xref: /freebsd/crypto/krb5/src/lib/krb5/ccache/cc_kcm.c (revision f1c4c3daccbaf3820f0e2224de53df12fc952fcc)
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 macOS, 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 "../os/os-proto.h"
46 #include <sys/socket.h>
47 #include <sys/un.h>
48 #ifdef __APPLE__
49 #include <mach/mach.h>
50 #include <servers/bootstrap.h>
51 #include "kcmrpc.h"
52 #endif
53 
54 #define MAX_REPLY_SIZE (10 * 1024 * 1024)
55 
56 const krb5_cc_ops krb5_kcm_ops;
57 
58 struct uuid_list {
59     unsigned char *uuidbytes;   /* all of the uuids concatenated together */
60     size_t count;
61     size_t pos;
62 };
63 
64 struct cred_list {
65     krb5_creds *creds;
66     size_t count;
67     size_t pos;
68 };
69 
70 struct kcm_cursor {
71     struct uuid_list *uuids;
72     struct cred_list *creds;
73 };
74 
75 struct kcmio {
76     SOCKET fd;
77 #ifdef __APPLE__
78     mach_port_t mport;
79 #endif
80 };
81 
82 /* This structure bundles together a KCM request and reply, to minimize how
83  * much we have to declare and clean up in each method. */
84 struct kcmreq {
85     struct k5buf reqbuf;
86     struct k5input reply;
87     void *reply_mem;
88 };
89 #define EMPTY_KCMREQ { EMPTY_K5BUF }
90 
91 struct kcm_cache_data {
92     char *residual;             /* immutable; may be accessed without lock */
93     k5_cc_mutex lock;           /* protects io */
94     struct kcmio *io;
95 };
96 
97 struct kcm_ptcursor {
98     char *residual;             /* primary or singleton subsidiary */
99     struct uuid_list *uuids;    /* NULL for singleton subsidiary */
100     struct kcmio *io;
101     krb5_boolean first;
102 };
103 
104 /* Map EINVAL or KRB5_CC_FORMAT to KRB5_KCM_MALFORMED_REPLY; pass through all
105  * other codes. */
106 static inline krb5_error_code
map_invalid(krb5_error_code code)107 map_invalid(krb5_error_code code)
108 {
109     return (code == EINVAL || code == KRB5_CC_FORMAT) ?
110         KRB5_KCM_MALFORMED_REPLY : code;
111 }
112 
113 /*
114  * Map an MIT krb5 KRB5_TC flag word to the equivalent Heimdal flag word.  Note
115  * that there is no MIT krb5 equivalent for Heimdal's KRB5_TC_DONT_MATCH_REALM
116  * (which is like KRB5_TC_MATCH_SRV_NAMEONLY but also applies to the client
117  * principal) and no Heimdal equivalent for MIT krb5's KRB5_TC_SUPPORTED_KTYPES
118  * (which matches against enctypes from the krb5_context rather than the
119  * matching cred).
120  */
121 static inline krb5_flags
map_tcflags(krb5_flags mitflags)122 map_tcflags(krb5_flags mitflags)
123 {
124     krb5_flags heimflags = 0;
125 
126     if (mitflags & KRB5_TC_MATCH_TIMES)
127         heimflags |= KCM_TC_MATCH_TIMES;
128     if (mitflags & KRB5_TC_MATCH_IS_SKEY)
129         heimflags |= KCM_TC_MATCH_IS_SKEY;
130     if (mitflags & KRB5_TC_MATCH_FLAGS)
131         heimflags |= KCM_TC_MATCH_FLAGS;
132     if (mitflags & KRB5_TC_MATCH_TIMES_EXACT)
133         heimflags |= KCM_TC_MATCH_TIMES_EXACT;
134     if (mitflags & KRB5_TC_MATCH_FLAGS_EXACT)
135         heimflags |= KCM_TC_MATCH_FLAGS_EXACT;
136     if (mitflags & KRB5_TC_MATCH_AUTHDATA)
137         heimflags |= KCM_TC_MATCH_AUTHDATA;
138     if (mitflags & KRB5_TC_MATCH_SRV_NAMEONLY)
139         heimflags |= KCM_TC_MATCH_SRV_NAMEONLY;
140     if (mitflags & KRB5_TC_MATCH_2ND_TKT)
141         heimflags |= KCM_TC_MATCH_2ND_TKT;
142     if (mitflags & KRB5_TC_MATCH_KTYPE)
143         heimflags |= KCM_TC_MATCH_KEYTYPE;
144     return heimflags;
145 }
146 
147 /*
148  * Return true if code could indicate an unsupported operation.  Heimdal's KCM
149  * returns KRB5_FCC_INTERNAL.  sssd's KCM daemon (as of sssd 2.4) returns
150  * KRB5_CC_NO_SUPP if it recognizes the operation but does not implement it,
151  * and KRB5_CC_IO if it doesn't recognize the operation (which is unfortunate
152  * since it could also indicate a communication failure).
153  */
154 static krb5_boolean
unsupported_op_error(krb5_error_code code)155 unsupported_op_error(krb5_error_code code)
156 {
157     return code == KRB5_FCC_INTERNAL || code == KRB5_CC_IO ||
158         code == KRB5_CC_NOSUPP;
159 }
160 
161 /* Begin a request for the given opcode.  If cache is non-null, supply the
162  * cache name as a request parameter. */
163 static void
kcmreq_init(struct kcmreq * req,kcm_opcode opcode,krb5_ccache cache)164 kcmreq_init(struct kcmreq *req, kcm_opcode opcode, krb5_ccache cache)
165 {
166     unsigned char bytes[4];
167     const char *name;
168 
169     memset(req, 0, sizeof(*req));
170 
171     bytes[0] = KCM_PROTOCOL_VERSION_MAJOR;
172     bytes[1] = KCM_PROTOCOL_VERSION_MINOR;
173     store_16_be(opcode, bytes + 2);
174 
175     k5_buf_init_dynamic(&req->reqbuf);
176     k5_buf_add_len(&req->reqbuf, bytes, 4);
177     if (cache != NULL) {
178         name = ((struct kcm_cache_data *)cache->data)->residual;
179         k5_buf_add_len(&req->reqbuf, name, strlen(name) + 1);
180     }
181 }
182 
183 #ifdef __APPLE__
184 
185 /* The maximum length of an in-band request or reply as defined by the RPC
186  * protocol. */
187 #define MAX_INBAND_SIZE 2048
188 
189 /* Connect or reconnect to the KCM daemon via Mach RPC, if possible. */
190 static krb5_error_code
kcmio_mach_connect(krb5_context context,struct kcmio * io)191 kcmio_mach_connect(krb5_context context, struct kcmio *io)
192 {
193     krb5_error_code ret;
194     kern_return_t st;
195     mach_port_t mport;
196     char *service;
197 
198     ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
199                              KRB5_CONF_KCM_MACH_SERVICE, NULL,
200                              DEFAULT_KCM_MACH_SERVICE, &service);
201     if (ret)
202         return ret;
203     if (strcmp(service, "-") == 0) {
204         profile_release_string(service);
205         return KRB5_KCM_NO_SERVER;
206     }
207 
208     st = bootstrap_look_up(bootstrap_port, service, &mport);
209     profile_release_string(service);
210     if (st)
211         return KRB5_KCM_NO_SERVER;
212     if (io->mport != MACH_PORT_NULL)
213         mach_port_deallocate(mach_task_self(), io->mport);
214     io->mport = mport;
215     return 0;
216 }
217 
218 /* Invoke the Mach RPC to get a reply from the KCM daemon. */
219 static krb5_error_code
kcmio_mach_call(krb5_context context,struct kcmio * io,void * data,size_t len,void ** reply_out,size_t * len_out)220 kcmio_mach_call(krb5_context context, struct kcmio *io, void *data,
221                 size_t len, void **reply_out, size_t *len_out)
222 {
223     krb5_error_code ret;
224     size_t inband_req_len = 0, outband_req_len = 0, reply_len;
225     char *inband_req = NULL, *outband_req = NULL, *outband_reply, *copy;
226     char inband_reply[MAX_INBAND_SIZE];
227     mach_msg_type_number_t inband_reply_len, outband_reply_len;
228     const void *reply;
229     kern_return_t st;
230     int code;
231 
232     *reply_out = NULL;
233     *len_out = 0;
234 
235     /* Use the in-band or out-of-band request buffer depending on len. */
236     if (len <= MAX_INBAND_SIZE) {
237         inband_req = data;
238         inband_req_len = len;
239     } else {
240         outband_req = data;
241         outband_req_len = len;
242     }
243 
244     st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
245                         outband_req_len, &code, inband_reply,
246                         &inband_reply_len, &outband_reply, &outband_reply_len);
247     if (st == MACH_SEND_INVALID_DEST) {
248         /* Get a new port and try again. */
249         st = kcmio_mach_connect(context, io);
250         if (st)
251             return KRB5_KCM_RPC_ERROR;
252         st = k5_kcmrpc_call(io->mport, inband_req, inband_req_len, outband_req,
253                             outband_req_len, &code, inband_reply,
254                             &inband_reply_len, &outband_reply,
255                             &outband_reply_len);
256     }
257     if (st)
258         return KRB5_KCM_RPC_ERROR;
259 
260     if (code) {
261         ret = code;
262         goto cleanup;
263     }
264 
265     /* The reply could be in the in-band or out-of-band reply buffer. */
266     reply = outband_reply_len ? outband_reply : inband_reply;
267     reply_len = outband_reply_len ? outband_reply_len : inband_reply_len;
268     copy = k5memdup(reply, reply_len, &ret);
269     if (copy == NULL)
270         goto cleanup;
271 
272     *reply_out = copy;
273     *len_out = reply_len;
274 
275 cleanup:
276     if (outband_reply_len) {
277         vm_deallocate(mach_task_self(), (vm_address_t)outband_reply,
278                       outband_reply_len);
279     }
280     return ret;
281 }
282 
283 /* Release any Mach RPC state within io. */
284 static void
kcmio_mach_close(struct kcmio * io)285 kcmio_mach_close(struct kcmio *io)
286 {
287     if (io->mport != MACH_PORT_NULL)
288         mach_port_deallocate(mach_task_self(), io->mport);
289 }
290 
291 #else /* __APPLE__ */
292 
293 #define kcmio_mach_connect(context, io) EINVAL
294 #define kcmio_mach_call(context, io, data, len, reply_out, len_out) EINVAL
295 #define kcmio_mach_close(io)
296 
297 #endif
298 
299 /* Connect to the KCM daemon via a Unix domain socket. */
300 static krb5_error_code
kcmio_unix_socket_connect(krb5_context context,struct kcmio * io)301 kcmio_unix_socket_connect(krb5_context context, struct kcmio *io)
302 {
303     krb5_error_code ret;
304     SOCKET fd = INVALID_SOCKET;
305     struct sockaddr_un addr;
306     char *path = NULL;
307 
308     ret = profile_get_string(context->profile, KRB5_CONF_LIBDEFAULTS,
309                              KRB5_CONF_KCM_SOCKET, NULL,
310                              DEFAULT_KCM_SOCKET_PATH, &path);
311     if (ret)
312         goto cleanup;
313     if (strcmp(path, "-") == 0) {
314         ret = KRB5_KCM_NO_SERVER;
315         goto cleanup;
316     }
317 
318     fd = socket(AF_UNIX, SOCK_STREAM, 0);
319     if (fd == INVALID_SOCKET) {
320         ret = SOCKET_ERRNO;
321         goto cleanup;
322     }
323 
324     memset(&addr, 0, sizeof(addr));
325     addr.sun_family = AF_UNIX;
326     strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
327     if (SOCKET_CONNECT(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
328         ret = (SOCKET_ERRNO == ENOENT) ? KRB5_KCM_NO_SERVER : SOCKET_ERRNO;
329         goto cleanup;
330     }
331 
332     io->fd = fd;
333     fd = INVALID_SOCKET;
334 
335 cleanup:
336     if (fd != INVALID_SOCKET)
337         closesocket(fd);
338     profile_release_string(path);
339     return ret;
340 }
341 
342 /* Write a KCM request: 4-byte big-endian length, then the marshalled
343  * request. */
344 static krb5_error_code
kcmio_unix_socket_write(krb5_context context,struct kcmio * io,void * request,size_t len)345 kcmio_unix_socket_write(krb5_context context, struct kcmio *io, void *request,
346                         size_t len)
347 {
348     char lenbytes[4];
349     sg_buf sg[2];
350     int ret;
351     krb5_boolean reconnected = FALSE;
352 
353     SG_SET(&sg[0], lenbytes, sizeof(lenbytes));
354     SG_SET(&sg[1], request, len);
355     store_32_be(len, lenbytes);
356 
357     for (;;) {
358         ret = krb5int_net_writev(context, io->fd, sg, 2);
359         if (ret >= 0)
360             return 0;
361         ret = errno;
362         if (ret != EPIPE || reconnected)
363             return ret;
364 
365         /*
366          * Try once to reconnect on an EPIPE, in case the server has an idle
367          * timeout (like sssd does) and we went too long between ccache
368          * operations.  Reconnecting might also help if the server was
369          * restarted for an upgrade--although the server must be designed to
370          * always listen for connections on the socket during upgrades, or a
371          * single reconnect attempt won't be robust.
372          */
373         close(io->fd);
374         ret = kcmio_unix_socket_connect(context, io);
375         if (ret)
376             return ret;
377         reconnected = TRUE;
378     }
379 }
380 
381 /* Read a KCM reply: 4-byte big-endian length, 4-byte big-endian status code,
382  * then the marshalled reply. */
383 static krb5_error_code
kcmio_unix_socket_read(krb5_context context,struct kcmio * io,void ** reply_out,size_t * len_out)384 kcmio_unix_socket_read(krb5_context context, struct kcmio *io,
385                        void **reply_out, size_t *len_out)
386 {
387     krb5_error_code code;
388     char lenbytes[4], codebytes[4], *reply;
389     size_t len;
390     int st;
391 
392     *reply_out = NULL;
393     *len_out = 0;
394 
395     st = krb5_net_read(context, io->fd, lenbytes, 4);
396     if (st != 4)
397         return (st == -1) ? errno : KRB5_CC_IO;
398     len = load_32_be(lenbytes);
399     if (len > MAX_REPLY_SIZE)
400         return KRB5_KCM_REPLY_TOO_BIG;
401 
402     st = krb5_net_read(context, io->fd, codebytes, 4);
403     if (st != 4)
404         return (st == -1) ? errno : KRB5_CC_IO;
405     code = load_32_be(codebytes);
406     if (code != 0)
407         return code;
408 
409     reply = malloc(len);
410     if (reply == NULL)
411         return ENOMEM;
412     st = krb5_net_read(context, io->fd, reply, len);
413     if (st == -1 || (size_t)st != len) {
414         free(reply);
415         return (st < 0) ? errno : KRB5_CC_IO;
416     }
417 
418     *reply_out = reply;
419     *len_out = len;
420     return 0;
421 }
422 
423 static krb5_error_code
kcmio_connect(krb5_context context,struct kcmio ** io_out)424 kcmio_connect(krb5_context context, struct kcmio **io_out)
425 {
426     krb5_error_code ret;
427     struct kcmio *io;
428 
429     *io_out = NULL;
430     io = calloc(1, sizeof(*io));
431     if (io == NULL)
432         return ENOMEM;
433     io->fd = INVALID_SOCKET;
434 
435     /* Try Mach RPC (macOS only), then fall back to Unix domain sockets */
436     ret = kcmio_mach_connect(context, io);
437     if (ret)
438         ret = kcmio_unix_socket_connect(context, io);
439     if (ret) {
440         free(io);
441         return ret;
442     }
443 
444     *io_out = io;
445     return 0;
446 }
447 
448 /* Check req->reqbuf for an error condition and return it.  Otherwise, send the
449  * request to the KCM daemon and get a response. */
450 static krb5_error_code
kcmio_call(krb5_context context,struct kcmio * io,struct kcmreq * req)451 kcmio_call(krb5_context context, struct kcmio *io, struct kcmreq *req)
452 {
453     krb5_error_code ret;
454     size_t reply_len = 0;
455 
456     if (k5_buf_status(&req->reqbuf) != 0)
457         return ENOMEM;
458 
459     if (io->fd != INVALID_SOCKET) {
460         ret = kcmio_unix_socket_write(context, io, req->reqbuf.data,
461                                       req->reqbuf.len);
462         if (ret)
463             return ret;
464         ret = kcmio_unix_socket_read(context, io, &req->reply_mem, &reply_len);
465         if (ret)
466             return ret;
467     } else {
468         /* We must be using Mach RPC. */
469         ret = kcmio_mach_call(context, io, req->reqbuf.data, req->reqbuf.len,
470                               &req->reply_mem, &reply_len);
471         if (ret)
472             return ret;
473     }
474 
475     /* Read the status code from the marshalled reply. */
476     k5_input_init(&req->reply, req->reply_mem, reply_len);
477     ret = k5_input_get_uint32_be(&req->reply);
478     return req->reply.status ? KRB5_KCM_MALFORMED_REPLY : ret;
479 }
480 
481 static void
kcmio_close(struct kcmio * io)482 kcmio_close(struct kcmio *io)
483 {
484     if (io != NULL) {
485         kcmio_mach_close(io);
486         if (io->fd != INVALID_SOCKET)
487             closesocket(io->fd);
488         free(io);
489     }
490 }
491 
492 /* Fetch a zero-terminated name string from req->reply.  The returned pointer
493  * is an alias and must not be freed by the caller. */
494 static krb5_error_code
kcmreq_get_name(struct kcmreq * req,const char ** name_out)495 kcmreq_get_name(struct kcmreq *req, const char **name_out)
496 {
497     const unsigned char *end;
498     struct k5input *in = &req->reply;
499 
500     *name_out = NULL;
501     end = memchr(in->ptr, '\0', in->len);
502     if (end == NULL)
503         return KRB5_KCM_MALFORMED_REPLY;
504     *name_out = (const char *)in->ptr;
505     (void)k5_input_get_bytes(in, end + 1 - in->ptr);
506     return 0;
507 }
508 
509 /* Fetch a UUID list from req->reply.  UUID lists are not delimited, so we
510  * consume the rest of the input. */
511 static krb5_error_code
kcmreq_get_uuid_list(struct kcmreq * req,struct uuid_list ** uuids_out)512 kcmreq_get_uuid_list(struct kcmreq *req, struct uuid_list **uuids_out)
513 {
514     struct uuid_list *uuids;
515 
516     *uuids_out = NULL;
517 
518     if (req->reply.len % KCM_UUID_LEN != 0)
519         return KRB5_KCM_MALFORMED_REPLY;
520 
521     uuids = malloc(sizeof(*uuids));
522     if (uuids == NULL)
523         return ENOMEM;
524     uuids->count = req->reply.len / KCM_UUID_LEN;
525     uuids->pos = 0;
526 
527     if (req->reply.len > 0) {
528         uuids->uuidbytes = malloc(req->reply.len);
529         if (uuids->uuidbytes == NULL) {
530             free(uuids);
531             return ENOMEM;
532         }
533         memcpy(uuids->uuidbytes, req->reply.ptr, req->reply.len);
534         (void)k5_input_get_bytes(&req->reply, req->reply.len);
535     } else {
536         uuids->uuidbytes = NULL;
537     }
538 
539     *uuids_out = uuids;
540     return 0;
541 }
542 
543 static void
free_uuid_list(struct uuid_list * uuids)544 free_uuid_list(struct uuid_list *uuids)
545 {
546     if (uuids != NULL)
547         free(uuids->uuidbytes);
548     free(uuids);
549 }
550 
551 static void
free_cred_list(struct cred_list * list)552 free_cred_list(struct cred_list *list)
553 {
554     size_t i;
555 
556     if (list == NULL)
557         return;
558 
559     /* Creds are transferred to the caller as list->pos is incremented, so we
560      * can start freeing there. */
561     for (i = list->pos; i < list->count; i++)
562         krb5_free_cred_contents(NULL, &list->creds[i]);
563     free(list->creds);
564     free(list);
565 }
566 
567 /* Fetch a cred list from req->reply. */
568 static krb5_error_code
kcmreq_get_cred_list(struct kcmreq * req,struct cred_list ** creds_out)569 kcmreq_get_cred_list(struct kcmreq *req, struct cred_list **creds_out)
570 {
571     struct cred_list *list;
572     const unsigned char *data;
573     krb5_error_code ret = 0;
574     size_t count, len, i;
575 
576     *creds_out = NULL;
577 
578     /* Check a rough bound on the count to prevent very large allocations. */
579     count = k5_input_get_uint32_be(&req->reply);
580     if (count > req->reply.len / 4)
581         return KRB5_KCM_MALFORMED_REPLY;
582 
583     list = malloc(sizeof(*list));
584     if (list == NULL)
585         return ENOMEM;
586 
587     list->creds = NULL;
588     list->count = count;
589     list->pos = 0;
590     list->creds = k5calloc(count, sizeof(*list->creds), &ret);
591     if (list->creds == NULL) {
592         free(list);
593         return ret;
594     }
595 
596     for (i = 0; i < count; i++) {
597         len = k5_input_get_uint32_be(&req->reply);
598         data = k5_input_get_bytes(&req->reply, len);
599         if (data == NULL)
600             break;
601         ret = k5_unmarshal_cred(data, len, 4, &list->creds[i]);
602         if (ret)
603             break;
604     }
605     if (i < count) {
606         free_cred_list(list);
607         return (ret == ENOMEM) ? ENOMEM : KRB5_KCM_MALFORMED_REPLY;
608     }
609 
610     *creds_out = list;
611     return 0;
612 }
613 
614 static void
kcmreq_free(struct kcmreq * req)615 kcmreq_free(struct kcmreq *req)
616 {
617     k5_buf_free(&req->reqbuf);
618     free(req->reply_mem);
619 }
620 
621 /* Create a krb5_ccache structure.  If io is NULL, make a new connection for
622  * the cache.  Otherwise, always take ownership of io. */
623 static krb5_error_code
make_cache(krb5_context context,const char * residual,struct kcmio * io,krb5_ccache * cache_out)624 make_cache(krb5_context context, const char *residual, struct kcmio *io,
625            krb5_ccache *cache_out)
626 {
627     krb5_error_code ret;
628     krb5_ccache cache = NULL;
629     struct kcm_cache_data *data = NULL;
630     char *residual_copy = NULL;
631 
632     *cache_out = NULL;
633 
634     if (io == NULL) {
635         ret = kcmio_connect(context, &io);
636         if (ret)
637             return ret;
638     }
639 
640     cache = malloc(sizeof(*cache));
641     if (cache == NULL)
642         goto oom;
643     data = calloc(1, sizeof(*data));
644     if (data == NULL)
645         goto oom;
646     residual_copy = strdup(residual);
647     if (residual_copy == NULL)
648         goto oom;
649     if (k5_cc_mutex_init(&data->lock) != 0)
650         goto oom;
651 
652     data->residual = residual_copy;
653     data->io = io;
654     cache->ops = &krb5_kcm_ops;
655     cache->data = data;
656     cache->magic = KV5M_CCACHE;
657     *cache_out = cache;
658     return 0;
659 
660 oom:
661     free(cache);
662     free(data);
663     free(residual_copy);
664     kcmio_close(io);
665     return ENOMEM;
666 }
667 
668 /* Lock cache's I/O structure and use it to call the KCM daemon. */
669 static krb5_error_code
cache_call(krb5_context context,krb5_ccache cache,struct kcmreq * req)670 cache_call(krb5_context context, krb5_ccache cache, struct kcmreq *req)
671 {
672     krb5_error_code ret;
673     struct kcm_cache_data *data = cache->data;
674 
675     k5_cc_mutex_lock(context, &data->lock);
676     ret = kcmio_call(context, data->io, req);
677     k5_cc_mutex_unlock(context, &data->lock);
678     return ret;
679 }
680 
681 /* Try to propagate the KDC time offset from the cache to the krb5 context. */
682 static void
get_kdc_offset(krb5_context context,krb5_ccache cache)683 get_kdc_offset(krb5_context context, krb5_ccache cache)
684 {
685     struct kcmreq req = EMPTY_KCMREQ;
686     int32_t time_offset;
687 
688     kcmreq_init(&req, KCM_OP_GET_KDC_OFFSET, cache);
689     if (cache_call(context, cache, &req) != 0)
690         goto cleanup;
691     time_offset = k5_input_get_uint32_be(&req.reply);
692     if (req.reply.status)
693         goto cleanup;
694     context->os_context.time_offset = time_offset;
695     context->os_context.usec_offset = 0;
696     context->os_context.os_flags &= ~KRB5_OS_TOFFSET_TIME;
697     context->os_context.os_flags |= KRB5_OS_TOFFSET_VALID;
698 
699 cleanup:
700     kcmreq_free(&req);
701 }
702 
703 /* Try to propagate the KDC offset from the krb5 context to the cache. */
704 static void
set_kdc_offset(krb5_context context,krb5_ccache cache)705 set_kdc_offset(krb5_context context, krb5_ccache cache)
706 {
707     struct kcmreq req;
708 
709     if (context->os_context.os_flags & KRB5_OS_TOFFSET_VALID) {
710         kcmreq_init(&req, KCM_OP_SET_KDC_OFFSET, cache);
711         k5_buf_add_uint32_be(&req.reqbuf, context->os_context.time_offset);
712         (void)cache_call(context, cache, &req);
713         kcmreq_free(&req);
714     }
715 }
716 
717 static const char * KRB5_CALLCONV
kcm_get_name(krb5_context context,krb5_ccache cache)718 kcm_get_name(krb5_context context, krb5_ccache cache)
719 {
720     return ((struct kcm_cache_data *)cache->data)->residual;
721 }
722 
723 /* Fetch the primary name within the collection.  The result is only valid for
724  * the lifetime of req and should not be freed. */
725 static krb5_error_code
get_primary_name(krb5_context context,struct kcmreq * req,struct kcmio * io,const char ** name_out)726 get_primary_name(krb5_context context, struct kcmreq *req, struct kcmio *io,
727                  const char **name_out)
728 {
729     krb5_error_code ret;
730 
731     *name_out = NULL;
732 
733     kcmreq_init(req, KCM_OP_GET_DEFAULT_CACHE, NULL);
734     ret = kcmio_call(context, io, req);
735     if (ret)
736         return ret;
737     return kcmreq_get_name(req, name_out);
738 }
739 
740 static krb5_error_code KRB5_CALLCONV
kcm_resolve(krb5_context context,krb5_ccache * cache_out,const char * residual)741 kcm_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
742 {
743     krb5_error_code ret;
744     struct kcmreq req = EMPTY_KCMREQ;
745     struct kcmio *io = NULL;
746     const char *defname = NULL;
747 
748     *cache_out = NULL;
749 
750     ret = kcmio_connect(context, &io);
751     if (ret)
752         goto cleanup;
753 
754     if (*residual == '\0') {
755         ret = get_primary_name(context, &req, io, &defname);
756         if (ret)
757             goto cleanup;
758         residual = defname;
759     }
760 
761     ret = make_cache(context, residual, io, cache_out);
762     io = NULL;
763 
764 cleanup:
765     kcmio_close(io);
766     kcmreq_free(&req);
767     return ret;
768 }
769 
770 krb5_error_code
k5_kcm_primary_name(krb5_context context,char ** name_out)771 k5_kcm_primary_name(krb5_context context, char **name_out)
772 {
773     krb5_error_code ret;
774     struct kcmreq req = EMPTY_KCMREQ;
775     struct kcmio *io = NULL;
776     const char *name;
777 
778     *name_out = NULL;
779 
780     ret = kcmio_connect(context, &io);
781     if (ret)
782         goto cleanup;
783     ret = get_primary_name(context, &req, io, &name);
784     if (ret)
785         goto cleanup;
786     *name_out = strdup(name);
787     ret = (*name_out == NULL) ? ENOMEM : 0;
788 
789 cleanup:
790     kcmio_close(io);
791     kcmreq_free(&req);
792     return ret;
793 }
794 
795 static krb5_error_code KRB5_CALLCONV
kcm_gen_new(krb5_context context,krb5_ccache * cache_out)796 kcm_gen_new(krb5_context context, krb5_ccache *cache_out)
797 {
798     krb5_error_code ret;
799     struct kcmreq req = EMPTY_KCMREQ;
800     struct kcmio *io = NULL;
801     const char *name;
802 
803     *cache_out = NULL;
804 
805     ret = kcmio_connect(context, &io);
806     if (ret)
807         goto cleanup;
808     kcmreq_init(&req, KCM_OP_GEN_NEW, NULL);
809     ret = kcmio_call(context, io, &req);
810     if (ret)
811         goto cleanup;
812     ret = kcmreq_get_name(&req, &name);
813     if (ret)
814         goto cleanup;
815     ret = make_cache(context, name, io, cache_out);
816     io = NULL;
817 
818 cleanup:
819     kcmreq_free(&req);
820     kcmio_close(io);
821     return ret;
822 }
823 
824 static krb5_error_code KRB5_CALLCONV
kcm_initialize(krb5_context context,krb5_ccache cache,krb5_principal princ)825 kcm_initialize(krb5_context context, krb5_ccache cache, krb5_principal princ)
826 {
827     krb5_error_code ret;
828     struct kcmreq req;
829 
830     kcmreq_init(&req, KCM_OP_INITIALIZE, cache);
831     k5_marshal_princ(&req.reqbuf, 4, princ);
832     ret = cache_call(context, cache, &req);
833     kcmreq_free(&req);
834     set_kdc_offset(context, cache);
835     return ret;
836 }
837 
838 static krb5_error_code KRB5_CALLCONV
kcm_close(krb5_context context,krb5_ccache cache)839 kcm_close(krb5_context context, krb5_ccache cache)
840 {
841     struct kcm_cache_data *data = cache->data;
842 
843     k5_cc_mutex_destroy(&data->lock);
844     kcmio_close(data->io);
845     free(data->residual);
846     free(data);
847     free(cache);
848     return 0;
849 }
850 
851 static krb5_error_code KRB5_CALLCONV
kcm_destroy(krb5_context context,krb5_ccache cache)852 kcm_destroy(krb5_context context, krb5_ccache cache)
853 {
854     krb5_error_code ret;
855     struct kcmreq req;
856 
857     kcmreq_init(&req, KCM_OP_DESTROY, cache);
858     ret = cache_call(context, cache, &req);
859     kcmreq_free(&req);
860     (void)kcm_close(context, cache);
861     return ret;
862 }
863 
864 static krb5_error_code KRB5_CALLCONV
kcm_store(krb5_context context,krb5_ccache cache,krb5_creds * cred)865 kcm_store(krb5_context context, krb5_ccache cache, krb5_creds *cred)
866 {
867     krb5_error_code ret;
868     struct kcmreq req;
869 
870     kcmreq_init(&req, KCM_OP_STORE, cache);
871     k5_marshal_cred(&req.reqbuf, 4, cred);
872     ret = cache_call(context, cache, &req);
873     kcmreq_free(&req);
874     return ret;
875 }
876 
877 static krb5_error_code KRB5_CALLCONV
kcm_retrieve(krb5_context context,krb5_ccache cache,krb5_flags flags,krb5_creds * mcred,krb5_creds * cred_out)878 kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
879              krb5_creds *mcred, krb5_creds *cred_out)
880 {
881     krb5_error_code ret;
882     struct kcmreq req = EMPTY_KCMREQ;
883     krb5_creds cred;
884     krb5_enctype *enctypes = NULL;
885 
886     memset(&cred, 0, sizeof(cred));
887 
888     /* Include KCM_GC_CACHED in flags to prevent Heimdal's sssd from making a
889      * TGS request itself. */
890     kcmreq_init(&req, KCM_OP_RETRIEVE, cache);
891     k5_buf_add_uint32_be(&req.reqbuf, map_tcflags(flags) | KCM_GC_CACHED);
892     k5_marshal_mcred(&req.reqbuf, mcred);
893     ret = cache_call(context, cache, &req);
894 
895     /* Fall back to iteration if the server does not support retrieval. */
896     if (unsupported_op_error(ret)) {
897         ret = k5_cc_retrieve_cred_default(context, cache, flags, mcred,
898                                           cred_out);
899         goto cleanup;
900     }
901     if (ret)
902         goto cleanup;
903 
904     ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, &cred);
905     if (ret)
906         goto cleanup;
907 
908     /* In rare cases we might retrieve a credential with a session key this
909      * context can't support, in which case we must retry using iteration. */
910     if (flags & KRB5_TC_SUPPORTED_KTYPES) {
911         ret = krb5_get_tgs_ktypes(context, cred.server, &enctypes);
912         if (ret)
913             goto cleanup;
914         if (!k5_etypes_contains(enctypes, cred.keyblock.enctype)) {
915             ret = k5_cc_retrieve_cred_default(context, cache, flags, mcred,
916                                               cred_out);
917             goto cleanup;
918         }
919     }
920 
921     *cred_out = cred;
922     memset(&cred, 0, sizeof(cred));
923 
924 cleanup:
925     kcmreq_free(&req);
926     krb5_free_cred_contents(context, &cred);
927     free(enctypes);
928     /* Heimdal's KCM returns KRB5_CC_END if no cred is found. */
929     return (ret == KRB5_CC_END) ? KRB5_CC_NOTFOUND : map_invalid(ret);
930 }
931 
932 static krb5_error_code KRB5_CALLCONV
kcm_get_princ(krb5_context context,krb5_ccache cache,krb5_principal * princ_out)933 kcm_get_princ(krb5_context context, krb5_ccache cache,
934               krb5_principal *princ_out)
935 {
936     krb5_error_code ret;
937     struct kcmreq req;
938     struct kcm_cache_data *data = cache->data;
939 
940     kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, cache);
941     ret = cache_call(context, cache, &req);
942     /* Heimdal KCM can respond with code 0 and no principal. */
943     if (!ret && req.reply.len == 0)
944         ret = KRB5_FCC_NOFILE;
945     if (ret == KRB5_FCC_NOFILE) {
946         k5_setmsg(context, ret, _("Credentials cache 'KCM:%s' not found"),
947                   data->residual);
948     }
949 
950     if (!ret)
951         ret = k5_unmarshal_princ(req.reply.ptr, req.reply.len, 4, princ_out);
952     kcmreq_free(&req);
953     return map_invalid(ret);
954 }
955 
956 static krb5_error_code KRB5_CALLCONV
kcm_start_seq_get(krb5_context context,krb5_ccache cache,krb5_cc_cursor * cursor_out)957 kcm_start_seq_get(krb5_context context, krb5_ccache cache,
958                   krb5_cc_cursor *cursor_out)
959 {
960     krb5_error_code ret;
961     struct kcmreq req = EMPTY_KCMREQ;
962     struct uuid_list *uuids = NULL;
963     struct cred_list *creds = NULL;
964     struct kcm_cursor *cursor;
965 
966     *cursor_out = NULL;
967 
968     get_kdc_offset(context, cache);
969 
970     kcmreq_init(&req, KCM_OP_GET_CRED_LIST, cache);
971     ret = cache_call(context, cache, &req);
972     if (ret == 0) {
973         /* GET_CRED_LIST is available. */
974         ret = kcmreq_get_cred_list(&req, &creds);
975         if (ret)
976             goto cleanup;
977     } else if (unsupported_op_error(ret)) {
978         /* Fall back to GET_CRED_UUID_LIST. */
979         kcmreq_free(&req);
980         kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache);
981         ret = cache_call(context, cache, &req);
982         if (ret)
983             goto cleanup;
984         ret = kcmreq_get_uuid_list(&req, &uuids);
985         if (ret)
986             goto cleanup;
987     } else {
988         goto cleanup;
989     }
990 
991     cursor = k5alloc(sizeof(*cursor), &ret);
992     if (cursor == NULL)
993         goto cleanup;
994     cursor->uuids = uuids;
995     uuids = NULL;
996     cursor->creds = creds;
997     creds = NULL;
998     *cursor_out = (krb5_cc_cursor)cursor;
999 
1000 cleanup:
1001     free_cred_list(creds);
1002     free_uuid_list(uuids);
1003     kcmreq_free(&req);
1004     return ret;
1005 }
1006 
1007 static krb5_error_code
next_cred_by_uuid(krb5_context context,krb5_ccache cache,struct uuid_list * uuids,krb5_creds * cred_out)1008 next_cred_by_uuid(krb5_context context, krb5_ccache cache,
1009                   struct uuid_list *uuids, krb5_creds *cred_out)
1010 {
1011     krb5_error_code ret;
1012     struct kcmreq req;
1013 
1014     memset(cred_out, 0, sizeof(*cred_out));
1015 
1016     if (uuids->pos >= uuids->count)
1017         return KRB5_CC_END;
1018 
1019     kcmreq_init(&req, KCM_OP_GET_CRED_BY_UUID, cache);
1020     k5_buf_add_len(&req.reqbuf, uuids->uuidbytes + (uuids->pos * KCM_UUID_LEN),
1021                    KCM_UUID_LEN);
1022     uuids->pos++;
1023     ret = cache_call(context, cache, &req);
1024     if (!ret)
1025         ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, cred_out);
1026     kcmreq_free(&req);
1027     return map_invalid(ret);
1028 }
1029 
1030 static krb5_error_code KRB5_CALLCONV
kcm_next_cred(krb5_context context,krb5_ccache cache,krb5_cc_cursor * cursor,krb5_creds * cred_out)1031 kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
1032               krb5_creds *cred_out)
1033 {
1034     struct kcm_cursor *c = (struct kcm_cursor *)*cursor;
1035     struct cred_list *list;
1036 
1037     if (c->uuids != NULL)
1038         return next_cred_by_uuid(context, cache, c->uuids, cred_out);
1039 
1040     list = c->creds;
1041     if (list->pos >= list->count)
1042         return KRB5_CC_END;
1043 
1044     /* Transfer memory ownership of one cred to the caller. */
1045     *cred_out = list->creds[list->pos];
1046     memset(&list->creds[list->pos], 0, sizeof(*list->creds));
1047     list->pos++;
1048 
1049     return 0;
1050 }
1051 
1052 static krb5_error_code KRB5_CALLCONV
kcm_end_seq_get(krb5_context context,krb5_ccache cache,krb5_cc_cursor * cursor)1053 kcm_end_seq_get(krb5_context context, krb5_ccache cache,
1054                 krb5_cc_cursor *cursor)
1055 {
1056     struct kcm_cursor *c = *cursor;
1057 
1058     if (c == NULL)
1059         return 0;
1060     free_uuid_list(c->uuids);
1061     free_cred_list(c->creds);
1062     free(c);
1063     *cursor = NULL;
1064     return 0;
1065 }
1066 
1067 static krb5_error_code KRB5_CALLCONV
kcm_remove_cred(krb5_context context,krb5_ccache cache,krb5_flags flags,krb5_creds * mcred)1068 kcm_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
1069                 krb5_creds *mcred)
1070 {
1071     krb5_error_code ret;
1072     struct kcmreq req;
1073 
1074     kcmreq_init(&req, KCM_OP_REMOVE_CRED, cache);
1075     k5_buf_add_uint32_be(&req.reqbuf, map_tcflags(flags));
1076     k5_marshal_mcred(&req.reqbuf, mcred);
1077     ret = cache_call(context, cache, &req);
1078     kcmreq_free(&req);
1079     return ret;
1080 }
1081 
1082 static krb5_error_code KRB5_CALLCONV
kcm_set_flags(krb5_context context,krb5_ccache cache,krb5_flags flags)1083 kcm_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
1084 {
1085     /* We don't currently care about any flags for this type. */
1086     return 0;
1087 }
1088 
1089 static krb5_error_code KRB5_CALLCONV
kcm_get_flags(krb5_context context,krb5_ccache cache,krb5_flags * flags_out)1090 kcm_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out)
1091 {
1092     /* We don't currently have any operational flags for this type. */
1093     *flags_out = 0;
1094     return 0;
1095 }
1096 
1097 /* Construct a per-type cursor, always taking ownership of io and uuids. */
1098 static krb5_error_code
make_ptcursor(const char * residual,struct uuid_list * uuids,struct kcmio * io,krb5_cc_ptcursor * cursor_out)1099 make_ptcursor(const char *residual, struct uuid_list *uuids, struct kcmio *io,
1100               krb5_cc_ptcursor *cursor_out)
1101 {
1102     krb5_cc_ptcursor cursor = NULL;
1103     struct kcm_ptcursor *data = NULL;
1104     char *residual_copy = NULL;
1105 
1106     *cursor_out = NULL;
1107 
1108     if (residual != NULL) {
1109         residual_copy = strdup(residual);
1110         if (residual_copy == NULL)
1111             goto oom;
1112     }
1113     cursor = malloc(sizeof(*cursor));
1114     if (cursor == NULL)
1115         goto oom;
1116     data = malloc(sizeof(*data));
1117     if (data == NULL)
1118         goto oom;
1119 
1120     data->residual = residual_copy;
1121     data->uuids = uuids;
1122     data->io = io;
1123     data->first = TRUE;
1124     cursor->ops = &krb5_kcm_ops;
1125     cursor->data = data;
1126     *cursor_out = cursor;
1127     return 0;
1128 
1129 oom:
1130     kcmio_close(io);
1131     free_uuid_list(uuids);
1132     free(residual_copy);
1133     free(data);
1134     free(cursor);
1135     return ENOMEM;
1136 }
1137 
1138 static krb5_error_code KRB5_CALLCONV
kcm_ptcursor_new(krb5_context context,krb5_cc_ptcursor * cursor_out)1139 kcm_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
1140 {
1141     krb5_error_code ret;
1142     struct kcmreq req = EMPTY_KCMREQ;
1143     struct kcmio *io = NULL;
1144     struct uuid_list *uuids = NULL;
1145     const char *defname, *primary;
1146 
1147     *cursor_out = NULL;
1148 
1149     /* Don't try to use KCM for the cache collection unless the default cache
1150      * name has the KCM type. */
1151     defname = krb5_cc_default_name(context);
1152     if (defname == NULL || strncmp(defname, "KCM:", 4) != 0)
1153         return make_ptcursor(NULL, NULL, NULL, cursor_out);
1154 
1155     ret = kcmio_connect(context, &io);
1156     if (ret)
1157         return ret;
1158 
1159     /* If defname is a subsidiary cache, return a singleton cursor. */
1160     if (strlen(defname) > 4)
1161         return make_ptcursor(defname + 4, NULL, io, cursor_out);
1162 
1163     kcmreq_init(&req, KCM_OP_GET_CACHE_UUID_LIST, NULL);
1164     ret = kcmio_call(context, io, &req);
1165     if (ret == KRB5_FCC_NOFILE) {
1166         /* There are no accessible caches; return an empty cursor. */
1167         ret = make_ptcursor(NULL, NULL, NULL, cursor_out);
1168         goto cleanup;
1169     }
1170     if (ret)
1171         goto cleanup;
1172     ret = kcmreq_get_uuid_list(&req, &uuids);
1173     if (ret)
1174         goto cleanup;
1175 
1176     kcmreq_free(&req);
1177     kcmreq_init(&req, KCM_OP_GET_DEFAULT_CACHE, NULL);
1178     ret = kcmio_call(context, io, &req);
1179     if (ret)
1180         goto cleanup;
1181     ret = kcmreq_get_name(&req, &primary);
1182     if (ret)
1183         goto cleanup;
1184 
1185     ret = make_ptcursor(primary, uuids, io, cursor_out);
1186     uuids = NULL;
1187     io = NULL;
1188 
1189 cleanup:
1190     free_uuid_list(uuids);
1191     kcmio_close(io);
1192     kcmreq_free(&req);
1193     return ret;
1194 }
1195 
1196 /* Return true if name is an initialized cache. */
1197 static krb5_boolean
name_exists(krb5_context context,struct kcmio * io,const char * name)1198 name_exists(krb5_context context, struct kcmio *io, const char *name)
1199 {
1200     krb5_error_code ret;
1201     struct kcmreq req;
1202 
1203     kcmreq_init(&req, KCM_OP_GET_PRINCIPAL, NULL);
1204     k5_buf_add_len(&req.reqbuf, name, strlen(name) + 1);
1205     ret = kcmio_call(context, io, &req);
1206     kcmreq_free(&req);
1207     return ret == 0;
1208 }
1209 
1210 static krb5_error_code KRB5_CALLCONV
kcm_ptcursor_next(krb5_context context,krb5_cc_ptcursor cursor,krb5_ccache * cache_out)1211 kcm_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
1212                   krb5_ccache *cache_out)
1213 {
1214     krb5_error_code ret = 0;
1215     struct kcmreq req = EMPTY_KCMREQ;
1216     struct kcm_ptcursor *data = cursor->data;
1217     struct uuid_list *uuids;
1218     const unsigned char *id;
1219     const char *name;
1220 
1221     *cache_out = NULL;
1222 
1223     /* Return the primary or specified subsidiary cache if we haven't yet. */
1224     if (data->first && data->residual != NULL) {
1225         data->first = FALSE;
1226         if (name_exists(context, data->io, data->residual))
1227             return make_cache(context, data->residual, NULL, cache_out);
1228     }
1229 
1230     uuids = data->uuids;
1231     if (uuids == NULL)
1232         return 0;
1233 
1234     while (uuids->pos < uuids->count) {
1235         /* Get the name of the next cache. */
1236         id = &uuids->uuidbytes[KCM_UUID_LEN * uuids->pos++];
1237         kcmreq_free(&req);
1238         kcmreq_init(&req, KCM_OP_GET_CACHE_BY_UUID, NULL);
1239         k5_buf_add_len(&req.reqbuf, id, KCM_UUID_LEN);
1240         ret = kcmio_call(context, data->io, &req);
1241         /* Continue if the cache has been deleted. */
1242         if (ret == KRB5_CC_END || ret == KRB5_FCC_NOFILE) {
1243             ret = 0;
1244             continue;
1245         }
1246         if (ret)
1247             goto cleanup;
1248         ret = kcmreq_get_name(&req, &name);
1249         if (ret)
1250             goto cleanup;
1251 
1252         /* Don't yield the primary cache twice. */
1253         if (strcmp(name, data->residual) == 0)
1254             continue;
1255 
1256         ret = make_cache(context, name, NULL, cache_out);
1257         break;
1258     }
1259 
1260 cleanup:
1261     kcmreq_free(&req);
1262     return ret;
1263 }
1264 
1265 static krb5_error_code KRB5_CALLCONV
kcm_ptcursor_free(krb5_context context,krb5_cc_ptcursor * cursor)1266 kcm_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
1267 {
1268     struct kcm_ptcursor *data = (*cursor)->data;
1269 
1270     free(data->residual);
1271     free_uuid_list(data->uuids);
1272     kcmio_close(data->io);
1273     free(data);
1274     free(*cursor);
1275     *cursor = NULL;
1276     return 0;
1277 }
1278 
1279 static krb5_error_code KRB5_CALLCONV
kcm_replace(krb5_context context,krb5_ccache cache,krb5_principal princ,krb5_creds ** creds)1280 kcm_replace(krb5_context context, krb5_ccache cache, krb5_principal princ,
1281             krb5_creds **creds)
1282 {
1283     krb5_error_code ret;
1284     struct kcmreq req = EMPTY_KCMREQ;
1285     size_t pos;
1286     uint8_t *lenptr;
1287     int ncreds, i;
1288     krb5_os_context octx = &context->os_context;
1289     int32_t offset;
1290 
1291     kcmreq_init(&req, KCM_OP_REPLACE, cache);
1292     offset = (octx->os_flags & KRB5_OS_TOFFSET_VALID) ? octx->time_offset : 0;
1293     k5_buf_add_uint32_be(&req.reqbuf, offset);
1294     k5_marshal_princ(&req.reqbuf, 4, princ);
1295     for (ncreds = 0; creds[ncreds] != NULL; ncreds++);
1296     k5_buf_add_uint32_be(&req.reqbuf, ncreds);
1297     for (i = 0; creds[i] != NULL; i++) {
1298         /* Store a dummy length, then fix it up after marshalling the cred. */
1299         pos = req.reqbuf.len;
1300         k5_buf_add_uint32_be(&req.reqbuf, 0);
1301         k5_marshal_cred(&req.reqbuf, 4, creds[i]);
1302         if (k5_buf_status(&req.reqbuf) == 0) {
1303             lenptr = (uint8_t *)req.reqbuf.data + pos;
1304             store_32_be(req.reqbuf.len - (pos + 4), lenptr);
1305         }
1306     }
1307     ret = cache_call(context, cache, &req);
1308     kcmreq_free(&req);
1309 
1310     if (unsupported_op_error(ret))
1311         return k5_nonatomic_replace(context, cache, princ, creds);
1312 
1313     return ret;
1314 }
1315 
1316 static krb5_error_code KRB5_CALLCONV
kcm_lock(krb5_context context,krb5_ccache cache)1317 kcm_lock(krb5_context context, krb5_ccache cache)
1318 {
1319     k5_cc_mutex_lock(context, &((struct kcm_cache_data *)cache->data)->lock);
1320     return 0;
1321 }
1322 
1323 static krb5_error_code KRB5_CALLCONV
kcm_unlock(krb5_context context,krb5_ccache cache)1324 kcm_unlock(krb5_context context, krb5_ccache cache)
1325 {
1326     k5_cc_mutex_unlock(context, &((struct kcm_cache_data *)cache->data)->lock);
1327     return 0;
1328 }
1329 
1330 static krb5_error_code KRB5_CALLCONV
kcm_switch_to(krb5_context context,krb5_ccache cache)1331 kcm_switch_to(krb5_context context, krb5_ccache cache)
1332 {
1333     krb5_error_code ret;
1334     struct kcmreq req;
1335 
1336     kcmreq_init(&req, KCM_OP_SET_DEFAULT_CACHE, cache);
1337     ret = cache_call(context, cache, &req);
1338     kcmreq_free(&req);
1339     return ret;
1340 }
1341 
1342 const krb5_cc_ops krb5_kcm_ops = {
1343     0,
1344     "KCM",
1345     kcm_get_name,
1346     kcm_resolve,
1347     kcm_gen_new,
1348     kcm_initialize,
1349     kcm_destroy,
1350     kcm_close,
1351     kcm_store,
1352     kcm_retrieve,
1353     kcm_get_princ,
1354     kcm_start_seq_get,
1355     kcm_next_cred,
1356     kcm_end_seq_get,
1357     kcm_remove_cred,
1358     kcm_set_flags,
1359     kcm_get_flags,
1360     kcm_ptcursor_new,
1361     kcm_ptcursor_next,
1362     kcm_ptcursor_free,
1363     kcm_replace,
1364     NULL, /* wasdefault */
1365     kcm_lock,
1366     kcm_unlock,
1367     kcm_switch_to,
1368 };
1369 
1370 #endif /* not _WIN32 */
1371