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