]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - crypto/heimdal/kcm/protocol.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / crypto / heimdal / kcm / protocol.c
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
38 static void
39 kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name);
40
41
42 int
43 kcm_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
53 static krb5_error_code
54 kcm_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  */
72 static krb5_error_code
73 kcm_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  */
115 static krb5_error_code
116 kcm_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  */
146 static krb5_error_code
147 kcm_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  */
212 static krb5_error_code
213 kcm_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  */
245 static krb5_error_code
246 kcm_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  */
303 static krb5_error_code
304 kcm_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  */
394 static krb5_error_code
395 kcm_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  */
437 static krb5_error_code
438 kcm_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  */
483 static krb5_error_code
484 kcm_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  */
540 static krb5_error_code
541 kcm_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  */
598 static krb5_error_code
599 kcm_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  */
645 static krb5_error_code
646 kcm_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  */
699 static krb5_error_code
700 kcm_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  */
753 static krb5_error_code
754 kcm_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  */
845 static krb5_error_code
846 kcm_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  */
928 static krb5_error_code
929 kcm_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
1012 static krb5_error_code
1013 kcm_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
1024 static krb5_error_code
1025 kcm_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
1060 struct kcm_default_cache *default_caches;
1061
1062 static krb5_error_code
1063 kcm_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
1097 static void
1098 kcm_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
1115 static krb5_error_code
1116 kcm_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
1154 static krb5_error_code
1155 kcm_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
1185 static krb5_error_code
1186 kcm_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
1223 struct 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
1233 static struct kcm_ntlm_cred *ntlm_head;
1234
1235 static void
1236 free_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
1254 static struct kcm_ntlm_cred *
1255 find_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
1268 static krb5_error_code
1269 kcm_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
1331 static krb5_error_code
1332 kcm_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
1375 static krb5_error_code
1376 kcm_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
1431 static krb5_error_code
1432 kcm_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
1668 static krb5_error_code
1669 kcm_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
1699 static 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
1732 const char *
1733 kcm_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
1741 krb5_error_code
1742 kcm_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
1797 out:
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