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