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