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