xref: /minix/crypto/external/bsd/heimdal/dist/kcm/cache.c (revision 0a6a1f1d)
1 /*	$NetBSD: cache.c,v 1.2 2014/07/24 22:54:10 joerg 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 
39 HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
40 kcm_ccache_data *ccache_head = NULL;
41 static unsigned int ccache_nextid = 0;
42 
kcm_ccache_nextid(pid_t pid,uid_t uid,gid_t gid)43 char *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 
57 krb5_error_code
kcm_ccache_resolve(krb5_context context,const char * name,kcm_ccache * ccache)58 kcm_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 
90 krb5_error_code
kcm_ccache_resolve_by_uuid(krb5_context context,kcmuuid_t uuid,kcm_ccache * ccache)91 kcm_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 
123 krb5_error_code
kcm_ccache_get_uuids(krb5_context context,kcm_client * client,kcm_operation opcode,krb5_storage * sp)124 kcm_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 
kcm_debug_ccache(krb5_context context)150 krb5_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 
190 static void
kcm_free_ccache_data_internal(krb5_context context,kcm_ccache_data * cache)191 kcm_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 
228 krb5_error_code
kcm_ccache_destroy(krb5_context context,const char * name)229 kcm_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 
258 out:
259     HEIMDAL_MUTEX_unlock(&ccache_mutex);
260 
261     return ret;
262 }
263 
264 static krb5_error_code
kcm_ccache_alloc(krb5_context context,const char * name,kcm_ccache * ccache)265 kcm_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 
333 out:
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 
342 krb5_error_code
kcm_ccache_remove_creds_internal(krb5_context context,kcm_ccache ccache)343 kcm_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 
362 krb5_error_code
kcm_ccache_remove_creds(krb5_context context,kcm_ccache ccache)363 kcm_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 
377 krb5_error_code
kcm_zero_ccache_data_internal(krb5_context context,kcm_ccache_data * cache)378 kcm_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 
396 krb5_error_code
kcm_zero_ccache_data(krb5_context context,kcm_ccache cache)397 kcm_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 
411 krb5_error_code
kcm_retain_ccache(krb5_context context,kcm_ccache ccache)412 kcm_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 
424 krb5_error_code
kcm_release_ccache(krb5_context context,kcm_ccache c)425 kcm_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 
443 krb5_error_code
kcm_ccache_gen_new(krb5_context context,pid_t pid,uid_t uid,gid_t gid,kcm_ccache * ccache)444 kcm_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 
464 krb5_error_code
kcm_ccache_new(krb5_context context,const char * name,kcm_ccache * ccache)465 kcm_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 
483 krb5_error_code
kcm_ccache_destroy_if_empty(krb5_context context,kcm_ccache ccache)484 kcm_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 
499 krb5_error_code
kcm_ccache_store_cred(krb5_context context,kcm_ccache ccache,krb5_creds * creds,int copy)500 kcm_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 
517 struct kcm_creds *
kcm_ccache_find_cred_uuid(krb5_context context,kcm_ccache ccache,kcmuuid_t uuid)518 kcm_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 
533 krb5_error_code
kcm_ccache_store_cred_internal(krb5_context context,kcm_ccache ccache,krb5_creds * creds,int copy,krb5_creds ** credp)534 kcm_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 
568 krb5_error_code
kcm_ccache_remove_cred_internal(krb5_context context,kcm_ccache ccache,krb5_flags whichfields,const krb5_creds * mcreds)569 kcm_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 
595 krb5_error_code
kcm_ccache_remove_cred(krb5_context context,kcm_ccache ccache,krb5_flags whichfields,const krb5_creds * mcreds)596 kcm_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 
612 krb5_error_code
kcm_ccache_retrieve_cred_internal(krb5_context context,kcm_ccache ccache,krb5_flags whichfields,const krb5_creds * mcreds,krb5_creds ** creds)613 kcm_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 
642 krb5_error_code
kcm_ccache_retrieve_cred(krb5_context context,kcm_ccache ccache,krb5_flags whichfields,const krb5_creds * mcreds,krb5_creds ** credp)643 kcm_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 
661 char *
kcm_ccache_first_name(kcm_client * client)662 kcm_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