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