1/*	$NetBSD: cache.c,v 1.6 2023/06/19 21:41:41 christos Exp $	*/
2
3/*
4 * Copyright (c) 2005, PADL Software Pty Ltd.
5 * All rights reserved.
6 *
7 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of PADL Software nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include "kcm_locl.h"
38
39HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
40kcm_ccache_data *ccache_head = NULL;
41static unsigned int ccache_nextid = 0;
42
43char *kcm_ccache_nextid(pid_t pid, uid_t uid, gid_t gid)
44{
45    unsigned n;
46    char *name;
47    int ret;
48
49    HEIMDAL_MUTEX_lock(&ccache_mutex);
50    n = ++ccache_nextid;
51    HEIMDAL_MUTEX_unlock(&ccache_mutex);
52
53    ret = asprintf(&name, "%ld:%u", (long)uid, n);
54    if (ret == -1)
55	return NULL;
56
57    return name;
58}
59
60krb5_error_code
61kcm_ccache_resolve(krb5_context context,
62		   const char *name,
63		   kcm_ccache *ccache)
64{
65    kcm_ccache p;
66    krb5_error_code ret;
67
68    *ccache = NULL;
69
70    ret = KRB5_FCC_NOFILE;
71
72    HEIMDAL_MUTEX_lock(&ccache_mutex);
73
74    for (p = ccache_head; p != NULL; p = p->next) {
75	if ((p->flags & KCM_FLAGS_VALID) == 0)
76	    continue;
77	if (strcmp(p->name, name) == 0) {
78	    ret = 0;
79	    break;
80	}
81    }
82
83    if (ret == 0) {
84	kcm_retain_ccache(context, p);
85	*ccache = p;
86    }
87
88    HEIMDAL_MUTEX_unlock(&ccache_mutex);
89
90    return ret;
91}
92
93krb5_error_code
94kcm_ccache_resolve_by_uuid(krb5_context context,
95			   kcmuuid_t uuid,
96			   kcm_ccache *ccache)
97{
98    kcm_ccache p;
99    krb5_error_code ret;
100
101    *ccache = NULL;
102
103    ret = KRB5_FCC_NOFILE;
104
105    HEIMDAL_MUTEX_lock(&ccache_mutex);
106
107    for (p = ccache_head; p != NULL; p = p->next) {
108	if ((p->flags & KCM_FLAGS_VALID) == 0)
109	    continue;
110	if (memcmp(p->uuid, uuid, sizeof(kcmuuid_t)) == 0) {
111	    ret = 0;
112	    break;
113	}
114    }
115
116    if (ret == 0) {
117	kcm_retain_ccache(context, p);
118	*ccache = p;
119    }
120
121    HEIMDAL_MUTEX_unlock(&ccache_mutex);
122
123    return ret;
124}
125
126krb5_error_code
127kcm_ccache_get_uuids(krb5_context context, kcm_client *client, kcm_operation opcode, krb5_storage *sp)
128{
129    krb5_error_code ret;
130    kcm_ccache p;
131
132    ret = KRB5_FCC_NOFILE;
133
134    HEIMDAL_MUTEX_lock(&ccache_mutex);
135
136    for (p = ccache_head; p != NULL; p = p->next) {
137	if ((p->flags & KCM_FLAGS_VALID) == 0)
138	    continue;
139	ret = kcm_access(context, client, opcode, p);
140	if (ret) {
141	    ret = 0;
142	    continue;
143	}
144	krb5_storage_write(sp, p->uuid, sizeof(p->uuid));
145    }
146
147    HEIMDAL_MUTEX_unlock(&ccache_mutex);
148
149    return ret;
150}
151
152
153krb5_error_code kcm_debug_ccache(krb5_context context)
154{
155    kcm_ccache p;
156
157    for (p = ccache_head; p != NULL; p = p->next) {
158	char *cpn = NULL, *spn = NULL;
159	int ncreds = 0;
160	struct kcm_creds *k;
161
162	if ((p->flags & KCM_FLAGS_VALID) == 0) {
163	    kcm_log(7, "cache %08x: empty slot");
164	    continue;
165	}
166
167	KCM_ASSERT_VALID(p);
168
169	for (k = p->creds; k != NULL; k = k->next)
170	    ncreds++;
171
172	if (p->client != NULL)
173	    krb5_unparse_name(context, p->client, &cpn);
174	if (p->server != NULL)
175	    krb5_unparse_name(context, p->server, &spn);
176
177	kcm_log(7, "cache %08x: name %s refcnt %d flags %04x mode %04o "
178		"uid %d gid %d client %s server %s ncreds %d",
179		p, p->name, p->refcnt, p->flags, p->mode, p->uid, p->gid,
180		(cpn == NULL) ? "<none>" : cpn,
181		(spn == NULL) ? "<none>" : spn,
182		ncreds);
183
184	if (cpn != NULL)
185	    free(cpn);
186	if (spn != NULL)
187	    free(spn);
188    }
189
190    return 0;
191}
192
193static void
194kcm_free_ccache_data_internal(krb5_context context,
195			      kcm_ccache_data *cache)
196{
197    KCM_ASSERT_VALID(cache);
198
199    if (cache->name != NULL) {
200	free(cache->name);
201	cache->name = NULL;
202    }
203
204    if (cache->flags & KCM_FLAGS_USE_KEYTAB) {
205	krb5_kt_close(context, cache->key.keytab);
206	cache->key.keytab = NULL;
207    } else if (cache->flags & KCM_FLAGS_USE_CACHED_KEY) {
208	krb5_free_keyblock_contents(context, &cache->key.keyblock);
209	krb5_keyblock_zero(&cache->key.keyblock);
210    }
211
212    cache->flags = 0;
213    cache->mode = 0;
214    cache->uid = -1;
215    cache->gid = -1;
216    cache->session = -1;
217
218    kcm_zero_ccache_data_internal(context, cache);
219
220    cache->tkt_life = 0;
221    cache->renew_life = 0;
222
223    cache->next = NULL;
224    cache->refcnt = 0;
225
226    HEIMDAL_MUTEX_unlock(&cache->mutex);
227    HEIMDAL_MUTEX_destroy(&cache->mutex);
228}
229
230
231krb5_error_code
232kcm_ccache_destroy(krb5_context context, const char *name)
233{
234    kcm_ccache *p, ccache;
235    krb5_error_code ret;
236
237    ret = KRB5_FCC_NOFILE;
238
239    HEIMDAL_MUTEX_lock(&ccache_mutex);
240    for (p = &ccache_head; *p != NULL; p = &(*p)->next) {
241	if (((*p)->flags & KCM_FLAGS_VALID) == 0)
242	    continue;
243	if (strcmp((*p)->name, name) == 0) {
244	    ret = 0;
245	    break;
246	}
247    }
248    if (ret)
249	goto out;
250
251    if ((*p)->refcnt != 1) {
252	ret = EAGAIN;
253	goto out;
254    }
255
256    ccache = *p;
257    *p = (*p)->next;
258    kcm_free_ccache_data_internal(context, ccache);
259    free(ccache);
260
261out:
262    HEIMDAL_MUTEX_unlock(&ccache_mutex);
263
264    return ret;
265}
266
267static krb5_error_code
268kcm_ccache_alloc(krb5_context context,
269		 const char *name,
270		 kcm_ccache *ccache)
271{
272    kcm_ccache slot = NULL, p;
273    krb5_error_code ret;
274    int new_slot = 0;
275
276    *ccache = NULL;
277
278    /* First, check for duplicates */
279    HEIMDAL_MUTEX_lock(&ccache_mutex);
280    ret = 0;
281    for (p = ccache_head; p != NULL; p = p->next) {
282	if (p->flags & KCM_FLAGS_VALID) {
283	    if (strcmp(p->name, name) == 0) {
284		ret = KRB5_CC_WRITE;
285		break;
286	    }
287	} else if (slot == NULL)
288	    slot = p;
289    }
290
291    if (ret)
292	goto out;
293
294    /*
295     * Create an enpty slot for us.
296     */
297    if (slot == NULL) {
298	slot = (kcm_ccache_data *)malloc(sizeof(*slot));
299	if (slot == NULL) {
300	    ret = KRB5_CC_NOMEM;
301	    goto out;
302	}
303	slot->next = ccache_head;
304	HEIMDAL_MUTEX_init(&slot->mutex);
305	new_slot = 1;
306    }
307
308    RAND_bytes(slot->uuid, sizeof(slot->uuid));
309
310    slot->name = strdup(name);
311    if (slot->name == NULL) {
312	ret = KRB5_CC_NOMEM;
313	goto out;
314    }
315
316    slot->refcnt = 1;
317    slot->flags = KCM_FLAGS_VALID;
318    slot->mode = S_IRUSR | S_IWUSR;
319    slot->uid = -1;
320    slot->gid = -1;
321    slot->client = NULL;
322    slot->server = NULL;
323    slot->creds = NULL;
324    slot->key.keytab = NULL;
325    slot->tkt_life = 0;
326    slot->renew_life = 0;
327    slot->kdc_offset = 0;
328
329    if (new_slot)
330	ccache_head = slot;
331
332    *ccache = slot;
333
334    HEIMDAL_MUTEX_unlock(&ccache_mutex);
335    return 0;
336
337out:
338    HEIMDAL_MUTEX_unlock(&ccache_mutex);
339    if (new_slot && slot != NULL) {
340	HEIMDAL_MUTEX_destroy(&slot->mutex);
341	free(slot);
342    }
343    return ret;
344}
345
346krb5_error_code
347kcm_ccache_remove_creds_internal(krb5_context context,
348				 kcm_ccache ccache)
349{
350    struct kcm_creds *k;
351
352    k = ccache->creds;
353    while (k != NULL) {
354	struct kcm_creds *old;
355
356	krb5_free_cred_contents(context, &k->cred);
357	old = k;
358	k = k->next;
359	free(old);
360    }
361    ccache->creds = NULL;
362
363    return 0;
364}
365
366krb5_error_code
367kcm_ccache_remove_creds(krb5_context context,
368			kcm_ccache ccache)
369{
370    krb5_error_code ret;
371
372    KCM_ASSERT_VALID(ccache);
373
374    HEIMDAL_MUTEX_lock(&ccache->mutex);
375    ret = kcm_ccache_remove_creds_internal(context, ccache);
376    HEIMDAL_MUTEX_unlock(&ccache->mutex);
377
378    return ret;
379}
380
381krb5_error_code
382kcm_zero_ccache_data_internal(krb5_context context,
383			      kcm_ccache_data *cache)
384{
385    if (cache->client != NULL) {
386	krb5_free_principal(context, cache->client);
387	cache->client = NULL;
388    }
389
390    if (cache->server != NULL) {
391	krb5_free_principal(context, cache->server);
392	cache->server = NULL;
393    }
394
395    kcm_ccache_remove_creds_internal(context, cache);
396
397    return 0;
398}
399
400krb5_error_code
401kcm_zero_ccache_data(krb5_context context,
402		     kcm_ccache cache)
403{
404    krb5_error_code ret;
405
406    KCM_ASSERT_VALID(cache);
407
408    HEIMDAL_MUTEX_lock(&cache->mutex);
409    ret = kcm_zero_ccache_data_internal(context, cache);
410    HEIMDAL_MUTEX_unlock(&cache->mutex);
411
412    return ret;
413}
414
415krb5_error_code
416kcm_retain_ccache(krb5_context context,
417		  kcm_ccache ccache)
418{
419    KCM_ASSERT_VALID(ccache);
420
421    HEIMDAL_MUTEX_lock(&ccache->mutex);
422    ccache->refcnt++;
423    HEIMDAL_MUTEX_unlock(&ccache->mutex);
424
425    return 0;
426}
427
428krb5_error_code
429kcm_release_ccache(krb5_context context, kcm_ccache c)
430{
431    krb5_error_code ret = 0;
432
433    KCM_ASSERT_VALID(c);
434
435    HEIMDAL_MUTEX_lock(&c->mutex);
436    if (c->refcnt == 1) {
437	kcm_free_ccache_data_internal(context, c);
438	free(c);
439    } else {
440	c->refcnt--;
441	HEIMDAL_MUTEX_unlock(&c->mutex);
442    }
443
444    return ret;
445}
446
447krb5_error_code
448kcm_ccache_gen_new(krb5_context context,
449		   pid_t pid,
450		   uid_t uid,
451		   gid_t gid,
452		   kcm_ccache *ccache)
453{
454    krb5_error_code ret;
455    char *name;
456
457    name = kcm_ccache_nextid(pid, uid, gid);
458    if (name == NULL) {
459	return KRB5_CC_NOMEM;
460    }
461
462    ret = kcm_ccache_new(context, name, ccache);
463
464    free(name);
465    return ret;
466}
467
468krb5_error_code
469kcm_ccache_new(krb5_context context,
470	       const char *name,
471	       kcm_ccache *ccache)
472{
473    krb5_error_code ret;
474
475    ret = kcm_ccache_alloc(context, name, ccache);
476    if (ret == 0) {
477	/*
478	 * one reference is held by the linked list,
479	 * one by the caller
480	 */
481	kcm_retain_ccache(context, *ccache);
482    }
483
484    return ret;
485}
486
487krb5_error_code
488kcm_ccache_destroy_if_empty(krb5_context context,
489			    kcm_ccache ccache)
490{
491    krb5_error_code ret;
492
493    KCM_ASSERT_VALID(ccache);
494
495    if (ccache->creds == NULL) {
496	ret = kcm_ccache_destroy(context, ccache->name);
497    } else
498	ret = 0;
499
500    return ret;
501}
502
503krb5_error_code
504kcm_ccache_store_cred(krb5_context context,
505		      kcm_ccache ccache,
506		      krb5_creds *creds,
507		      int copy)
508{
509    krb5_error_code ret;
510    krb5_creds *tmp;
511
512    KCM_ASSERT_VALID(ccache);
513
514    HEIMDAL_MUTEX_lock(&ccache->mutex);
515    ret = kcm_ccache_store_cred_internal(context, ccache, creds, copy, &tmp);
516    HEIMDAL_MUTEX_unlock(&ccache->mutex);
517
518    return ret;
519}
520
521struct kcm_creds *
522kcm_ccache_find_cred_uuid(krb5_context context,
523			  kcm_ccache ccache,
524			  kcmuuid_t uuid)
525{
526    struct kcm_creds *c;
527
528    for (c = ccache->creds; c != NULL; c = c->next)
529	if (memcmp(c->uuid, uuid, sizeof(c->uuid)) == 0)
530	    return c;
531
532    return NULL;
533}
534
535
536
537krb5_error_code
538kcm_ccache_store_cred_internal(krb5_context context,
539			       kcm_ccache ccache,
540			       krb5_creds *creds,
541			       int copy,
542			       krb5_creds **credp)
543{
544    struct kcm_creds **c;
545    krb5_error_code ret;
546
547    for (c = &ccache->creds; *c != NULL; c = &(*c)->next)
548	;
549
550    *c = (struct kcm_creds *)calloc(1, sizeof(**c));
551    if (*c == NULL)
552	return KRB5_CC_NOMEM;
553
554    RAND_bytes((*c)->uuid, sizeof((*c)->uuid));
555
556    *credp = &(*c)->cred;
557
558    if (copy) {
559	ret = krb5_copy_creds_contents(context, creds, *credp);
560	if (ret) {
561	    free(*c);
562	    *c = NULL;
563	}
564    } else {
565	**credp = *creds;
566	ret = 0;
567    }
568
569    return ret;
570}
571
572krb5_error_code
573kcm_ccache_remove_cred_internal(krb5_context context,
574				kcm_ccache ccache,
575				krb5_flags whichfields,
576				const krb5_creds *mcreds)
577{
578    krb5_error_code ret;
579    struct kcm_creds **c;
580
581    ret = KRB5_CC_NOTFOUND;
582
583    for (c = &ccache->creds; *c != NULL; c = &(*c)->next) {
584	if (krb5_compare_creds(context, whichfields, mcreds, &(*c)->cred)) {
585	    struct kcm_creds *cred = *c;
586
587	    *c = cred->next;
588	    krb5_free_cred_contents(context, &cred->cred);
589	    free(cred);
590	    ret = 0;
591	    if (*c == NULL)
592		break;
593	}
594    }
595
596    return ret;
597}
598
599krb5_error_code
600kcm_ccache_remove_cred(krb5_context context,
601		       kcm_ccache ccache,
602		       krb5_flags whichfields,
603		       const krb5_creds *mcreds)
604{
605    krb5_error_code ret;
606
607    KCM_ASSERT_VALID(ccache);
608
609    HEIMDAL_MUTEX_lock(&ccache->mutex);
610    ret = kcm_ccache_remove_cred_internal(context, ccache, whichfields, mcreds);
611    HEIMDAL_MUTEX_unlock(&ccache->mutex);
612
613    return ret;
614}
615
616krb5_error_code
617kcm_ccache_retrieve_cred_internal(krb5_context context,
618			 	  kcm_ccache ccache,
619			 	  krb5_flags whichfields,
620			 	  const krb5_creds *mcreds,
621			 	  krb5_creds **creds)
622{
623    krb5_boolean match;
624    struct kcm_creds *c;
625    krb5_error_code ret;
626
627    memset(creds, 0, sizeof(*creds));
628
629    ret = KRB5_CC_END;
630
631    match = FALSE;
632    for (c = ccache->creds; c != NULL; c = c->next) {
633	match = krb5_compare_creds(context, whichfields, mcreds, &c->cred);
634	if (match)
635	    break;
636    }
637
638    if (match) {
639	ret = 0;
640	*creds = &c->cred;
641    }
642
643    return ret;
644}
645
646krb5_error_code
647kcm_ccache_retrieve_cred(krb5_context context,
648			 kcm_ccache ccache,
649			 krb5_flags whichfields,
650			 const krb5_creds *mcreds,
651			 krb5_creds **credp)
652{
653    krb5_error_code ret;
654
655    KCM_ASSERT_VALID(ccache);
656
657    HEIMDAL_MUTEX_lock(&ccache->mutex);
658    ret = kcm_ccache_retrieve_cred_internal(context, ccache,
659					    whichfields, mcreds, credp);
660    HEIMDAL_MUTEX_unlock(&ccache->mutex);
661
662    return ret;
663}
664
665char *
666kcm_ccache_first_name(kcm_client *client)
667{
668    kcm_ccache p;
669    char *name = NULL;
670
671    HEIMDAL_MUTEX_lock(&ccache_mutex);
672
673    for (p = ccache_head; p != NULL; p = p->next) {
674	if (kcm_is_same_session(client, p->uid, p->session))
675	    break;
676    }
677    if (p)
678	name = strdup(p->name);
679    HEIMDAL_MUTEX_unlock(&ccache_mutex);
680    return name;
681}
682