1 /*        $NetBSD: kcm.c,v 1.3 2019/12/15 22:50:50 christos Exp $     */
2 
3 /*
4  * Copyright (c) 2005, PADL Software Pty Ltd.
5  * All rights reserved.
6  *
7  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * 3. Neither the name of PADL Software nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #include "krb5_locl.h"
38 
39 #ifdef HAVE_KCM
40 /*
41  * Client library for Kerberos Credentials Manager (KCM) daemon
42  */
43 
44 #include <krb5/kcm.h>
45 #include <heim-ipc.h>
46 
47 static krb5_error_code
48 kcm_set_kdc_offset(krb5_context, krb5_ccache, krb5_deltat);
49 
50 static const char *kcm_ipc_name = "ANY:org.h5l.kcm";
51 
52 typedef struct krb5_kcmcache {
53     char *name;
54 } krb5_kcmcache;
55 
56 typedef struct krb5_kcm_cursor {
57     unsigned long offset;
58     unsigned long length;
59     kcmuuid_t *uuids;
60 } *krb5_kcm_cursor;
61 
62 
63 #define KCMCACHE(X) ((krb5_kcmcache *)(X)->data.data)
64 #define CACHENAME(X)          (KCMCACHE(X)->name)
65 #define KCMCURSOR(C)          ((krb5_kcm_cursor)(C))
66 
67 static HEIMDAL_MUTEX kcm_mutex = HEIMDAL_MUTEX_INITIALIZER;
68 static heim_ipc kcm_ipc = NULL;
69 
70 static krb5_error_code
kcm_send_request(krb5_context context,krb5_storage * request,krb5_data * response_data)71 kcm_send_request(krb5_context context,
72                      krb5_storage *request,
73                      krb5_data *response_data)
74 {
75     krb5_error_code ret = 0;
76     krb5_data request_data;
77 
78     HEIMDAL_MUTEX_lock(&kcm_mutex);
79     if (kcm_ipc == NULL)
80           ret = heim_ipc_init_context(kcm_ipc_name, &kcm_ipc);
81     HEIMDAL_MUTEX_unlock(&kcm_mutex);
82     if (ret)
83           return KRB5_CC_NOSUPP;
84 
85     ret = krb5_storage_to_data(request, &request_data);
86     if (ret) {
87           krb5_clear_error_message(context);
88           return KRB5_CC_NOMEM;
89     }
90 
91     ret = heim_ipc_call(kcm_ipc, &request_data, response_data, NULL);
92     krb5_data_free(&request_data);
93 
94     if (ret) {
95           krb5_clear_error_message(context);
96           ret = KRB5_CC_NOSUPP;
97     }
98 
99     return ret;
100 }
101 
102 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_kcm_storage_request(krb5_context context,uint16_t opcode,krb5_storage ** storage_p)103 krb5_kcm_storage_request(krb5_context context,
104                                uint16_t opcode,
105                                krb5_storage **storage_p)
106 {
107     krb5_storage *sp;
108     krb5_error_code ret;
109 
110     *storage_p = NULL;
111 
112     sp = krb5_storage_emem();
113     if (sp == NULL) {
114           krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
115           return KRB5_CC_NOMEM;
116     }
117 
118     /* Send MAJOR | VERSION | OPCODE */
119     ret  = krb5_store_int8(sp, KCM_PROTOCOL_VERSION_MAJOR);
120     if (ret)
121           goto fail;
122     ret = krb5_store_int8(sp, KCM_PROTOCOL_VERSION_MINOR);
123     if (ret)
124           goto fail;
125     ret = krb5_store_int16(sp, opcode);
126     if (ret)
127           goto fail;
128 
129     *storage_p = sp;
130  fail:
131     if (ret) {
132           krb5_set_error_message(context, ret,
133                                      N_("Failed to encode KCM request", ""));
134           krb5_storage_free(sp);
135     }
136 
137     return ret;
138 }
139 
140 static krb5_error_code
kcm_alloc(krb5_context context,const char * name,krb5_ccache * id)141 kcm_alloc(krb5_context context, const char *name, krb5_ccache *id)
142 {
143     krb5_kcmcache *k;
144 
145     k = malloc(sizeof(*k));
146     if (k == NULL) {
147           krb5_set_error_message(context, KRB5_CC_NOMEM,
148                                      N_("malloc: out of memory", ""));
149           return KRB5_CC_NOMEM;
150     }
151 
152     if (name != NULL) {
153           k->name = strdup(name);
154           if (k->name == NULL) {
155               free(k);
156               krb5_set_error_message(context, KRB5_CC_NOMEM,
157                                            N_("malloc: out of memory", ""));
158               return KRB5_CC_NOMEM;
159           }
160     } else
161           k->name = NULL;
162 
163     (*id)->data.data = k;
164     (*id)->data.length = sizeof(*k);
165 
166     return 0;
167 }
168 
169 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_kcm_call(krb5_context context,krb5_storage * request,krb5_storage ** response_p,krb5_data * response_data_p)170 krb5_kcm_call(krb5_context context,
171                 krb5_storage *request,
172                 krb5_storage **response_p,
173                 krb5_data *response_data_p)
174 {
175     krb5_data response_data;
176     krb5_error_code ret;
177     int32_t status;
178     krb5_storage *response;
179 
180     if (response_p != NULL)
181           *response_p = NULL;
182 
183     krb5_data_zero(&response_data);
184 
185     ret = kcm_send_request(context, request, &response_data);
186     if (ret)
187           return ret;
188 
189     response = krb5_storage_from_data(&response_data);
190     if (response == NULL) {
191           krb5_data_free(&response_data);
192           return KRB5_CC_IO;
193     }
194 
195     ret = krb5_ret_int32(response, &status);
196     if (ret) {
197           krb5_storage_free(response);
198           krb5_data_free(&response_data);
199           return KRB5_CC_FORMAT;
200     }
201 
202     if (status) {
203           krb5_storage_free(response);
204           krb5_data_free(&response_data);
205           return status;
206     }
207 
208     if (response_p != NULL) {
209           *response_data_p = response_data;
210           *response_p = response;
211 
212           return 0;
213     }
214 
215     krb5_storage_free(response);
216     krb5_data_free(&response_data);
217 
218     return 0;
219 }
220 
221 static void
kcm_free(krb5_context context,krb5_ccache * id)222 kcm_free(krb5_context context, krb5_ccache *id)
223 {
224     krb5_kcmcache *k = KCMCACHE(*id);
225 
226     if (k != NULL) {
227           if (k->name != NULL)
228               free(k->name);
229           memset_s(k, sizeof(*k), 0, sizeof(*k));
230           krb5_data_free(&(*id)->data);
231     }
232 }
233 
234 static const char *
kcm_get_name(krb5_context context,krb5_ccache id)235 kcm_get_name(krb5_context context,
236                krb5_ccache id)
237 {
238     return CACHENAME(id);
239 }
240 
241 static krb5_error_code
kcm_resolve(krb5_context context,krb5_ccache * id,const char * res)242 kcm_resolve(krb5_context context, krb5_ccache *id, const char *res)
243 {
244     return kcm_alloc(context, res, id);
245 }
246 
247 /*
248  * Request:
249  *
250  * Response:
251  *      NameZ
252  */
253 static krb5_error_code
kcm_gen_new(krb5_context context,krb5_ccache * id)254 kcm_gen_new(krb5_context context, krb5_ccache *id)
255 {
256     krb5_kcmcache *k;
257     krb5_error_code ret;
258     krb5_storage *request, *response;
259     krb5_data response_data;
260 
261     ret = kcm_alloc(context, NULL, id);
262     if (ret)
263           return ret;
264 
265     k = KCMCACHE(*id);
266 
267     ret = krb5_kcm_storage_request(context, KCM_OP_GEN_NEW, &request);
268     if (ret) {
269           kcm_free(context, id);
270           return ret;
271     }
272 
273     ret = krb5_kcm_call(context, request, &response, &response_data);
274     if (ret) {
275           krb5_storage_free(request);
276           kcm_free(context, id);
277           return ret;
278     }
279 
280     ret = krb5_ret_stringz(response, &k->name);
281     if (ret)
282           ret = KRB5_CC_IO;
283 
284     krb5_storage_free(request);
285     krb5_storage_free(response);
286     krb5_data_free(&response_data);
287 
288     if (ret)
289           kcm_free(context, id);
290 
291     return ret;
292 }
293 
294 /*
295  * Request:
296  *      NameZ
297  *      Principal
298  *
299  * Response:
300  *
301  */
302 static krb5_error_code
kcm_initialize(krb5_context context,krb5_ccache id,krb5_principal primary_principal)303 kcm_initialize(krb5_context context,
304                  krb5_ccache id,
305                  krb5_principal primary_principal)
306 {
307     krb5_error_code ret;
308     krb5_kcmcache *k = KCMCACHE(id);
309     krb5_storage *request;
310 
311     ret = krb5_kcm_storage_request(context, KCM_OP_INITIALIZE, &request);
312     if (ret)
313           return ret;
314 
315     ret = krb5_store_stringz(request, k->name);
316     if (ret) {
317           krb5_storage_free(request);
318           return ret;
319     }
320 
321     ret = krb5_store_principal(request, primary_principal);
322     if (ret) {
323           krb5_storage_free(request);
324           return ret;
325     }
326 
327     ret = krb5_kcm_call(context, request, NULL, NULL);
328 
329     krb5_storage_free(request);
330 
331     if (context->kdc_sec_offset)
332           kcm_set_kdc_offset(context, id, context->kdc_sec_offset);
333 
334     return ret;
335 }
336 
337 static krb5_error_code
kcm_close(krb5_context context,krb5_ccache id)338 kcm_close(krb5_context context,
339             krb5_ccache id)
340 {
341     kcm_free(context, &id);
342     return 0;
343 }
344 
345 /*
346  * Request:
347  *      NameZ
348  *
349  * Response:
350  *
351  */
352 static krb5_error_code
kcm_destroy(krb5_context context,krb5_ccache id)353 kcm_destroy(krb5_context context,
354               krb5_ccache id)
355 {
356     krb5_error_code ret;
357     krb5_kcmcache *k = KCMCACHE(id);
358     krb5_storage *request;
359 
360     ret = krb5_kcm_storage_request(context, KCM_OP_DESTROY, &request);
361     if (ret)
362           return ret;
363 
364     ret = krb5_store_stringz(request, k->name);
365     if (ret) {
366           krb5_storage_free(request);
367           return ret;
368     }
369 
370     ret = krb5_kcm_call(context, request, NULL, NULL);
371 
372     krb5_storage_free(request);
373     return ret;
374 }
375 
376 /*
377  * Request:
378  *      NameZ
379  *      Creds
380  *
381  * Response:
382  *
383  */
384 static krb5_error_code
kcm_store_cred(krb5_context context,krb5_ccache id,krb5_creds * creds)385 kcm_store_cred(krb5_context context,
386                  krb5_ccache id,
387                  krb5_creds *creds)
388 {
389     krb5_error_code ret;
390     krb5_kcmcache *k = KCMCACHE(id);
391     krb5_storage *request;
392 
393     ret = krb5_kcm_storage_request(context, KCM_OP_STORE, &request);
394     if (ret)
395           return ret;
396 
397     ret = krb5_store_stringz(request, k->name);
398     if (ret) {
399           krb5_storage_free(request);
400           return ret;
401     }
402 
403     ret = krb5_store_creds(request, creds);
404     if (ret) {
405           krb5_storage_free(request);
406           return ret;
407     }
408 
409     ret = krb5_kcm_call(context, request, NULL, NULL);
410 
411     krb5_storage_free(request);
412     return ret;
413 }
414 
415 #if 0
416 /*
417  * Request:
418  *      NameZ
419  *      WhichFields
420  *      MatchCreds
421  *
422  * Response:
423  *      Creds
424  *
425  */
426 static krb5_error_code
427 kcm_retrieve(krb5_context context,
428                krb5_ccache id,
429                krb5_flags which,
430                const krb5_creds *mcred,
431                krb5_creds *creds)
432 {
433     krb5_error_code ret;
434     krb5_kcmcache *k = KCMCACHE(id);
435     krb5_storage *request, *response;
436     krb5_data response_data;
437 
438     ret = krb5_kcm_storage_request(context, KCM_OP_RETRIEVE, &request);
439     if (ret)
440           return ret;
441 
442     ret = krb5_store_stringz(request, k->name);
443     if (ret) {
444           krb5_storage_free(request);
445           return ret;
446     }
447 
448     ret = krb5_store_int32(request, which);
449     if (ret) {
450           krb5_storage_free(request);
451           return ret;
452     }
453 
454     ret = krb5_store_creds_tag(request, rk_UNCONST(mcred));
455     if (ret) {
456           krb5_storage_free(request);
457           return ret;
458     }
459 
460     ret = krb5_kcm_call(context, request, &response, &response_data);
461     if (ret) {
462           krb5_storage_free(request);
463           return ret;
464     }
465 
466     ret = krb5_ret_creds(response, creds);
467     if (ret)
468           ret = KRB5_CC_IO;
469 
470     krb5_storage_free(request);
471     krb5_storage_free(response);
472     krb5_data_free(&response_data);
473 
474     return ret;
475 }
476 #endif
477 
478 /*
479  * Request:
480  *      NameZ
481  *
482  * Response:
483  *      Principal
484  */
485 static krb5_error_code
kcm_get_principal(krb5_context context,krb5_ccache id,krb5_principal * principal)486 kcm_get_principal(krb5_context context,
487                       krb5_ccache id,
488                       krb5_principal *principal)
489 {
490     krb5_error_code ret;
491     krb5_kcmcache *k = KCMCACHE(id);
492     krb5_storage *request, *response;
493     krb5_data response_data;
494 
495     ret = krb5_kcm_storage_request(context, KCM_OP_GET_PRINCIPAL, &request);
496     if (ret)
497           return ret;
498 
499     ret = krb5_store_stringz(request, k->name);
500     if (ret) {
501           krb5_storage_free(request);
502           return ret;
503     }
504 
505     ret = krb5_kcm_call(context, request, &response, &response_data);
506     if (ret) {
507           krb5_storage_free(request);
508           return ret;
509     }
510 
511     ret = krb5_ret_principal(response, principal);
512     if (ret)
513           ret = KRB5_CC_IO;
514 
515     krb5_storage_free(request);
516     krb5_storage_free(response);
517     krb5_data_free(&response_data);
518 
519     return ret;
520 }
521 
522 /*
523  * Request:
524  *      NameZ
525  *
526  * Response:
527  *      Cursor
528  *
529  */
530 static krb5_error_code
kcm_get_first(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)531 kcm_get_first (krb5_context context,
532                  krb5_ccache id,
533                  krb5_cc_cursor *cursor)
534 {
535     krb5_error_code ret;
536     krb5_kcm_cursor c;
537     krb5_kcmcache *k = KCMCACHE(id);
538     krb5_storage *request, *response;
539     krb5_data response_data;
540 
541     ret = krb5_kcm_storage_request(context, KCM_OP_GET_CRED_UUID_LIST, &request);
542     if (ret)
543           return ret;
544 
545     ret = krb5_store_stringz(request, k->name);
546     if (ret) {
547           krb5_storage_free(request);
548           return ret;
549     }
550 
551     ret = krb5_kcm_call(context, request, &response, &response_data);
552     krb5_storage_free(request);
553     if (ret)
554           return ret;
555 
556     c = calloc(1, sizeof(*c));
557     if (c == NULL) {
558           ret = krb5_enomem(context);
559           return ret;
560     }
561 
562     while (1) {
563           ssize_t sret;
564           kcmuuid_t uuid;
565           void *ptr;
566 
567           sret = krb5_storage_read(response, &uuid, sizeof(uuid));
568           if (sret == 0) {
569               ret = 0;
570               break;
571           } else if (sret != sizeof(uuid)) {
572               ret = EINVAL;
573               break;
574           }
575 
576           ptr = realloc(c->uuids, sizeof(c->uuids[0]) * (c->length + 1));
577           if (ptr == NULL) {
578               free(c->uuids);
579               free(c);
580               return krb5_enomem(context);
581           }
582           c->uuids = ptr;
583 
584           memcpy(&c->uuids[c->length], &uuid, sizeof(uuid));
585           c->length += 1;
586     }
587 
588     krb5_storage_free(response);
589     krb5_data_free(&response_data);
590 
591     if (ret) {
592         free(c->uuids);
593         free(c);
594           return ret;
595     }
596 
597     *cursor = c;
598 
599     return 0;
600 }
601 
602 /*
603  * Request:
604  *      NameZ
605  *      Cursor
606  *
607  * Response:
608  *      Creds
609  */
610 static krb5_error_code
kcm_get_next(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * creds)611 kcm_get_next (krb5_context context,
612                     krb5_ccache id,
613                     krb5_cc_cursor *cursor,
614                     krb5_creds *creds)
615 {
616     krb5_error_code ret;
617     krb5_kcmcache *k = KCMCACHE(id);
618     krb5_kcm_cursor c = KCMCURSOR(*cursor);
619     krb5_storage *request, *response;
620     krb5_data response_data;
621     ssize_t sret;
622 
623  again:
624 
625     if (c->offset >= c->length)
626           return KRB5_CC_END;
627 
628     ret = krb5_kcm_storage_request(context, KCM_OP_GET_CRED_BY_UUID, &request);
629     if (ret)
630           return ret;
631 
632     ret = krb5_store_stringz(request, k->name);
633     if (ret) {
634           krb5_storage_free(request);
635           return ret;
636     }
637 
638     sret = krb5_storage_write(request,
639                                     &c->uuids[c->offset],
640                                     sizeof(c->uuids[c->offset]));
641     c->offset++;
642     if (sret != sizeof(c->uuids[c->offset])) {
643           krb5_storage_free(request);
644           krb5_clear_error_message(context);
645           return ENOMEM;
646     }
647 
648     ret = krb5_kcm_call(context, request, &response, &response_data);
649     krb5_storage_free(request);
650     if (ret == KRB5_CC_END) {
651           goto again;
652     }
653 
654     ret = krb5_ret_creds(response, creds);
655     if (ret)
656           ret = KRB5_CC_IO;
657 
658     krb5_storage_free(response);
659     krb5_data_free(&response_data);
660 
661     return ret;
662 }
663 
664 /*
665  * Request:
666  *      NameZ
667  *      Cursor
668  *
669  * Response:
670  *
671  */
672 static krb5_error_code
kcm_end_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)673 kcm_end_get (krb5_context context,
674                krb5_ccache id,
675                krb5_cc_cursor *cursor)
676 {
677     krb5_kcm_cursor c = KCMCURSOR(*cursor);
678 
679     free(c->uuids);
680     free(c);
681 
682     *cursor = NULL;
683 
684     return 0;
685 }
686 
687 /*
688  * Request:
689  *      NameZ
690  *      WhichFields
691  *      MatchCreds
692  *
693  * Response:
694  *
695  */
696 static krb5_error_code
kcm_remove_cred(krb5_context context,krb5_ccache id,krb5_flags which,krb5_creds * cred)697 kcm_remove_cred(krb5_context context,
698                     krb5_ccache id,
699                     krb5_flags which,
700                     krb5_creds *cred)
701 {
702     krb5_error_code ret;
703     krb5_kcmcache *k = KCMCACHE(id);
704     krb5_storage *request;
705 
706     ret = krb5_kcm_storage_request(context, KCM_OP_REMOVE_CRED, &request);
707     if (ret)
708           return ret;
709 
710     ret = krb5_store_stringz(request, k->name);
711     if (ret) {
712           krb5_storage_free(request);
713           return ret;
714     }
715 
716     ret = krb5_store_int32(request, which);
717     if (ret) {
718           krb5_storage_free(request);
719           return ret;
720     }
721 
722     ret = krb5_store_creds_tag(request, cred);
723     if (ret) {
724           krb5_storage_free(request);
725           return ret;
726     }
727 
728     ret = krb5_kcm_call(context, request, NULL, NULL);
729 
730     krb5_storage_free(request);
731     return ret;
732 }
733 
734 static krb5_error_code
kcm_set_flags(krb5_context context,krb5_ccache id,krb5_flags flags)735 kcm_set_flags(krb5_context context,
736                 krb5_ccache id,
737                 krb5_flags flags)
738 {
739     krb5_error_code ret;
740     krb5_kcmcache *k = KCMCACHE(id);
741     krb5_storage *request;
742 
743     ret = krb5_kcm_storage_request(context, KCM_OP_SET_FLAGS, &request);
744     if (ret)
745           return ret;
746 
747     ret = krb5_store_stringz(request, k->name);
748     if (ret) {
749           krb5_storage_free(request);
750           return ret;
751     }
752 
753     ret = krb5_store_int32(request, flags);
754     if (ret) {
755           krb5_storage_free(request);
756           return ret;
757     }
758 
759     ret = krb5_kcm_call(context, request, NULL, NULL);
760 
761     krb5_storage_free(request);
762     return ret;
763 }
764 
765 static int
kcm_get_version(krb5_context context,krb5_ccache id)766 kcm_get_version(krb5_context context,
767                     krb5_ccache id)
768 {
769     return 0;
770 }
771 
772 /*
773  * Send nothing
774  * get back list of uuids
775  */
776 
777 static krb5_error_code
kcm_get_cache_first(krb5_context context,krb5_cc_cursor * cursor)778 kcm_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
779 {
780     krb5_error_code ret;
781     krb5_kcm_cursor c;
782     krb5_storage *request, *response;
783     krb5_data response_data;
784 
785     *cursor = NULL;
786 
787     c = calloc(1, sizeof(*c));
788     if (c == NULL) {
789           ret = krb5_enomem(context);
790           goto out;
791     }
792 
793     ret = krb5_kcm_storage_request(context, KCM_OP_GET_CACHE_UUID_LIST, &request);
794     if (ret)
795           goto out;
796 
797     ret = krb5_kcm_call(context, request, &response, &response_data);
798     krb5_storage_free(request);
799     if (ret)
800           goto out;
801 
802     while (1) {
803           ssize_t sret;
804           kcmuuid_t uuid;
805           void *ptr;
806 
807           sret = krb5_storage_read(response, &uuid, sizeof(uuid));
808           if (sret == 0) {
809               ret = 0;
810               break;
811           } else if (sret != sizeof(uuid)) {
812               ret = EINVAL;
813               goto out;
814           }
815 
816           ptr = realloc(c->uuids, sizeof(c->uuids[0]) * (c->length + 1));
817           if (ptr == NULL) {
818               ret = krb5_enomem(context);
819               goto out;
820           }
821           c->uuids = ptr;
822 
823           memcpy(&c->uuids[c->length], &uuid, sizeof(uuid));
824           c->length += 1;
825     }
826 
827     krb5_storage_free(response);
828     krb5_data_free(&response_data);
829 
830  out:
831     if (ret && c) {
832         free(c->uuids);
833         free(c);
834     } else
835           *cursor = c;
836 
837     return ret;
838 }
839 
840 /*
841  * Send uuid
842  * Recv cache name
843  */
844 
845 static krb5_error_code
kcm_get_cache_next(krb5_context context,krb5_cc_cursor cursor,const krb5_cc_ops * ops,krb5_ccache * id)846 kcm_get_cache_next(krb5_context context, krb5_cc_cursor cursor, const krb5_cc_ops *ops, krb5_ccache *id)
847 {
848     krb5_error_code ret;
849     krb5_kcm_cursor c = KCMCURSOR(cursor);
850     krb5_storage *request, *response;
851     krb5_data response_data;
852     ssize_t sret;
853     char *name;
854 
855     *id = NULL;
856 
857  again:
858 
859     if (c->offset >= c->length)
860           return KRB5_CC_END;
861 
862     ret = krb5_kcm_storage_request(context, KCM_OP_GET_CACHE_BY_UUID, &request);
863     if (ret)
864           return ret;
865 
866     sret = krb5_storage_write(request,
867                                     &c->uuids[c->offset],
868                                     sizeof(c->uuids[c->offset]));
869     c->offset++;
870     if (sret != sizeof(c->uuids[c->offset])) {
871           krb5_storage_free(request);
872           krb5_clear_error_message(context);
873           return ENOMEM;
874     }
875 
876     ret = krb5_kcm_call(context, request, &response, &response_data);
877     krb5_storage_free(request);
878     if (ret == KRB5_CC_END)
879           goto again;
880 
881     ret = krb5_ret_stringz(response, &name);
882     krb5_storage_free(response);
883     krb5_data_free(&response_data);
884 
885     if (ret == 0) {
886           ret = _krb5_cc_allocate(context, ops, id);
887           if (ret == 0)
888               ret = kcm_alloc(context, name, id);
889           krb5_xfree(name);
890     }
891 
892     return ret;
893 }
894 
895 static krb5_error_code
kcm_get_cache_next_kcm(krb5_context context,krb5_cc_cursor cursor,krb5_ccache * id)896 kcm_get_cache_next_kcm(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
897 {
898 #ifndef KCM_IS_API_CACHE
899     return kcm_get_cache_next(context, cursor, &krb5_kcm_ops, id);
900 #else
901     return KRB5_CC_END;
902 #endif
903 }
904 
905 static krb5_error_code
kcm_get_cache_next_api(krb5_context context,krb5_cc_cursor cursor,krb5_ccache * id)906 kcm_get_cache_next_api(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
907 {
908     return kcm_get_cache_next(context, cursor, &krb5_akcm_ops, id);
909 }
910 
911 
912 static krb5_error_code
kcm_end_cache_get(krb5_context context,krb5_cc_cursor cursor)913 kcm_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
914 {
915     krb5_kcm_cursor c = KCMCURSOR(cursor);
916 
917     free(c->uuids);
918     free(c);
919     return 0;
920 }
921 
922 
923 static krb5_error_code
kcm_move(krb5_context context,krb5_ccache from,krb5_ccache to)924 kcm_move(krb5_context context, krb5_ccache from, krb5_ccache to)
925 {
926     krb5_error_code ret;
927     krb5_kcmcache *oldk = KCMCACHE(from);
928     krb5_kcmcache *newk = KCMCACHE(to);
929     krb5_storage *request;
930 
931     ret = krb5_kcm_storage_request(context, KCM_OP_MOVE_CACHE, &request);
932     if (ret)
933           return ret;
934 
935     ret = krb5_store_stringz(request, oldk->name);
936     if (ret) {
937           krb5_storage_free(request);
938           return ret;
939     }
940 
941     ret = krb5_store_stringz(request, newk->name);
942     if (ret) {
943           krb5_storage_free(request);
944           return ret;
945     }
946     ret = krb5_kcm_call(context, request, NULL, NULL);
947 
948     krb5_storage_free(request);
949     return ret;
950 }
951 
952 static krb5_error_code
kcm_get_default_name(krb5_context context,const krb5_cc_ops * ops,const char * defstr,char ** str)953 kcm_get_default_name(krb5_context context, const krb5_cc_ops *ops,
954                          const char *defstr, char **str)
955 {
956     krb5_error_code ret;
957     krb5_storage *request, *response;
958     krb5_data response_data;
959     char *name;
960     int aret;
961 
962     *str = NULL;
963 
964     ret = krb5_kcm_storage_request(context, KCM_OP_GET_DEFAULT_CACHE, &request);
965     if (ret)
966           return ret;
967 
968     ret = krb5_kcm_call(context, request, &response, &response_data);
969     krb5_storage_free(request);
970     if (ret)
971           return _krb5_expand_default_cc_name(context, defstr, str);
972 
973     ret = krb5_ret_stringz(response, &name);
974     krb5_storage_free(response);
975     krb5_data_free(&response_data);
976     if (ret)
977           return ret;
978 
979     aret = asprintf(str, "%s:%s", ops->prefix, name);
980     free(name);
981     if (aret == -1 || str == NULL)
982           return ENOMEM;
983 
984     return 0;
985 }
986 
987 static krb5_error_code
kcm_get_default_name_api(krb5_context context,char ** str)988 kcm_get_default_name_api(krb5_context context, char **str)
989 {
990     return kcm_get_default_name(context, &krb5_akcm_ops,
991                                         KRB5_DEFAULT_CCNAME_KCM_API, str);
992 }
993 
994 static krb5_error_code
kcm_get_default_name_kcm(krb5_context context,char ** str)995 kcm_get_default_name_kcm(krb5_context context, char **str)
996 {
997     return kcm_get_default_name(context, &krb5_kcm_ops,
998                                         KRB5_DEFAULT_CCNAME_KCM_KCM, str);
999 }
1000 
1001 static krb5_error_code
kcm_set_default(krb5_context context,krb5_ccache id)1002 kcm_set_default(krb5_context context, krb5_ccache id)
1003 {
1004     krb5_error_code ret;
1005     krb5_storage *request;
1006     krb5_kcmcache *k = KCMCACHE(id);
1007 
1008     ret = krb5_kcm_storage_request(context, KCM_OP_SET_DEFAULT_CACHE, &request);
1009     if (ret)
1010           return ret;
1011 
1012     ret = krb5_store_stringz(request, k->name);
1013     if (ret) {
1014           krb5_storage_free(request);
1015           return ret;
1016     }
1017 
1018     ret = krb5_kcm_call(context, request, NULL, NULL);
1019     krb5_storage_free(request);
1020 
1021     return ret;
1022 }
1023 
1024 static krb5_error_code
kcm_lastchange(krb5_context context,krb5_ccache id,krb5_timestamp * mtime)1025 kcm_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1026 {
1027     *mtime = time(NULL);
1028     return 0;
1029 }
1030 
1031 static krb5_error_code
kcm_set_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat kdc_offset)1032 kcm_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1033 {
1034     krb5_kcmcache *k = KCMCACHE(id);
1035     krb5_error_code ret;
1036     krb5_storage *request;
1037 
1038     ret = krb5_kcm_storage_request(context, KCM_OP_SET_KDC_OFFSET, &request);
1039     if (ret)
1040           return ret;
1041 
1042     ret = krb5_store_stringz(request, k->name);
1043     if (ret) {
1044           krb5_storage_free(request);
1045           return ret;
1046     }
1047     ret = krb5_store_int32(request, kdc_offset);
1048     if (ret) {
1049           krb5_storage_free(request);
1050           return ret;
1051     }
1052 
1053     ret = krb5_kcm_call(context, request, NULL, NULL);
1054     krb5_storage_free(request);
1055 
1056     return ret;
1057 }
1058 
1059 static krb5_error_code
kcm_get_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat * kdc_offset)1060 kcm_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1061 {
1062     krb5_kcmcache *k = KCMCACHE(id);
1063     krb5_error_code ret;
1064     krb5_storage *request, *response;
1065     krb5_data response_data;
1066     int32_t offset;
1067 
1068     ret = krb5_kcm_storage_request(context, KCM_OP_GET_KDC_OFFSET, &request);
1069     if (ret)
1070           return ret;
1071 
1072     ret = krb5_store_stringz(request, k->name);
1073     if (ret) {
1074           krb5_storage_free(request);
1075           return ret;
1076     }
1077 
1078     ret = krb5_kcm_call(context, request, &response, &response_data);
1079     krb5_storage_free(request);
1080     if (ret)
1081           return ret;
1082 
1083     ret = krb5_ret_int32(response, &offset);
1084     krb5_storage_free(response);
1085     krb5_data_free(&response_data);
1086     if (ret)
1087           return ret;
1088 
1089     *kdc_offset = offset;
1090 
1091     return 0;
1092 }
1093 
1094 /**
1095  * Variable containing the KCM based credential cache implemention.
1096  *
1097  * @ingroup krb5_ccache
1098  */
1099 
1100 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_kcm_ops = {
1101     KRB5_CC_OPS_VERSION,
1102     "KCM",
1103     kcm_get_name,
1104     kcm_resolve,
1105     kcm_gen_new,
1106     kcm_initialize,
1107     kcm_destroy,
1108     kcm_close,
1109     kcm_store_cred,
1110     NULL /* kcm_retrieve */,
1111     kcm_get_principal,
1112     kcm_get_first,
1113     kcm_get_next,
1114     kcm_end_get,
1115     kcm_remove_cred,
1116     kcm_set_flags,
1117     kcm_get_version,
1118     kcm_get_cache_first,
1119     kcm_get_cache_next_kcm,
1120     kcm_end_cache_get,
1121     kcm_move,
1122     kcm_get_default_name_kcm,
1123     kcm_set_default,
1124     kcm_lastchange,
1125     kcm_set_kdc_offset,
1126     kcm_get_kdc_offset
1127 };
1128 
1129 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_akcm_ops = {
1130     KRB5_CC_OPS_VERSION,
1131     "API",
1132     kcm_get_name,
1133     kcm_resolve,
1134     kcm_gen_new,
1135     kcm_initialize,
1136     kcm_destroy,
1137     kcm_close,
1138     kcm_store_cred,
1139     NULL /* kcm_retrieve */,
1140     kcm_get_principal,
1141     kcm_get_first,
1142     kcm_get_next,
1143     kcm_end_get,
1144     kcm_remove_cred,
1145     kcm_set_flags,
1146     kcm_get_version,
1147     kcm_get_cache_first,
1148     kcm_get_cache_next_api,
1149     kcm_end_cache_get,
1150     kcm_move,
1151     kcm_get_default_name_api,
1152     kcm_set_default,
1153     kcm_lastchange,
1154     NULL,
1155     NULL
1156 };
1157 
1158 
1159 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
_krb5_kcm_is_running(krb5_context context)1160 _krb5_kcm_is_running(krb5_context context)
1161 {
1162     krb5_error_code ret;
1163     krb5_ccache_data ccdata;
1164     krb5_ccache id = &ccdata;
1165     krb5_boolean running;
1166 
1167     ret = kcm_alloc(context, NULL, &id);
1168     if (ret)
1169           return 0;
1170 
1171     running = (_krb5_kcm_noop(context, id) == 0);
1172 
1173     kcm_free(context, &id);
1174 
1175     return running;
1176 }
1177 
1178 /*
1179  * Request:
1180  *
1181  * Response:
1182  *
1183  */
1184 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_kcm_noop(krb5_context context,krb5_ccache id)1185 _krb5_kcm_noop(krb5_context context,
1186                  krb5_ccache id)
1187 {
1188     krb5_error_code ret;
1189     krb5_storage *request;
1190 
1191     ret = krb5_kcm_storage_request(context, KCM_OP_NOOP, &request);
1192     if (ret)
1193           return ret;
1194 
1195     ret = krb5_kcm_call(context, request, NULL, NULL);
1196 
1197     krb5_storage_free(request);
1198     return ret;
1199 }
1200 
1201 
1202 /*
1203  * Request:
1204  *      NameZ
1205  *      ServerPrincipalPresent
1206  *      ServerPrincipal OPTIONAL
1207  *      Key
1208  *
1209  * Repsonse:
1210  *
1211  */
1212 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_kcm_get_initial_ticket(krb5_context context,krb5_ccache id,krb5_principal server,krb5_keyblock * key)1213 _krb5_kcm_get_initial_ticket(krb5_context context,
1214                                    krb5_ccache id,
1215                                    krb5_principal server,
1216                                    krb5_keyblock *key)
1217 {
1218     krb5_kcmcache *k = KCMCACHE(id);
1219     krb5_error_code ret;
1220     krb5_storage *request;
1221 
1222     ret = krb5_kcm_storage_request(context, KCM_OP_GET_INITIAL_TICKET, &request);
1223     if (ret)
1224           return ret;
1225 
1226     ret = krb5_store_stringz(request, k->name);
1227     if (ret) {
1228           krb5_storage_free(request);
1229           return ret;
1230     }
1231 
1232     ret = krb5_store_int8(request, (server == NULL) ? 0 : 1);
1233     if (ret) {
1234           krb5_storage_free(request);
1235           return ret;
1236     }
1237 
1238     if (server != NULL) {
1239           ret = krb5_store_principal(request, server);
1240           if (ret) {
1241               krb5_storage_free(request);
1242               return ret;
1243           }
1244     }
1245 
1246     ret = krb5_store_keyblock(request, *key);
1247     if (ret) {
1248           krb5_storage_free(request);
1249           return ret;
1250     }
1251 
1252     ret = krb5_kcm_call(context, request, NULL, NULL);
1253 
1254     krb5_storage_free(request);
1255     return ret;
1256 }
1257 
1258 
1259 /*
1260  * Request:
1261  *      NameZ
1262  *      KDCFlags
1263  *      EncryptionType
1264  *      ServerPrincipal
1265  *
1266  * Repsonse:
1267  *
1268  */
1269 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_kcm_get_ticket(krb5_context context,krb5_ccache id,krb5_kdc_flags flags,krb5_enctype enctype,krb5_principal server)1270 _krb5_kcm_get_ticket(krb5_context context,
1271                          krb5_ccache id,
1272                          krb5_kdc_flags flags,
1273                          krb5_enctype enctype,
1274                          krb5_principal server)
1275 {
1276     krb5_error_code ret;
1277     krb5_kcmcache *k = KCMCACHE(id);
1278     krb5_storage *request;
1279 
1280     ret = krb5_kcm_storage_request(context, KCM_OP_GET_TICKET, &request);
1281     if (ret)
1282           return ret;
1283 
1284     ret = krb5_store_stringz(request, k->name);
1285     if (ret) {
1286           krb5_storage_free(request);
1287           return ret;
1288     }
1289 
1290     ret = krb5_store_int32(request, flags.i);
1291     if (ret) {
1292           krb5_storage_free(request);
1293           return ret;
1294     }
1295 
1296     ret = krb5_store_int32(request, enctype);
1297     if (ret) {
1298           krb5_storage_free(request);
1299           return ret;
1300     }
1301 
1302     ret = krb5_store_principal(request, server);
1303     if (ret) {
1304           krb5_storage_free(request);
1305           return ret;
1306     }
1307 
1308     ret = krb5_kcm_call(context, request, NULL, NULL);
1309 
1310     krb5_storage_free(request);
1311     return ret;
1312 }
1313 
1314 #endif /* HAVE_KCM */
1315