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 "kcm_locl.h"
36#include <heimntlm.h>
37#include <heimscram.h>
38
39static void
40kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name);
41
42int
43kcm_is_same_session(kcm_client *client, uid_t uid, pid_t session)
44{
45    /*
46     * Only same session
47     * Let user access any credential regardless of session.
48     * Deny otherwise.
49     */
50
51    if (use_uid_matching && client->uid != 0 && client->uid == uid) {
52	kcm_log(1, "allowed (uid matching)");
53	return 1;
54    } else if (client->session == session) {
55	kcm_log(1, "allowed (session matching)");
56	return 1;
57    }
58
59
60    kcm_log(1, "denied");
61    return 0;
62}
63
64static krb5_error_code
65kcm_op_noop(krb5_context context,
66	    kcm_client *client,
67	    kcm_operation opcode,
68	    krb5_storage *request,
69	    krb5_storage *response)
70{
71    KCM_LOG_REQUEST(context, client, opcode);
72
73    return 0;
74}
75
76/*
77 * Request:
78 *	NameZ
79 * Response:
80 *	NameZ
81 *
82 */
83static krb5_error_code
84kcm_op_get_name(krb5_context context,
85		kcm_client *client,
86		kcm_operation opcode,
87		krb5_storage *request,
88		krb5_storage *response)
89
90{
91    krb5_error_code ret;
92    char *name = NULL;
93    kcm_ccache ccache;
94
95    ret = krb5_ret_stringz(request, &name);
96    if (ret)
97	return ret;
98
99    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
100
101    ret = kcm_ccache_resolve_client(context, client, opcode,
102				    name, &ccache);
103    if (ret) {
104	free(name);
105	return ret;
106    }
107
108    ret = krb5_store_stringz(response, ccache->name);
109    if (ret) {
110	kcm_release_ccache(context, ccache);
111	free(name);
112	return ret;
113    }
114
115    free(name);
116    kcm_release_ccache(context, ccache);
117    return 0;
118}
119
120/*
121 * Request:
122 *
123 * Response:
124 *	NameZ
125 */
126static krb5_error_code
127kcm_op_gen_new(krb5_context context,
128	       kcm_client *client,
129	       kcm_operation opcode,
130	       krb5_storage *request,
131	       krb5_storage *response)
132{
133    krb5_error_code ret;
134    char *name;
135
136    KCM_LOG_REQUEST(context, client, opcode);
137
138    /* deprecated */
139
140    name = kcm_ccache_nextid(client->pid, client->uid);
141    if (name == NULL) {
142	return KRB5_CC_NOMEM;
143    }
144
145    ret = krb5_store_stringz(response, name);
146    free(name);
147
148    return ret;
149}
150
151/*
152 * Request:
153 *	NameZ
154 *	Principal
155 *
156 * Response:
157 *
158 */
159static krb5_error_code
160kcm_op_initialize(krb5_context context,
161		  kcm_client *client,
162		  kcm_operation opcode,
163		  krb5_storage *request,
164		  krb5_storage *response)
165{
166    kcm_ccache ccache;
167    krb5_principal principal;
168    krb5_error_code ret;
169    char *name;
170#if 0
171    kcm_event event;
172#endif
173
174    KCM_LOG_REQUEST(context, client, opcode);
175
176    ret = krb5_ret_stringz(request, &name);
177    if (ret)
178	return ret;
179
180    ret = krb5_ret_principal(request, &principal);
181    if (ret) {
182	free(name);
183	return ret;
184    }
185
186    ret = kcm_ccache_new_client(context, client, name, &ccache);
187    if (ret) {
188	free(name);
189	krb5_free_principal(context, principal);
190	return ret;
191    }
192
193    ccache->client = principal;
194
195    free(name);
196
197    kcm_release_ccache(context, ccache);
198
199    kcm_data_changed = 1;
200
201    return ret;
202}
203
204/*
205 * Request:
206 *	NameZ
207 *
208 * Response:
209 *
210 */
211static krb5_error_code
212kcm_op_destroy(krb5_context context,
213	       kcm_client *client,
214	       kcm_operation opcode,
215	       krb5_storage *request,
216	       krb5_storage *response)
217{
218    krb5_error_code ret;
219    char *name;
220
221    ret = krb5_ret_stringz(request, &name);
222    if (ret)
223	return ret;
224
225    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
226
227    ret = kcm_ccache_destroy_client(context, client, name);
228    if (ret == 0)
229	kcm_drop_default_cache(context, client, name);
230
231    free(name);
232
233    kcm_data_changed = 1;
234
235    return ret;
236}
237
238/*
239 * Request:
240 *	NameZ
241 *	Creds
242 *
243 * Response:
244 *
245 */
246static krb5_error_code
247kcm_op_store(krb5_context context,
248	     kcm_client *client,
249	     kcm_operation opcode,
250	     krb5_storage *request,
251	     krb5_storage *response)
252{
253    krb5_creds creds;
254    krb5_error_code ret;
255    kcm_ccache ccache;
256    char *name;
257
258    ret = krb5_ret_stringz(request, &name);
259    if (ret)
260	return ret;
261
262    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
263
264    ret = krb5_ret_creds(request, &creds);
265    if (ret) {
266	free(name);
267	return ret;
268    }
269
270    ret = kcm_ccache_resolve_client(context, client, opcode,
271				    name, &ccache);
272    if (ret) {
273	free(name);
274	krb5_free_cred_contents(context, &creds);
275	return ret;
276    }
277
278    ret = kcm_ccache_store_cred(context, ccache, &creds, 0);
279    if (ret) {
280	free(name);
281	krb5_free_cred_contents(context, &creds);
282	kcm_release_ccache(context, ccache);
283	return ret;
284    }
285
286    if (creds.client && krb5_principal_is_root_krbtgt(context, creds.server))
287	kcm_ccache_enqueue_default(context, ccache, &creds);
288
289    free(name);
290    kcm_release_ccache(context, ccache);
291
292    kcm_data_changed = 1;
293
294    return 0;
295}
296
297/*
298 * Request:
299 *	NameZ
300 *	WhichFields
301 *	MatchCreds
302 *
303 * Response:
304 *	Creds
305 *
306 */
307static krb5_error_code
308kcm_op_retrieve(krb5_context context,
309		kcm_client *client,
310		kcm_operation opcode,
311		krb5_storage *request,
312		krb5_storage *response)
313{
314    uint32_t flags;
315    krb5_creds mcreds;
316    krb5_error_code ret;
317    kcm_ccache ccache;
318    char *name;
319    krb5_creds *credp;
320
321    ret = krb5_ret_stringz(request, &name);
322    if (ret)
323	return ret;
324
325    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
326
327    ret = krb5_ret_uint32(request, &flags);
328    if (ret) {
329	free(name);
330	goto out;
331    }
332
333    if (flags & KRB5_TC_MATCH_REFERRAL)
334	flags |= KRB5_TC_DONT_MATCH_REALM;
335
336    ret = krb5_ret_creds_tag(request, &mcreds);
337    if (ret) {
338	free(name);
339	goto out;
340    }
341
342    if (disallow_getting_krbtgt &&
343	mcreds.server->name.name_string.len == 2 &&
344	strcmp(mcreds.server->name.name_string.val[0], KRB5_TGS_NAME) == 0)
345    {
346	krb5_free_cred_contents(context, &mcreds);
347	ret = KRB5_FCC_PERM;
348	free(name);
349	goto out;
350    }
351
352    ret = kcm_ccache_resolve_client(context, client, opcode,
353				    name, &ccache);
354    if (ret) {
355	krb5_free_cred_contents(context, &mcreds);
356	goto out;
357    }
358
359    ret = kcm_ccache_retrieve_cred(context, ccache, flags,
360				   &mcreds, &credp);
361    if (ret == 0)
362	ret = krb5_store_creds(response, credp);
363
364    kcm_release_ccache(context, ccache);
365    krb5_free_cred_contents(context, &mcreds);
366
367 out:
368    free(name);
369    return ret;
370}
371
372/*
373 * Request:
374 *	NameZ
375 *
376 * Response:
377 *	Principal
378 */
379static krb5_error_code
380kcm_op_get_principal(krb5_context context,
381		     kcm_client *client,
382		     kcm_operation opcode,
383		     krb5_storage *request,
384		     krb5_storage *response)
385{
386    krb5_error_code ret;
387    kcm_ccache ccache;
388    char *name;
389
390    ret = krb5_ret_stringz(request, &name);
391    if (ret)
392	return ret;
393
394    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
395
396    ret = kcm_ccache_resolve_client(context, client, opcode,
397				    name, &ccache);
398    if (ret) {
399	free(name);
400	return ret;
401    }
402
403    if (ccache->client == NULL)
404	ret = KRB5_CC_NOTFOUND;
405    else
406	ret = krb5_store_principal(response, ccache->client);
407
408    free(name);
409    kcm_release_ccache(context, ccache);
410
411    return ret;
412}
413
414/*
415 * Request:
416 *	NameZ
417 *
418 * Response:
419 *	UUIDs
420 *
421 */
422static krb5_error_code
423kcm_op_get_cred_uuid_list(krb5_context context,
424			  kcm_client *client,
425			  kcm_operation opcode,
426			  krb5_storage *request,
427			  krb5_storage *response)
428{
429    struct kcm_creds *creds;
430    krb5_error_code ret;
431    kcm_ccache ccache;
432    char *name;
433
434    ret = krb5_ret_stringz(request, &name);
435    if (ret)
436	return ret;
437
438    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
439
440    ret = kcm_ccache_resolve_client(context, client, opcode,
441				    name, &ccache);
442    free(name);
443    if (ret)
444	return ret;
445
446    for (creds = ccache->creds ; creds ; creds = creds->next) {
447	ssize_t sret;
448	sret = krb5_storage_write(response, &creds->uuid, sizeof(creds->uuid));
449	if (sret != sizeof(creds->uuid)) {
450	    ret = ENOMEM;
451	    break;
452	}
453    }
454
455    kcm_release_ccache(context, ccache);
456
457    return ret;
458}
459
460/*
461 * Request:
462 *	NameZ
463 *	Cursor
464 *
465 * Response:
466 *	Creds
467 */
468static krb5_error_code
469kcm_op_get_cred_by_uuid(krb5_context context,
470			kcm_client *client,
471			kcm_operation opcode,
472			krb5_storage *request,
473			krb5_storage *response)
474{
475    krb5_error_code ret;
476    kcm_ccache ccache;
477    char *name;
478    struct kcm_creds *c;
479    kcmuuid_t uuid;
480    ssize_t sret;
481
482    ret = krb5_ret_stringz(request, &name);
483    if (ret)
484	return ret;
485
486    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
487
488    ret = kcm_ccache_resolve_client(context, client, opcode,
489				    name, &ccache);
490    free(name);
491    if (ret)
492	return ret;
493
494    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
495    if (sret != sizeof(uuid)) {
496	kcm_release_ccache(context, ccache);
497	krb5_clear_error_message(context);
498	return KRB5_CC_IO;
499    }
500
501    c = kcm_ccache_find_cred_uuid(context, ccache, uuid);
502    if (c == NULL) {
503	kcm_release_ccache(context, ccache);
504	return KRB5_CC_END;
505    }
506
507    HEIMDAL_MUTEX_lock(&ccache->mutex);
508    ret = krb5_store_creds(response, &c->cred);
509    HEIMDAL_MUTEX_unlock(&ccache->mutex);
510
511    kcm_release_ccache(context, ccache);
512
513    return ret;
514}
515
516/*
517 * Request:
518 *	NameZ
519 *	WhichFields
520 *	MatchCreds
521 *
522 * Response:
523 *
524 */
525static krb5_error_code
526kcm_op_remove_cred(krb5_context context,
527		   kcm_client *client,
528		   kcm_operation opcode,
529		   krb5_storage *request,
530		   krb5_storage *response)
531{
532    uint32_t whichfields;
533    krb5_creds mcreds;
534    krb5_error_code ret;
535    kcm_ccache ccache;
536    char *name;
537
538    ret = krb5_ret_stringz(request, &name);
539    if (ret)
540	return ret;
541
542    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
543
544    ret = krb5_ret_uint32(request, &whichfields);
545    if (ret) {
546	free(name);
547	return ret;
548    }
549
550    ret = krb5_ret_creds_tag(request, &mcreds);
551    if (ret) {
552	free(name);
553	return ret;
554    }
555
556    ret = kcm_ccache_resolve_client(context, client, opcode,
557				    name, &ccache);
558    if (ret) {
559	free(name);
560	krb5_free_cred_contents(context, &mcreds);
561	return ret;
562    }
563
564    ret = kcm_ccache_remove_cred(context, ccache, whichfields, &mcreds);
565
566    /* XXX need to remove any events that match */
567
568    free(name);
569    krb5_free_cred_contents(context, &mcreds);
570    kcm_release_ccache(context, ccache);
571
572    kcm_data_changed = 1;
573
574    return ret;
575}
576
577/*
578 * Request:
579 *	NameZ
580 *	Flags
581 *
582 * Response:
583 *
584 */
585static krb5_error_code
586kcm_op_set_flags(krb5_context context,
587		 kcm_client *client,
588		 kcm_operation opcode,
589		 krb5_storage *request,
590		 krb5_storage *response)
591{
592    uint32_t flags;
593    krb5_error_code ret;
594    kcm_ccache ccache;
595    char *name;
596
597    ret = krb5_ret_stringz(request, &name);
598    if (ret)
599	return ret;
600
601    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
602
603    ret = krb5_ret_uint32(request, &flags);
604    if (ret) {
605	free(name);
606	return ret;
607    }
608
609    ret = kcm_ccache_resolve_client(context, client, opcode,
610				    name, &ccache);
611    if (ret) {
612	free(name);
613	return ret;
614    }
615
616    /* we don't really support any flags yet */
617    free(name);
618    kcm_release_ccache(context, ccache);
619
620    return 0;
621}
622
623/*
624 * Request:
625 *	NameZ
626 *	UID
627 *	GID
628 *
629 * Response:
630 *
631 */
632static krb5_error_code
633kcm_op_chown(krb5_context context,
634	     kcm_client *client,
635	     kcm_operation opcode,
636	     krb5_storage *request,
637	     krb5_storage *response)
638{
639    uint32_t uid;
640    uint32_t gid;
641    krb5_error_code ret;
642    kcm_ccache ccache;
643    char *name;
644
645    ret = krb5_ret_stringz(request, &name);
646    if (ret)
647	return ret;
648
649    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
650
651    ret = krb5_ret_uint32(request, &uid);
652    if (ret) {
653	free(name);
654	return ret;
655    }
656
657    ret = krb5_ret_uint32(request, &gid);
658    if (ret) {
659	free(name);
660	return ret;
661    }
662
663    ret = kcm_ccache_resolve_client(context, client, opcode,
664				    name, &ccache);
665    if (ret) {
666	free(name);
667	return ret;
668    }
669
670    free(name);
671    kcm_release_ccache(context, ccache);
672
673    kcm_data_changed = 1;
674
675    return ret;
676}
677
678/*
679 * Request:
680 *	NameZ
681 *	Mode
682 *
683 * Response:
684 *
685 */
686static krb5_error_code
687kcm_op_chmod(krb5_context context,
688	     kcm_client *client,
689	     kcm_operation opcode,
690	     krb5_storage *request,
691	     krb5_storage *response)
692{
693    uint16_t mode;
694    krb5_error_code ret;
695    kcm_ccache ccache;
696    char *name;
697
698    ret = krb5_ret_stringz(request, &name);
699    if (ret)
700	return ret;
701
702    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
703
704    ret = krb5_ret_uint16(request, &mode);
705    if (ret) {
706	free(name);
707	return ret;
708    }
709
710    ret = kcm_ccache_resolve_client(context, client, opcode,
711				    name, &ccache);
712    if (ret) {
713	free(name);
714	return ret;
715    }
716
717    ret = kcm_chmod(context, client, ccache, mode);
718
719    free(name);
720    kcm_release_ccache(context, ccache);
721
722    kcm_data_changed = 1;
723
724    return ret;
725}
726
727/*
728 * Protocol extensions for moving ticket acquisition responsibility
729 * from client to KCM follow.
730 */
731
732/*
733 * Request:
734 *	NameZ
735 *	clientPrincipal
736 *	ServerPrincipalPresent
737 *	ServerPrincipal OPTIONAL
738 *	password
739 *
740 * Repsonse:
741 *
742 */
743static krb5_error_code
744kcm_op_get_initial_ticket(krb5_context context,
745			  kcm_client *client,
746			  kcm_operation opcode,
747			  krb5_storage *request,
748			  krb5_storage *response)
749{
750    char *name, *password;
751    krb5_error_code ret;
752    kcm_ccache ccache;
753    int8_t not_tgt = 0;
754    krb5_principal cprincipal = NULL;
755    krb5_principal server = NULL;
756
757    ret = krb5_ret_stringz(request, &name);
758    if (ret)
759	return ret;
760
761    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
762
763
764    ret = krb5_ret_principal(request, &cprincipal);
765    if (ret) {
766	free(name);
767	return ret;
768    }
769
770    ret = krb5_ret_int8(request, &not_tgt);
771    if (ret) {
772	free(name);
773	return ret;
774    }
775
776    if (not_tgt) {
777	ret = krb5_ret_principal(request, &server);
778    } else {
779	ret = krb5_make_principal(context,&server, cprincipal->realm,
780				  KRB5_TGS_NAME, cprincipal->realm,
781				  NULL);
782    }
783    if (ret) {
784	krb5_free_principal(context, cprincipal);
785	free(name);
786	return ret;
787    }
788
789    ret = krb5_ret_stringz(request, &password);
790    if (ret) {
791	free(name);
792	krb5_free_principal(context, cprincipal);
793	if (server != NULL)
794	    krb5_free_principal(context, server);
795	return ret;
796    }
797
798    ret = kcm_ccache_resolve_client(context, client, opcode,
799				    name, &ccache);
800    if (ret == 0) {
801	HEIMDAL_MUTEX_lock(&ccache->mutex);
802
803	if (ccache->client)
804	    krb5_free_principal(context, ccache->client);
805	if (ccache->server)
806	    krb5_free_principal(context, ccache->server);
807	if (ccache->password) {
808	    memset(ccache->password, 0, strlen(ccache->password));
809	    free(ccache->password);
810	}
811
812	ccache->client = cprincipal;
813	ccache->server = server;
814	ccache->password = password;
815    	ccache->flags |= KCM_FLAGS_USE_PASSWORD;
816	ccache->renew_life = 3600 * 24 * 7; /* 1 week */
817
818	kcm_ccache_update_acquire_status(kcm_context, ccache, KCM_STATUS_ACQUIRE_START, 0);
819
820	HEIMDAL_MUTEX_unlock(&ccache->mutex);
821
822	kcm_release_ccache(context, ccache);
823
824	kcm_data_changed = 1;
825    } else {
826	krb5_free_principal(context, cprincipal);
827	if (server)
828	    krb5_free_principal(context, server);
829	memset(password, 0, strlen(password));
830	free(password);
831    }
832
833
834    free(name);
835
836    return ret;
837}
838
839/*
840 * Request:
841 *	NameZ
842 *	ServerPrincipal
843 *	KDCFlags
844 *	EncryptionType
845 *
846 * Repsonse:
847 *
848 */
849static krb5_error_code
850kcm_op_get_ticket(krb5_context context,
851		  kcm_client *client,
852		  kcm_operation opcode,
853		  krb5_storage *request,
854		  krb5_storage *response)
855{
856    krb5_error_code ret;
857    kcm_ccache ccache;
858    char *name;
859    krb5_principal server = NULL;
860    krb5_ccache_data ccdata;
861    krb5_creds in, *out;
862    krb5_kdc_flags flags;
863
864    memset(&in, 0, sizeof(in));
865
866    ret = krb5_ret_stringz(request, &name);
867    if (ret)
868	return ret;
869
870    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
871
872    ret = krb5_ret_uint32(request, &flags.i);
873    if (ret) {
874	free(name);
875	return ret;
876    }
877
878    ret = krb5_ret_int32(request, &in.session.keytype);
879    if (ret) {
880	free(name);
881	return ret;
882    }
883
884    ret = krb5_ret_principal(request, &server);
885    if (ret) {
886	free(name);
887	return ret;
888    }
889
890    ret = kcm_ccache_resolve_client(context, client, opcode,
891				    name, &ccache);
892    if (ret) {
893	krb5_free_principal(context, server);
894	free(name);
895	return ret;
896    }
897
898    HEIMDAL_MUTEX_lock(&ccache->mutex);
899
900    /* Fake up an internal ccache */
901    kcm_internal_ccache(context, ccache, &ccdata);
902
903    in.client = ccache->client;
904    in.server = server;
905    in.times.endtime = 0;
906
907    /* glue cc layer will store creds */
908    ret = krb5_get_credentials_with_flags(context, 0, flags,
909					  &ccdata, &in, &out);
910
911    HEIMDAL_MUTEX_unlock(&ccache->mutex);
912
913    krb5_free_principal(context, server);
914
915    if (ret == 0)
916	krb5_free_cred_contents(context, out);
917
918    kcm_release_ccache(context, ccache);
919    free(name);
920
921    kcm_data_changed = 1;
922
923    return ret;
924}
925
926/*
927 * Request:
928 *	OldNameZ
929 *	NewNameZ
930 *
931 * Repsonse:
932 *
933 */
934static krb5_error_code
935kcm_op_move_cache(krb5_context context,
936		  kcm_client *client,
937		  kcm_operation opcode,
938		  krb5_storage *request,
939		  krb5_storage *response)
940{
941    krb5_error_code ret;
942    kcm_ccache oldid, newid;
943    char *oldname, *newname;
944
945    ret = krb5_ret_stringz(request, &oldname);
946    if (ret)
947	return ret;
948
949    KCM_LOG_REQUEST_NAME(context, client, opcode, oldname);
950
951    ret = krb5_ret_stringz(request, &newname);
952    if (ret) {
953	free(oldname);
954	return ret;
955    }
956
957    /* if we are renaming to ourself, done! */
958    if (strcmp(newname, oldname) == 0) {
959	free(oldname);
960	free(newname);
961	return 0;
962    }
963
964    ret = kcm_ccache_resolve_client(context, client, opcode, oldname, &oldid);
965    if (ret) {
966	free(oldname);
967	free(newname);
968	return ret;
969    }
970
971    /* Check if new credential cache exists, if not create one. */
972    ret = kcm_ccache_resolve_client(context, client, opcode, newname, &newid);
973    if (ret == KRB5_FCC_NOFILE)
974	ret = kcm_ccache_new_client(context, client, newname, &newid);
975    free(newname);
976
977    if (ret) {
978	free(oldname);
979	kcm_release_ccache(context, oldid);
980	return ret;
981    }
982
983    HEIMDAL_MUTEX_lock(&oldid->mutex);
984    HEIMDAL_MUTEX_lock(&newid->mutex);
985
986    /* move content */
987    {
988	struct kcm_ccache_data tmp;
989
990#define MOVE(n,o,f) { tmp.f = n->f ; n->f = o->f; o->f = tmp.f; }
991
992	MOVE(newid, oldid, flags);
993	MOVE(newid, oldid, client);
994	MOVE(newid, oldid, server);
995	MOVE(newid, oldid, creds);
996	MOVE(newid, oldid, tkt_life);
997	MOVE(newid, oldid, renew_life);
998	MOVE(newid, oldid, password);
999	MOVE(newid, oldid, keytab);
1000	MOVE(newid, oldid, kdc_offset);
1001	MOVE(newid, oldid, expire);
1002#undef MOVE
1003    }
1004
1005    kcm_update_renew_time(newid);
1006
1007    if (newid->expire && (newid->flags & KCM_MASK_KEY_PRESENT) == 0 && time(NULL) < newid->expire)
1008	kcm_update_expire_time(newid, newid->expire);
1009
1010    HEIMDAL_MUTEX_unlock(&oldid->mutex);
1011    HEIMDAL_MUTEX_unlock(&newid->mutex);
1012
1013    kcm_release_ccache(context, oldid);
1014    kcm_release_ccache(context, newid);
1015
1016    ret = kcm_ccache_destroy_client(context, client, oldname);
1017    if (ret == 0)
1018	kcm_drop_default_cache(context, client, oldname);
1019
1020    free(oldname);
1021
1022    kcm_data_changed = 1;
1023
1024    return ret;
1025}
1026
1027static krb5_error_code
1028kcm_op_get_cache_uuid_list(krb5_context context,
1029			   kcm_client *client,
1030			   kcm_operation opcode,
1031			   krb5_storage *request,
1032			   krb5_storage *response)
1033{
1034    KCM_LOG_REQUEST(context, client, opcode);
1035
1036    return kcm_ccache_get_uuids(context, client, opcode, response);
1037}
1038
1039static krb5_error_code
1040kcm_op_get_cache_by_uuid(krb5_context context,
1041			 kcm_client *client,
1042			 kcm_operation opcode,
1043			 krb5_storage *request,
1044			 krb5_storage *response)
1045{
1046    krb5_error_code ret;
1047    kcmuuid_t uuid;
1048    ssize_t sret;
1049    kcm_ccache cache;
1050
1051    KCM_LOG_REQUEST(context, client, opcode);
1052
1053    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
1054    if (sret != sizeof(uuid)) {
1055	krb5_clear_error_message(context);
1056	return KRB5_CC_IO;
1057    }
1058
1059    ret = kcm_ccache_resolve_by_uuid(context, uuid, &cache);
1060    if (ret)
1061	return ret;
1062
1063    ret = kcm_access(context, client, opcode, cache);
1064    if (ret)
1065	ret = KRB5_FCC_NOFILE;
1066
1067    if (ret == 0)
1068	ret = krb5_store_stringz(response, cache->name);
1069
1070    kcm_release_ccache(context, cache);
1071
1072    return ret;
1073}
1074
1075struct kcm_default_cache *default_caches;
1076
1077static krb5_error_code
1078kcm_op_get_default_cache(krb5_context context,
1079			 kcm_client *client,
1080			 kcm_operation opcode,
1081			 krb5_storage *request,
1082			 krb5_storage *response)
1083{
1084    struct kcm_default_cache *c;
1085    krb5_error_code ret;
1086    const char *name = NULL;
1087    char *n = NULL;
1088
1089    KCM_LOG_REQUEST(context, client, opcode);
1090
1091    for (c = default_caches; c != NULL; c = c->next) {
1092	if (kcm_is_same_session(client, c->uid, c->session)) {
1093	    name = c->name;
1094	    break;
1095	}
1096    }
1097    if (name == NULL)
1098	name = n = kcm_ccache_first_name(client);
1099
1100    if (name == NULL) {
1101	asprintf(&n, "%d", (int)client->uid);
1102	name = n;
1103    }
1104    if (name == NULL)
1105	return ENOMEM;
1106    ret = krb5_store_stringz(response, name);
1107    if (n)
1108	free(n);
1109    return ret;
1110}
1111
1112static void
1113kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name)
1114{
1115    struct kcm_default_cache **c;
1116
1117    for (c = &default_caches; *c != NULL; c = &(*c)->next) {
1118	if (!kcm_is_same_session(client, (*c)->uid, (*c)->session))
1119	    continue;
1120	if (strcmp((*c)->name, name) == 0) {
1121	    struct kcm_default_cache *h = *c;
1122	    *c = (*c)->next;
1123	    free(h->name);
1124	    free(h);
1125	    break;
1126	}
1127    }
1128}
1129
1130static krb5_error_code
1131kcm_op_set_default_cache(krb5_context context,
1132			 kcm_client *client,
1133			 kcm_operation opcode,
1134			 krb5_storage *request,
1135			 krb5_storage *response)
1136{
1137    struct kcm_default_cache *c;
1138    krb5_error_code ret;
1139    char *name;
1140
1141    ret = krb5_ret_stringz(request, &name);
1142    if (ret)
1143	return ret;
1144
1145    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1146
1147    for (c = default_caches; c != NULL; c = c->next) {
1148	if (kcm_is_same_session(client, c->uid, c->session))
1149	    break;
1150    }
1151    if (c == NULL) {
1152	c = calloc(1, sizeof(*c));
1153	if (c == NULL) {
1154	    free(name);
1155	    return ENOMEM;
1156	}
1157	c->session = client->session;
1158	c->uid = client->uid;
1159	c->name = name;
1160
1161	c->next = default_caches;
1162	default_caches = c;
1163    } else {
1164	free(c->name);
1165	c->name = name;
1166    }
1167
1168    return 0;
1169}
1170
1171static krb5_error_code
1172kcm_op_get_kdc_offset(krb5_context context,
1173		      kcm_client *client,
1174		      kcm_operation opcode,
1175		      krb5_storage *request,
1176		      krb5_storage *response)
1177{
1178    krb5_error_code ret;
1179    kcm_ccache ccache;
1180    char *name;
1181
1182    ret = krb5_ret_stringz(request, &name);
1183    if (ret)
1184	return ret;
1185
1186    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1187
1188    ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1189    free(name);
1190    if (ret)
1191	return ret;
1192
1193    HEIMDAL_MUTEX_lock(&ccache->mutex);
1194    ret = krb5_store_int32(response, ccache->kdc_offset);
1195    HEIMDAL_MUTEX_unlock(&ccache->mutex);
1196
1197    kcm_release_ccache(context, ccache);
1198
1199    return ret;
1200}
1201
1202static krb5_error_code
1203kcm_op_set_kdc_offset(krb5_context context,
1204		      kcm_client *client,
1205		      kcm_operation opcode,
1206		      krb5_storage *request,
1207		      krb5_storage *response)
1208{
1209    krb5_error_code ret;
1210    kcm_ccache ccache;
1211    int32_t offset;
1212    char *name;
1213
1214    ret = krb5_ret_stringz(request, &name);
1215    if (ret)
1216	return ret;
1217
1218    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1219
1220    ret = krb5_ret_int32(request, &offset);
1221    if (ret) {
1222	free(name);
1223	return ret;
1224    }
1225
1226    ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1227    free(name);
1228    if (ret)
1229	return ret;
1230
1231    HEIMDAL_MUTEX_lock(&ccache->mutex);
1232    ccache->kdc_offset = offset;
1233    HEIMDAL_MUTEX_unlock(&ccache->mutex);
1234
1235    kcm_release_ccache(context, ccache);
1236
1237    return ret;
1238}
1239
1240static krb5_error_code
1241kcm_op_retain_kcred(krb5_context context,
1242		    kcm_client *client,
1243		    kcm_operation opcode,
1244		    krb5_storage *request,
1245		    krb5_storage *response)
1246{
1247    krb5_error_code ret;
1248    kcm_ccache ccache;
1249    char *name;
1250
1251    ret = krb5_ret_stringz(request, &name);
1252    if (ret)
1253	return ret;
1254
1255    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1256
1257    ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1258    if (ret) {
1259	free(name);
1260	return ret;
1261    }
1262
1263    HEIMDAL_MUTEX_lock(&ccache->mutex);
1264    ccache->holdcount++;
1265    kcm_log(1, "retain_kcred: holdcount for %s is %ld", name, ccache->holdcount);
1266    HEIMDAL_MUTEX_unlock(&ccache->mutex);
1267
1268    kcm_release_ccache(context, ccache);
1269    free(name);
1270
1271    kcm_data_changed = 1;
1272
1273    return 0;
1274}
1275
1276static krb5_error_code
1277kcm_op_release_kcred(krb5_context context,
1278		     kcm_client *client,
1279		     kcm_operation opcode,
1280		     krb5_storage *request,
1281		     krb5_storage *response)
1282{
1283    krb5_error_code ret;
1284    kcm_ccache ccache;
1285    char *name;
1286    int destroy = 0;
1287
1288    ret = krb5_ret_stringz(request, &name);
1289    if (ret)
1290	return ret;
1291
1292    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1293
1294    ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1295    if (ret) {
1296	free(name);
1297	return ret;
1298    }
1299
1300    HEIMDAL_MUTEX_lock(&ccache->mutex);
1301    ccache->holdcount--;
1302    if (ccache->holdcount < 1)
1303	destroy = 1;
1304    kcm_log(1, "release_kcred: holdcount for %s is %ld", name, ccache->holdcount);
1305    HEIMDAL_MUTEX_unlock(&ccache->mutex);
1306
1307    kcm_release_ccache(context, ccache);
1308
1309    if (destroy) {
1310	kcm_log(1, "holdcount for %s is zero, removing", name);
1311
1312	ret = kcm_ccache_destroy_client(context, client, name);
1313	if (ret == 0)
1314	    kcm_drop_default_cache(context, client, name);
1315    }
1316    free(name);
1317
1318    kcm_data_changed = 1;
1319
1320    return 0;
1321}
1322
1323static krb5_error_code
1324kcm_op_get_uuid(krb5_context context,
1325		kcm_client *client,
1326		kcm_operation opcode,
1327		krb5_storage *request,
1328		krb5_storage *response)
1329{
1330    krb5_error_code ret;
1331    kcm_ccache ccache;
1332    char *name;
1333    krb5_uuid uuid;
1334
1335    ret = krb5_ret_stringz(request, &name);
1336    if (ret)
1337	return ret;
1338
1339    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1340
1341    ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1342    free(name);
1343    if (ret) {
1344	return ret;
1345    }
1346
1347    HEIMDAL_MUTEX_lock(&ccache->mutex);
1348    memcpy(uuid, ccache->uuid, sizeof(uuid));
1349    HEIMDAL_MUTEX_unlock(&ccache->mutex);
1350
1351    kcm_release_ccache(context, ccache);
1352
1353    (void)krb5_storage_write(response, uuid, sizeof(uuid));
1354
1355    return 0;
1356}
1357
1358
1359/*
1360 *
1361 */
1362
1363enum kcm_cred_type { KCM_NTLM_CRED, KCM_SCRAM_CRED };
1364
1365struct kcm_ntlm_cred {
1366    enum kcm_cred_type type;
1367    kcmuuid_t uuid;
1368    char *user;
1369    char *domain;
1370#define nthash u.ntlm
1371    union {
1372	krb5_data ntlm;
1373	char *password;
1374    } u;
1375    uid_t uid;
1376    pid_t session;
1377    long refcount;
1378    heim_dict_t labels;
1379    struct kcm_ntlm_cred *next;
1380};
1381
1382static struct kcm_ntlm_cred *ntlm_head;
1383static HEIMDAL_MUTEX cred_mutex = HEIMDAL_MUTEX_INITIALIZER;
1384
1385#define CHECK(s) do { if ((s)) { goto out; } } while(0)
1386
1387static krb5_error_code
1388kcm_unparse_digest_one(krb5_storage *inner, struct kcm_ntlm_cred *c)
1389{
1390    __block krb5_error_code ret;
1391
1392    if (c->type == KCM_NTLM_CRED)
1393	CHECK(ret = krb5_store_stringz(inner, "ntlm-cache"));
1394    else if (c->type == KCM_SCRAM_CRED)
1395	CHECK(ret = krb5_store_stringz(inner, "scram-cache"));
1396    else
1397	heim_assert(false, "unknown cred type");
1398
1399    CHECK(ret = krb5_store_uuid(inner, c->uuid));
1400    CHECK(ret = krb5_store_stringz(inner, c->user));
1401    if (c->domain) {
1402	CHECK(ret = krb5_store_uint8(inner, 1));
1403	CHECK(ret = krb5_store_stringz(inner, c->domain));
1404    } else {
1405	CHECK(ret = krb5_store_uint8(inner, 0));
1406    }
1407
1408    if (c->type == KCM_NTLM_CRED)
1409	CHECK(ret = krb5_store_data(inner, c->u.ntlm));
1410    else if (c->type == KCM_SCRAM_CRED)
1411	CHECK(ret = krb5_store_stringz(inner, c->u.password));
1412
1413    CHECK(ret = krb5_store_int32(inner, c->uid));
1414    CHECK(ret = krb5_store_int32(inner, c->session));
1415    CHECK(ret = krb5_store_uint32(inner, (uint32_t)c->refcount));
1416
1417    heim_dict_iterate(c->labels, ^(heim_object_t key, heim_object_t value) {
1418	    heim_data_t d = value;
1419	    krb5_data data;
1420	    data.data = (void *)heim_data_get_bytes(d);
1421	    data.length = heim_data_get_length(d);
1422	    if (ret) return;
1423	    ret = krb5_store_uint8(inner, 1);
1424	    if (ret) return;
1425	    char *k = heim_string_copy_utf8(key);
1426	    ret = krb5_store_stringz(inner, k);
1427	    free(k);
1428	    if (ret) return;
1429	    ret = krb5_store_data(inner, data);
1430	    if (ret) return;
1431	});
1432    CHECK(ret);
1433    CHECK(ret = krb5_store_uint8(inner, 0));
1434 out:
1435    return ret;
1436}
1437
1438krb5_error_code
1439kcm_unparse_digest_all(krb5_context context, krb5_storage *sp)
1440{
1441    struct kcm_ntlm_cred *c;
1442    krb5_error_code r = 0;
1443
1444    HEIMDAL_MUTEX_lock(&cred_mutex);
1445
1446    for (c = ntlm_head; r == 0 && c != NULL; c = c->next) {
1447
1448	r = kcm_unparse_wrap(sp, "digest-cache", c->session, ^(krb5_storage *inner) {
1449		return kcm_unparse_digest_one(inner, c);
1450	    });
1451    }
1452    if (r)
1453	kcm_log(10, "failed to write digest-cred: %d", r);
1454
1455    HEIMDAL_MUTEX_unlock(&cred_mutex);
1456
1457    return r;
1458}
1459
1460krb5_error_code
1461kcm_parse_digest_one(krb5_context context, krb5_storage *sp)
1462{
1463    struct kcm_ntlm_cred *c;
1464    krb5_error_code ret;
1465    char *type = NULL;
1466    uint32_t u32;
1467    int32_t s32;
1468    uint8_t u8;
1469
1470    c = calloc(1, sizeof(*c));
1471
1472    CHECK(ret = krb5_ret_stringz(sp, &type));
1473
1474    if (strcmp(type, "ntlm-cache") == 0) {
1475	c->type = KCM_NTLM_CRED;
1476    } else if (strcmp(type, "scram-cache") == 0) {
1477	c->type = KCM_SCRAM_CRED;
1478    } else {
1479	free(type);
1480	return EINVAL;
1481    }
1482
1483    CHECK(ret = krb5_ret_uuid(sp, c->uuid));
1484    CHECK(ret = krb5_ret_stringz(sp, &c->user));
1485    CHECK(ret = krb5_ret_uint8(sp, &u8));
1486    if (u8) {
1487	CHECK(ret = krb5_ret_stringz(sp, &c->domain));
1488    }
1489
1490    if (c->type == KCM_NTLM_CRED)
1491	CHECK(ret = krb5_ret_data(sp, &c->u.ntlm));
1492    else if (c->type == KCM_SCRAM_CRED)
1493	CHECK(ret = krb5_ret_stringz(sp, &c->u.password));
1494
1495    CHECK(ret = krb5_ret_int32(sp, &s32));
1496    c->uid = s32;
1497    CHECK(ret = krb5_ret_uint32(sp, &u32));
1498    c->session = u32;
1499    CHECK(ret = krb5_ret_uint32(sp, &u32));
1500    c->refcount = u32;
1501
1502    c->labels = heim_dict_create(0);
1503
1504    while (1) {
1505	krb5_data data;
1506	char *str;
1507	CHECK(ret = krb5_ret_uint8(sp, &u8));
1508	if (u8 == 0)
1509	    break;
1510
1511	CHECK(ret = krb5_ret_stringz(sp, &str));
1512	heim_string_t s = heim_string_create(str);
1513	free(str);
1514	CHECK(ret = krb5_ret_data(sp, &data));
1515	heim_data_t d = heim_data_create(data.data, data.length);
1516	krb5_data_free(&data);
1517	heim_dict_add_value(c->labels, s, d);
1518	heim_release(s);
1519	heim_release(d);
1520    }
1521
1522    c->next = ntlm_head;
1523    ntlm_head = c;
1524
1525 out:
1526    free(type);
1527    if (ret) {
1528	kcm_log(10, "failed to read %s: %d", type, ret);
1529	/* free_cred(c); */
1530    }
1531    return ret;
1532}
1533
1534static void
1535free_cred(struct kcm_ntlm_cred *cred)
1536{
1537    free(cred->user);
1538    free(cred->domain);
1539
1540    if (cred->type == KCM_NTLM_CRED) {
1541	krb5_data_free(&cred->nthash);
1542    } else if (cred->type == KCM_SCRAM_CRED) {
1543	free(cred->u.password);
1544    } else {
1545	abort();
1546    }
1547    heim_release(cred->labels);
1548    free(cred);
1549}
1550
1551
1552static struct kcm_ntlm_cred *
1553find_ntlm_cred(enum kcm_cred_type type, const char *user, const char *domain, kcm_client *client)
1554{
1555    struct kcm_ntlm_cred *c;
1556
1557    for (c = ntlm_head; c != NULL; c = c->next)
1558	if (c->type == type && (user[0] == '\0' || strcasecmp(user, c->user) == 0) &&
1559	    (domain == NULL || domain[0] == '\0' || strcasecmp(domain, c->domain) == 0) &&
1560	    kcm_is_same_session(client, c->uid, c->session))
1561	    return c;
1562
1563    return NULL;
1564}
1565
1566static struct kcm_ntlm_cred *
1567create_cred(enum kcm_cred_type type)
1568{
1569    struct kcm_ntlm_cred *cred;
1570
1571    cred = calloc(1, sizeof(*cred));
1572    if (cred == NULL)
1573	return NULL;
1574
1575    cred->type = type;
1576    cred->labels = heim_dict_create(0);
1577    cred->refcount = 1;
1578
1579    CCRandomCopyBytes(kCCRandomDefault, cred->uuid, sizeof(cred->uuid));
1580
1581    return cred;
1582}
1583
1584/*
1585 * name
1586 * domain
1587 * ntlm hash
1588 *
1589 * Reply:
1590 *   uuid
1591 */
1592
1593static krb5_error_code
1594kcm_op_add_ntlm_cred(krb5_context context,
1595		     kcm_client *client,
1596		     kcm_operation opcode,
1597		     krb5_storage *request,
1598		     krb5_storage *response)
1599{
1600    struct kcm_ntlm_cred *cred, *c;
1601    krb5_error_code ret;
1602
1603    cred = create_cred(KCM_NTLM_CRED);
1604    if (cred == NULL)
1605	return ENOMEM;
1606
1607    ret = krb5_ret_stringz(request, &cred->user);
1608    if (ret)
1609	goto error;
1610
1611    ret = krb5_ret_stringz(request, &cred->domain);
1612    if (ret)
1613	goto error;
1614
1615    ret = krb5_ret_data(request, &cred->nthash);
1616    if (ret)
1617	goto error;
1618
1619    HEIMDAL_MUTEX_lock(&cred_mutex);
1620
1621    /* search for dups */
1622    c = find_ntlm_cred(KCM_NTLM_CRED, cred->user, cred->domain, client);
1623    if (c) {
1624	krb5_data hash = c->nthash;
1625	c->nthash = cred->nthash;
1626	cred->nthash = hash;
1627	free_cred(cred);
1628	cred = c;
1629    } else {
1630	cred->next = ntlm_head;
1631	ntlm_head = cred;
1632    }
1633
1634    cred->uid = client->uid;
1635    cred->session = client->session;
1636
1637    HEIMDAL_MUTEX_unlock(&cred_mutex);
1638
1639    /* write response */
1640    (void)krb5_storage_write(response, &cred->uuid, sizeof(cred->uuid));
1641
1642    kcm_data_changed = 1;
1643
1644    return 0;
1645
1646 error:
1647    free_cred(cred);
1648
1649    return ret;
1650}
1651
1652/*
1653 * { "HAVE_NTLM_CRED",		NULL },
1654 *
1655 * input:
1656 *  name
1657 *  domain
1658 */
1659
1660static krb5_error_code
1661kcm_op_have_ntlm_cred(krb5_context context,
1662		     kcm_client *client,
1663		     kcm_operation opcode,
1664		     krb5_storage *request,
1665		     krb5_storage *response)
1666{
1667    struct kcm_ntlm_cred *c;
1668    char *user = NULL, *domain = NULL;
1669    krb5_error_code ret;
1670
1671    ret = krb5_ret_stringz(request, &user);
1672    if (ret)
1673	goto error;
1674
1675    ret = krb5_ret_stringz(request, &domain);
1676    if (ret)
1677	goto error;
1678
1679    HEIMDAL_MUTEX_lock(&cred_mutex);
1680
1681    c = find_ntlm_cred(KCM_NTLM_CRED, user, domain, client);
1682    if (c == NULL)
1683	ret = ENOENT;
1684
1685    kcm_log(10, "ntlm checking for ntlm cred for %s@%s, -> %s",
1686	    user, domain, (c == NULL ? "no" : "yes"));
1687
1688    if (c)
1689      (void)krb5_storage_write(response, &c->uuid, sizeof(c->uuid));
1690
1691    HEIMDAL_MUTEX_unlock(&cred_mutex);
1692
1693 error:
1694    free(user);
1695    if (domain)
1696	free(domain);
1697
1698    return ret;
1699}
1700
1701/*
1702 * { "DEL_CRED",		NULL },
1703 *
1704 * input:
1705 *  uuid
1706 */
1707
1708static krb5_error_code
1709kcm_op_del_cred(krb5_context context,
1710		kcm_client *client,
1711		kcm_operation opcode,
1712		krb5_storage *request,
1713		krb5_storage *response)
1714{
1715    struct kcm_ntlm_cred **cp, *c;
1716    kcmuuid_t uuid;
1717    ssize_t sret;
1718
1719    KCM_LOG_REQUEST(context, client, opcode);
1720
1721    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
1722    if (sret != sizeof(uuid)) {
1723	krb5_clear_error_message(context);
1724	return KRB5_CC_IO;
1725    }
1726
1727    HEIMDAL_MUTEX_lock(&cred_mutex);
1728
1729    for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
1730	if ((*cp)->type == KCM_NTLM_CRED &&
1731	    memcmp((*cp)->uuid, uuid, sizeof(uuid)) == 0 &&
1732	    kcm_is_same_session(client, (*cp)->uid, (*cp)->session))
1733	{
1734	    c = *cp;
1735	    *cp = c->next;
1736
1737	    free_cred(c);
1738	    kcm_data_changed = 1;
1739	    break;
1740	}
1741    }
1742
1743    HEIMDAL_MUTEX_unlock(&cred_mutex);
1744
1745    return 0;
1746}
1747
1748static struct ntlm_challenge {
1749    struct ntlm_challenge *next;
1750    uint8_t challenge[8];
1751    time_t ts;
1752} *ntlm_challenges = NULL;
1753
1754static void
1755ntlm_delete_chain(struct ntlm_challenge *c)
1756{
1757    while (c) {
1758	struct ntlm_challenge *next = c->next;
1759	free(c);
1760	c = next;
1761    }
1762}
1763
1764static int
1765ntlm_expiredp(struct ntlm_challenge *c, time_t now)
1766{
1767    return c->ts > now - heim_ntlm_time_skew;
1768}
1769
1770static int
1771check_ntlm_challage(uint8_t chal[8])
1772{
1773    struct ntlm_challenge **q = &ntlm_challenges;
1774    time_t t = time(NULL);
1775    while (*q) {
1776	if (ntlm_expiredp(*q, t)) {
1777	    struct ntlm_challenge *c = *q;
1778	    *q = NULL;
1779	    ntlm_delete_chain(c);
1780	    return 0;
1781	}
1782	if (memcmp((*q)->challenge, chal, sizeof((*q)->challenge)) == 0)
1783	    return EAUTH;
1784
1785	q = &(*q)->next;
1786    }
1787    return 0;
1788}
1789
1790/*
1791 * { "SET_NTLM_CHALLAGE",	NULL }
1792 *
1793 * request:
1794 *   challage 8 byte
1795 */
1796
1797static krb5_error_code
1798kcm_op_add_ntlm_challenge(krb5_context context,
1799			  kcm_client *client,
1800			  kcm_operation opcode,
1801			  krb5_storage *request,
1802			  krb5_storage *response)
1803{
1804    uint8_t chal[8];
1805    struct ntlm_challenge *c;
1806    ssize_t sret;
1807
1808    KCM_LOG_REQUEST(context, client, opcode);
1809
1810    if (client->uid != 0)
1811	return EAUTH;
1812
1813    c = malloc(sizeof(*c));
1814    if (c == NULL)
1815	return ENOMEM;
1816
1817    sret = krb5_storage_read(request, c->challenge, sizeof(c->challenge));
1818    if (sret != sizeof(chal)) {
1819	free(c);
1820	return KRB5_CC_IO;
1821    }
1822
1823    c->ts = time(NULL);
1824    c->next = ntlm_challenges;
1825    ntlm_challenges = c;
1826
1827    kcm_data_changed = 1;
1828
1829    return 0;
1830}
1831
1832krb5_error_code
1833kcm_parse_ntlm_challenge_one(krb5_context context, krb5_storage *sp)
1834{
1835    struct ntlm_challenge *c, **q;
1836    krb5_error_code ret;
1837    int32_t ts;
1838    ssize_t sret;
1839
1840    c = malloc(sizeof(*c));
1841    if (c == NULL)
1842	return ENOMEM;
1843
1844    sret = krb5_storage_read(sp, c->challenge, sizeof(c->challenge));
1845    if (sret != sizeof(c->challenge)) {
1846	free(c);
1847	return KRB5_CC_IO;
1848    }
1849
1850    ret = krb5_ret_int32(sp, &ts);
1851    if (ret) {
1852	free(c);
1853	return ret;
1854    }
1855    c->ts = ts;
1856    c->next = NULL;
1857
1858    if (ntlm_expiredp(c, time(NULL))) {
1859	free(c);
1860    } else {
1861	/* find end and add c, XXX performance */
1862	for (q = &ntlm_challenges; *q != NULL; q = &(*q)->next)
1863	    ;
1864	*q = c;
1865    }
1866
1867    return 0;
1868}
1869
1870krb5_error_code
1871kcm_unparse_challenge_all(krb5_context context, krb5_storage *sp)
1872{
1873    struct ntlm_challenge *c;
1874    krb5_error_code r = 0;
1875    time_t now = time(NULL);
1876
1877    for (c = ntlm_challenges; r == 0 && c != NULL; c = c->next) {
1878
1879	if (ntlm_expiredp(c, now)) /* stop when they have expired */
1880	    break;
1881
1882	r = kcm_unparse_wrap(sp, "ntlm-chal", 0, ^(krb5_storage *inner) {
1883		ssize_t sret;
1884		sret = krb5_storage_write(inner, c->challenge,
1885					  sizeof(c->challenge));
1886		if (sret != sizeof(c->challenge))
1887		    return EINVAL;
1888		return  krb5_store_int32(inner, (int32_t)c->ts);
1889	    });
1890    }
1891    if (r)
1892	kcm_log(10, "failed to write ntlm-chal: %d", r);
1893    return r;
1894}
1895
1896/*
1897 *
1898 */
1899
1900static int
1901ntlm_domain_is_hostname(const char *name)
1902{
1903    return (name[0] == '\\');
1904}
1905
1906/*
1907 * { "DO_NTLM_AUTH",		NULL },
1908 *
1909 * input:
1910 *  name:string
1911 *  domain:string
1912 *  type2:data
1913 *
1914 * reply:
1915 *  type3:data
1916 *  flags:int32
1917 *  session-key:data
1918 */
1919
1920static krb5_error_code
1921kcm_op_do_ntlm(krb5_context context,
1922	       kcm_client *client,
1923	       kcm_operation opcode,
1924	       krb5_storage *request,
1925	       krb5_storage *response)
1926{
1927#ifdef ENABLE_NTLM
1928    struct kcm_ntlm_cred *c;
1929    struct ntlm_type2 type2;
1930    struct ntlm_type3 type3;
1931    char *user = NULL, *domain = NULL, *targetname = NULL;
1932    struct ntlm_buf ndata, sessionkey, tidata;
1933    krb5_data type2data, cb, type1data, tempdata;
1934    krb5_error_code ret;
1935    uint32_t type1flags, flags = 0;
1936    const char *type = "unknown";
1937    char flagname[256];
1938    size_t mic_offset = 0;
1939
1940    KCM_LOG_REQUEST(context, client, opcode);
1941
1942    krb5_data_zero(&cb);
1943    krb5_data_zero(&type1data);
1944    krb5_data_zero(&type2data);
1945    memset(&tidata, 0, sizeof(tidata));
1946    memset(&type2, 0, sizeof(type2));
1947    memset(&type3, 0, sizeof(type3));
1948    sessionkey.data = NULL;
1949    sessionkey.length = 0;
1950
1951    HEIMDAL_MUTEX_lock(&cred_mutex);
1952
1953    ret = krb5_ret_stringz(request, &user);
1954    if (ret)
1955	goto error;
1956
1957    ret = krb5_ret_stringz(request, &domain);
1958    if (ret)
1959	goto error;
1960
1961    kcm_log(10, "NTLM AUTH with cred %s\\%s", domain, user);
1962
1963    c = find_ntlm_cred(KCM_NTLM_CRED, user, domain, client);
1964    if (c == NULL) {
1965	ret = EINVAL;
1966	goto error;
1967    }
1968
1969    ret = krb5_ret_data(request, &type2data);
1970    if (ret)
1971	goto error;
1972
1973    ret = krb5_ret_data(request, &cb);
1974    if (ret)
1975	goto error;
1976
1977    ret = krb5_ret_data(request, &type1data);
1978    if (ret)
1979	goto error;
1980
1981    ret = krb5_ret_stringz(request, &targetname);
1982    if (ret)
1983	goto error;
1984
1985    ret = krb5_ret_uint32(request, &type1flags);
1986    if (ret)
1987	goto error;
1988
1989    ndata.data = type2data.data;
1990    ndata.length = type2data.length;
1991
1992    ret = heim_ntlm_decode_type2(&ndata, &type2);
1993    if (ret)
1994	goto error;
1995
1996    kcm_log(10, "checking for ntlm mirror attack");
1997    ret = check_ntlm_challage(type2.challenge);
1998    if (ret) {
1999	kcm_log(0, "ntlm mirror attack detected");
2000	goto error;
2001    }
2002
2003    /* if service name or case matching with domain, let pick the domain */
2004    if (ntlm_domain_is_hostname(c->domain) || strcasecmp(domain, type2.targetname) == 0) {
2005	free(domain);
2006	domain = type2.targetname;
2007	if (domain == NULL) {
2008	    ret = ENOMEM;
2009	    goto error;
2010	}
2011    } else {
2012	free(domain);
2013	domain = c->domain;
2014    }
2015
2016    type3.username = c->user;
2017    type3.flags = type2.flags;
2018    /* only allow what we negotiated ourself */
2019    type3.flags &= type1flags;
2020    type3.targetname = domain;
2021    type3.ws = rk_UNCONST("workstation");
2022
2023    /*
2024     * Only do NTLM Version 1 if they force us
2025     */
2026
2027    if (gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_FORCE_V1, NULL)) {
2028
2029	type = "v1";
2030
2031	if (type2.flags & NTLM_NEG_NTLM2_SESSION) {
2032	    unsigned char nonce[8];
2033
2034	    if (CCRandomCopyBytes(kCCRandomDefault, nonce, sizeof(nonce))) {
2035		ret = EINVAL;
2036		goto error;
2037	    }
2038
2039	    ret = heim_ntlm_calculate_ntlm2_sess(nonce,
2040						 type2.challenge,
2041						 c->nthash.data,
2042						 &type3.lm,
2043						 &type3.ntlm);
2044	} else {
2045	    ret = heim_ntlm_calculate_ntlm1(c->nthash.data,
2046					    c->nthash.length,
2047					    type2.challenge,
2048					    &type3.ntlm);
2049
2050	}
2051	if (ret)
2052	    goto error;
2053
2054	if (type3.flags & NTLM_NEG_KEYEX) {
2055	    ret = heim_ntlm_build_ntlm1_master(c->nthash.data,
2056					       c->nthash.length,
2057					       &sessionkey,
2058					       &type3.sessionkey);
2059	} else {
2060	    ret = heim_ntlm_v1_base_session(c->nthash.data,
2061					    c->nthash.length,
2062					    &sessionkey);
2063	}
2064	if (ret)
2065	    goto error;
2066
2067    } else {
2068	unsigned char ntlmv2[16];
2069	struct ntlm_targetinfo ti;
2070	static uint8_t zeros[16] = { 0 };
2071
2072	type = "v2";
2073
2074	/* verify infotarget */
2075
2076	ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti);
2077	if (ret)
2078	    goto error;
2079
2080	if (ti.avflags & NTLM_TI_AV_FLAG_GUEST)
2081	    flags |= KCM_NTLM_FLAG_AV_GUEST;
2082
2083	if (ti.channel_bindings.data)
2084	    free(ti.channel_bindings.data);
2085	if (ti.targetname)
2086	    free(ti.targetname);
2087
2088	/*
2089	 * We are going to use MIC, tell server so it can reject the
2090	 * authenticate if the mic is missing.
2091	 */
2092	ti.avflags |= NTLM_TI_AV_FLAG_MIC;
2093	ti.targetname = targetname;
2094
2095	if (cb.length == 0) {
2096	    ti.channel_bindings.data = zeros;
2097	    ti.channel_bindings.length = sizeof(zeros);
2098	} else {
2099	    kcm_log(10, "using channelbindings of size %lu", (unsigned long)cb.length);
2100	    ti.channel_bindings.data = cb.data;
2101	    ti.channel_bindings.length = cb.length;
2102	}
2103
2104	ret = heim_ntlm_encode_targetinfo(&ti, TRUE, &tidata);
2105
2106	ti.targetname = NULL;
2107	ti.channel_bindings.data = NULL;
2108	ti.channel_bindings.length = 0;
2109
2110	heim_ntlm_free_targetinfo(&ti);
2111	if (ret)
2112	    goto error;
2113
2114	/*
2115	 * Prefer NTLM_NEG_EXTENDED_SESSION over NTLM_NEG_LM_KEY as
2116	 * decribed in 2.2.2.5.
2117	 */
2118
2119	if (type3.flags & NTLM_NEG_NTLM2_SESSION)
2120	    type3.flags &= ~NTLM_NEG_LM_KEY;
2121
2122	if ((type3.flags & NTLM_NEG_LM_KEY) &&
2123	    gss_mo_get(GSS_NTLM_MECHANISM, GSS_C_NTLM_SUPPORT_LM2, NULL)) {
2124	    ret = heim_ntlm_calculate_lm2(c->nthash.data,
2125					  c->nthash.length,
2126					  type3.username,
2127					  domain,
2128					  type2.challenge,
2129					  ntlmv2,
2130					  &type3.lm);
2131	} else {
2132	    type3.lm.data = malloc(24);
2133	    if (type3.lm.data == NULL) {
2134		ret = ENOMEM;
2135	    } else {
2136		type3.lm.length = 24;
2137		memset(type3.lm.data, 0, type3.lm.length);
2138	    }
2139	}
2140	if (ret)
2141	    goto error;
2142
2143	ret = heim_ntlm_calculate_ntlm2(c->nthash.data,
2144					c->nthash.length,
2145					type3.username,
2146					domain,
2147					type2.challenge,
2148					&tidata,
2149					ntlmv2,
2150					&type3.ntlm);
2151	if (ret)
2152	    goto error;
2153
2154	if (type3.flags & NTLM_NEG_KEYEX) {
2155	    ret = heim_ntlm_build_ntlm2_master(ntlmv2, sizeof(ntlmv2),
2156					       &type3.ntlm,
2157					       &sessionkey,
2158					       &type3.sessionkey);
2159	} else {
2160	    ret = heim_ntlm_v2_base_session(ntlmv2, sizeof(ntlmv2), &type3.ntlm, &sessionkey);
2161	}
2162
2163	memset(ntlmv2, 0, sizeof(ntlmv2));
2164	if (ret)
2165	    goto error;
2166    }
2167
2168    ret = heim_ntlm_encode_type3(&type3, &ndata, &mic_offset);
2169    if (ret)
2170	goto error;
2171    if (ndata.length < CC_MD5_DIGEST_LENGTH) {
2172	ret = EINVAL;
2173	goto error;
2174    }
2175
2176    if (mic_offset && mic_offset < ndata.length - CC_MD5_DIGEST_LENGTH) {
2177	CCHmacContext mic;
2178	uint8_t *p = (uint8_t *)ndata.data + mic_offset;
2179	CCHmacInit(&mic, kCCHmacAlgMD5, sessionkey.data, sessionkey.length);
2180	CCHmacUpdate(&mic, type1data.data, type1data.length);
2181	CCHmacUpdate(&mic, type2data.data, type2data.length);
2182	CCHmacUpdate(&mic, ndata.data, ndata.length);
2183	CCHmacFinal(&mic, p);
2184    }
2185
2186    tempdata.data = ndata.data;
2187    tempdata.length = ndata.length;
2188    ret = krb5_store_data(response, tempdata);
2189    heim_ntlm_free_buf(&ndata);
2190
2191    if (ret) goto error;
2192
2193    ret = krb5_store_int32(response, flags);
2194    if (ret) goto error;
2195
2196    tempdata.data = sessionkey.data;
2197    tempdata.length = sessionkey.length;
2198
2199    ret = krb5_store_data(response, tempdata);
2200    if (ret) goto error;
2201    ret = krb5_store_string(response, c->user);
2202    if (ret) goto error;
2203    ret = krb5_store_string(response, domain);
2204    if (ret) goto error;
2205    ret = krb5_store_uint32(response, type3.flags);
2206    if (ret) goto error;
2207
2208    heim_ntlm_unparse_flags(type3.flags, flagname, sizeof(flagname));
2209
2210    kcm_log(0, "ntlm %s request processed for %s\\%s flags: %s",
2211	    type, domain, c->user, flagname);
2212
2213 error:
2214    HEIMDAL_MUTEX_unlock(&cred_mutex);
2215
2216    krb5_data_free(&cb);
2217    krb5_data_free(&type1data);
2218    krb5_data_free(&type2data);
2219    if (type3.lm.data)
2220	free(type3.lm.data);
2221    if (type3.ntlm.data)
2222	free(type3.ntlm.data);
2223    if (type3.sessionkey.data)
2224	free(type3.sessionkey.data);
2225    if (targetname)
2226	free(targetname);
2227    heim_ntlm_free_type2(&type2);
2228    heim_ntlm_free_buf(&sessionkey);
2229    heim_ntlm_free_buf(&tidata);
2230    free(user);
2231
2232    return ret;
2233#else
2234    return EINVAL;
2235#endif
2236}
2237
2238
2239/*
2240 * { "GET_NTLM_UUID_LIST",	NULL }
2241 *
2242 * reply:
2243 *   1 user domain uuid
2244 *   0 [ end of list ]
2245 */
2246
2247static krb5_error_code
2248kcm_op_get_ntlm_user_list(krb5_context context,
2249			  kcm_client *client,
2250			  kcm_operation opcode,
2251			  krb5_storage *request,
2252			  krb5_storage *response)
2253{
2254    struct kcm_ntlm_cred *c;
2255    krb5_error_code ret;
2256    ssize_t sret;
2257
2258    KCM_LOG_REQUEST(context, client, opcode);
2259
2260    HEIMDAL_MUTEX_lock(&cred_mutex);
2261
2262    for (c = ntlm_head; c != NULL; c = c->next) {
2263	if (c->type != KCM_NTLM_CRED || !kcm_is_same_session(client, c->uid, c->session))
2264	    continue;
2265
2266	ret = krb5_store_uint32(response, 1);
2267	if (ret)
2268	    goto out;
2269	ret = krb5_store_stringz(response, c->user);
2270	if (ret)
2271	    goto out;
2272	ret = krb5_store_stringz(response, c->domain);
2273	if (ret)
2274	    goto out;
2275	sret = krb5_storage_write(response, c->uuid, sizeof(c->uuid));
2276	if (sret != sizeof(c->uuid)) {
2277	    ret = ENOMEM;
2278	    goto out;
2279	}
2280    }
2281    ret = krb5_store_uint32(response, 0);
2282 out:
2283    HEIMDAL_MUTEX_unlock(&cred_mutex);
2284    return ret;
2285}
2286
2287static krb5_error_code
2288kcm_op_add_scram_cred(krb5_context context,
2289		     kcm_client *client,
2290		     kcm_operation opcode,
2291		     krb5_storage *request,
2292		     krb5_storage *response)
2293{
2294    struct kcm_ntlm_cred *cred, *c;
2295    krb5_error_code ret;
2296
2297    KCM_LOG_REQUEST(context, client, opcode);
2298
2299    cred = create_cred(KCM_SCRAM_CRED);
2300    if (cred == NULL)
2301	return ENOMEM;
2302
2303    ret = krb5_ret_stringz(request, &cred->user);
2304    if (ret)
2305	goto error;
2306
2307    ret = krb5_ret_stringz(request, &cred->u.password);
2308    if (ret)
2309	goto error;
2310
2311    HEIMDAL_MUTEX_lock(&cred_mutex);
2312
2313    /* search for dups */
2314    c = find_ntlm_cred(KCM_SCRAM_CRED, cred->user, NULL, client);
2315    if (c) {
2316	char *pw = c->u.password;
2317	c->u.password = cred->u.password;
2318	cred->u.password = pw;
2319	free_cred(cred);
2320	cred = c;
2321    } else {
2322	cred->next = ntlm_head;
2323	ntlm_head = cred;
2324    }
2325
2326    cred->uid = client->uid;
2327    cred->session = client->session;
2328
2329    /* write response */
2330    (void)krb5_storage_write(response, cred->uuid, sizeof(cred->uuid));
2331
2332
2333    HEIMDAL_MUTEX_unlock(&cred_mutex);
2334    kcm_data_changed = 1;
2335
2336    return 0;
2337
2338 error:
2339    free_cred(cred);
2340
2341    return ret;
2342}
2343
2344static krb5_error_code
2345kcm_op_have_scram_cred(krb5_context context,
2346		       kcm_client *client,
2347		       kcm_operation opcode,
2348		       krb5_storage *request,
2349		       krb5_storage *response)
2350{
2351    struct kcm_ntlm_cred *c;
2352    char *user = NULL;
2353    krb5_error_code ret;
2354
2355    KCM_LOG_REQUEST(context, client, opcode);
2356
2357    ret = krb5_ret_stringz(request, &user);
2358    if (ret)
2359	return ret;
2360
2361    HEIMDAL_MUTEX_lock(&cred_mutex);
2362
2363    c = find_ntlm_cred(KCM_SCRAM_CRED, user, NULL, client);
2364    if (c == NULL)
2365	ret = ENOENT;
2366
2367    if (c)
2368      (void)krb5_storage_write(response, c->uuid, sizeof(c->uuid));
2369
2370    HEIMDAL_MUTEX_unlock(&cred_mutex);
2371
2372    free(user);
2373
2374    return ret;
2375}
2376
2377static krb5_error_code
2378kcm_op_del_scram_cred(krb5_context context,
2379		     kcm_client *client,
2380		     kcm_operation opcode,
2381		     krb5_storage *request,
2382		     krb5_storage *response)
2383{
2384    struct kcm_ntlm_cred **cp, *c;
2385    char *user = NULL;
2386    krb5_error_code ret;
2387
2388    KCM_LOG_REQUEST(context, client, opcode);
2389
2390    ret = krb5_ret_stringz(request, &user);
2391    if (ret)
2392	return ret;
2393
2394    HEIMDAL_MUTEX_lock(&cred_mutex);
2395
2396    for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
2397	if ((*cp)->type == KCM_SCRAM_CRED && strcasecmp(user, (*cp)->user) == 0 &&
2398	    kcm_is_same_session(client, (*cp)->uid, (*cp)->session))
2399	{
2400	    c = *cp;
2401	    *cp = c->next;
2402
2403	    free_cred(c);
2404	    kcm_data_changed = 1;
2405	    break;
2406	}
2407    }
2408
2409    HEIMDAL_MUTEX_unlock(&cred_mutex);
2410
2411    free(user);
2412
2413    return ret;
2414}
2415
2416/*
2417 * IN:
2418 *   clientname: stringz
2419 *   iterations: uint32_t
2420 *   salt: krb5_data
2421 *   c1: krb5_data
2422 *   s1: krb5_data
2423 *   c2noproof: krb5_data
2424 */
2425
2426static krb5_error_code
2427kcm_op_do_scram(krb5_context context,
2428		kcm_client *client,
2429		kcm_operation opcode,
2430		krb5_storage *request,
2431		krb5_storage *response)
2432{
2433#ifdef ENABLE_SCRAM
2434    heim_scram_data proof, server, client_key, stored, server_key, session_key;
2435    heim_scram_method method = HEIM_SCRAM_DIGEST_SHA1;
2436    krb5_data salt, c1, s1, c2noproof;
2437    struct kcm_ntlm_cred *c;
2438    krb5_error_code ret;
2439    uint32_t iterations;
2440    unsigned char *p, *q;
2441    char *user = NULL;
2442    size_t n;
2443
2444    KCM_LOG_REQUEST(context, client, opcode);
2445
2446    memset(&proof, 0, sizeof(proof));
2447    memset(&server, 0, sizeof(server));
2448    memset(&client_key, 0, sizeof(client_key));
2449    memset(&stored, 0, sizeof(stored));
2450    memset(&server_key, 0, sizeof(server_key));
2451    memset(&session_key, 0, sizeof(session_key));
2452    krb5_data_zero(&salt);
2453    krb5_data_zero(&c1);
2454    krb5_data_zero(&s1);
2455    krb5_data_zero(&c2noproof);
2456
2457    HEIMDAL_MUTEX_lock(&cred_mutex);
2458
2459    ret = krb5_ret_stringz(request, &user);
2460    if (ret)
2461	goto out;
2462
2463    c = find_ntlm_cred(KCM_SCRAM_CRED, user, NULL, client);
2464    if (c == NULL) {
2465	ret = ENOENT;
2466	goto out;
2467    }
2468
2469    ret = krb5_ret_uint32(request, &iterations);
2470    if (ret)
2471	goto out;
2472
2473    ret = krb5_ret_data(request, &salt);
2474    if (ret)
2475	goto out;
2476
2477    ret = krb5_ret_data(request, &c1);
2478    if (ret)
2479	goto out;
2480
2481    ret = krb5_ret_data(request, &s1);
2482    if (ret)
2483	goto out;
2484
2485    ret = krb5_ret_data(request, &c2noproof);
2486    if (ret)
2487	goto out;
2488
2489    ret = heim_scram_stored_key(method, c->u.password, iterations, &salt,
2490				&client_key, &stored, &server_key);
2491    if (ret)
2492	goto out;
2493
2494    ret = heim_scram_generate(method, &stored, &server_key,
2495			      &c1, &s1, &c2noproof, &proof, &server);
2496    if (ret)
2497	goto out;
2498
2499
2500    ret = heim_scram_session_key(method, &stored, &client_key,
2501				 &c1, &s1, &c2noproof,
2502				 &session_key);
2503    if (ret)
2504	goto out;
2505
2506    /*
2507     * Now client_key XOR proof
2508     */
2509    p = proof.data;
2510    q = client_key.data;
2511
2512    for (n = 0 ; n < client_key.length; n++)
2513	p[n] = p[n] ^ q[n];
2514
2515    ret = krb5_store_data(response, proof);
2516    if (ret)
2517	goto out;
2518    ret = krb5_store_data(response, server);
2519    if (ret)
2520	goto out;
2521    ret = krb5_store_data(response, session_key);
2522    if (ret)
2523	goto out;
2524
2525out:
2526    HEIMDAL_MUTEX_unlock(&cred_mutex);
2527    if (user)
2528	free(user);
2529
2530    krb5_data_free(&salt);
2531    krb5_data_free(&c1);
2532    krb5_data_free(&s1);
2533    krb5_data_free(&c2noproof);
2534
2535    heim_scram_data_free(&proof);
2536    heim_scram_data_free(&server);
2537    heim_scram_data_free(&client_key);
2538    heim_scram_data_free(&stored);
2539    heim_scram_data_free(&server_key);
2540    heim_scram_data_free(&session_key);
2541
2542    return ret;
2543#else
2544    return EINVAL;
2545#endif
2546}
2547
2548static krb5_error_code
2549kcm_op_get_scram_user_list(krb5_context context,
2550			   kcm_client *client,
2551			   kcm_operation opcode,
2552			   krb5_storage *request,
2553			   krb5_storage *response)
2554{
2555    struct kcm_ntlm_cred *c;
2556    krb5_error_code ret;
2557    ssize_t sret;
2558
2559    KCM_LOG_REQUEST(context, client, opcode);
2560
2561    for (c = ntlm_head; c != NULL; c = c->next) {
2562	if (c->type != KCM_SCRAM_CRED || !kcm_is_same_session(client, c->uid, c->session))
2563	    continue;
2564
2565	ret = krb5_store_uint32(response, 1);
2566	if (ret)
2567	    return ret;
2568	ret = krb5_store_stringz(response, c->user);
2569	if (ret)
2570	    return ret;
2571
2572	sret = krb5_storage_write(response, c->uuid, sizeof(c->uuid));
2573	if (sret != sizeof(c->uuid)) {
2574	    ret = ENOMEM;
2575	    return ret;
2576	}
2577    }
2578    return krb5_store_uint32(response, 0);
2579}
2580
2581static krb5_error_code
2582kcm_op_retain_cred(krb5_context context,
2583		   kcm_client *client,
2584		   kcm_operation opcode,
2585		   krb5_storage *request,
2586		   krb5_storage *response)
2587{
2588    struct kcm_ntlm_cred *c;
2589    kcmuuid_t uuid;
2590    ssize_t sret;
2591
2592    KCM_LOG_REQUEST(context, client, opcode);
2593
2594    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
2595    if (sret != sizeof(uuid)) {
2596	krb5_clear_error_message(context);
2597	return KRB5_CC_IO;
2598    }
2599
2600    for (c = ntlm_head; c != NULL; c = c->next) {
2601	if (!kcm_is_same_session(client, c->uid, c->session))
2602	    continue;
2603
2604	if (memcmp(uuid, c->uuid, sizeof(c->uuid)) == 0) {
2605	    c->refcount++;
2606	    kcm_data_changed = 1;
2607	    return 0;
2608	}
2609    }
2610
2611    return 0;
2612}
2613
2614static krb5_error_code
2615kcm_op_release_cred(krb5_context context,
2616		    kcm_client *client,
2617		    kcm_operation opcode,
2618		    krb5_storage *request,
2619		    krb5_storage *response)
2620{
2621    struct kcm_ntlm_cred **cp;
2622    kcmuuid_t uuid;
2623    ssize_t sret;
2624
2625    KCM_LOG_REQUEST(context, client, opcode);
2626
2627    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
2628    if (sret != sizeof(uuid)) {
2629	krb5_clear_error_message(context);
2630	return KRB5_CC_IO;
2631    }
2632
2633    for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
2634	struct kcm_ntlm_cred *c = *cp;
2635
2636	if (!kcm_is_same_session(client, c->uid, c->session))
2637	    continue;
2638
2639	if (memcmp(uuid, c->uuid, sizeof(uuid)) == 0) {
2640	    c->refcount--;
2641	    if (c->refcount < 1) {
2642		*cp = c->next;
2643		free_cred(c);
2644	    }
2645	    kcm_data_changed = 1;
2646	    return 0;
2647	}
2648    }
2649    return 0;
2650}
2651
2652static krb5_error_code
2653kcm_op_cred_label_get(krb5_context context,
2654		      kcm_client *client,
2655		      kcm_operation opcode,
2656		      krb5_storage *request,
2657		      krb5_storage *response)
2658{
2659    struct kcm_ntlm_cred *c;
2660    krb5_error_code ret;
2661    heim_string_t s;
2662    char *label;
2663    kcmuuid_t uuid;
2664    ssize_t sret;
2665
2666    KCM_LOG_REQUEST(context, client, opcode);
2667
2668    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
2669    if (sret != sizeof(uuid)) {
2670	krb5_clear_error_message(context);
2671	return KRB5_CC_IO;
2672    }
2673
2674    ret = krb5_ret_stringz(request, &label);
2675    if (ret)
2676	return ret;
2677
2678    s = heim_string_create(label);
2679    free(label);
2680
2681    for (c = ntlm_head; c != NULL; c = c->next) {
2682	if (!kcm_is_same_session(client, c->uid, c->session))
2683	    continue;
2684
2685	if (memcmp(uuid, c->uuid, sizeof(c->uuid)) == 0) {
2686	    heim_data_t d;
2687
2688	    d = heim_dict_copy_value(c->labels, s);
2689	    if (d) {
2690		krb5_data data;
2691		data.length = heim_data_get_length(d);
2692		data.data = (void *)heim_data_get_bytes(d);
2693
2694		krb5_store_data(response, data);
2695		heim_release(d);
2696		break;
2697	    }
2698	}
2699    }
2700    heim_release(s);
2701
2702    if (c == NULL)
2703	return ENOENT;
2704
2705    return 0;
2706}
2707
2708static krb5_error_code
2709kcm_op_cred_label_set(krb5_context context,
2710		      kcm_client *client,
2711		      kcm_operation opcode,
2712		      krb5_storage *request,
2713		      krb5_storage *response)
2714{
2715    struct kcm_ntlm_cred *c;
2716    kcmuuid_t uuid;
2717    krb5_data data;
2718    char *label = NULL;
2719    ssize_t sret;
2720
2721    KCM_LOG_REQUEST(context, client, opcode);
2722
2723    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
2724    if (sret != sizeof(uuid)) {
2725	krb5_clear_error_message(context);
2726	return KRB5_CC_IO;
2727    }
2728
2729    krb5_ret_stringz(request, &label);
2730    krb5_ret_data(request, &data);
2731
2732    HEIMDAL_MUTEX_lock(&cred_mutex);
2733
2734    for (c = ntlm_head; c != NULL; c = c->next) {
2735
2736	if (!kcm_is_same_session(client, c->uid, c->session))
2737	    continue;
2738
2739	if (memcmp(uuid, c->uuid, sizeof(uuid)) == 0) {
2740	    heim_string_t s;
2741
2742	    s = heim_string_create(label);
2743
2744	    if (data.length) {
2745		heim_data_t d;
2746
2747		d = heim_data_create(data.data, data.length);
2748
2749		heim_dict_add_value(c->labels, s, d);
2750		heim_release(d);
2751	    } else {
2752		heim_dict_delete_key(c->labels, s);
2753	    }
2754	    kcm_data_changed = 1;
2755	    heim_release(s);
2756	    break;
2757	}
2758    }
2759
2760    HEIMDAL_MUTEX_unlock(&cred_mutex);
2761
2762    krb5_data_free(&data);
2763    free(label);
2764
2765    if (c == NULL)
2766	return ENOENT;
2767
2768    return 0;
2769}
2770
2771
2772
2773/*
2774 *
2775 */
2776
2777static struct kcm_op kcm_ops[] = {
2778    { "NOOP", 			kcm_op_noop },
2779    { "GET_NAME",		kcm_op_get_name },
2780    { "RESOLVE",		kcm_op_noop },
2781    { "GEN_NEW", 		kcm_op_gen_new },
2782    { "INITIALIZE",		kcm_op_initialize },
2783    { "DESTROY",		kcm_op_destroy },
2784    { "STORE",			kcm_op_store },
2785    { "RETRIEVE",		kcm_op_retrieve },
2786    { "GET_PRINCIPAL",		kcm_op_get_principal },
2787    { "GET_CRED_UUID_LIST",	kcm_op_get_cred_uuid_list },
2788    { "GET_CRED_BY_UUID",	kcm_op_get_cred_by_uuid },
2789    { "REMOVE_CRED",		kcm_op_remove_cred },
2790    { "SET_FLAGS",		kcm_op_set_flags },
2791    { "CHOWN",			kcm_op_chown },
2792    { "CHMOD",			kcm_op_chmod },
2793    { "GET_INITIAL_TICKET",	kcm_op_get_initial_ticket },
2794    { "GET_TICKET",		kcm_op_get_ticket },
2795    { "MOVE_CACHE",		kcm_op_move_cache },
2796    { "GET_CACHE_UUID_LIST",	kcm_op_get_cache_uuid_list },
2797    { "GET_CACHE_BY_UUID",	kcm_op_get_cache_by_uuid },
2798    { "GET_DEFAULT_CACHE",      kcm_op_get_default_cache },
2799    { "SET_DEFAULT_CACHE",      kcm_op_set_default_cache },
2800    { "GET_KDC_OFFSET",      	kcm_op_get_kdc_offset },
2801    { "SET_KDC_OFFSET",      	kcm_op_set_kdc_offset },
2802    { "RETAIN_KCRED",		kcm_op_retain_kcred },
2803    { "RELEASE_KCRED",		kcm_op_release_kcred },
2804    { "GET_UUID",		kcm_op_get_uuid },
2805    { "ADD_NTLM_CRED",		kcm_op_add_ntlm_cred },
2806    { "HAVE_NTLM_CRED",		kcm_op_have_ntlm_cred },
2807    { "SET_NTLM_CHALLEGE",	kcm_op_add_ntlm_challenge },
2808    { "DO_NTLM_AUTH",		kcm_op_do_ntlm },
2809    { "SET_NTLM_USER_LIST",	kcm_op_get_ntlm_user_list },
2810    { "ADD_SCRAM_CRED",		kcm_op_add_scram_cred },
2811    { "HAVE_SCRAM_CRED",	kcm_op_have_scram_cred },
2812    { "DEL_SCRAM_CRED",		kcm_op_del_scram_cred },
2813    { "DO_SCRAM_AUTH",		kcm_op_do_scram },
2814    { "GET_SCRAM_USER_LIST",	kcm_op_get_scram_user_list },
2815    { "DEL_CRED",		kcm_op_del_cred },
2816    { "RETAIN_CRED",		kcm_op_retain_cred },
2817    { "RELEASE_CRED",		kcm_op_release_cred },
2818    { "CRED_LABEL_GET",		kcm_op_cred_label_get },
2819    { "CRED_LABEL_SET",		kcm_op_cred_label_set }
2820};
2821
2822
2823const char *
2824kcm_op2string(kcm_operation opcode)
2825{
2826    if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0]))
2827	return "Unknown operation";
2828
2829    return kcm_ops[opcode].name;
2830}
2831
2832krb5_error_code
2833kcm_dispatch(krb5_context context,
2834	     kcm_client *client,
2835	     krb5_data *req_data,
2836	     krb5_data *resp_data)
2837{
2838    krb5_error_code ret;
2839    kcm_method method;
2840    krb5_storage *req_sp = NULL;
2841    krb5_storage *resp_sp = NULL;
2842    uint16_t opcode;
2843
2844    resp_sp = krb5_storage_emem();
2845    if (resp_sp == NULL) {
2846	return ENOMEM;
2847    }
2848
2849    if (client->pid == -1) {
2850	kcm_log(0, "Client had invalid process number");
2851	ret = KRB5_FCC_INTERNAL;
2852	goto out;
2853    }
2854
2855    req_sp = krb5_storage_from_data(req_data);
2856    if (req_sp == NULL) {
2857	kcm_log(0, "Process %d: failed to initialize storage from data",
2858		client->pid);
2859	ret = KRB5_CC_IO;
2860	goto out;
2861    }
2862
2863    ret = krb5_ret_uint16(req_sp, &opcode);
2864    if (ret) {
2865	kcm_log(0, "Process %d: didn't send a message", client->pid);
2866	goto out;
2867    }
2868
2869    if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0])) {
2870	kcm_log(0, "Process %d: invalid operation code %d",
2871		client->pid, opcode);
2872	ret = KRB5_FCC_INTERNAL;
2873	goto out;
2874    }
2875    method = kcm_ops[opcode].method;
2876    if (method == NULL) {
2877	kcm_log(0, "Process %d: operation code %s not implemented",
2878		client->pid, kcm_op2string(opcode));
2879	ret = KRB5_FCC_INTERNAL;
2880	goto out;
2881    }
2882
2883    /* seek past place for status code */
2884    krb5_storage_seek(resp_sp, 4, SEEK_SET);
2885
2886    ret = (*method)(context, client, opcode, req_sp, resp_sp);
2887
2888out:
2889    if (req_sp != NULL) {
2890	krb5_storage_free(req_sp);
2891    }
2892
2893    krb5_storage_seek(resp_sp, 0, SEEK_SET);
2894    krb5_store_int32(resp_sp, ret);
2895
2896    ret = krb5_storage_to_data(resp_sp, resp_data);
2897    krb5_storage_free(resp_sp);
2898
2899    return ret;
2900}
2901
2902