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