add store_cred.c
[metze/heimdal/wip.git] / kcm / cache.c
1 /*
2  * Copyright (c) 2005, PADL Software Pty Ltd.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of PADL Software nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32
33 #include "kcm_locl.h"
34
35 RCSID("$Id$");
36
37 static HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
38 static kcm_ccache_data *ccache_head = NULL;
39 static unsigned int ccache_nextid = 0;
40
41 char *kcm_ccache_nextid(pid_t pid, uid_t uid, gid_t gid)
42 {
43     unsigned n;
44     char *name;
45
46     HEIMDAL_MUTEX_lock(&ccache_mutex);
47     n = ++ccache_nextid;
48     HEIMDAL_MUTEX_unlock(&ccache_mutex);
49
50     asprintf(&name, "%d:%u", uid, n);
51
52     return name;
53 }
54
55 static krb5_error_code
56 kcm_ccache_resolve_internal(krb5_context context,
57                             const char *name,
58                             kcm_ccache *ccache)
59 {
60     kcm_ccache p;
61     krb5_error_code ret;
62
63     *ccache = NULL;
64
65     ret = KRB5_FCC_NOFILE;
66
67     HEIMDAL_MUTEX_lock(&ccache_mutex);
68
69     for (p = ccache_head; p != NULL; p = p->next) {
70         if ((p->flags & KCM_FLAGS_VALID) == 0)
71             continue;
72         if (strcmp(p->name, name) == 0) {
73             ret = 0;
74             break;
75         }
76     }
77
78     if (ret == 0) {
79         kcm_retain_ccache(context, p);
80         *ccache = p;
81     }
82
83     HEIMDAL_MUTEX_unlock(&ccache_mutex);
84
85     return ret;
86 }
87
88 krb5_error_code kcm_debug_ccache(krb5_context context)
89 {
90     kcm_ccache p;
91
92     for (p = ccache_head; p != NULL; p = p->next) {
93         char *cpn = NULL, *spn = NULL;
94         int ncreds = 0;
95         struct kcm_creds *k;
96
97         if ((p->flags & KCM_FLAGS_VALID) == 0) {
98             kcm_log(7, "cache %08x: empty slot");
99             continue;
100         }
101
102         KCM_ASSERT_VALID(p);
103
104         for (k = p->creds; k != NULL; k = k->next)
105             ncreds++;
106
107         if (p->client != NULL)
108             krb5_unparse_name(context, p->client, &cpn);
109         if (p->server != NULL)
110             krb5_unparse_name(context, p->server, &spn);
111         
112         kcm_log(7, "cache %08x: name %s refcnt %d flags %04x mode %04o "
113                 "uid %d gid %d client %s server %s ncreds %d",
114                 p, p->name, p->refcnt, p->flags, p->mode, p->uid, p->gid,
115                 (cpn == NULL) ? "<none>" : cpn,
116                 (spn == NULL) ? "<none>" : spn,
117                 ncreds);
118
119         if (cpn != NULL)
120             free(cpn);
121         if (spn != NULL)
122             free(spn);
123     }
124
125     return 0;
126 }
127
128 static krb5_error_code
129 kcm_ccache_destroy_internal(krb5_context context, const char *name)
130 {
131     kcm_ccache *p;
132     krb5_error_code ret;
133
134     ret = KRB5_FCC_NOFILE;
135
136     HEIMDAL_MUTEX_lock(&ccache_mutex);
137     for (p = &ccache_head; *p != NULL; p = &(*p)->next) {
138         if (((*p)->flags & KCM_FLAGS_VALID) == 0)
139             continue;
140         if (strcmp((*p)->name, name) == 0) {
141             ret = 0;
142             break;
143         }
144     }
145
146     if (ret)
147         goto out;
148
149     kcm_release_ccache(context, p);
150
151 out:
152     HEIMDAL_MUTEX_unlock(&ccache_mutex);
153
154     return ret;
155 }
156
157 static krb5_error_code
158 kcm_ccache_alloc(krb5_context context,
159                  const char *name,
160                  kcm_ccache *ccache)
161 {
162     kcm_ccache slot = NULL, p;
163     krb5_error_code ret;
164     int new_slot = 0;
165
166     *ccache = NULL;
167
168     /* First, check for duplicates */
169     HEIMDAL_MUTEX_lock(&ccache_mutex);
170     ret = 0;
171     for (p = ccache_head; p != NULL; p = p->next) {
172         if (p->flags & KCM_FLAGS_VALID) {
173             if (strcmp(p->name, name) == 0) {
174                 ret = KRB5_CC_WRITE;
175                 break;
176             }
177         } else if (slot == NULL)
178             slot = p;
179     }
180
181     if (ret)
182         goto out;
183
184     /*
185      * Create an enpty slot for us.
186      */
187     if (slot == NULL) {
188         slot = (kcm_ccache_data *)malloc(sizeof(*slot));
189         if (slot == NULL) {
190             ret = KRB5_CC_NOMEM;
191             goto out;
192         }
193         slot->next = ccache_head;
194         HEIMDAL_MUTEX_init(&slot->mutex);
195         new_slot = 1;
196     }
197
198     slot->name = strdup(name);
199     if (slot->name == NULL) {
200         ret = KRB5_CC_NOMEM;
201         goto out;
202     }
203
204     slot->refcnt = 1;
205     slot->flags = KCM_FLAGS_VALID;
206     slot->mode = S_IRUSR | S_IWUSR;
207     slot->uid = -1;
208     slot->gid = -1;
209     slot->client = NULL;
210     slot->server = NULL;
211     slot->creds = NULL;
212     slot->key.keytab = NULL;
213     slot->tkt_life = 0;
214     slot->renew_life = 0;
215
216     if (new_slot)
217         ccache_head = slot;
218
219     *ccache = slot;
220
221     HEIMDAL_MUTEX_unlock(&ccache_mutex);
222     return 0;
223
224 out:
225     HEIMDAL_MUTEX_unlock(&ccache_mutex);
226     if (new_slot && slot != NULL) {
227         HEIMDAL_MUTEX_destroy(&slot->mutex);
228         free(slot);
229     }
230     return ret;
231 }
232
233 krb5_error_code
234 kcm_ccache_remove_creds_internal(krb5_context context,
235                                  kcm_ccache ccache)
236 {
237     struct kcm_creds *k;
238
239     k = ccache->creds;
240     while (k != NULL) {
241         struct kcm_creds *old;
242
243         krb5_free_cred_contents(context, &k->cred);
244         old = k;
245         k = k->next;
246         free(old);
247     }
248     ccache->creds = NULL;
249
250     return 0;
251 }
252
253 krb5_error_code
254 kcm_ccache_remove_creds(krb5_context context,
255                         kcm_ccache ccache)
256 {
257     krb5_error_code ret;
258
259     KCM_ASSERT_VALID(ccache);
260
261     HEIMDAL_MUTEX_lock(&ccache->mutex);
262     ret = kcm_ccache_remove_creds_internal(context, ccache);
263     HEIMDAL_MUTEX_unlock(&ccache->mutex);
264
265     return ret;
266 }
267
268 krb5_error_code
269 kcm_zero_ccache_data_internal(krb5_context context,
270                               kcm_ccache_data *cache)
271 {
272     if (cache->client != NULL) {
273         krb5_free_principal(context, cache->client);
274         cache->client = NULL;
275     }
276
277     if (cache->server != NULL) {
278         krb5_free_principal(context, cache->server);
279         cache->server = NULL;
280     }
281
282     kcm_ccache_remove_creds_internal(context, cache);
283
284     return 0;
285 }
286
287 krb5_error_code
288 kcm_zero_ccache_data(krb5_context context,
289                      kcm_ccache cache)
290 {
291     krb5_error_code ret;
292
293     KCM_ASSERT_VALID(cache);
294
295     HEIMDAL_MUTEX_lock(&cache->mutex);
296     ret = kcm_zero_ccache_data_internal(context, cache);
297     HEIMDAL_MUTEX_unlock(&cache->mutex);
298
299     return ret;
300 }
301
302 static krb5_error_code
303 kcm_free_ccache_data_internal(krb5_context context,
304                               kcm_ccache_data *cache)
305 {
306     KCM_ASSERT_VALID(cache);
307
308     if (cache->name != NULL) {
309         free(cache->name);
310         cache->name = NULL;
311     }
312
313     if (cache->flags & KCM_FLAGS_USE_KEYTAB) {
314         krb5_kt_close(context, cache->key.keytab);
315         cache->key.keytab = NULL;
316     } else if (cache->flags & KCM_FLAGS_USE_CACHED_KEY) {
317         krb5_free_keyblock_contents(context, &cache->key.keyblock);
318         krb5_keyblock_zero(&cache->key.keyblock);
319     }
320
321     cache->flags = 0;
322     cache->mode = 0;
323     cache->uid = -1;
324     cache->gid = -1;
325
326     kcm_zero_ccache_data_internal(context, cache);
327
328     cache->tkt_life = 0;
329     cache->renew_life = 0;
330
331     cache->next = NULL;
332     cache->refcnt = 0;
333
334     HEIMDAL_MUTEX_unlock(&cache->mutex);
335     HEIMDAL_MUTEX_destroy(&cache->mutex);
336
337     return 0;
338 }
339
340 krb5_error_code
341 kcm_retain_ccache(krb5_context context,
342                   kcm_ccache ccache)
343 {
344     KCM_ASSERT_VALID(ccache);
345
346     HEIMDAL_MUTEX_lock(&ccache->mutex);
347     ccache->refcnt++;
348     HEIMDAL_MUTEX_unlock(&ccache->mutex);
349
350     return 0;
351 }
352
353 krb5_error_code
354 kcm_release_ccache(krb5_context context,
355                    kcm_ccache *ccache)
356 {
357     kcm_ccache c = *ccache;
358     krb5_error_code ret = 0;
359
360     KCM_ASSERT_VALID(c);
361
362     HEIMDAL_MUTEX_lock(&c->mutex);
363     if (c->refcnt == 1) {
364         ret = kcm_free_ccache_data_internal(context, c);
365         if (ret == 0)
366             free(c);
367     } else {
368         c->refcnt--;
369         HEIMDAL_MUTEX_unlock(&c->mutex);
370     }
371
372     *ccache = NULL;
373
374     return ret;
375 }
376
377 krb5_error_code
378 kcm_ccache_gen_new(krb5_context context,
379                    pid_t pid,
380                    uid_t uid,
381                    gid_t gid,
382                    kcm_ccache *ccache)
383 {
384     krb5_error_code ret;
385     char *name;
386
387     name = kcm_ccache_nextid(pid, uid, gid);
388     if (name == NULL) {
389         return KRB5_CC_NOMEM;
390     }
391
392     ret = kcm_ccache_new(context, name, ccache);
393
394     free(name);
395     return ret;
396 }
397
398 krb5_error_code
399 kcm_ccache_new(krb5_context context,
400                const char *name,
401                kcm_ccache *ccache)
402 {
403     krb5_error_code ret;
404
405     ret = kcm_ccache_alloc(context, name, ccache);
406     if (ret == 0) {
407         /*
408          * one reference is held by the linked list,
409          * one by the caller
410          */
411         kcm_retain_ccache(context, *ccache);
412     }
413
414     return ret;
415 }
416
417 krb5_error_code
418 kcm_ccache_resolve(krb5_context context,
419                    const char *name,
420                    kcm_ccache *ccache)
421 {
422     krb5_error_code ret;
423
424     ret = kcm_ccache_resolve_internal(context, name, ccache);
425
426     return ret;
427 }
428
429 krb5_error_code
430 kcm_ccache_destroy(krb5_context context,
431                    const char *name)
432 {
433     krb5_error_code ret;
434
435     ret = kcm_ccache_destroy_internal(context, name);
436
437     return ret;
438 }
439
440 krb5_error_code
441 kcm_ccache_destroy_if_empty(krb5_context context,
442                             kcm_ccache ccache)
443 {
444     krb5_error_code ret;
445
446     KCM_ASSERT_VALID(ccache);
447
448     if (ccache->creds == NULL) {
449         ret = kcm_ccache_destroy_internal(context, ccache->name);
450     } else
451         ret = 0;
452
453     return ret;
454 }
455
456 krb5_error_code
457 kcm_ccache_store_cred(krb5_context context,
458                       kcm_ccache ccache,
459                       krb5_creds *creds,
460                       int copy)
461 {
462     krb5_error_code ret;
463     krb5_creds *tmp;
464
465     KCM_ASSERT_VALID(ccache);
466
467     HEIMDAL_MUTEX_lock(&ccache->mutex);
468     ret = kcm_ccache_store_cred_internal(context, ccache, creds, copy, &tmp);
469     HEIMDAL_MUTEX_unlock(&ccache->mutex);
470
471     return ret;
472 }
473
474 struct kcm_creds *
475 kcm_ccache_find_cred_uuid(krb5_context context,
476                           kcm_ccache ccache,
477                           kcmuuid_t uuid)
478 {
479     struct kcm_creds *c;
480
481     for (c = ccache->creds; c != NULL; c = c->next)
482         if (memcmp(c->uuid, uuid, sizeof(c->uuid)) == 0)
483             return c;
484
485     return NULL;
486 }
487
488
489
490 krb5_error_code
491 kcm_ccache_store_cred_internal(krb5_context context,
492                                kcm_ccache ccache,
493                                krb5_creds *creds,
494                                int copy,
495                                krb5_creds **credp)
496 {
497     struct kcm_creds **c;
498     krb5_error_code ret;
499
500     for (c = &ccache->creds; *c != NULL; c = &(*c)->next)
501         ;
502
503     *c = (struct kcm_creds *)calloc(1, sizeof(**c));
504     if (*c == NULL)
505         return KRB5_CC_NOMEM;
506
507     RAND_bytes((*c)->uuid, sizeof((*c)->uuid));
508
509     *credp = &(*c)->cred;
510
511     if (copy) {
512         ret = krb5_copy_creds_contents(context, creds, *credp);
513         if (ret) {
514             free(*c);
515             *c = NULL;
516         }
517     } else {
518         **credp = *creds;
519         ret = 0;
520     }
521
522     return ret;
523 }
524
525 krb5_error_code
526 kcm_ccache_remove_cred_internal(krb5_context context,
527                                 kcm_ccache ccache,
528                                 krb5_flags whichfields,
529                                 const krb5_creds *mcreds)
530 {
531     krb5_error_code ret;
532     struct kcm_creds **c;
533
534     ret = KRB5_CC_NOTFOUND;
535
536     for (c = &ccache->creds; *c != NULL; c = &(*c)->next) {
537         if (krb5_compare_creds(context, whichfields, mcreds, &(*c)->cred)) {
538             struct kcm_creds *cred = *c;
539
540             *c = cred->next;
541             krb5_free_cred_contents(context, &cred->cred);
542             free(cred);
543             ret = 0;
544         }
545     }
546
547     return ret;
548 }
549
550 krb5_error_code
551 kcm_ccache_remove_cred(krb5_context context,
552                        kcm_ccache ccache,
553                        krb5_flags whichfields,
554                        const krb5_creds *mcreds)
555 {
556     krb5_error_code ret;
557
558     KCM_ASSERT_VALID(ccache);
559
560     HEIMDAL_MUTEX_lock(&ccache->mutex);
561     ret = kcm_ccache_remove_cred_internal(context, ccache, whichfields, mcreds);
562     HEIMDAL_MUTEX_unlock(&ccache->mutex);
563
564     return ret;
565 }
566
567 krb5_error_code
568 kcm_ccache_retrieve_cred_internal(krb5_context context,
569                                   kcm_ccache ccache,
570                                   krb5_flags whichfields,
571                                   const krb5_creds *mcreds,
572                                   krb5_creds **creds)
573 {
574     krb5_boolean match;
575     struct kcm_creds *c;
576     krb5_error_code ret;
577
578     memset(creds, 0, sizeof(*creds));
579
580     ret = KRB5_CC_END;
581
582     match = FALSE;
583     for (c = ccache->creds; c != NULL; c = c->next) {
584         match = krb5_compare_creds(context, whichfields, mcreds, &c->cred);
585         if (match)
586             break;
587     }
588
589     if (match) {
590         ret = 0;
591         *creds = &c->cred;
592     }
593
594     return ret;
595 }
596
597 krb5_error_code
598 kcm_ccache_retrieve_cred(krb5_context context,
599                          kcm_ccache ccache,
600                          krb5_flags whichfields,
601                          const krb5_creds *mcreds,
602                          krb5_creds **credp)
603 {
604     krb5_error_code ret;
605
606     KCM_ASSERT_VALID(ccache);
607
608     HEIMDAL_MUTEX_lock(&ccache->mutex);
609     ret = kcm_ccache_retrieve_cred_internal(context, ccache,
610                                             whichfields, mcreds, credp);
611     HEIMDAL_MUTEX_unlock(&ccache->mutex);
612
613     return ret;
614 }