protocol.c revision 256281
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
38static void
39kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name);
40
41
42int
43kcm_is_same_session(kcm_client *client, uid_t uid, pid_t session)
44{
45#if 0 /* XXX pppd is running in diffrent session the user */
46    if (session != -1)
47	return (client->session == session);
48    else
49#endif
50	return  (client->uid == uid);
51}
52
53static krb5_error_code
54kcm_op_noop(krb5_context context,
55	    kcm_client *client,
56	    kcm_operation opcode,
57	    krb5_storage *request,
58	    krb5_storage *response)
59{
60    KCM_LOG_REQUEST(context, client, opcode);
61
62    return 0;
63}
64
65/*
66 * Request:
67 *	NameZ
68 * Response:
69 *	NameZ
70 *
71 */
72static krb5_error_code
73kcm_op_get_name(krb5_context context,
74		kcm_client *client,
75		kcm_operation opcode,
76		krb5_storage *request,
77		krb5_storage *response)
78
79{
80    krb5_error_code ret;
81    char *name = NULL;
82    kcm_ccache ccache;
83
84    ret = krb5_ret_stringz(request, &name);
85    if (ret)
86	return ret;
87
88    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
89
90    ret = kcm_ccache_resolve_client(context, client, opcode,
91				    name, &ccache);
92    if (ret) {
93	free(name);
94	return ret;
95    }
96
97    ret = krb5_store_stringz(response, ccache->name);
98    if (ret) {
99	kcm_release_ccache(context, ccache);
100	free(name);
101	return ret;
102    }
103
104    free(name);
105    kcm_release_ccache(context, ccache);
106    return 0;
107}
108
109/*
110 * Request:
111 *
112 * Response:
113 *	NameZ
114 */
115static krb5_error_code
116kcm_op_gen_new(krb5_context context,
117	       kcm_client *client,
118	       kcm_operation opcode,
119	       krb5_storage *request,
120	       krb5_storage *response)
121{
122    krb5_error_code ret;
123    char *name;
124
125    KCM_LOG_REQUEST(context, client, opcode);
126
127    name = kcm_ccache_nextid(client->pid, client->uid, client->gid);
128    if (name == NULL) {
129	return KRB5_CC_NOMEM;
130    }
131
132    ret = krb5_store_stringz(response, name);
133    free(name);
134
135    return ret;
136}
137
138/*
139 * Request:
140 *	NameZ
141 *	Principal
142 *
143 * Response:
144 *
145 */
146static krb5_error_code
147kcm_op_initialize(krb5_context context,
148		  kcm_client *client,
149		  kcm_operation opcode,
150		  krb5_storage *request,
151		  krb5_storage *response)
152{
153    kcm_ccache ccache;
154    krb5_principal principal;
155    krb5_error_code ret;
156    char *name;
157#if 0
158    kcm_event event;
159#endif
160
161    KCM_LOG_REQUEST(context, client, opcode);
162
163    ret = krb5_ret_stringz(request, &name);
164    if (ret)
165	return ret;
166
167    ret = krb5_ret_principal(request, &principal);
168    if (ret) {
169	free(name);
170	return ret;
171    }
172
173    ret = kcm_ccache_new_client(context, client, name, &ccache);
174    if (ret) {
175	free(name);
176	krb5_free_principal(context, principal);
177	return ret;
178    }
179
180    ccache->client = principal;
181
182    free(name);
183
184#if 0
185    /*
186     * Create a new credentials cache. To mitigate DoS attacks we will
187     * expire it in 30 minutes unless it has some credentials added
188     * to it
189     */
190
191    event.fire_time = 30 * 60;
192    event.expire_time = 0;
193    event.backoff_time = 0;
194    event.action = KCM_EVENT_DESTROY_EMPTY_CACHE;
195    event.ccache = ccache;
196
197    ret = kcm_enqueue_event_relative(context, &event);
198#endif
199
200    kcm_release_ccache(context, ccache);
201
202    return ret;
203}
204
205/*
206 * Request:
207 *	NameZ
208 *
209 * Response:
210 *
211 */
212static krb5_error_code
213kcm_op_destroy(krb5_context context,
214	       kcm_client *client,
215	       kcm_operation opcode,
216	       krb5_storage *request,
217	       krb5_storage *response)
218{
219    krb5_error_code ret;
220    char *name;
221
222    ret = krb5_ret_stringz(request, &name);
223    if (ret)
224	return ret;
225
226    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
227
228    ret = kcm_ccache_destroy_client(context, client, name);
229    if (ret == 0)
230	kcm_drop_default_cache(context, client, name);
231
232    free(name);
233
234    return ret;
235}
236
237/*
238 * Request:
239 *	NameZ
240 *	Creds
241 *
242 * Response:
243 *
244 */
245static krb5_error_code
246kcm_op_store(krb5_context context,
247	     kcm_client *client,
248	     kcm_operation opcode,
249	     krb5_storage *request,
250	     krb5_storage *response)
251{
252    krb5_creds creds;
253    krb5_error_code ret;
254    kcm_ccache ccache;
255    char *name;
256
257    ret = krb5_ret_stringz(request, &name);
258    if (ret)
259	return ret;
260
261    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
262
263    ret = krb5_ret_creds(request, &creds);
264    if (ret) {
265	free(name);
266	return ret;
267    }
268
269    ret = kcm_ccache_resolve_client(context, client, opcode,
270				    name, &ccache);
271    if (ret) {
272	free(name);
273	krb5_free_cred_contents(context, &creds);
274	return ret;
275    }
276
277    ret = kcm_ccache_store_cred(context, ccache, &creds, 0);
278    if (ret) {
279	free(name);
280	krb5_free_cred_contents(context, &creds);
281	kcm_release_ccache(context, ccache);
282	return ret;
283    }
284
285    kcm_ccache_enqueue_default(context, ccache, &creds);
286
287    free(name);
288    kcm_release_ccache(context, ccache);
289
290    return 0;
291}
292
293/*
294 * Request:
295 *	NameZ
296 *	WhichFields
297 *	MatchCreds
298 *
299 * Response:
300 *	Creds
301 *
302 */
303static krb5_error_code
304kcm_op_retrieve(krb5_context context,
305		kcm_client *client,
306		kcm_operation opcode,
307		krb5_storage *request,
308		krb5_storage *response)
309{
310    uint32_t flags;
311    krb5_creds mcreds;
312    krb5_error_code ret;
313    kcm_ccache ccache;
314    char *name;
315    krb5_creds *credp;
316    int free_creds = 0;
317
318    ret = krb5_ret_stringz(request, &name);
319    if (ret)
320	return ret;
321
322    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
323
324    ret = krb5_ret_uint32(request, &flags);
325    if (ret) {
326	free(name);
327	return ret;
328    }
329
330    ret = krb5_ret_creds_tag(request, &mcreds);
331    if (ret) {
332	free(name);
333	return ret;
334    }
335
336    if (disallow_getting_krbtgt &&
337	mcreds.server->name.name_string.len == 2 &&
338	strcmp(mcreds.server->name.name_string.val[0], KRB5_TGS_NAME) == 0)
339    {
340	free(name);
341	krb5_free_cred_contents(context, &mcreds);
342	return KRB5_FCC_PERM;
343    }
344
345    ret = kcm_ccache_resolve_client(context, client, opcode,
346				    name, &ccache);
347    if (ret) {
348	free(name);
349	krb5_free_cred_contents(context, &mcreds);
350	return ret;
351    }
352
353    ret = kcm_ccache_retrieve_cred(context, ccache, flags,
354				   &mcreds, &credp);
355    if (ret && ((flags & KRB5_GC_CACHED) == 0) &&
356	!krb5_is_config_principal(context, mcreds.server)) {
357	krb5_ccache_data ccdata;
358
359	/* try and acquire */
360	HEIMDAL_MUTEX_lock(&ccache->mutex);
361
362	/* Fake up an internal ccache */
363	kcm_internal_ccache(context, ccache, &ccdata);
364
365	/* glue cc layer will store creds */
366	ret = krb5_get_credentials(context, 0, &ccdata, &mcreds, &credp);
367	if (ret == 0)
368	    free_creds = 1;
369
370	HEIMDAL_MUTEX_unlock(&ccache->mutex);
371    }
372
373    if (ret == 0) {
374	ret = krb5_store_creds(response, credp);
375    }
376
377    free(name);
378    krb5_free_cred_contents(context, &mcreds);
379    kcm_release_ccache(context, ccache);
380
381    if (free_creds)
382	krb5_free_cred_contents(context, credp);
383
384    return ret;
385}
386
387/*
388 * Request:
389 *	NameZ
390 *
391 * Response:
392 *	Principal
393 */
394static krb5_error_code
395kcm_op_get_principal(krb5_context context,
396		     kcm_client *client,
397		     kcm_operation opcode,
398		     krb5_storage *request,
399		     krb5_storage *response)
400{
401    krb5_error_code ret;
402    kcm_ccache ccache;
403    char *name;
404
405    ret = krb5_ret_stringz(request, &name);
406    if (ret)
407	return ret;
408
409    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
410
411    ret = kcm_ccache_resolve_client(context, client, opcode,
412				    name, &ccache);
413    if (ret) {
414	free(name);
415	return ret;
416    }
417
418    if (ccache->client == NULL)
419	ret = KRB5_CC_NOTFOUND;
420    else
421	ret = krb5_store_principal(response, ccache->client);
422
423    free(name);
424    kcm_release_ccache(context, ccache);
425
426    return 0;
427}
428
429/*
430 * Request:
431 *	NameZ
432 *
433 * Response:
434 *	UUIDs
435 *
436 */
437static krb5_error_code
438kcm_op_get_cred_uuid_list(krb5_context context,
439			  kcm_client *client,
440			  kcm_operation opcode,
441			  krb5_storage *request,
442			  krb5_storage *response)
443{
444    struct kcm_creds *creds;
445    krb5_error_code ret;
446    kcm_ccache ccache;
447    char *name;
448
449    ret = krb5_ret_stringz(request, &name);
450    if (ret)
451	return ret;
452
453    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
454
455    ret = kcm_ccache_resolve_client(context, client, opcode,
456				    name, &ccache);
457    free(name);
458    if (ret)
459	return ret;
460
461    for (creds = ccache->creds ; creds ; creds = creds->next) {
462	ssize_t sret;
463	sret = krb5_storage_write(response, &creds->uuid, sizeof(creds->uuid));
464	if (sret != sizeof(creds->uuid)) {
465	    ret = ENOMEM;
466	    break;
467	}
468    }
469
470    kcm_release_ccache(context, ccache);
471
472    return ret;
473}
474
475/*
476 * Request:
477 *	NameZ
478 *	Cursor
479 *
480 * Response:
481 *	Creds
482 */
483static krb5_error_code
484kcm_op_get_cred_by_uuid(krb5_context context,
485			kcm_client *client,
486			kcm_operation opcode,
487			krb5_storage *request,
488			krb5_storage *response)
489{
490    krb5_error_code ret;
491    kcm_ccache ccache;
492    char *name;
493    struct kcm_creds *c;
494    kcmuuid_t uuid;
495    ssize_t sret;
496
497    ret = krb5_ret_stringz(request, &name);
498    if (ret)
499	return ret;
500
501    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
502
503    ret = kcm_ccache_resolve_client(context, client, opcode,
504				    name, &ccache);
505    free(name);
506    if (ret)
507	return ret;
508
509    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
510    if (sret != sizeof(uuid)) {
511	kcm_release_ccache(context, ccache);
512	krb5_clear_error_message(context);
513	return KRB5_CC_IO;
514    }
515
516    c = kcm_ccache_find_cred_uuid(context, ccache, uuid);
517    if (c == NULL) {
518	kcm_release_ccache(context, ccache);
519	return KRB5_CC_END;
520    }
521
522    HEIMDAL_MUTEX_lock(&ccache->mutex);
523    ret = krb5_store_creds(response, &c->cred);
524    HEIMDAL_MUTEX_unlock(&ccache->mutex);
525
526    kcm_release_ccache(context, ccache);
527
528    return ret;
529}
530
531/*
532 * Request:
533 *	NameZ
534 *	WhichFields
535 *	MatchCreds
536 *
537 * Response:
538 *
539 */
540static krb5_error_code
541kcm_op_remove_cred(krb5_context context,
542		   kcm_client *client,
543		   kcm_operation opcode,
544		   krb5_storage *request,
545		   krb5_storage *response)
546{
547    uint32_t whichfields;
548    krb5_creds mcreds;
549    krb5_error_code ret;
550    kcm_ccache ccache;
551    char *name;
552
553    ret = krb5_ret_stringz(request, &name);
554    if (ret)
555	return ret;
556
557    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
558
559    ret = krb5_ret_uint32(request, &whichfields);
560    if (ret) {
561	free(name);
562	return ret;
563    }
564
565    ret = krb5_ret_creds_tag(request, &mcreds);
566    if (ret) {
567	free(name);
568	return ret;
569    }
570
571    ret = kcm_ccache_resolve_client(context, client, opcode,
572				    name, &ccache);
573    if (ret) {
574	free(name);
575	krb5_free_cred_contents(context, &mcreds);
576	return ret;
577    }
578
579    ret = kcm_ccache_remove_cred(context, ccache, whichfields, &mcreds);
580
581    /* XXX need to remove any events that match */
582
583    free(name);
584    krb5_free_cred_contents(context, &mcreds);
585    kcm_release_ccache(context, ccache);
586
587    return ret;
588}
589
590/*
591 * Request:
592 *	NameZ
593 *	Flags
594 *
595 * Response:
596 *
597 */
598static krb5_error_code
599kcm_op_set_flags(krb5_context context,
600		 kcm_client *client,
601		 kcm_operation opcode,
602		 krb5_storage *request,
603		 krb5_storage *response)
604{
605    uint32_t flags;
606    krb5_error_code ret;
607    kcm_ccache ccache;
608    char *name;
609
610    ret = krb5_ret_stringz(request, &name);
611    if (ret)
612	return ret;
613
614    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
615
616    ret = krb5_ret_uint32(request, &flags);
617    if (ret) {
618	free(name);
619	return ret;
620    }
621
622    ret = kcm_ccache_resolve_client(context, client, opcode,
623				    name, &ccache);
624    if (ret) {
625	free(name);
626	return ret;
627    }
628
629    /* we don't really support any flags yet */
630    free(name);
631    kcm_release_ccache(context, ccache);
632
633    return 0;
634}
635
636/*
637 * Request:
638 *	NameZ
639 *	UID
640 *	GID
641 *
642 * Response:
643 *
644 */
645static krb5_error_code
646kcm_op_chown(krb5_context context,
647	     kcm_client *client,
648	     kcm_operation opcode,
649	     krb5_storage *request,
650	     krb5_storage *response)
651{
652    uint32_t uid;
653    uint32_t gid;
654    krb5_error_code ret;
655    kcm_ccache ccache;
656    char *name;
657
658    ret = krb5_ret_stringz(request, &name);
659    if (ret)
660	return ret;
661
662    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
663
664    ret = krb5_ret_uint32(request, &uid);
665    if (ret) {
666	free(name);
667	return ret;
668    }
669
670    ret = krb5_ret_uint32(request, &gid);
671    if (ret) {
672	free(name);
673	return ret;
674    }
675
676    ret = kcm_ccache_resolve_client(context, client, opcode,
677				    name, &ccache);
678    if (ret) {
679	free(name);
680	return ret;
681    }
682
683    ret = kcm_chown(context, client, ccache, uid, gid);
684
685    free(name);
686    kcm_release_ccache(context, ccache);
687
688    return ret;
689}
690
691/*
692 * Request:
693 *	NameZ
694 *	Mode
695 *
696 * Response:
697 *
698 */
699static krb5_error_code
700kcm_op_chmod(krb5_context context,
701	     kcm_client *client,
702	     kcm_operation opcode,
703	     krb5_storage *request,
704	     krb5_storage *response)
705{
706    uint16_t mode;
707    krb5_error_code ret;
708    kcm_ccache ccache;
709    char *name;
710
711    ret = krb5_ret_stringz(request, &name);
712    if (ret)
713	return ret;
714
715    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
716
717    ret = krb5_ret_uint16(request, &mode);
718    if (ret) {
719	free(name);
720	return ret;
721    }
722
723    ret = kcm_ccache_resolve_client(context, client, opcode,
724				    name, &ccache);
725    if (ret) {
726	free(name);
727	return ret;
728    }
729
730    ret = kcm_chmod(context, client, ccache, mode);
731
732    free(name);
733    kcm_release_ccache(context, ccache);
734
735    return ret;
736}
737
738/*
739 * Protocol extensions for moving ticket acquisition responsibility
740 * from client to KCM follow.
741 */
742
743/*
744 * Request:
745 *	NameZ
746 *	ServerPrincipalPresent
747 *	ServerPrincipal OPTIONAL
748 *	Key
749 *
750 * Repsonse:
751 *
752 */
753static krb5_error_code
754kcm_op_get_initial_ticket(krb5_context context,
755			  kcm_client *client,
756			  kcm_operation opcode,
757			  krb5_storage *request,
758			  krb5_storage *response)
759{
760    krb5_error_code ret;
761    kcm_ccache ccache;
762    char *name;
763    int8_t not_tgt = 0;
764    krb5_principal server = NULL;
765    krb5_keyblock key;
766
767    krb5_keyblock_zero(&key);
768
769    ret = krb5_ret_stringz(request, &name);
770    if (ret)
771	return ret;
772
773    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
774
775    ret = krb5_ret_int8(request, &not_tgt);
776    if (ret) {
777	free(name);
778	return ret;
779    }
780
781    if (not_tgt) {
782	ret = krb5_ret_principal(request, &server);
783	if (ret) {
784	    free(name);
785	    return ret;
786	}
787    }
788
789    ret = krb5_ret_keyblock(request, &key);
790    if (ret) {
791	free(name);
792	if (server != NULL)
793	    krb5_free_principal(context, server);
794	return ret;
795    }
796
797    ret = kcm_ccache_resolve_client(context, client, opcode,
798				    name, &ccache);
799    if (ret == 0) {
800	HEIMDAL_MUTEX_lock(&ccache->mutex);
801
802	if (ccache->server != NULL) {
803	    krb5_free_principal(context, ccache->server);
804	    ccache->server = NULL;
805	}
806
807	krb5_free_keyblock(context, &ccache->key.keyblock);
808
809	ccache->server = server;
810	ccache->key.keyblock = key;
811    	ccache->flags |= KCM_FLAGS_USE_CACHED_KEY;
812
813	ret = kcm_ccache_enqueue_default(context, ccache, NULL);
814	if (ret) {
815	    ccache->server = NULL;
816	    krb5_keyblock_zero(&ccache->key.keyblock);
817	    ccache->flags &= ~(KCM_FLAGS_USE_CACHED_KEY);
818	}
819
820	HEIMDAL_MUTEX_unlock(&ccache->mutex);
821    }
822
823    free(name);
824
825    if (ret != 0) {
826	krb5_free_principal(context, server);
827	krb5_free_keyblock(context, &key);
828    }
829
830    kcm_release_ccache(context, ccache);
831
832    return ret;
833}
834
835/*
836 * Request:
837 *	NameZ
838 *	ServerPrincipal
839 *	KDCFlags
840 *	EncryptionType
841 *
842 * Repsonse:
843 *
844 */
845static krb5_error_code
846kcm_op_get_ticket(krb5_context context,
847		  kcm_client *client,
848		  kcm_operation opcode,
849		  krb5_storage *request,
850		  krb5_storage *response)
851{
852    krb5_error_code ret;
853    kcm_ccache ccache;
854    char *name;
855    krb5_principal server = NULL;
856    krb5_ccache_data ccdata;
857    krb5_creds in, *out;
858    krb5_kdc_flags flags;
859
860    memset(&in, 0, sizeof(in));
861
862    ret = krb5_ret_stringz(request, &name);
863    if (ret)
864	return ret;
865
866    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
867
868    ret = krb5_ret_uint32(request, &flags.i);
869    if (ret) {
870	free(name);
871	return ret;
872    }
873
874    ret = krb5_ret_int32(request, &in.session.keytype);
875    if (ret) {
876	free(name);
877	return ret;
878    }
879
880    ret = krb5_ret_principal(request, &server);
881    if (ret) {
882	free(name);
883	return ret;
884    }
885
886    ret = kcm_ccache_resolve_client(context, client, opcode,
887				    name, &ccache);
888    if (ret) {
889	krb5_free_principal(context, server);
890	free(name);
891	return ret;
892    }
893
894    HEIMDAL_MUTEX_lock(&ccache->mutex);
895
896    /* Fake up an internal ccache */
897    kcm_internal_ccache(context, ccache, &ccdata);
898
899    in.client = ccache->client;
900    in.server = server;
901    in.times.endtime = 0;
902
903    /* glue cc layer will store creds */
904    ret = krb5_get_credentials_with_flags(context, 0, flags,
905					  &ccdata, &in, &out);
906
907    HEIMDAL_MUTEX_unlock(&ccache->mutex);
908
909    krb5_free_principal(context, server);
910
911    if (ret == 0)
912	krb5_free_cred_contents(context, out);
913
914    kcm_release_ccache(context, ccache);
915    free(name);
916
917    return ret;
918}
919
920/*
921 * Request:
922 *	OldNameZ
923 *	NewNameZ
924 *
925 * Repsonse:
926 *
927 */
928static krb5_error_code
929kcm_op_move_cache(krb5_context context,
930		  kcm_client *client,
931		  kcm_operation opcode,
932		  krb5_storage *request,
933		  krb5_storage *response)
934{
935    krb5_error_code ret;
936    kcm_ccache oldid, newid;
937    char *oldname, *newname;
938
939    ret = krb5_ret_stringz(request, &oldname);
940    if (ret)
941	return ret;
942
943    KCM_LOG_REQUEST_NAME(context, client, opcode, oldname);
944
945    ret = krb5_ret_stringz(request, &newname);
946    if (ret) {
947	free(oldname);
948	return ret;
949    }
950
951    /* move to ourself is simple, done! */
952    if (strcmp(oldname, newname) == 0) {
953	free(oldname);
954	free(newname);
955	return 0;
956    }
957
958    ret = kcm_ccache_resolve_client(context, client, opcode, oldname, &oldid);
959    if (ret) {
960	free(oldname);
961	free(newname);
962	return ret;
963    }
964
965    /* Check if new credential cache exists, if not create one. */
966    ret = kcm_ccache_resolve_client(context, client, opcode, newname, &newid);
967    if (ret == KRB5_FCC_NOFILE)
968	ret = kcm_ccache_new_client(context, client, newname, &newid);
969    free(newname);
970
971    if (ret) {
972	free(oldname);
973	kcm_release_ccache(context, oldid);
974	return ret;
975    }
976
977    HEIMDAL_MUTEX_lock(&oldid->mutex);
978    HEIMDAL_MUTEX_lock(&newid->mutex);
979
980    /* move content */
981    {
982	kcm_ccache_data tmp;
983
984#define MOVE(n,o,f) { tmp.f = n->f ; n->f = o->f; o->f = tmp.f; }
985
986	MOVE(newid, oldid, flags);
987	MOVE(newid, oldid, client);
988	MOVE(newid, oldid, server);
989	MOVE(newid, oldid, creds);
990	MOVE(newid, oldid, tkt_life);
991	MOVE(newid, oldid, renew_life);
992	MOVE(newid, oldid, key);
993	MOVE(newid, oldid, kdc_offset);
994#undef MOVE
995    }
996
997    HEIMDAL_MUTEX_unlock(&oldid->mutex);
998    HEIMDAL_MUTEX_unlock(&newid->mutex);
999
1000    kcm_release_ccache(context, oldid);
1001    kcm_release_ccache(context, newid);
1002
1003    ret = kcm_ccache_destroy_client(context, client, oldname);
1004    if (ret == 0)
1005	kcm_drop_default_cache(context, client, oldname);
1006
1007    free(oldname);
1008
1009    return ret;
1010}
1011
1012static krb5_error_code
1013kcm_op_get_cache_uuid_list(krb5_context context,
1014			   kcm_client *client,
1015			   kcm_operation opcode,
1016			   krb5_storage *request,
1017			   krb5_storage *response)
1018{
1019    KCM_LOG_REQUEST(context, client, opcode);
1020
1021    return kcm_ccache_get_uuids(context, client, opcode, response);
1022}
1023
1024static krb5_error_code
1025kcm_op_get_cache_by_uuid(krb5_context context,
1026			 kcm_client *client,
1027			 kcm_operation opcode,
1028			 krb5_storage *request,
1029			 krb5_storage *response)
1030{
1031    krb5_error_code ret;
1032    kcmuuid_t uuid;
1033    ssize_t sret;
1034    kcm_ccache cache;
1035
1036    KCM_LOG_REQUEST(context, client, opcode);
1037
1038    sret = krb5_storage_read(request, &uuid, sizeof(uuid));
1039    if (sret != sizeof(uuid)) {
1040	krb5_clear_error_message(context);
1041	return KRB5_CC_IO;
1042    }
1043
1044    ret = kcm_ccache_resolve_by_uuid(context, uuid, &cache);
1045    if (ret)
1046	return ret;
1047
1048    ret = kcm_access(context, client, opcode, cache);
1049    if (ret)
1050	ret = KRB5_FCC_NOFILE;
1051
1052    if (ret == 0)
1053	ret = krb5_store_stringz(response, cache->name);
1054
1055    kcm_release_ccache(context, cache);
1056
1057    return ret;
1058}
1059
1060struct kcm_default_cache *default_caches;
1061
1062static krb5_error_code
1063kcm_op_get_default_cache(krb5_context context,
1064			 kcm_client *client,
1065			 kcm_operation opcode,
1066			 krb5_storage *request,
1067			 krb5_storage *response)
1068{
1069    struct kcm_default_cache *c;
1070    krb5_error_code ret;
1071    const char *name = NULL;
1072    char *n = NULL;
1073
1074    KCM_LOG_REQUEST(context, client, opcode);
1075
1076    for (c = default_caches; c != NULL; c = c->next) {
1077	if (kcm_is_same_session(client, c->uid, c->session)) {
1078	    name = c->name;
1079	    break;
1080	}
1081    }
1082    if (name == NULL)
1083	name = n = kcm_ccache_first_name(client);
1084
1085    if (name == NULL) {
1086	asprintf(&n, "%d", (int)client->uid);
1087	name = n;
1088    }
1089    if (name == NULL)
1090	return ENOMEM;
1091    ret = krb5_store_stringz(response, name);
1092    if (n)
1093	free(n);
1094    return ret;
1095}
1096
1097static void
1098kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name)
1099{
1100    struct kcm_default_cache **c;
1101
1102    for (c = &default_caches; *c != NULL; c = &(*c)->next) {
1103	if (!kcm_is_same_session(client, (*c)->uid, (*c)->session))
1104	    continue;
1105	if (strcmp((*c)->name, name) == 0) {
1106	    struct kcm_default_cache *h = *c;
1107	    *c = (*c)->next;
1108	    free(h->name);
1109	    free(h);
1110	    break;
1111	}
1112    }
1113}
1114
1115static krb5_error_code
1116kcm_op_set_default_cache(krb5_context context,
1117			 kcm_client *client,
1118			 kcm_operation opcode,
1119			 krb5_storage *request,
1120			 krb5_storage *response)
1121{
1122    struct kcm_default_cache *c;
1123    krb5_error_code ret;
1124    char *name;
1125
1126    ret = krb5_ret_stringz(request, &name);
1127    if (ret)
1128	return ret;
1129
1130    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1131
1132    for (c = default_caches; c != NULL; c = c->next) {
1133	if (kcm_is_same_session(client, c->uid, c->session))
1134	    break;
1135    }
1136    if (c == NULL) {
1137	c = malloc(sizeof(*c));
1138	if (c == NULL)
1139	    return ENOMEM;
1140	c->session = client->session;
1141	c->uid = client->uid;
1142	c->name = strdup(name);
1143
1144	c->next = default_caches;
1145	default_caches = c;
1146    } else {
1147	free(c->name);
1148	c->name = strdup(name);
1149    }
1150
1151    return 0;
1152}
1153
1154static krb5_error_code
1155kcm_op_get_kdc_offset(krb5_context context,
1156		      kcm_client *client,
1157		      kcm_operation opcode,
1158		      krb5_storage *request,
1159		      krb5_storage *response)
1160{
1161    krb5_error_code ret;
1162    kcm_ccache ccache;
1163    char *name;
1164
1165    ret = krb5_ret_stringz(request, &name);
1166    if (ret)
1167	return ret;
1168
1169    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1170
1171    ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1172    free(name);
1173    if (ret)
1174	return ret;
1175
1176    HEIMDAL_MUTEX_lock(&ccache->mutex);
1177    ret = krb5_store_int32(response, ccache->kdc_offset);
1178    HEIMDAL_MUTEX_unlock(&ccache->mutex);
1179
1180    kcm_release_ccache(context, ccache);
1181
1182    return ret;
1183}
1184
1185static krb5_error_code
1186kcm_op_set_kdc_offset(krb5_context context,
1187		      kcm_client *client,
1188		      kcm_operation opcode,
1189		      krb5_storage *request,
1190		      krb5_storage *response)
1191{
1192    krb5_error_code ret;
1193    kcm_ccache ccache;
1194    int32_t offset;
1195    char *name;
1196
1197    ret = krb5_ret_stringz(request, &name);
1198    if (ret)
1199	return ret;
1200
1201    KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1202
1203    ret = krb5_ret_int32(request, &offset);
1204    if (ret) {
1205	free(name);
1206	return ret;
1207    }
1208
1209    ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1210    free(name);
1211    if (ret)
1212	return ret;
1213
1214    HEIMDAL_MUTEX_lock(&ccache->mutex);
1215    ccache->kdc_offset = offset;
1216    HEIMDAL_MUTEX_unlock(&ccache->mutex);
1217
1218    kcm_release_ccache(context, ccache);
1219
1220    return ret;
1221}
1222
1223struct kcm_ntlm_cred {
1224    kcmuuid_t uuid;
1225    char *user;
1226    char *domain;
1227    krb5_data nthash;
1228    uid_t uid;
1229    pid_t session;
1230    struct kcm_ntlm_cred *next;
1231};
1232
1233static struct kcm_ntlm_cred *ntlm_head;
1234
1235static void
1236free_cred(struct kcm_ntlm_cred *cred)
1237{
1238    free(cred->user);
1239    free(cred->domain);
1240    krb5_data_free(&cred->nthash);
1241    free(cred);
1242}
1243
1244
1245/*
1246 * name
1247 * domain
1248 * ntlm hash
1249 *
1250 * Reply:
1251 *   uuid
1252 */
1253
1254static struct kcm_ntlm_cred *
1255find_ntlm_cred(const char *user, const char *domain, kcm_client *client)
1256{
1257    struct kcm_ntlm_cred *c;
1258
1259    for (c = ntlm_head; c != NULL; c = c->next)
1260	if ((user[0] == '\0' || strcmp(user, c->user) == 0) &&
1261	    (domain == NULL || strcmp(domain, c->domain) == 0) &&
1262	    kcm_is_same_session(client, c->uid, c->session))
1263	    return c;
1264
1265    return NULL;
1266}
1267
1268static krb5_error_code
1269kcm_op_add_ntlm_cred(krb5_context context,
1270		     kcm_client *client,
1271		     kcm_operation opcode,
1272		     krb5_storage *request,
1273		     krb5_storage *response)
1274{
1275    struct kcm_ntlm_cred *cred, *c;
1276    krb5_error_code ret;
1277
1278    cred = calloc(1, sizeof(*cred));
1279    if (cred == NULL)
1280	return ENOMEM;
1281
1282    RAND_bytes(cred->uuid, sizeof(cred->uuid));
1283
1284    ret = krb5_ret_stringz(request, &cred->user);
1285    if (ret)
1286	goto error;
1287
1288    ret = krb5_ret_stringz(request, &cred->domain);
1289    if (ret)
1290	goto error;
1291
1292    ret = krb5_ret_data(request, &cred->nthash);
1293    if (ret)
1294	goto error;
1295
1296    /* search for dups */
1297    c = find_ntlm_cred(cred->user, cred->domain, client);
1298    if (c) {
1299	krb5_data hash = c->nthash;
1300	c->nthash = cred->nthash;
1301	cred->nthash = hash;
1302	free_cred(cred);
1303	cred = c;
1304    } else {
1305	cred->next = ntlm_head;
1306	ntlm_head = cred;
1307    }
1308
1309    cred->uid = client->uid;
1310    cred->session = client->session;
1311
1312    /* write response */
1313    (void)krb5_storage_write(response, &cred->uuid, sizeof(cred->uuid));
1314
1315    return 0;
1316
1317 error:
1318    free_cred(cred);
1319
1320    return ret;
1321}
1322
1323/*
1324 * { "HAVE_NTLM_CRED",		NULL },
1325 *
1326 * input:
1327 *  name
1328 *  domain
1329 */
1330
1331static krb5_error_code
1332kcm_op_have_ntlm_cred(krb5_context context,
1333		     kcm_client *client,
1334		     kcm_operation opcode,
1335		     krb5_storage *request,
1336		     krb5_storage *response)
1337{
1338    struct kcm_ntlm_cred *c;
1339    char *user = NULL, *domain = NULL;
1340    krb5_error_code ret;
1341
1342    ret = krb5_ret_stringz(request, &user);
1343    if (ret)
1344	goto error;
1345
1346    ret = krb5_ret_stringz(request, &domain);
1347    if (ret)
1348	goto error;
1349
1350    if (domain[0] == '\0') {
1351	free(domain);
1352	domain = NULL;
1353    }
1354
1355    c = find_ntlm_cred(user, domain, client);
1356    if (c == NULL)
1357	ret = ENOENT;
1358
1359 error:
1360    free(user);
1361    if (domain)
1362	free(domain);
1363
1364    return ret;
1365}
1366
1367/*
1368 * { "DEL_NTLM_CRED",		NULL },
1369 *
1370 * input:
1371 *  name
1372 *  domain
1373 */
1374
1375static krb5_error_code
1376kcm_op_del_ntlm_cred(krb5_context context,
1377		     kcm_client *client,
1378		     kcm_operation opcode,
1379		     krb5_storage *request,
1380		     krb5_storage *response)
1381{
1382    struct kcm_ntlm_cred **cp, *c;
1383    char *user = NULL, *domain = NULL;
1384    krb5_error_code ret;
1385
1386    ret = krb5_ret_stringz(request, &user);
1387    if (ret)
1388	goto error;
1389
1390    ret = krb5_ret_stringz(request, &domain);
1391    if (ret)
1392	goto error;
1393
1394    for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
1395	if (strcmp(user, (*cp)->user) == 0 && strcmp(domain, (*cp)->domain) == 0 &&
1396	    kcm_is_same_session(client, (*cp)->uid, (*cp)->session))
1397	{
1398	    c = *cp;
1399	    *cp = c->next;
1400
1401	    free_cred(c);
1402	    break;
1403	}
1404    }
1405
1406 error:
1407    free(user);
1408    free(domain);
1409
1410    return ret;
1411}
1412
1413/*
1414 * { "DO_NTLM_AUTH",		NULL },
1415 *
1416 * input:
1417 *  name:string
1418 *  domain:string
1419 *  type2:data
1420 *
1421 * reply:
1422 *  type3:data
1423 *  flags:int32
1424 *  session-key:data
1425 */
1426
1427#define NTLM_FLAG_SESSIONKEY 1
1428#define NTLM_FLAG_NTLM2_SESSION 2
1429#define NTLM_FLAG_KEYEX 4
1430
1431static krb5_error_code
1432kcm_op_do_ntlm(krb5_context context,
1433	       kcm_client *client,
1434	       kcm_operation opcode,
1435	       krb5_storage *request,
1436	       krb5_storage *response)
1437{
1438    struct kcm_ntlm_cred *c;
1439    struct ntlm_type2 type2;
1440    struct ntlm_type3 type3;
1441    char *user = NULL, *domain = NULL;
1442    struct ntlm_buf ndata, sessionkey;
1443    krb5_data data;
1444    krb5_error_code ret;
1445    uint32_t flags = 0;
1446
1447    memset(&type2, 0, sizeof(type2));
1448    memset(&type3, 0, sizeof(type3));
1449    sessionkey.data = NULL;
1450    sessionkey.length = 0;
1451
1452    ret = krb5_ret_stringz(request, &user);
1453    if (ret)
1454	goto error;
1455
1456    ret = krb5_ret_stringz(request, &domain);
1457    if (ret)
1458	goto error;
1459
1460    if (domain[0] == '\0') {
1461	free(domain);
1462	domain = NULL;
1463    }
1464
1465    c = find_ntlm_cred(user, domain, client);
1466    if (c == NULL) {
1467	ret = EINVAL;
1468	goto error;
1469    }
1470
1471    ret = krb5_ret_data(request, &data);
1472    if (ret)
1473	goto error;
1474
1475    ndata.data = data.data;
1476    ndata.length = data.length;
1477
1478    ret = heim_ntlm_decode_type2(&ndata, &type2);
1479    krb5_data_free(&data);
1480    if (ret)
1481	goto error;
1482
1483    if (domain && strcmp(domain, type2.targetname) == 0) {
1484	ret = EINVAL;
1485	goto error;
1486    }
1487
1488    type3.username = c->user;
1489    type3.flags = type2.flags;
1490    type3.targetname = type2.targetname;
1491    type3.ws = rk_UNCONST("workstation");
1492
1493    /*
1494     * NTLM Version 1 if no targetinfo buffer.
1495     */
1496
1497    if (1 || type2.targetinfo.length == 0) {
1498	struct ntlm_buf sessionkey;
1499
1500	if (type2.flags & NTLM_NEG_NTLM2_SESSION) {
1501	    unsigned char nonce[8];
1502
1503	    if (RAND_bytes(nonce, sizeof(nonce)) != 1) {
1504		ret = EINVAL;
1505		goto error;
1506	    }
1507
1508	    ret = heim_ntlm_calculate_ntlm2_sess(nonce,
1509						 type2.challenge,
1510						 c->nthash.data,
1511						 &type3.lm,
1512						 &type3.ntlm);
1513	} else {
1514	    ret = heim_ntlm_calculate_ntlm1(c->nthash.data,
1515					    c->nthash.length,
1516					    type2.challenge,
1517					    &type3.ntlm);
1518
1519	}
1520	if (ret)
1521	    goto error;
1522
1523	ret = heim_ntlm_build_ntlm1_master(c->nthash.data,
1524					   c->nthash.length,
1525					   &sessionkey,
1526					   &type3.sessionkey);
1527	if (ret) {
1528	    if (type3.lm.data)
1529		free(type3.lm.data);
1530	    if (type3.ntlm.data)
1531		free(type3.ntlm.data);
1532	    goto error;
1533	}
1534
1535	free(sessionkey.data);
1536	if (ret) {
1537	    if (type3.lm.data)
1538		free(type3.lm.data);
1539	    if (type3.ntlm.data)
1540		free(type3.ntlm.data);
1541	    goto error;
1542	}
1543	flags |= NTLM_FLAG_SESSIONKEY;
1544#if 0
1545    } else {
1546	struct ntlm_buf sessionkey;
1547	unsigned char ntlmv2[16];
1548	struct ntlm_targetinfo ti;
1549
1550	/* verify infotarget */
1551
1552	ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti);
1553	if(ret) {
1554	    _gss_ntlm_delete_sec_context(minor_status,
1555					 context_handle, NULL);
1556	    *minor_status = ret;
1557	    return GSS_S_FAILURE;
1558	}
1559
1560	if (ti.domainname && strcmp(ti.domainname, name->domain) != 0) {
1561	    _gss_ntlm_delete_sec_context(minor_status,
1562					 context_handle, NULL);
1563	    *minor_status = EINVAL;
1564	    return GSS_S_FAILURE;
1565	}
1566
1567	ret = heim_ntlm_calculate_ntlm2(ctx->client->key.data,
1568					ctx->client->key.length,
1569					type3.username,
1570					name->domain,
1571					type2.challenge,
1572					&type2.targetinfo,
1573					ntlmv2,
1574					&type3.ntlm);
1575	if (ret) {
1576	    _gss_ntlm_delete_sec_context(minor_status,
1577					 context_handle, NULL);
1578	    *minor_status = ret;
1579	    return GSS_S_FAILURE;
1580	}
1581
1582	ret = heim_ntlm_build_ntlm1_master(ntlmv2, sizeof(ntlmv2),
1583					   &sessionkey,
1584					   &type3.sessionkey);
1585	memset(ntlmv2, 0, sizeof(ntlmv2));
1586	if (ret) {
1587	    _gss_ntlm_delete_sec_context(minor_status,
1588					 context_handle, NULL);
1589	    *minor_status = ret;
1590	    return GSS_S_FAILURE;
1591	}
1592
1593	flags |= NTLM_FLAG_NTLM2_SESSION |
1594	         NTLM_FLAG_SESSION;
1595
1596	if (type3.flags & NTLM_NEG_KEYEX)
1597	    flags |= NTLM_FLAG_KEYEX;
1598
1599	ret = krb5_data_copy(&ctx->sessionkey,
1600			     sessionkey.data, sessionkey.length);
1601	free(sessionkey.data);
1602	if (ret) {
1603	    _gss_ntlm_delete_sec_context(minor_status,
1604					 context_handle, NULL);
1605	    *minor_status = ret;
1606	    return GSS_S_FAILURE;
1607	}
1608#endif
1609    }
1610
1611#if 0
1612    if (flags & NTLM_FLAG_NTLM2_SESSION) {
1613	_gss_ntlm_set_key(&ctx->u.v2.send, 0, (ctx->flags & NTLM_NEG_KEYEX),
1614			  ctx->sessionkey.data,
1615			  ctx->sessionkey.length);
1616	_gss_ntlm_set_key(&ctx->u.v2.recv, 1, (ctx->flags & NTLM_NEG_KEYEX),
1617			  ctx->sessionkey.data,
1618			  ctx->sessionkey.length);
1619    } else {
1620	flags |= NTLM_FLAG_SESSION;
1621	RC4_set_key(&ctx->u.v1.crypto_recv.key,
1622		    ctx->sessionkey.length,
1623		    ctx->sessionkey.data);
1624	RC4_set_key(&ctx->u.v1.crypto_send.key,
1625		    ctx->sessionkey.length,
1626		    ctx->sessionkey.data);
1627    }
1628#endif
1629
1630    ret = heim_ntlm_encode_type3(&type3, &ndata);
1631    if (ret)
1632	goto error;
1633
1634    data.data = ndata.data;
1635    data.length = ndata.length;
1636    ret = krb5_store_data(response, data);
1637    heim_ntlm_free_buf(&ndata);
1638    if (ret) goto error;
1639
1640    ret = krb5_store_int32(response, flags);
1641    if (ret) goto error;
1642
1643    data.data = sessionkey.data;
1644    data.length = sessionkey.length;
1645
1646    ret = krb5_store_data(response, data);
1647    if (ret) goto error;
1648
1649 error:
1650    free(type3.username);
1651    heim_ntlm_free_type2(&type2);
1652    free(user);
1653    if (domain)
1654	free(domain);
1655
1656    return ret;
1657}
1658
1659
1660/*
1661 * { "GET_NTLM_UUID_LIST",	NULL }
1662 *
1663 * reply:
1664 *   1 user domain
1665 *   0 [ end of list ]
1666 */
1667
1668static krb5_error_code
1669kcm_op_get_ntlm_user_list(krb5_context context,
1670			  kcm_client *client,
1671			  kcm_operation opcode,
1672			  krb5_storage *request,
1673			  krb5_storage *response)
1674{
1675    struct kcm_ntlm_cred *c;
1676    krb5_error_code ret;
1677
1678    for (c = ntlm_head; c != NULL; c = c->next) {
1679	if (!kcm_is_same_session(client, c->uid, c->session))
1680	    continue;
1681
1682	ret = krb5_store_uint32(response, 1);
1683	if (ret)
1684	    return ret;
1685	ret = krb5_store_stringz(response, c->user);
1686	if (ret)
1687	    return ret;
1688	ret = krb5_store_stringz(response, c->domain);
1689	if (ret)
1690	    return ret;
1691    }
1692    return krb5_store_uint32(response, 0);
1693}
1694
1695/*
1696 *
1697 */
1698
1699static struct kcm_op kcm_ops[] = {
1700    { "NOOP", 			kcm_op_noop },
1701    { "GET_NAME",		kcm_op_get_name },
1702    { "RESOLVE",		kcm_op_noop },
1703    { "GEN_NEW", 		kcm_op_gen_new },
1704    { "INITIALIZE",		kcm_op_initialize },
1705    { "DESTROY",		kcm_op_destroy },
1706    { "STORE",			kcm_op_store },
1707    { "RETRIEVE",		kcm_op_retrieve },
1708    { "GET_PRINCIPAL",		kcm_op_get_principal },
1709    { "GET_CRED_UUID_LIST",	kcm_op_get_cred_uuid_list },
1710    { "GET_CRED_BY_UUID",	kcm_op_get_cred_by_uuid },
1711    { "REMOVE_CRED",		kcm_op_remove_cred },
1712    { "SET_FLAGS",		kcm_op_set_flags },
1713    { "CHOWN",			kcm_op_chown },
1714    { "CHMOD",			kcm_op_chmod },
1715    { "GET_INITIAL_TICKET",	kcm_op_get_initial_ticket },
1716    { "GET_TICKET",		kcm_op_get_ticket },
1717    { "MOVE_CACHE",		kcm_op_move_cache },
1718    { "GET_CACHE_UUID_LIST",	kcm_op_get_cache_uuid_list },
1719    { "GET_CACHE_BY_UUID",	kcm_op_get_cache_by_uuid },
1720    { "GET_DEFAULT_CACHE",      kcm_op_get_default_cache },
1721    { "SET_DEFAULT_CACHE",      kcm_op_set_default_cache },
1722    { "GET_KDC_OFFSET",      	kcm_op_get_kdc_offset },
1723    { "SET_KDC_OFFSET",      	kcm_op_set_kdc_offset },
1724    { "ADD_NTLM_CRED",		kcm_op_add_ntlm_cred },
1725    { "HAVE_USER_CRED",		kcm_op_have_ntlm_cred },
1726    { "DEL_NTLM_CRED",		kcm_op_del_ntlm_cred },
1727    { "DO_NTLM_AUTH",		kcm_op_do_ntlm },
1728    { "GET_NTLM_USER_LIST",	kcm_op_get_ntlm_user_list }
1729};
1730
1731
1732const char *
1733kcm_op2string(kcm_operation opcode)
1734{
1735    if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0]))
1736	return "Unknown operation";
1737
1738    return kcm_ops[opcode].name;
1739}
1740
1741krb5_error_code
1742kcm_dispatch(krb5_context context,
1743	     kcm_client *client,
1744	     krb5_data *req_data,
1745	     krb5_data *resp_data)
1746{
1747    krb5_error_code ret;
1748    kcm_method method;
1749    krb5_storage *req_sp = NULL;
1750    krb5_storage *resp_sp = NULL;
1751    uint16_t opcode;
1752
1753    resp_sp = krb5_storage_emem();
1754    if (resp_sp == NULL) {
1755	return ENOMEM;
1756    }
1757
1758    if (client->pid == -1) {
1759	kcm_log(0, "Client had invalid process number");
1760	ret = KRB5_FCC_INTERNAL;
1761	goto out;
1762    }
1763
1764    req_sp = krb5_storage_from_data(req_data);
1765    if (req_sp == NULL) {
1766	kcm_log(0, "Process %d: failed to initialize storage from data",
1767		client->pid);
1768	ret = KRB5_CC_IO;
1769	goto out;
1770    }
1771
1772    ret = krb5_ret_uint16(req_sp, &opcode);
1773    if (ret) {
1774	kcm_log(0, "Process %d: didn't send a message", client->pid);
1775	goto out;
1776    }
1777
1778    if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0])) {
1779	kcm_log(0, "Process %d: invalid operation code %d",
1780		client->pid, opcode);
1781	ret = KRB5_FCC_INTERNAL;
1782	goto out;
1783    }
1784    method = kcm_ops[opcode].method;
1785    if (method == NULL) {
1786	kcm_log(0, "Process %d: operation code %s not implemented",
1787		client->pid, kcm_op2string(opcode));
1788	ret = KRB5_FCC_INTERNAL;
1789	goto out;
1790    }
1791
1792    /* seek past place for status code */
1793    krb5_storage_seek(resp_sp, 4, SEEK_SET);
1794
1795    ret = (*method)(context, client, opcode, req_sp, resp_sp);
1796
1797out:
1798    if (req_sp != NULL) {
1799	krb5_storage_free(req_sp);
1800    }
1801
1802    krb5_storage_seek(resp_sp, 0, SEEK_SET);
1803    krb5_store_int32(resp_sp, ret);
1804
1805    ret = krb5_storage_to_data(resp_sp, resp_data);
1806    krb5_storage_free(resp_sp);
1807
1808    return ret;
1809}
1810
1811