xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/mcache.c (revision afab4e300d3a9fb07dd8c80daf53d0feb3345706)
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 
40 typedef 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 
55 static HEIMDAL_MUTEX mcc_mutex = HEIMDAL_MUTEX_INITIALIZER;
56 static struct krb5_mcache *mcc_head;
57 
58 #define	MCACHE(X)	((krb5_mcache *)(X)->data.data)
59 
60 #define MISDEAD(X)	((X)->dead)
61 
62 static const char* KRB5_CALLCONV
mcc_get_name(krb5_context context,krb5_ccache id)63 mcc_get_name(krb5_context context,
64 	     krb5_ccache id)
65 {
66     return MCACHE(id)->name;
67 }
68 
69 static krb5_mcache * KRB5_CALLCONV
mcc_alloc(const char * name)70 mcc_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 
111 static krb5_error_code KRB5_CALLCONV
mcc_resolve(krb5_context context,krb5_ccache * id,const char * res)112 mcc_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 
145 static krb5_error_code KRB5_CALLCONV
mcc_gen_new(krb5_context context,krb5_ccache * id)146 mcc_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 
164 static void KRB5_CALLCONV
mcc_destroy_internal(krb5_context context,krb5_mcache * m)165 mcc_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 
190 static krb5_error_code KRB5_CALLCONV
mcc_initialize(krb5_context context,krb5_ccache id,krb5_principal primary_principal)191 mcc_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 
216 static int
mcc_close_internal(krb5_mcache * m)217 mcc_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 
234 static krb5_error_code KRB5_CALLCONV
mcc_close(krb5_context context,krb5_ccache id)235 mcc_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 
247 static krb5_error_code KRB5_CALLCONV
mcc_destroy(krb5_context context,krb5_ccache id)248 mcc_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 
278 static krb5_error_code KRB5_CALLCONV
mcc_store_cred(krb5_context context,krb5_ccache id,krb5_creds * creds)279 mcc_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 
316 static krb5_error_code KRB5_CALLCONV
mcc_get_principal(krb5_context context,krb5_ccache id,krb5_principal * principal)317 mcc_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 
336 static krb5_error_code KRB5_CALLCONV
mcc_get_first(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)337 mcc_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 
354 static krb5_error_code KRB5_CALLCONV
mcc_get_next(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * creds)355 mcc_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 
380 static krb5_error_code KRB5_CALLCONV
mcc_end_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)381 mcc_end_get (krb5_context context,
382 	     krb5_ccache id,
383 	     krb5_cc_cursor *cursor)
384 {
385     return 0;
386 }
387 
388 static krb5_error_code KRB5_CALLCONV
mcc_remove_cred(krb5_context context,krb5_ccache id,krb5_flags which,krb5_creds * mcreds)389 mcc_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 
412 static krb5_error_code KRB5_CALLCONV
mcc_set_flags(krb5_context context,krb5_ccache id,krb5_flags flags)413 mcc_set_flags(krb5_context context,
414 	      krb5_ccache id,
415 	      krb5_flags flags)
416 {
417     return 0; /* XXX */
418 }
419 
420 struct mcache_iter {
421     krb5_mcache *cache;
422 };
423 
424 static krb5_error_code KRB5_CALLCONV
mcc_get_cache_first(krb5_context context,krb5_cc_cursor * cursor)425 mcc_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 
446 static krb5_error_code KRB5_CALLCONV
mcc_get_cache_next(krb5_context context,krb5_cc_cursor cursor,krb5_ccache * id)447 mcc_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 
478 static krb5_error_code KRB5_CALLCONV
mcc_end_cache_get(krb5_context context,krb5_cc_cursor cursor)479 mcc_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 
490 static krb5_error_code KRB5_CALLCONV
mcc_move(krb5_context context,krb5_ccache from,krb5_ccache to)491 mcc_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 
529 static krb5_error_code KRB5_CALLCONV
mcc_default_name(krb5_context context,char ** str)530 mcc_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 
538 static krb5_error_code KRB5_CALLCONV
mcc_lastchange(krb5_context context,krb5_ccache id,krb5_timestamp * mtime)539 mcc_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 
548 static krb5_error_code KRB5_CALLCONV
mcc_set_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat kdc_offset)549 mcc_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 
558 static krb5_error_code KRB5_CALLCONV
mcc_get_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat * kdc_offset)559 mcc_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 
575 KRB5_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