Don't return a freed pointer in allocate_ccache()
[abartlet/lorikeet-heimdal.git/.git] / 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     ret = kcm_ccache_resolve_client(context, client, opcode, oldname, &oldid);
952     if (ret) {
953         free(oldname);
954         free(newname);
955         return ret;
956     }
957
958     /* Check if new credential cache exists, if not create one. */
959     ret = kcm_ccache_resolve_client(context, client, opcode, newname, &newid);
960     if (ret == KRB5_FCC_NOFILE)
961         ret = kcm_ccache_new_client(context, client, newname, &newid);
962     free(newname);
963
964     if (ret) {
965         free(oldname);
966         kcm_release_ccache(context, oldid);
967         return ret;
968     }
969
970     HEIMDAL_MUTEX_lock(&oldid->mutex);
971     HEIMDAL_MUTEX_lock(&newid->mutex);
972
973     /* move content */
974     {
975         kcm_ccache_data tmp;
976
977 #define MOVE(n,o,f) { tmp.f = n->f ; n->f = o->f; o->f = tmp.f; }
978
979         MOVE(newid, oldid, flags);
980         MOVE(newid, oldid, client);
981         MOVE(newid, oldid, server);
982         MOVE(newid, oldid, creds);
983         MOVE(newid, oldid, tkt_life);
984         MOVE(newid, oldid, renew_life);
985         MOVE(newid, oldid, key);
986         MOVE(newid, oldid, kdc_offset);
987 #undef MOVE
988     }
989
990     HEIMDAL_MUTEX_unlock(&oldid->mutex);
991     HEIMDAL_MUTEX_unlock(&newid->mutex);
992
993     kcm_release_ccache(context, oldid);
994     kcm_release_ccache(context, newid);
995
996     ret = kcm_ccache_destroy_client(context, client, oldname);
997     if (ret == 0)
998         kcm_drop_default_cache(context, client, oldname);
999
1000     free(oldname);
1001
1002     return ret;
1003 }
1004
1005 static krb5_error_code
1006 kcm_op_get_cache_uuid_list(krb5_context context,
1007                            kcm_client *client,
1008                            kcm_operation opcode,
1009                            krb5_storage *request,
1010                            krb5_storage *response)
1011 {
1012     KCM_LOG_REQUEST(context, client, opcode);
1013
1014     return kcm_ccache_get_uuids(context, client, opcode, response);
1015 }
1016
1017 static krb5_error_code
1018 kcm_op_get_cache_by_uuid(krb5_context context,
1019                          kcm_client *client,
1020                          kcm_operation opcode,
1021                          krb5_storage *request,
1022                          krb5_storage *response)
1023 {
1024     krb5_error_code ret;
1025     kcmuuid_t uuid;
1026     ssize_t sret;
1027     kcm_ccache cache;
1028
1029     KCM_LOG_REQUEST(context, client, opcode);
1030
1031     sret = krb5_storage_read(request, &uuid, sizeof(uuid));
1032     if (sret != sizeof(uuid)) {
1033         krb5_clear_error_message(context);
1034         return KRB5_CC_IO;
1035     }
1036
1037     ret = kcm_ccache_resolve_by_uuid(context, uuid, &cache);
1038     if (ret)
1039         return ret;
1040
1041     ret = kcm_access(context, client, opcode, cache);
1042     if (ret)
1043         ret = KRB5_FCC_NOFILE;
1044
1045     if (ret == 0)
1046         ret = krb5_store_stringz(response, cache->name);
1047
1048     kcm_release_ccache(context, cache);
1049
1050     return ret;
1051 }
1052
1053 struct kcm_default_cache *default_caches;
1054
1055 static krb5_error_code
1056 kcm_op_get_default_cache(krb5_context context,
1057                          kcm_client *client,
1058                          kcm_operation opcode,
1059                          krb5_storage *request,
1060                          krb5_storage *response)
1061 {
1062     struct kcm_default_cache *c;
1063     krb5_error_code ret;
1064     const char *name = NULL;
1065     char *n = NULL;
1066
1067     KCM_LOG_REQUEST(context, client, opcode);
1068
1069     for (c = default_caches; c != NULL; c = c->next) {
1070         if (kcm_is_same_session(client, c->uid, c->session)) {
1071             name = c->name;
1072             break;
1073         }
1074     }
1075     if (name == NULL)
1076         name = n = kcm_ccache_first_name(client);
1077
1078     if (name == NULL) {
1079         asprintf(&n, "%d", (int)client->uid);
1080         name = n;
1081     }
1082     if (name == NULL)
1083         return ENOMEM;
1084     ret = krb5_store_stringz(response, name);
1085     if (n)
1086         free(n);
1087     return ret;
1088 }
1089
1090 static void
1091 kcm_drop_default_cache(krb5_context context, kcm_client *client, char *name)
1092 {
1093     struct kcm_default_cache **c;
1094
1095     for (c = &default_caches; *c != NULL; c = &(*c)->next) {
1096         if (!kcm_is_same_session(client, (*c)->uid, (*c)->session))
1097             continue;
1098         if (strcmp((*c)->name, name) == 0) {
1099             struct kcm_default_cache *h = *c;
1100             *c = (*c)->next;
1101             free(h->name);
1102             free(h);
1103             break;
1104         }
1105     }
1106 }
1107
1108 static krb5_error_code
1109 kcm_op_set_default_cache(krb5_context context,
1110                          kcm_client *client,
1111                          kcm_operation opcode,
1112                          krb5_storage *request,
1113                          krb5_storage *response)
1114 {
1115     struct kcm_default_cache *c;
1116     krb5_error_code ret;
1117     char *name;
1118
1119     ret = krb5_ret_stringz(request, &name);
1120     if (ret)
1121         return ret;
1122
1123     KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1124
1125     for (c = default_caches; c != NULL; c = c->next) {
1126         if (kcm_is_same_session(client, c->uid, c->session))
1127             break;
1128     }
1129     if (c == NULL) {
1130         c = malloc(sizeof(*c));
1131         if (c == NULL)
1132             return ENOMEM;
1133         c->session = client->session;
1134         c->uid = client->uid;
1135         c->name = strdup(name);
1136
1137         c->next = default_caches;
1138         default_caches = c;
1139     } else {
1140         free(c->name);
1141         c->name = strdup(name);
1142     }
1143
1144     return 0;
1145 }
1146
1147 static krb5_error_code
1148 kcm_op_get_kdc_offset(krb5_context context,
1149                       kcm_client *client,
1150                       kcm_operation opcode,
1151                       krb5_storage *request,
1152                       krb5_storage *response)
1153 {
1154     krb5_error_code ret;
1155     kcm_ccache ccache;
1156     char *name;
1157
1158     ret = krb5_ret_stringz(request, &name);
1159     if (ret)
1160         return ret;
1161
1162     KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1163
1164     ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1165     free(name);
1166     if (ret)
1167         return ret;
1168
1169     HEIMDAL_MUTEX_lock(&ccache->mutex);
1170     ret = krb5_store_int32(response, ccache->kdc_offset);
1171     HEIMDAL_MUTEX_unlock(&ccache->mutex);
1172
1173     kcm_release_ccache(context, ccache);
1174
1175     return ret;
1176 }
1177
1178 static krb5_error_code
1179 kcm_op_set_kdc_offset(krb5_context context,
1180                       kcm_client *client,
1181                       kcm_operation opcode,
1182                       krb5_storage *request,
1183                       krb5_storage *response)
1184 {
1185     krb5_error_code ret;
1186     kcm_ccache ccache;
1187     int32_t offset;
1188     char *name;
1189
1190     ret = krb5_ret_stringz(request, &name);
1191     if (ret)
1192         return ret;
1193
1194     KCM_LOG_REQUEST_NAME(context, client, opcode, name);
1195
1196     ret = krb5_ret_int32(request, &offset);
1197     if (ret) {
1198         free(name);
1199         return ret;
1200     }
1201
1202     ret = kcm_ccache_resolve_client(context, client, opcode, name, &ccache);
1203     free(name);
1204     if (ret)
1205         return ret;
1206
1207     HEIMDAL_MUTEX_lock(&ccache->mutex);
1208     ccache->kdc_offset = offset;
1209     HEIMDAL_MUTEX_unlock(&ccache->mutex);
1210
1211     kcm_release_ccache(context, ccache);
1212
1213     return ret;
1214 }
1215
1216 struct kcm_ntlm_cred {
1217     kcmuuid_t uuid;
1218     char *user;
1219     char *domain;
1220     krb5_data nthash;
1221     uid_t uid;
1222     pid_t session;
1223     struct kcm_ntlm_cred *next;
1224 };
1225
1226 static struct kcm_ntlm_cred *ntlm_head;
1227
1228 static void
1229 free_cred(struct kcm_ntlm_cred *cred)
1230 {
1231     free(cred->user);
1232     free(cred->domain);
1233     krb5_data_free(&cred->nthash);
1234     free(cred);
1235 }
1236
1237
1238 /*
1239  * name
1240  * domain
1241  * ntlm hash
1242  *
1243  * Reply:
1244  *   uuid
1245  */
1246
1247 static struct kcm_ntlm_cred *
1248 find_ntlm_cred(const char *user, const char *domain, kcm_client *client)
1249 {
1250     struct kcm_ntlm_cred *c;
1251
1252     for (c = ntlm_head; c != NULL; c = c->next)
1253         if ((user[0] == '\0' || strcmp(user, c->user) == 0) && 
1254             (domain == NULL || strcmp(domain, c->domain) == 0) &&
1255             kcm_is_same_session(client, c->uid, c->session))
1256             return c;
1257
1258     return NULL;
1259 }
1260
1261 static krb5_error_code
1262 kcm_op_add_ntlm_cred(krb5_context context,
1263                      kcm_client *client,
1264                      kcm_operation opcode,
1265                      krb5_storage *request,
1266                      krb5_storage *response)
1267 {
1268     struct kcm_ntlm_cred *cred, *c;
1269     krb5_error_code ret;
1270
1271     cred = calloc(1, sizeof(*cred));
1272     if (cred == NULL)
1273         return ENOMEM;
1274
1275     RAND_bytes(cred->uuid, sizeof(cred->uuid));
1276
1277     ret = krb5_ret_stringz(request, &cred->user);
1278     if (ret)
1279         goto error;
1280
1281     ret = krb5_ret_stringz(request, &cred->domain);
1282     if (ret)
1283         goto error;
1284
1285     ret = krb5_ret_data(request, &cred->nthash);
1286     if (ret)
1287         goto error;
1288
1289     /* search for dups */
1290     c = find_ntlm_cred(cred->user, cred->domain, client);
1291     if (c) {
1292         krb5_data hash = c->nthash;
1293         c->nthash = cred->nthash;
1294         cred->nthash = hash;
1295         free_cred(cred);
1296         cred = c;
1297     } else {
1298         cred->next = ntlm_head;
1299         ntlm_head = cred;
1300     }
1301
1302     cred->uid = client->uid;
1303     cred->session = client->session;
1304
1305     /* write response */
1306     (void)krb5_storage_write(response, &cred->uuid, sizeof(cred->uuid));
1307
1308     return 0;
1309
1310  error:
1311     free_cred(cred);
1312
1313     return ret;
1314 }
1315
1316 /*
1317  * { "HAVE_NTLM_CRED",          NULL },
1318  *
1319  * input:
1320  *  name
1321  *  domain
1322  */
1323
1324 static krb5_error_code
1325 kcm_op_have_ntlm_cred(krb5_context context,
1326                      kcm_client *client,
1327                      kcm_operation opcode,
1328                      krb5_storage *request,
1329                      krb5_storage *response)
1330 {
1331     struct kcm_ntlm_cred *c;
1332     char *user = NULL, *domain = NULL;
1333     krb5_error_code ret;
1334
1335     ret = krb5_ret_stringz(request, &user);
1336     if (ret)
1337         goto error;
1338
1339     ret = krb5_ret_stringz(request, &domain);
1340     if (ret)
1341         goto error;
1342
1343     if (domain[0] == '\0') {
1344         free(domain);
1345         domain = NULL;
1346     }
1347
1348     c = find_ntlm_cred(user, domain, client);
1349     if (c == NULL)
1350         ret = ENOENT;
1351
1352  error:
1353     free(user);
1354     if (domain)
1355         free(domain);
1356
1357     return ret;
1358 }
1359
1360 /*
1361  * { "DEL_NTLM_CRED",           NULL },
1362  *
1363  * input:
1364  *  name
1365  *  domain
1366  */
1367
1368 static krb5_error_code
1369 kcm_op_del_ntlm_cred(krb5_context context,
1370                      kcm_client *client,
1371                      kcm_operation opcode,
1372                      krb5_storage *request,
1373                      krb5_storage *response)
1374 {
1375     struct kcm_ntlm_cred **cp, *c;
1376     char *user = NULL, *domain = NULL;
1377     krb5_error_code ret;
1378
1379     ret = krb5_ret_stringz(request, &user);
1380     if (ret)
1381         goto error;
1382
1383     ret = krb5_ret_stringz(request, &domain);
1384     if (ret)
1385         goto error;
1386
1387     for (cp = &ntlm_head; *cp != NULL; cp = &(*cp)->next) {
1388         if (strcmp(user, (*cp)->user) == 0 && strcmp(domain, (*cp)->domain) == 0 &&
1389             kcm_is_same_session(client, (*cp)->uid, (*cp)->session))
1390         {
1391             c = *cp;
1392             *cp = c->next;
1393
1394             free_cred(c);
1395             break;
1396         }
1397     }
1398
1399  error:
1400     free(user);
1401     free(domain);
1402
1403     return ret;
1404 }
1405
1406 /*
1407  * { "DO_NTLM_AUTH",            NULL },
1408  *
1409  * input:
1410  *  name:string
1411  *  domain:string
1412  *  type2:data
1413  *
1414  * reply:
1415  *  type3:data
1416  *  flags:int32
1417  *  session-key:data
1418  */
1419
1420 #define NTLM_FLAG_SESSIONKEY 1
1421 #define NTLM_FLAG_NTLM2_SESSION 2
1422 #define NTLM_FLAG_KEYEX 4
1423
1424 static krb5_error_code
1425 kcm_op_do_ntlm(krb5_context context,
1426                kcm_client *client,
1427                kcm_operation opcode,
1428                krb5_storage *request,
1429                krb5_storage *response)
1430 {
1431     struct kcm_ntlm_cred *c;
1432     struct ntlm_type2 type2;
1433     struct ntlm_type3 type3;
1434     char *user = NULL, *domain = NULL;
1435     struct ntlm_buf ndata, sessionkey;
1436     krb5_data data;
1437     krb5_error_code ret;
1438     uint32_t flags = 0;
1439
1440     memset(&type2, 0, sizeof(type2));
1441     memset(&type3, 0, sizeof(type3));
1442     sessionkey.data = NULL;
1443     sessionkey.length = 0;
1444     
1445     ret = krb5_ret_stringz(request, &user);
1446     if (ret)
1447         goto error;
1448
1449     ret = krb5_ret_stringz(request, &domain);
1450     if (ret)
1451         goto error;
1452
1453     if (domain[0] == '\0') {
1454         free(domain);
1455         domain = NULL;
1456     }
1457
1458     c = find_ntlm_cred(user, domain, client);
1459     if (c == NULL) {
1460         ret = EINVAL;
1461         goto error;
1462     }
1463
1464     ret = krb5_ret_data(request, &data);
1465     if (ret)
1466         goto error;
1467
1468     ndata.data = data.data;
1469     ndata.length = data.length;
1470
1471     ret = heim_ntlm_decode_type2(&ndata, &type2);
1472     krb5_data_free(&data);
1473     if (ret)
1474         goto error;
1475
1476     if (domain && strcmp(domain, type2.targetname) == 0) {
1477         ret = EINVAL;
1478         goto error;
1479     }
1480
1481     type3.username = c->user;
1482     type3.flags = type2.flags;
1483     type3.targetname = type2.targetname;
1484     type3.ws = rk_UNCONST("workstation");
1485     
1486     /*
1487      * NTLM Version 1 if no targetinfo buffer.
1488      */
1489     
1490     if (1 || type2.targetinfo.length == 0) {
1491         struct ntlm_buf sessionkey;
1492         
1493         if (type2.flags & NTLM_NEG_NTLM2_SESSION) {
1494             unsigned char nonce[8];
1495             
1496             if (RAND_bytes(nonce, sizeof(nonce)) != 1) {
1497                 ret = EINVAL;
1498                 goto error;
1499             }
1500             
1501             ret = heim_ntlm_calculate_ntlm2_sess(nonce,
1502                                                  type2.challange,
1503                                                  c->nthash.data,
1504                                                  &type3.lm,
1505                                                  &type3.ntlm);
1506         } else {
1507             ret = heim_ntlm_calculate_ntlm1(c->nthash.data,
1508                                             c->nthash.length,
1509                                             type2.challange,
1510                                             &type3.ntlm);
1511             
1512         }
1513         if (ret)
1514             goto error;
1515         
1516         ret = heim_ntlm_build_ntlm1_master(c->nthash.data,
1517                                            c->nthash.length,
1518                                            &sessionkey,
1519                                            &type3.sessionkey);
1520         if (ret) {
1521             if (type3.lm.data)
1522                 free(type3.lm.data);
1523             if (type3.ntlm.data)
1524                 free(type3.ntlm.data);
1525             goto error;
1526         }
1527
1528         free(sessionkey.data);
1529         if (ret) {
1530             if (type3.lm.data)
1531                 free(type3.lm.data);
1532             if (type3.ntlm.data)
1533                 free(type3.ntlm.data);
1534             goto error;
1535         }
1536         flags |= NTLM_FLAG_SESSIONKEY;
1537 #if 0   
1538     } else {
1539         struct ntlm_buf sessionkey;
1540         unsigned char ntlmv2[16];
1541         struct ntlm_targetinfo ti;
1542         
1543         /* verify infotarget */
1544         
1545         ret = heim_ntlm_decode_targetinfo(&type2.targetinfo, 1, &ti);
1546         if(ret) {
1547             _gss_ntlm_delete_sec_context(minor_status,
1548                                          context_handle, NULL);
1549             *minor_status = ret;
1550             return GSS_S_FAILURE;
1551         }
1552         
1553         if (ti.domainname && strcmp(ti.domainname, name->domain) != 0) {
1554             _gss_ntlm_delete_sec_context(minor_status,
1555                                          context_handle, NULL);
1556             *minor_status = EINVAL;
1557             return GSS_S_FAILURE;
1558         }
1559         
1560         ret = heim_ntlm_calculate_ntlm2(ctx->client->key.data,
1561                                         ctx->client->key.length,
1562                                         type3.username,
1563                                         name->domain,
1564                                         type2.challange,
1565                                         &type2.targetinfo,
1566                                         ntlmv2,
1567                                         &type3.ntlm);
1568         if (ret) {
1569             _gss_ntlm_delete_sec_context(minor_status,
1570                                          context_handle, NULL);
1571             *minor_status = ret;
1572             return GSS_S_FAILURE;
1573         }
1574         
1575         ret = heim_ntlm_build_ntlm1_master(ntlmv2, sizeof(ntlmv2),
1576                                            &sessionkey,
1577                                            &type3.sessionkey);
1578         memset(ntlmv2, 0, sizeof(ntlmv2));
1579         if (ret) {
1580             _gss_ntlm_delete_sec_context(minor_status,
1581                                          context_handle, NULL);
1582             *minor_status = ret;
1583             return GSS_S_FAILURE;
1584         }
1585         
1586         flags |= NTLM_FLAG_NTLM2_SESSION |
1587                  NTLM_FLAG_SESSION;
1588         
1589         if (type3.flags & NTLM_NEG_KEYEX)
1590             flags |= NTLM_FLAG_KEYEX;
1591
1592         ret = krb5_data_copy(&ctx->sessionkey,
1593                              sessionkey.data, sessionkey.length);
1594         free(sessionkey.data);
1595         if (ret) {
1596             _gss_ntlm_delete_sec_context(minor_status,
1597                                          context_handle, NULL);
1598             *minor_status = ret;
1599             return GSS_S_FAILURE;
1600         }
1601 #endif
1602     }
1603     
1604 #if 0
1605     if (flags & NTLM_FLAG_NTLM2_SESSION) {
1606         _gss_ntlm_set_key(&ctx->u.v2.send, 0, (ctx->flags & NTLM_NEG_KEYEX),
1607                           ctx->sessionkey.data,
1608                           ctx->sessionkey.length);
1609         _gss_ntlm_set_key(&ctx->u.v2.recv, 1, (ctx->flags & NTLM_NEG_KEYEX),
1610                           ctx->sessionkey.data,
1611                           ctx->sessionkey.length);
1612     } else {
1613         flags |= NTLM_FLAG_SESSION;
1614         RC4_set_key(&ctx->u.v1.crypto_recv.key,
1615                     ctx->sessionkey.length,
1616                     ctx->sessionkey.data);
1617         RC4_set_key(&ctx->u.v1.crypto_send.key,
1618                     ctx->sessionkey.length,
1619                     ctx->sessionkey.data);
1620     }
1621 #endif
1622
1623     ret = heim_ntlm_encode_type3(&type3, &ndata);
1624     if (ret)
1625         goto error;
1626         
1627     data.data = ndata.data;
1628     data.length = ndata.length;
1629     ret = krb5_store_data(response, data);
1630     heim_ntlm_free_buf(&ndata);
1631     if (ret) goto error;
1632
1633     ret = krb5_store_int32(response, flags);
1634     if (ret) goto error;
1635
1636     data.data = sessionkey.data;
1637     data.length = sessionkey.length;
1638
1639     ret = krb5_store_data(response, data);
1640     if (ret) goto error;
1641
1642  error:
1643     free(type3.username);
1644     heim_ntlm_free_type2(&type2);
1645     free(user);
1646     if (domain)
1647         free(domain);
1648
1649     return ret;
1650 }
1651
1652
1653 /*
1654  * { "GET_NTLM_UUID_LIST",      NULL }
1655  *
1656  * reply:
1657  *   1 user domain
1658  *   0 [ end of list ]
1659  */
1660
1661 static krb5_error_code
1662 kcm_op_get_ntlm_user_list(krb5_context context,
1663                           kcm_client *client,
1664                           kcm_operation opcode,
1665                           krb5_storage *request,
1666                           krb5_storage *response)
1667 {
1668     struct kcm_ntlm_cred *c;
1669     krb5_error_code ret;
1670
1671     for (c = ntlm_head; c != NULL; c = c->next) {
1672         if (!kcm_is_same_session(client, c->uid, c->session))
1673             continue;
1674
1675         ret = krb5_store_uint32(response, 1);
1676         if (ret)
1677             return ret;
1678         ret = krb5_store_stringz(response, c->user);
1679         if (ret)
1680             return ret;
1681         ret = krb5_store_stringz(response, c->domain);
1682         if (ret)
1683             return ret;
1684     }
1685     return krb5_store_uint32(response, 0);
1686 }
1687
1688 /*
1689  *
1690  */
1691
1692 static struct kcm_op kcm_ops[] = {
1693     { "NOOP",                   kcm_op_noop },
1694     { "GET_NAME",               kcm_op_get_name },
1695     { "RESOLVE",                kcm_op_noop },
1696     { "GEN_NEW",                kcm_op_gen_new },
1697     { "INITIALIZE",             kcm_op_initialize },
1698     { "DESTROY",                kcm_op_destroy },
1699     { "STORE",                  kcm_op_store },
1700     { "RETRIEVE",               kcm_op_retrieve },
1701     { "GET_PRINCIPAL",          kcm_op_get_principal },
1702     { "GET_CRED_UUID_LIST",     kcm_op_get_cred_uuid_list },
1703     { "GET_CRED_BY_UUID",       kcm_op_get_cred_by_uuid },
1704     { "REMOVE_CRED",            kcm_op_remove_cred },
1705     { "SET_FLAGS",              kcm_op_set_flags },
1706     { "CHOWN",                  kcm_op_chown },
1707     { "CHMOD",                  kcm_op_chmod },
1708     { "GET_INITIAL_TICKET",     kcm_op_get_initial_ticket },
1709     { "GET_TICKET",             kcm_op_get_ticket },
1710     { "MOVE_CACHE",             kcm_op_move_cache },
1711     { "GET_CACHE_UUID_LIST",    kcm_op_get_cache_uuid_list },
1712     { "GET_CACHE_BY_UUID",      kcm_op_get_cache_by_uuid },
1713     { "GET_DEFAULT_CACHE",      kcm_op_get_default_cache },
1714     { "SET_DEFAULT_CACHE",      kcm_op_set_default_cache },
1715     { "GET_KDC_OFFSET",         kcm_op_get_kdc_offset },
1716     { "SET_KDC_OFFSET",         kcm_op_set_kdc_offset },
1717     { "ADD_NTLM_CRED",          kcm_op_add_ntlm_cred },
1718     { "HAVE_USER_CRED",         kcm_op_have_ntlm_cred },
1719     { "DEL_NTLM_CRED",          kcm_op_del_ntlm_cred },
1720     { "DO_NTLM_AUTH",           kcm_op_do_ntlm },
1721     { "GET_NTLM_USER_LIST",     kcm_op_get_ntlm_user_list }
1722 };
1723
1724
1725 const char *
1726 kcm_op2string(kcm_operation opcode)
1727 {
1728     if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0]))
1729         return "Unknown operation";
1730
1731     return kcm_ops[opcode].name;
1732 }
1733
1734 krb5_error_code
1735 kcm_dispatch(krb5_context context,
1736              kcm_client *client,
1737              krb5_data *req_data,
1738              krb5_data *resp_data)
1739 {
1740     krb5_error_code ret;
1741     kcm_method method;
1742     krb5_storage *req_sp = NULL;
1743     krb5_storage *resp_sp = NULL;
1744     uint16_t opcode;
1745
1746     resp_sp = krb5_storage_emem();
1747     if (resp_sp == NULL) {
1748         return ENOMEM;
1749     }
1750
1751     if (client->pid == -1) {
1752         kcm_log(0, "Client had invalid process number");
1753         ret = KRB5_FCC_INTERNAL;
1754         goto out;
1755     }
1756
1757     req_sp = krb5_storage_from_data(req_data);
1758     if (req_sp == NULL) {
1759         kcm_log(0, "Process %d: failed to initialize storage from data",
1760                 client->pid);
1761         ret = KRB5_CC_IO;
1762         goto out;
1763     }
1764
1765     ret = krb5_ret_uint16(req_sp, &opcode);
1766     if (ret) {
1767         kcm_log(0, "Process %d: didn't send a message", client->pid);
1768         goto out;
1769     }
1770
1771     if (opcode >= sizeof(kcm_ops)/sizeof(kcm_ops[0])) {
1772         kcm_log(0, "Process %d: invalid operation code %d",
1773                 client->pid, opcode);
1774         ret = KRB5_FCC_INTERNAL;
1775         goto out;
1776     }
1777     method = kcm_ops[opcode].method;
1778     if (method == NULL) {
1779         kcm_log(0, "Process %d: operation code %s not implemented",
1780                 client->pid, kcm_op2string(opcode));
1781         ret = KRB5_FCC_INTERNAL;
1782         goto out;
1783     }
1784
1785     /* seek past place for status code */
1786     krb5_storage_seek(resp_sp, 4, SEEK_SET);
1787
1788     ret = (*method)(context, client, opcode, req_sp, resp_sp);
1789
1790 out:
1791     if (req_sp != NULL) {
1792         krb5_storage_free(req_sp);
1793     }
1794
1795     krb5_storage_seek(resp_sp, 0, SEEK_SET);
1796     krb5_store_int32(resp_sp, ret);
1797
1798     ret = krb5_storage_to_data(resp_sp, resp_data);
1799     krb5_storage_free(resp_sp);
1800
1801     return ret;
1802 }
1803