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