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