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