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
37HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
38kcm_ccache_data *ccache_head = NULL;
39static unsigned int ccache_nextid = 0;
40
41char *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
55krb5_error_code
56kcm_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
88krb5_error_code
89kcm_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
121krb5_error_code
122kcm_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
148krb5_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
188static void
189kcm_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
226krb5_error_code
227kcm_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
256out:
257    HEIMDAL_MUTEX_unlock(&ccache_mutex);
258
259    return ret;
260}
261
262static krb5_error_code
263kcm_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
331out:
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
340krb5_error_code
341kcm_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
360krb5_error_code
361kcm_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
375krb5_error_code
376kcm_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
394krb5_error_code
395kcm_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
409krb5_error_code
410kcm_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
422krb5_error_code
423kcm_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
441krb5_error_code
442kcm_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
462krb5_error_code
463kcm_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
481krb5_error_code
482kcm_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
497krb5_error_code
498kcm_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
515struct kcm_creds *
516kcm_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
531krb5_error_code
532kcm_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
566krb5_error_code
567kcm_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
593krb5_error_code
594kcm_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
610krb5_error_code
611kcm_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
640krb5_error_code
641kcm_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
659char *
660kcm_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