s4:heimdal: import lorikeet-heimdal-202201172009 (commit 5a0b45cd723628b3690ea848548b...
[samba.git] / source4 / heimdal / lib / krb5 / mcache.c
1 /*
2  * Copyright (c) 1997-2004 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35
36 #include "krb5_locl.h"
37
38 typedef struct krb5_mcache {
39     char *name;
40     unsigned int refcnt;
41     unsigned int anonymous:1;
42     unsigned int dead:1;
43     krb5_principal primary_principal;
44     struct link {
45         krb5_creds cred;
46         struct link *next;
47     } *creds;
48     struct krb5_mcache *next;
49     time_t mtime;
50     krb5_deltat kdc_offset;
51     HEIMDAL_MUTEX mutex;
52 } krb5_mcache;
53
54 static HEIMDAL_MUTEX mcc_mutex = HEIMDAL_MUTEX_INITIALIZER;
55 static struct krb5_mcache *mcc_head;
56
57 #define MCACHE(X)       ((krb5_mcache *)(X)->data.data)
58
59 #define MISDEAD(X)      ((X)->dead)
60
61 static krb5_error_code KRB5_CALLCONV
62 mcc_get_name_2(krb5_context context,
63                krb5_ccache id,
64                const char **name,
65                const char **col,
66                const char **sub)
67 {
68     if (name)
69         *name = MCACHE(id)->name;
70     if (col)
71         *col = NULL;
72     if (sub)
73         *sub = MCACHE(id)->name;
74     return 0;
75 }
76
77 static krb5_error_code
78 mcc_alloc(krb5_context context, const char *name, krb5_mcache **out)
79 {
80     krb5_mcache *m, *m_c;
81     size_t counter = 0;
82     int ret = 0;
83     unsigned create_anonymous = 0;
84
85     *out = NULL;
86     ALLOC(m, 1);
87     if(m == NULL)
88         return krb5_enomem(context);
89
90 again:
91     if (counter > 3) {
92         free(m->name);
93         free(m);
94         return EAGAIN; /* XXX */
95     }
96     if(name == NULL) {
97         ret = asprintf(&m->name, "u%p-%llu", m, (unsigned long long)counter);
98     } else if (strcmp(name, "anonymous") == 0) {
99         ret = asprintf(&m->name, "anonymous-%p-%llu", m, (unsigned long long)counter);
100         create_anonymous = 1;
101     } else {
102         m->name = strdup(name);
103     }
104     if(ret < 0 || m->name == NULL) {
105         free(m);
106         return krb5_enomem(context);
107     }
108
109     /* check for dups first */
110     HEIMDAL_MUTEX_lock(&mcc_mutex);
111     for (m_c = mcc_head; m_c != NULL; m_c = m_c->next)
112         if (strcmp(m->name, m_c->name) == 0)
113             break;
114     if (m_c) {
115         free(m->name);
116         free(m);
117         if (name && !create_anonymous) {
118             /* We raced with another thread to create this cache */
119             m = m_c;
120             HEIMDAL_MUTEX_lock(&(m->mutex));
121             m->refcnt++;
122             HEIMDAL_MUTEX_unlock(&(m->mutex));
123         } else {
124             /* How likely are we to conflict on new_unique anyways?? */
125             counter++;
126             free(m->name);
127             m->name = NULL;
128             HEIMDAL_MUTEX_unlock(&mcc_mutex);
129             goto again;
130         }
131         HEIMDAL_MUTEX_unlock(&mcc_mutex);
132         *out = m;
133         return 0;
134     }
135
136     m->anonymous = create_anonymous;
137     m->dead = 0;
138     m->refcnt = 1;
139     m->primary_principal = NULL;
140     m->creds = NULL;
141     m->mtime = time(NULL);
142     m->kdc_offset = 0;
143     m->next = mcc_head;
144     HEIMDAL_MUTEX_init(&(m->mutex));
145     mcc_head = m;
146     HEIMDAL_MUTEX_unlock(&mcc_mutex);
147     *out = m;
148     return 0;
149 }
150
151 static krb5_error_code KRB5_CALLCONV
152 mcc_resolve_2(krb5_context context,
153               krb5_ccache *id,
154               const char *res,
155               const char *sub)
156 {
157     krb5_error_code ret;
158     krb5_mcache *m;
159
160     if ((ret = mcc_alloc(context, sub && *sub ? sub : res, &m)))
161         return ret;
162
163     (*id)->data.data = m;
164     (*id)->data.length = sizeof(*m);
165
166     return 0;
167 }
168
169
170 static krb5_error_code KRB5_CALLCONV
171 mcc_gen_new(krb5_context context, krb5_ccache *id)
172 {
173     krb5_error_code ret;
174     krb5_mcache *m;
175
176     if ((ret = mcc_alloc(context, NULL, &m)))
177         return ret;
178
179     (*id)->data.data = m;
180     (*id)->data.length = sizeof(*m);
181
182     return 0;
183 }
184
185 static void KRB5_CALLCONV
186 mcc_destroy_internal(krb5_context context,
187                      krb5_mcache *m)
188 {
189     struct link *l;
190
191     if (m->primary_principal != NULL) {
192         krb5_free_principal (context, m->primary_principal);
193         m->primary_principal = NULL;
194     }
195     m->dead = 1;
196
197     l = m->creds;
198     while (l != NULL) {
199         struct link *old;
200
201         krb5_free_cred_contents (context, &l->cred);
202         old = l;
203         l = l->next;
204         free (old);
205     }
206
207     m->creds = NULL;
208     return;
209 }
210
211 static krb5_error_code KRB5_CALLCONV
212 mcc_initialize(krb5_context context,
213                krb5_ccache id,
214                krb5_principal primary_principal)
215 {
216     krb5_mcache *m = MCACHE(id);
217     krb5_error_code ret = 0;
218     HEIMDAL_MUTEX_lock(&(m->mutex));
219     heim_assert(m->refcnt != 0, "resurection released mcache");
220     /*
221      * It's important to destroy any existing
222      * creds here, that matches the baheviour
223      * of all other backends and also the
224      * MEMORY: backend in MIT.
225      */
226     mcc_destroy_internal(context, m);
227     m->dead = 0;
228     m->kdc_offset = 0;
229     m->mtime = time(NULL);
230     ret = krb5_copy_principal (context,
231                                primary_principal,
232                                &m->primary_principal);
233     HEIMDAL_MUTEX_unlock(&(m->mutex));
234     return ret;
235 }
236
237 static int
238 mcc_close_internal(krb5_mcache *m)
239 {
240     HEIMDAL_MUTEX_lock(&(m->mutex));
241     heim_assert(m->refcnt != 0, "closed dead cache mcache");
242     if (--m->refcnt != 0) {
243         HEIMDAL_MUTEX_unlock(&(m->mutex));
244         return 0;
245     }
246     if (MISDEAD(m)) {
247         free(m->name);
248         HEIMDAL_MUTEX_unlock(&(m->mutex));
249         return 1;
250     }
251     HEIMDAL_MUTEX_unlock(&(m->mutex));
252     return 0;
253 }
254
255 static krb5_error_code KRB5_CALLCONV
256 mcc_close(krb5_context context,
257           krb5_ccache id)
258 {
259     krb5_mcache *m = MCACHE(id);
260
261     if (mcc_close_internal(MCACHE(id))) {
262         HEIMDAL_MUTEX_destroy(&(m->mutex));
263         krb5_data_free(&id->data);
264     }
265     return 0;
266 }
267
268 static krb5_error_code KRB5_CALLCONV
269 mcc_destroy(krb5_context context,
270             krb5_ccache id)
271 {
272     krb5_mcache **n, *m = MCACHE(id);
273
274     HEIMDAL_MUTEX_lock(&mcc_mutex);
275     HEIMDAL_MUTEX_lock(&(m->mutex));
276     if (m->refcnt == 0)
277     {
278         HEIMDAL_MUTEX_unlock(&(m->mutex));
279         HEIMDAL_MUTEX_unlock(&mcc_mutex);
280         krb5_abortx(context, "mcc_destroy: refcnt already 0");
281     }
282
283     if (!MISDEAD(m)) {
284         /* if this is an active mcache, remove it from the linked
285            list, and free all data */
286         for(n = &mcc_head; n && *n; n = &(*n)->next) {
287             if(m == *n) {
288                 *n = m->next;
289                 break;
290             }
291         }
292         mcc_destroy_internal(context, m);
293     }
294     HEIMDAL_MUTEX_unlock(&(m->mutex));
295     HEIMDAL_MUTEX_unlock(&mcc_mutex);
296     return 0;
297 }
298
299 static krb5_error_code KRB5_CALLCONV
300 mcc_store_cred(krb5_context context,
301                krb5_ccache id,
302                krb5_creds *creds)
303 {
304     krb5_mcache *m = MCACHE(id);
305     krb5_error_code ret;
306     struct link *l;
307
308     HEIMDAL_MUTEX_lock(&(m->mutex));
309     if (MISDEAD(m))
310     {
311         HEIMDAL_MUTEX_unlock(&(m->mutex));
312         return ENOENT;
313     }
314
315     l = malloc (sizeof(*l));
316     if (l == NULL)
317         return krb5_enomem(context);
318     l->next = m->creds;
319     m->creds = l;
320     memset (&l->cred, 0, sizeof(l->cred));
321     ret = krb5_copy_creds_contents (context, creds, &l->cred);
322     if (ret) {
323         m->creds = l->next;
324         free (l);
325         HEIMDAL_MUTEX_unlock(&(m->mutex));
326         return ret;
327     }
328     m->mtime = time(NULL);
329         HEIMDAL_MUTEX_unlock(&(m->mutex));
330     return 0;
331 }
332
333 static krb5_error_code KRB5_CALLCONV
334 mcc_get_principal(krb5_context context,
335                   krb5_ccache id,
336                   krb5_principal *principal)
337 {
338     krb5_mcache *m = MCACHE(id);
339     krb5_error_code ret = 0;
340
341     HEIMDAL_MUTEX_lock(&(m->mutex));
342     if (MISDEAD(m) || m->primary_principal == NULL) {
343         HEIMDAL_MUTEX_unlock(&(m->mutex));
344         return ENOENT;
345     }
346     ret = krb5_copy_principal (context,
347                                m->primary_principal,
348                                principal);
349     HEIMDAL_MUTEX_unlock(&(m->mutex));
350     return ret;
351 }
352
353 static krb5_error_code KRB5_CALLCONV
354 mcc_get_first (krb5_context context,
355                 krb5_ccache id,
356                 krb5_cc_cursor *cursor)
357 {
358     krb5_mcache *m = MCACHE(id);
359
360     HEIMDAL_MUTEX_lock(&(m->mutex));
361     if (MISDEAD(m)) {
362         HEIMDAL_MUTEX_unlock(&(m->mutex));
363         return ENOENT;
364     }
365     *cursor = m->creds;
366
367     HEIMDAL_MUTEX_unlock(&(m->mutex));
368     return 0;
369 }
370
371 static krb5_error_code KRB5_CALLCONV
372 mcc_get_next (krb5_context context,
373               krb5_ccache id,
374               krb5_cc_cursor *cursor,
375               krb5_creds *creds)
376 {
377     krb5_mcache *m = MCACHE(id);
378     struct link *l;
379
380     HEIMDAL_MUTEX_lock(&(m->mutex));
381     if (MISDEAD(m)) {
382         HEIMDAL_MUTEX_unlock(&(m->mutex));
383         return ENOENT;
384     }
385     HEIMDAL_MUTEX_unlock(&(m->mutex));
386
387     l = *cursor;
388     if (l != NULL) {
389         *cursor = l->next;
390         return krb5_copy_creds_contents (context,
391                                          &l->cred,
392                                          creds);
393     } else
394         return KRB5_CC_END;
395 }
396
397 static krb5_error_code KRB5_CALLCONV
398 mcc_end_get (krb5_context context,
399              krb5_ccache id,
400              krb5_cc_cursor *cursor)
401 {
402     return 0;
403 }
404
405 static krb5_error_code KRB5_CALLCONV
406 mcc_remove_cred(krb5_context context,
407                  krb5_ccache id,
408                  krb5_flags which,
409                  krb5_creds *mcreds)
410 {
411     krb5_mcache *m = MCACHE(id);
412     struct link **q, *p;
413
414     HEIMDAL_MUTEX_lock(&(m->mutex));
415
416     for(q = &m->creds, p = *q; p; p = *q) {
417         if(krb5_compare_creds(context, which, mcreds, &p->cred)) {
418             *q = p->next;
419             krb5_free_cred_contents(context, &p->cred);
420             free(p);
421             m->mtime = time(NULL);
422         } else
423             q = &p->next;
424     }
425     HEIMDAL_MUTEX_unlock(&(m->mutex));
426     return 0;
427 }
428
429 static krb5_error_code KRB5_CALLCONV
430 mcc_set_flags(krb5_context context,
431               krb5_ccache id,
432               krb5_flags flags)
433 {
434     return 0; /* XXX */
435 }
436
437 struct mcache_iter {
438     krb5_mcache *cache;
439 };
440
441 static krb5_mcache *
442 mcc_get_cache_find_next_internal(krb5_mcache *next)
443 {
444     HEIMDAL_MUTEX_lock(&mcc_mutex);
445     for (; next != NULL && next->anonymous; next = next->next) {
446         /* noop: iterate over all anonymous entries */
447     }
448     if (next != NULL) {
449         HEIMDAL_MUTEX_lock(&(next->mutex));
450         next->refcnt++;
451         HEIMDAL_MUTEX_unlock(&(next->mutex));
452         next = next->next;
453     }
454     HEIMDAL_MUTEX_unlock(&mcc_mutex);
455
456     return next;
457 }
458
459 static krb5_error_code KRB5_CALLCONV
460 mcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
461 {
462     struct mcache_iter *iter;
463
464     iter = calloc(1, sizeof(*iter));
465     if (iter == NULL)
466         return krb5_enomem(context);
467
468     iter->cache = mcc_get_cache_find_next_internal(mcc_head);
469
470     *cursor = iter;
471     return 0;
472 }
473
474 static krb5_error_code KRB5_CALLCONV
475 mcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
476 {
477     struct mcache_iter *iter = cursor;
478     krb5_error_code ret;
479     krb5_mcache *m;
480
481     if (iter->cache == NULL)
482         return KRB5_CC_END;
483
484     m = iter->cache;
485     iter->cache = mcc_get_cache_find_next_internal(m);
486
487     ret = _krb5_cc_allocate(context, &krb5_mcc_ops, id);
488     if (ret)
489         return ret;
490
491     (*id)->data.data = m;
492     (*id)->data.length = sizeof(*m);
493
494     return 0;
495 }
496
497 static krb5_error_code KRB5_CALLCONV
498 mcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
499 {
500     struct mcache_iter *iter = cursor;
501
502     if (iter->cache)
503         mcc_close_internal(iter->cache);
504     iter->cache = NULL;
505     free(iter);
506     return 0;
507 }
508
509 static krb5_error_code KRB5_CALLCONV
510 mcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
511 {
512     krb5_mcache *mfrom = MCACHE(from), *mto = MCACHE(to);
513     struct link *creds;
514     krb5_principal principal;
515     krb5_mcache **n;
516
517     HEIMDAL_MUTEX_lock(&mcc_mutex);
518
519     /* drop the from cache from the linked list to avoid lookups */
520     for(n = &mcc_head; n && *n; n = &(*n)->next) {
521         if(mfrom == *n) {
522             *n = mfrom->next;
523             break;
524         }
525     }
526
527     HEIMDAL_MUTEX_lock(&(mfrom->mutex));
528     HEIMDAL_MUTEX_lock(&(mto->mutex));
529     /* swap creds */
530     creds = mto->creds;
531     mto->creds = mfrom->creds;
532     mfrom->creds = creds;
533     /* swap principal */
534     principal = mto->primary_principal;
535     mto->primary_principal = mfrom->primary_principal;
536     mfrom->primary_principal = principal;
537
538     mto->mtime = mfrom->mtime = time(NULL);
539
540     HEIMDAL_MUTEX_unlock(&(mfrom->mutex));
541     HEIMDAL_MUTEX_unlock(&(mto->mutex));
542     HEIMDAL_MUTEX_unlock(&mcc_mutex);
543
544     krb5_cc_destroy(context, from);
545     return 0;
546 }
547
548 static krb5_error_code KRB5_CALLCONV
549 mcc_default_name(krb5_context context, char **str)
550 {
551     *str = strdup("MEMORY:");
552     if (*str == NULL)
553         return krb5_enomem(context);
554     return 0;
555 }
556
557 static krb5_error_code KRB5_CALLCONV
558 mcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
559 {
560     krb5_mcache *m = MCACHE(id);
561     HEIMDAL_MUTEX_lock(&(m->mutex));
562     *mtime = m->mtime;
563     HEIMDAL_MUTEX_unlock(&(m->mutex));
564     return 0;
565 }
566
567 static krb5_error_code KRB5_CALLCONV
568 mcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
569 {
570     krb5_mcache *m = MCACHE(id);
571     HEIMDAL_MUTEX_lock(&(m->mutex));
572     m->kdc_offset = kdc_offset;
573     HEIMDAL_MUTEX_unlock(&(m->mutex));
574     return 0;
575 }
576
577 static krb5_error_code KRB5_CALLCONV
578 mcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
579 {
580     krb5_mcache *m = MCACHE(id);
581     HEIMDAL_MUTEX_lock(&(m->mutex));
582     *kdc_offset = m->kdc_offset;
583     HEIMDAL_MUTEX_unlock(&(m->mutex));
584     return 0;
585 }
586
587
588 /**
589  * Variable containing the MEMORY based credential cache implemention.
590  *
591  * @ingroup krb5_ccache
592  */
593
594 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_mcc_ops = {
595     KRB5_CC_OPS_VERSION_5,
596     "MEMORY",
597     NULL,
598     NULL,
599     mcc_gen_new,
600     mcc_initialize,
601     mcc_destroy,
602     mcc_close,
603     mcc_store_cred,
604     NULL, /* mcc_retrieve */
605     mcc_get_principal,
606     mcc_get_first,
607     mcc_get_next,
608     mcc_end_get,
609     mcc_remove_cred,
610     mcc_set_flags,
611     NULL,
612     mcc_get_cache_first,
613     mcc_get_cache_next,
614     mcc_end_cache_get,
615     mcc_move,
616     mcc_default_name,
617     NULL,
618     mcc_lastchange,
619     mcc_set_kdc_offset,
620     mcc_get_kdc_offset,
621     mcc_get_name_2,
622     mcc_resolve_2
623 };