s4:heimdal: import lorikeet-heimdal-202201172009 (commit 5a0b45cd723628b3690ea848548b...
[samba.git] / source4 / heimdal / kcm / cache.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
37 HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
38 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     int ret;
46
47     HEIMDAL_MUTEX_lock(&ccache_mutex);
48     n = ++ccache_nextid;
49     HEIMDAL_MUTEX_unlock(&ccache_mutex);
50
51     ret = asprintf(&name, "%ld:%u", (long)uid, n);
52     if (ret == -1)
53         return NULL;
54
55     return name;
56 }
57
58 krb5_error_code
59 kcm_ccache_resolve(krb5_context context,
60                    const char *name,
61                    kcm_ccache *ccache)
62 {
63     kcm_ccache p;
64     krb5_error_code ret;
65
66     *ccache = NULL;
67
68     ret = KRB5_FCC_NOFILE;
69
70     HEIMDAL_MUTEX_lock(&ccache_mutex);
71
72     for (p = ccache_head; p != NULL; p = p->next) {
73         if ((p->flags & KCM_FLAGS_VALID) == 0)
74             continue;
75         if (strcmp(p->name, name) == 0) {
76             ret = 0;
77             break;
78         }
79     }
80
81     if (ret == 0) {
82         kcm_retain_ccache(context, p);
83         *ccache = p;
84     }
85
86     HEIMDAL_MUTEX_unlock(&ccache_mutex);
87
88     return ret;
89 }
90
91 krb5_error_code
92 kcm_ccache_resolve_by_uuid(krb5_context context,
93                            kcmuuid_t uuid,
94                            kcm_ccache *ccache)
95 {
96     kcm_ccache p;
97     krb5_error_code ret;
98
99     *ccache = NULL;
100
101     ret = KRB5_FCC_NOFILE;
102
103     HEIMDAL_MUTEX_lock(&ccache_mutex);
104
105     for (p = ccache_head; p != NULL; p = p->next) {
106         if ((p->flags & KCM_FLAGS_VALID) == 0)
107             continue;
108         if (memcmp(p->uuid, uuid, sizeof(kcmuuid_t)) == 0) {
109             ret = 0;
110             break;
111         }
112     }
113
114     if (ret == 0) {
115         kcm_retain_ccache(context, p);
116         *ccache = p;
117     }
118
119     HEIMDAL_MUTEX_unlock(&ccache_mutex);
120
121     return ret;
122 }
123
124 krb5_error_code
125 kcm_ccache_get_uuids(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *sp)
126 {
127     krb5_error_code ret;
128     kcm_ccache p;
129
130     ret = KRB5_FCC_NOFILE;
131
132     HEIMDAL_MUTEX_lock(&ccache_mutex);
133
134     for (p = ccache_head; p != NULL; p = p->next) {
135         if ((p->flags & KCM_FLAGS_VALID) == 0)
136             continue;
137         ret = kcm_access(context, client, opcode, p);
138         if (ret) {
139             ret = 0;
140             continue;
141         }
142         krb5_storage_write(sp, p->uuid, sizeof(p->uuid));
143     }
144
145     HEIMDAL_MUTEX_unlock(&ccache_mutex);
146
147     return ret;
148 }
149
150
151 krb5_error_code kcm_debug_ccache(krb5_context context)
152 {
153     kcm_ccache p;
154
155     for (p = ccache_head; p != NULL; p = p->next) {
156         char *cpn = NULL, *spn = NULL;
157         int ncreds = 0;
158         struct kcm_creds *k;
159
160         if ((p->flags & KCM_FLAGS_VALID) == 0) {
161             kcm_log(7, "cache %08x: empty slot");
162             continue;
163         }
164
165         KCM_ASSERT_VALID(p);
166
167         for (k = p->creds; k != NULL; k = k->next)
168             ncreds++;
169
170         if (p->client != NULL)
171             krb5_unparse_name(context, p->client, &cpn);
172         if (p->server != NULL)
173             krb5_unparse_name(context, p->server, &spn);
174
175         kcm_log(7, "cache %08x: name %s refcnt %d flags %04x mode %04o "
176                 "uid %d gid %d client %s server %s ncreds %d",
177                 p, p->name, p->refcnt, p->flags, p->mode, p->uid, p->gid,
178                 (cpn == NULL) ? "<none>" : cpn,
179                 (spn == NULL) ? "<none>" : spn,
180                 ncreds);
181
182         if (cpn != NULL)
183             free(cpn);
184         if (spn != NULL)
185             free(spn);
186     }
187
188     return 0;
189 }
190
191 static void
192 kcm_free_ccache_data_internal(krb5_context context,
193                               kcm_ccache_data *cache)
194 {
195     KCM_ASSERT_VALID(cache);
196
197     if (cache->name != NULL) {
198         free(cache->name);
199         cache->name = NULL;
200     }
201
202     if (cache->flags & KCM_FLAGS_USE_KEYTAB) {
203         krb5_kt_close(context, cache->key.keytab);
204         cache->key.keytab = NULL;
205     } else if (cache->flags & KCM_FLAGS_USE_CACHED_KEY) {
206         krb5_free_keyblock_contents(context, &cache->key.keyblock);
207         krb5_keyblock_zero(&cache->key.keyblock);
208     }
209
210     cache->flags = 0;
211     cache->mode = 0;
212     cache->uid = -1;
213     cache->gid = -1;
214     cache->session = -1;
215
216     kcm_zero_ccache_data_internal(context, cache);
217
218     cache->tkt_life = 0;
219     cache->renew_life = 0;
220     cache->kdc_offset = 0;
221
222     cache->next = NULL;
223     cache->refcnt = 0;
224
225     HEIMDAL_MUTEX_unlock(&cache->mutex);
226     HEIMDAL_MUTEX_destroy(&cache->mutex);
227 }
228
229
230 krb5_error_code
231 kcm_ccache_destroy(krb5_context context, const char *name)
232 {
233     kcm_ccache *p, ccache;
234     krb5_error_code ret;
235
236     ret = KRB5_FCC_NOFILE;
237
238     HEIMDAL_MUTEX_lock(&ccache_mutex);
239     for (p = &ccache_head; *p != NULL; p = &(*p)->next) {
240         if (((*p)->flags & KCM_FLAGS_VALID) == 0)
241             continue;
242         if (strcmp((*p)->name, name) == 0) {
243             ret = 0;
244             break;
245         }
246     }
247     if (ret)
248         goto out;
249
250     if ((*p)->refcnt != 1) {
251         ret = EAGAIN;
252         goto out;
253     }
254
255     ccache = *p;
256     *p = (*p)->next;
257     kcm_free_ccache_data_internal(context, ccache);
258     free(ccache);
259
260 out:
261     HEIMDAL_MUTEX_unlock(&ccache_mutex);
262
263     return ret;
264 }
265
266 static krb5_error_code
267 kcm_ccache_alloc(krb5_context context,
268                  const char *name,
269                  kcm_ccache *ccache)
270 {
271     kcm_ccache slot = NULL, p;
272     krb5_error_code ret;
273     int new_slot = 0;
274
275     *ccache = NULL;
276
277     /* First, check for duplicates */
278     HEIMDAL_MUTEX_lock(&ccache_mutex);
279     ret = 0;
280     for (p = ccache_head; p != NULL; p = p->next) {
281         if (p->flags & KCM_FLAGS_VALID) {
282             if (strcmp(p->name, name) == 0) {
283                 ret = KRB5_CC_WRITE;
284                 break;
285             }
286         } else if (slot == NULL)
287             slot = p;
288     }
289
290     if (ret)
291         goto out;
292
293     /*
294      * Create an enpty slot for us.
295      */
296     if (slot == NULL) {
297         slot = (kcm_ccache_data *)malloc(sizeof(*slot));
298         if (slot == NULL) {
299             ret = KRB5_CC_NOMEM;
300             goto out;
301         }
302         slot->next = ccache_head;
303         HEIMDAL_MUTEX_init(&slot->mutex);
304         new_slot = 1;
305     }
306
307     RAND_bytes(slot->uuid, sizeof(slot->uuid));
308
309     slot->name = strdup(name);
310     if (slot->name == NULL) {
311         ret = KRB5_CC_NOMEM;
312         goto out;
313     }
314
315     slot->refcnt = 1;
316     slot->flags = KCM_FLAGS_VALID;
317     slot->mode = S_IRUSR | S_IWUSR;
318     slot->uid = -1;
319     slot->gid = -1;
320     slot->client = NULL;
321     slot->server = NULL;
322     slot->creds = NULL;
323     slot->key.keytab = NULL;
324     slot->tkt_life = 0;
325     slot->renew_life = 0;
326     slot->kdc_offset = 0;
327
328     if (new_slot)
329         ccache_head = slot;
330
331     *ccache = slot;
332
333     HEIMDAL_MUTEX_unlock(&ccache_mutex);
334     return 0;
335
336 out:
337     HEIMDAL_MUTEX_unlock(&ccache_mutex);
338     if (new_slot && slot != NULL) {
339         HEIMDAL_MUTEX_destroy(&slot->mutex);
340         free(slot);
341     }
342     return ret;
343 }
344
345 krb5_error_code
346 kcm_ccache_remove_creds_internal(krb5_context context,
347                                  kcm_ccache ccache)
348 {
349     struct kcm_creds *k;
350
351     k = ccache->creds;
352     while (k != NULL) {
353         struct kcm_creds *old;
354
355         krb5_free_cred_contents(context, &k->cred);
356         old = k;
357         k = k->next;
358         free(old);
359     }
360     ccache->creds = NULL;
361
362     return 0;
363 }
364
365 krb5_error_code
366 kcm_ccache_remove_creds(krb5_context context,
367                         kcm_ccache ccache)
368 {
369     krb5_error_code ret;
370
371     KCM_ASSERT_VALID(ccache);
372
373     HEIMDAL_MUTEX_lock(&ccache->mutex);
374     ret = kcm_ccache_remove_creds_internal(context, ccache);
375     HEIMDAL_MUTEX_unlock(&ccache->mutex);
376
377     return ret;
378 }
379
380 krb5_error_code
381 kcm_zero_ccache_data_internal(krb5_context context,
382                               kcm_ccache_data *cache)
383 {
384     if (cache->client != NULL) {
385         krb5_free_principal(context, cache->client);
386         cache->client = NULL;
387     }
388
389     if (cache->server != NULL) {
390         krb5_free_principal(context, cache->server);
391         cache->server = NULL;
392     }
393
394     kcm_ccache_remove_creds_internal(context, cache);
395
396     return 0;
397 }
398
399 krb5_error_code
400 kcm_zero_ccache_data(krb5_context context,
401                      kcm_ccache cache)
402 {
403     krb5_error_code ret;
404
405     KCM_ASSERT_VALID(cache);
406
407     HEIMDAL_MUTEX_lock(&cache->mutex);
408     ret = kcm_zero_ccache_data_internal(context, cache);
409     HEIMDAL_MUTEX_unlock(&cache->mutex);
410
411     return ret;
412 }
413
414 krb5_error_code
415 kcm_retain_ccache(krb5_context context,
416                   kcm_ccache ccache)
417 {
418     KCM_ASSERT_VALID(ccache);
419
420     HEIMDAL_MUTEX_lock(&ccache->mutex);
421     ccache->refcnt++;
422     HEIMDAL_MUTEX_unlock(&ccache->mutex);
423
424     return 0;
425 }
426
427 krb5_error_code
428 kcm_release_ccache(krb5_context context, kcm_ccache c)
429 {
430     krb5_error_code ret = 0;
431
432     KCM_ASSERT_VALID(c);
433
434     HEIMDAL_MUTEX_lock(&c->mutex);
435     if (c->refcnt == 1) {
436         kcm_free_ccache_data_internal(context, c);
437         free(c);
438     } else {
439         c->refcnt--;
440         HEIMDAL_MUTEX_unlock(&c->mutex);
441     }
442
443     return ret;
444 }
445
446 krb5_error_code
447 kcm_ccache_gen_new(krb5_context context,
448                    pid_t pid,
449                    uid_t uid,
450                    gid_t gid,
451                    kcm_ccache *ccache)
452 {
453     krb5_error_code ret;
454     char *name;
455
456     name = kcm_ccache_nextid(pid, uid, gid);
457     if (name == NULL) {
458         return KRB5_CC_NOMEM;
459     }
460
461     ret = kcm_ccache_new(context, name, ccache);
462
463     free(name);
464     return ret;
465 }
466
467 krb5_error_code
468 kcm_ccache_new(krb5_context context,
469                const char *name,
470                kcm_ccache *ccache)
471 {
472     krb5_error_code ret;
473
474     ret = kcm_ccache_alloc(context, name, ccache);
475     if (ret == 0) {
476         /*
477          * one reference is held by the linked list,
478          * one by the caller
479          */
480         kcm_retain_ccache(context, *ccache);
481     }
482
483     return ret;
484 }
485
486 krb5_error_code
487 kcm_ccache_destroy_if_empty(krb5_context context,
488                             kcm_ccache ccache)
489 {
490     krb5_error_code ret;
491
492     KCM_ASSERT_VALID(ccache);
493
494     if (ccache->creds == NULL) {
495         ret = kcm_ccache_destroy(context, ccache->name);
496     } else
497         ret = 0;
498
499     return ret;
500 }
501
502 krb5_error_code
503 kcm_ccache_store_cred(krb5_context context,
504                       kcm_ccache ccache,
505                       krb5_creds *creds,
506                       int copy)
507 {
508     krb5_error_code ret;
509     krb5_creds *tmp;
510
511     KCM_ASSERT_VALID(ccache);
512
513     HEIMDAL_MUTEX_lock(&ccache->mutex);
514     ret = kcm_ccache_store_cred_internal(context, ccache, creds, copy, &tmp);
515     HEIMDAL_MUTEX_unlock(&ccache->mutex);
516
517     return ret;
518 }
519
520 struct kcm_creds *
521 kcm_ccache_find_cred_uuid(krb5_context context,
522                           kcm_ccache ccache,
523                           kcmuuid_t uuid)
524 {
525     struct kcm_creds *c;
526
527     for (c = ccache->creds; c != NULL; c = c->next)
528         if (memcmp(c->uuid, uuid, sizeof(c->uuid)) == 0)
529             return c;
530
531     return NULL;
532 }
533
534
535
536 krb5_error_code
537 kcm_ccache_store_cred_internal(krb5_context context,
538                                kcm_ccache ccache,
539                                krb5_creds *creds,
540                                int copy,
541                                krb5_creds **credp)
542 {
543     struct kcm_creds **c;
544     krb5_error_code ret;
545
546     for (c = &ccache->creds; *c != NULL; c = &(*c)->next)
547         ;
548
549     *c = (struct kcm_creds *)calloc(1, sizeof(**c));
550     if (*c == NULL)
551         return KRB5_CC_NOMEM;
552
553     RAND_bytes((*c)->uuid, sizeof((*c)->uuid));
554
555     *credp = &(*c)->cred;
556
557     if (copy) {
558         ret = krb5_copy_creds_contents(context, creds, *credp);
559         if (ret) {
560             free(*c);
561             *c = NULL;
562         }
563     } else {
564         **credp = *creds;
565         ret = 0;
566     }
567
568     return ret;
569 }
570
571 krb5_error_code
572 kcm_ccache_remove_cred_internal(krb5_context context,
573                                 kcm_ccache ccache,
574                                 krb5_flags whichfields,
575                                 const krb5_creds *mcreds)
576 {
577     krb5_error_code ret;
578     struct kcm_creds **c;
579
580     ret = KRB5_CC_NOTFOUND;
581
582     for (c = &ccache->creds; *c != NULL; c = &(*c)->next) {
583         if (krb5_compare_creds(context, whichfields, mcreds, &(*c)->cred)) {
584             struct kcm_creds *cred = *c;
585
586             *c = cred->next;
587             krb5_free_cred_contents(context, &cred->cred);
588             free(cred);
589             ret = 0;
590             if (*c == NULL)
591                 break;
592         }
593     }
594
595     return ret;
596 }
597
598 krb5_error_code
599 kcm_ccache_remove_cred(krb5_context context,
600                        kcm_ccache ccache,
601                        krb5_flags whichfields,
602                        const krb5_creds *mcreds)
603 {
604     krb5_error_code ret;
605
606     KCM_ASSERT_VALID(ccache);
607
608     HEIMDAL_MUTEX_lock(&ccache->mutex);
609     ret = kcm_ccache_remove_cred_internal(context, ccache, whichfields, mcreds);
610     HEIMDAL_MUTEX_unlock(&ccache->mutex);
611
612     return ret;
613 }
614
615 krb5_error_code
616 kcm_ccache_retrieve_cred_internal(krb5_context context,
617                                   kcm_ccache ccache,
618                                   krb5_flags whichfields,
619                                   const krb5_creds *mcreds,
620                                   krb5_creds **creds)
621 {
622     krb5_boolean match;
623     struct kcm_creds *c;
624     krb5_error_code ret;
625
626     memset(creds, 0, sizeof(*creds));
627
628     ret = KRB5_CC_END;
629
630     match = FALSE;
631     for (c = ccache->creds; c != NULL; c = c->next) {
632         match = krb5_compare_creds(context, whichfields, mcreds, &c->cred);
633         if (match)
634             break;
635     }
636
637     if (match) {
638         ret = 0;
639         *creds = &c->cred;
640     }
641
642     return ret;
643 }
644
645 krb5_error_code
646 kcm_ccache_retrieve_cred(krb5_context context,
647                          kcm_ccache ccache,
648                          krb5_flags whichfields,
649                          const krb5_creds *mcreds,
650                          krb5_creds **credp)
651 {
652     krb5_error_code ret;
653
654     KCM_ASSERT_VALID(ccache);
655
656     HEIMDAL_MUTEX_lock(&ccache->mutex);
657     ret = kcm_ccache_retrieve_cred_internal(context, ccache,
658                                             whichfields, mcreds, credp);
659     HEIMDAL_MUTEX_unlock(&ccache->mutex);
660
661     return ret;
662 }
663
664 char *
665 kcm_ccache_first_name(kcm_client *client)
666 {
667     kcm_ccache p;
668     char *name = NULL;
669
670     HEIMDAL_MUTEX_lock(&ccache_mutex);
671
672     for (p = ccache_head; p != NULL; p = p->next) {
673         if (kcm_is_same_session(client, p->uid, p->session))
674             break;
675     }
676     if (p)
677         name = strdup(p->name);
678     HEIMDAL_MUTEX_unlock(&ccache_mutex);
679     return name;
680 }