Home | History | Annotate | Line # | Download | only in kcm
      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 
     39 HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
     40 kcm_ccache_data *ccache_head = NULL;
     41 static unsigned int ccache_nextid = 0;
     42 
     43 char *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 
     60 krb5_error_code
     61 kcm_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 
     93 krb5_error_code
     94 kcm_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 
    126 krb5_error_code
    127 kcm_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 
    153 krb5_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 
    193 static void
    194 kcm_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 
    231 krb5_error_code
    232 kcm_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 
    261 out:
    262     HEIMDAL_MUTEX_unlock(&ccache_mutex);
    263 
    264     return ret;
    265 }
    266 
    267 static krb5_error_code
    268 kcm_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 
    337 out:
    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 
    346 krb5_error_code
    347 kcm_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 
    366 krb5_error_code
    367 kcm_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 
    381 krb5_error_code
    382 kcm_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 
    400 krb5_error_code
    401 kcm_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 
    415 krb5_error_code
    416 kcm_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 
    428 krb5_error_code
    429 kcm_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 
    447 krb5_error_code
    448 kcm_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 
    468 krb5_error_code
    469 kcm_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 
    487 krb5_error_code
    488 kcm_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 
    503 krb5_error_code
    504 kcm_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 
    521 struct kcm_creds *
    522 kcm_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 
    537 krb5_error_code
    538 kcm_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 
    572 krb5_error_code
    573 kcm_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 
    599 krb5_error_code
    600 kcm_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 
    616 krb5_error_code
    617 kcm_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 
    646 krb5_error_code
    647 kcm_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 
    665 char *
    666 kcm_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