1/*
2 * Copyright (c) 2005, PADL Software Pty Ltd.
3 * All rights reserved.
4 *
5 * Portions Copyright (c) 2009 - 2011 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#ifdef HAVE_NOTIFY_H
38#include <mach/mach.h>
39#include <mach/mach_time.h>
40#endif
41
42#include <uuid/uuid.h>
43#include <bsm/libbsm.h>
44#include <vproc.h>
45#include <vproc_priv.h>
46
47static void kcm_release_ccache_locked(krb5_context, kcm_ccache);
48
49
50
51HEIMDAL_MUTEX ccache_mutex = HEIMDAL_MUTEX_INITIALIZER;
52TAILQ_HEAD(ccache_head, kcm_ccache_data);
53static struct ccache_head ccache_head = TAILQ_HEAD_INITIALIZER(ccache_head);
54static uint32_t ccache_nextid = 0;
55
56#ifdef HAVE_NOTIFY_H
57
58static uint64_t
59relative_nano_time(void)
60{
61     static uint64_t factor;
62     uint64_t now;
63
64    now = mach_absolute_time();
65
66    if (factor == 0) {
67	mach_timebase_info_data_t base;
68	(void)mach_timebase_info(&base);
69	factor = base.numer / base.denom;
70    }
71
72    return now * factor;
73}
74
75#endif
76
77/*
78 * Deliver a notification every NOTIFY_TIME_LIMIT
79 */
80
81static void
82notify_changed_caches(void)
83{
84#ifdef HAVE_NOTIFY_H
85    static uint64_t last_change;
86    static int notify_pending;
87
88#define NOTIFY_TIME_LIMIT (NSEC_PER_SEC / 2)
89
90    dispatch_async(dispatch_get_main_queue(), ^{
91	    uint64_t now, diff;
92
93	    if (notify_pending)
94		return;
95
96	    now = relative_nano_time();
97	    if (now < last_change)
98		diff = NOTIFY_TIME_LIMIT;
99	    else
100		diff = now - last_change;
101
102	    if (diff >= NOTIFY_TIME_LIMIT) {
103		notify_post(KRB5_KCM_NOTIFY_CACHE_CHANGED);
104		last_change = now;
105	    } else {
106		notify_pending = 1;
107		/* wait up to deliver the event */
108		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NOTIFY_TIME_LIMIT - diff), dispatch_get_main_queue(), ^{
109			notify_pending = 0;
110			last_change = relative_nano_time();
111			notify_post(KRB5_KCM_NOTIFY_CACHE_CHANGED);
112		    });
113	    }
114	});
115#endif
116}
117
118char *
119kcm_ccache_nextid(pid_t pid, uid_t uid)
120{
121    kcm_ccache c;
122    char *name = NULL;
123    unsigned n;
124
125    HEIMDAL_MUTEX_lock(&ccache_mutex);
126    while (name == NULL) {
127	n = ++ccache_nextid;
128	asprintf(&name, "%ld:%u", (long)uid, n);
129
130	/* find dups */
131	TAILQ_FOREACH(c, &ccache_head, members) {
132	    if (strcmp(c->name, name) == 0) {
133		free(name);
134		name = NULL;
135		break;
136	    }
137	}
138    }
139    HEIMDAL_MUTEX_unlock(&ccache_mutex);
140
141    return name;
142}
143
144krb5_error_code
145kcm_ccache_resolve_by_name(krb5_context context,
146			   const char *name,
147			   kcm_ccache *ccache)
148{
149    kcm_ccache p;
150    krb5_error_code ret;
151
152    *ccache = NULL;
153
154    ret = KRB5_FCC_NOFILE;
155
156    HEIMDAL_MUTEX_lock(&ccache_mutex);
157
158    TAILQ_FOREACH(p, &ccache_head, members) {
159	if (strcmp(p->name, name) == 0) {
160	    ret = 0;
161	    break;
162	}
163    }
164
165    if (ret == 0) {
166	kcm_retain_ccache(context, p);
167	*ccache = p;
168    }
169
170    HEIMDAL_MUTEX_unlock(&ccache_mutex);
171
172    return ret;
173}
174
175krb5_error_code
176kcm_ccache_resolve_by_uuid(krb5_context context,
177			   kcmuuid_t uuid,
178			   kcm_ccache *ccache)
179{
180    kcm_ccache p;
181    krb5_error_code ret;
182
183    *ccache = NULL;
184
185    ret = KRB5_FCC_NOFILE;
186
187    HEIMDAL_MUTEX_lock(&ccache_mutex);
188
189    TAILQ_FOREACH(p, &ccache_head, members) {
190	if (memcmp(p->uuid, uuid, sizeof(kcmuuid_t)) == 0) {
191	    ret = 0;
192	    break;
193	}
194    }
195
196    if (ret == 0) {
197	kcm_retain_ccache(context, p);
198	*ccache = p;
199    }
200
201    HEIMDAL_MUTEX_unlock(&ccache_mutex);
202
203    return ret;
204}
205
206krb5_error_code
207kcm_ccache_get_uuids(krb5_context context,
208		     kcm_client *client,
209		     kcm_operation opcode,
210		     krb5_storage *sp)
211{
212    kcm_ccache p;
213
214    HEIMDAL_MUTEX_lock(&ccache_mutex);
215
216    TAILQ_FOREACH(p, &ccache_head, members) {
217	krb5_error_code ret;
218	ret = kcm_access(context, client, opcode, p);
219	if (ret)
220	    continue;
221	krb5_storage_write(sp, p->uuid, sizeof(p->uuid));
222    }
223
224    HEIMDAL_MUTEX_unlock(&ccache_mutex);
225
226    return 0;
227}
228
229
230krb5_error_code
231kcm_debug_ccache(krb5_context context)
232{
233    kcm_ccache p;
234
235    TAILQ_FOREACH(p, &ccache_head, members) {
236	char *cpn = NULL, *spn = NULL;
237	int ncreds = 0;
238	struct kcm_creds *k;
239
240	KCM_ASSERT_VALID(p);
241
242	for (k = p->creds; k != NULL; k = k->next)
243	    ncreds++;
244
245	if (p->client != NULL)
246	    krb5_unparse_name(context, p->client, &cpn);
247	if (p->server != NULL)
248	    krb5_unparse_name(context, p->server, &spn);
249
250	kcm_log(7, "cache name %s refcnt %d flags %04x"
251		"uid %d client %s server %s ncreds %d",
252		p->name, p->refcnt, p->flags, p->uid,
253		(cpn == NULL) ? "<none>" : cpn,
254		(spn == NULL) ? "<none>" : spn,
255		ncreds);
256
257	if (cpn != NULL)
258	    free(cpn);
259	if (spn != NULL)
260	    free(spn);
261    }
262
263    return 0;
264}
265
266static void
267kcm_free_ccache_data_internal(krb5_context context,
268			      kcm_ccache cache)
269{
270    KCM_ASSERT_VALID(cache);
271
272    if (cache->name != NULL) {
273	free(cache->name);
274	cache->name = NULL;
275    }
276
277    if (cache->flags & KCM_FLAGS_USE_KEYTAB) {
278	krb5_kt_close(context, cache->keytab);
279	cache->keytab = NULL;
280    } else if (cache->flags & KCM_FLAGS_USE_PASSWORD) {
281	memset(cache->password, 0, strlen(cache->password));
282	free(cache->password);
283    }
284
285    cache->flags = 0;
286    cache->uid = -1;
287    cache->session = -1;
288
289    kcm_zero_ccache_data_internal(context, cache);
290
291    cache->tkt_life = 0;
292    cache->renew_life = 0;
293
294    cache->refcnt = 0;
295}
296
297
298krb5_error_code
299kcm_ccache_destroy(krb5_context context, const char *name)
300{
301    kcm_ccache p;
302
303    HEIMDAL_MUTEX_lock(&ccache_mutex);
304    TAILQ_FOREACH(p, &ccache_head, members) {
305	if (strcmp(p->name, name) == 0) {
306	    HEIMDAL_MUTEX_lock(&p->mutex);
307	    TAILQ_REMOVE(&ccache_head, p, members);
308	    break;
309	}
310    }
311    HEIMDAL_MUTEX_unlock(&ccache_mutex);
312    if (p == NULL)
313	return KRB5_FCC_NOFILE;
314
315    notify_changed_caches();
316
317    /* XXX blocking */
318    heim_ipc_event_cancel(p->renew_event);
319    heim_ipc_event_free(p->renew_event);
320    p->renew_event = NULL;
321
322    heim_ipc_event_cancel(p->expire_event);
323    heim_ipc_event_free(p->expire_event);
324    p->expire_event = NULL;
325
326    kcm_release_ccache_locked(context, p);
327
328    return 0;
329}
330
331#define KCM_EVENT_QUEUE_INTERVAL 60
332
333void
334kcm_update_renew_time(kcm_ccache ccache)
335{
336    time_t renewtime = time(NULL) + 3600 * 2;
337    time_t expire = ccache->expire;
338
339    /* if the ticket is about to expire in less then QUEUE_INTERVAL,
340     * don't bother */
341    if (time(NULL) + KCM_EVENT_QUEUE_INTERVAL > expire)
342	return;
343
344    if (renewtime > expire - KCM_EVENT_QUEUE_INTERVAL)
345	renewtime = expire - KCM_EVENT_QUEUE_INTERVAL;
346
347    kcm_log(1, "%s: will try to renew credentals in %d seconds",
348	    ccache->name, (int)(renewtime - time(NULL)));
349
350    heim_ipc_event_set_time(ccache->renew_event, renewtime);
351    ccache->renew_time = renewtime;
352}
353
354void
355kcm_update_expire_time(kcm_ccache cache, time_t t)
356{
357    if (t == 0) {
358	t = time(NULL);
359    } else if (t < time(NULL)) {
360	cache->next_refresh_time = 0;
361	return;
362    }
363    cache->next_refresh_time = t;
364    heim_ipc_event_set_time(cache->expire_event, t);
365}
366
367static void
368renew_func(heim_event_t event, void *ptr)
369{
370    kcm_ccache cache = ptr;
371    krb5_error_code ret;
372    time_t expire;
373
374    kcm_log(0, "cache: %s renewing", cache->name);
375
376    HEIMDAL_MUTEX_lock(&cache->mutex);
377
378    if (cache->flags & KCM_MASK_KEY_PRESENT) {
379	ret = kcm_ccache_acquire(kcm_context, cache, &expire);
380	krb5_warn(kcm_context, ret, "cache: %s acquire complete", cache->name);
381    } else {
382	ret = kcm_ccache_refresh(kcm_context, cache, &expire);
383	krb5_warn(kcm_context, ret, "cache: %s renew complete", cache->name);
384    }
385
386    switch (ret) {
387    case KRB5KRB_AP_ERR_BAD_INTEGRITY:
388    case KRB5KRB_AP_ERR_MODIFIED:
389    case KRB5KDC_ERR_PREAUTH_FAILED:
390	/* bad password, drop it like dead */
391	kcm_log(0, "cache: %s got bad password, stop renewing",
392		cache->name);
393	kcm_ccache_update_acquire_status(kcm_context, cache, KCM_STATUS_ACQUIRE_STOPPED, ret);
394	cache->flags &= ~KCM_MASK_KEY_PRESENT;
395	break;
396    case 0:
397	kcm_data_changed = 1;
398	cache->expire = expire;
399	kcm_ccache_update_acquire_status(kcm_context, cache, KCM_STATUS_ACQUIRE_SUCCESS, 0);
400
401	notify_changed_caches();
402
403	break;
404    default: {
405	const char *msg = krb5_get_error_message(kcm_context, ret);
406	kcm_log(0, "failed to renew: %s: %d", msg, ret);
407	krb5_free_error_message(kcm_context, msg);
408	kcm_ccache_update_acquire_status(kcm_context, cache, KCM_STATUS_ACQUIRE_FAILED, ret);
409	break;
410    }
411    }
412    kcm_update_renew_time(cache);
413    HEIMDAL_MUTEX_unlock(&cache->mutex);
414}
415
416static void
417expire_func(heim_event_t event, void *ptr)
418{
419    kcm_ccache cache = ptr;
420    krb5_error_code ret;
421
422    kcm_log(0, "cache: %s expired", cache->name);
423
424    HEIMDAL_MUTEX_lock(&cache->mutex);
425
426    heim_ipc_event_cancel(cache->renew_event);
427
428    cache->next_refresh_time = 0;
429
430    if (cache->flags & KCM_MASK_KEY_PRESENT){
431	time_t expire;
432
433	ret = kcm_ccache_acquire(kcm_context, cache, &expire);
434
435	switch (ret) {
436	case KRB5KRB_AP_ERR_BAD_INTEGRITY:
437	case KRB5KRB_AP_ERR_MODIFIED:
438	case KRB5KDC_ERR_PREAUTH_FAILED:
439	    /* bad password, drop it like dead */
440	    kcm_log(0, "cache: %s got bad password, stop renewing",
441		    cache->name);
442	    kcm_ccache_update_acquire_status(kcm_context, cache, KCM_STATUS_ACQUIRE_STOPPED, ret);
443	    cache->flags &= ~KCM_MASK_KEY_PRESENT;
444	    break;
445	case 0:
446	    kcm_data_changed = 1;
447	    kcm_log(0, "cache: %s got new tickets (expire in %d seconds)",
448		    cache->name, (int)(expire - time(NULL)));
449
450	    cache->expire = expire;
451	    kcm_ccache_update_acquire_status(kcm_context, cache, KCM_STATUS_ACQUIRE_SUCCESS, 0);
452	    notify_changed_caches();
453	    break;
454	default:
455	    kcm_data_changed = 1;
456	    kcm_update_expire_time(cache, time(NULL) + 300);
457	    kcm_ccache_update_acquire_status(kcm_context, cache, KCM_STATUS_ACQUIRE_FAILED, ret);
458	    notify_changed_caches();
459	    break;
460	}
461    } else {
462	kcm_log(0, "cache: %s expired", cache->name);
463	notify_changed_caches();
464    }
465    HEIMDAL_MUTEX_unlock(&cache->mutex);
466}
467
468static void
469release_cache(void *ctx)
470{
471    kcm_release_ccache(kcm_context, (kcm_ccache)ctx);
472}
473
474static krb5_error_code
475kcm_ccache_alloc(krb5_context context,
476		 const char *name,
477		 kcm_ccache *cache)
478{
479    kcm_ccache p = NULL;
480    krb5_error_code ret;
481
482    /* First, check for duplicates */
483    HEIMDAL_MUTEX_lock(&ccache_mutex);
484
485    TAILQ_FOREACH(p, &ccache_head, members) {
486	if (strcmp(p->name, name) == 0) {
487	    ret = KRB5_CC_WRITE;
488	    goto out;
489	}
490    }
491
492    /*
493     * Create an enpty cache for us.
494     */
495    p = calloc(1, sizeof(*p));
496    if (p == NULL) {
497	ret = KRB5_CC_NOMEM;
498	goto out;
499    }
500    HEIMDAL_MUTEX_init(&p->mutex);
501
502    CCRandomCopyBytes(kCCRandomDefault, p->uuid, sizeof(p->uuid));
503
504    p->name = strdup(name);
505    if (p->name == NULL) {
506	ret = KRB5_CC_NOMEM;
507	goto out;
508    }
509
510    p->refcnt = 3; /* on members, and both events */
511    p->holdcount = 1;
512    p->flags = 0;
513    p->uid = -1;
514    p->client = NULL;
515    p->server = NULL;
516    p->creds = NULL;
517    p->keytab = NULL;
518    p->password = NULL;
519    p->tkt_life = 0;
520    p->renew_life = 0;
521
522    p->renew_event = heim_ipc_event_create_f(renew_func, p);
523    p->expire_event = heim_ipc_event_create_f(expire_func, p);
524
525    heim_ipc_event_set_final_f(p->renew_event, release_cache);
526    heim_ipc_event_set_final_f(p->expire_event, release_cache);
527
528    TAILQ_INSERT_HEAD(&ccache_head, p, members);
529
530    *cache = p;
531
532    HEIMDAL_MUTEX_unlock(&ccache_mutex);
533    return 0;
534
535out:
536    HEIMDAL_MUTEX_unlock(&ccache_mutex);
537    if (p != NULL) {
538	HEIMDAL_MUTEX_destroy(&p->mutex);
539	free(p);
540    }
541    *cache = NULL;
542    return ret;
543}
544
545#define KRB5_CONF_NAME "krb5_ccache_conf_data"
546#define KRB5_REALM_NAME "X-CACHECONF:"
547
548krb5_error_code
549kcm_ccache_update_acquire_status(krb5_context context,
550				 kcm_ccache ccache,
551				 int status,
552				 krb5_error_code ret)
553{
554    krb5_creds cred;
555    uint8_t st[12];
556    uint32_t u32;
557
558    if ((ccache->flags & KCM_MASK_KEY_PRESENT) == 0)
559	return 0;
560    if (ccache->client == NULL)
561	return 0;
562
563    switch (status) {
564    case KCM_STATUS_ACQUIRE_START:
565	kcm_update_expire_time(ccache, 0);
566	break;
567
568    case KCM_STATUS_ACQUIRE_STOPPED:
569	ccache->next_refresh_time = 0;
570	break;
571
572    case KCM_STATUS_ACQUIRE_FAILED:
573	kcm_update_expire_time(ccache, time(NULL) + 300);
574	break;
575
576    case KCM_STATUS_ACQUIRE_SUCCESS: {
577	time_t next_refresh, now = time(NULL);
578
579	if (ccache->expire > now) {
580	    next_refresh = ccache->expire;
581	    /* try to acquire credential just before */
582	    if (ccache->expire - now > 300)
583		next_refresh -= 300;
584	    kcm_update_expire_time(ccache, next_refresh + 300);
585	} else {
586	    ccache->next_refresh_time = 0;
587	}
588	break;
589    }
590    default:
591	heim_assert(0, "invalid status");
592	break;
593    }
594
595    memcpy(st, "krb5", 4);
596    u32 = htonl(status); memcpy(&st[4], &u32, sizeof(u32));
597    u32 = htonl(ret);    memcpy(&st[8], &u32, sizeof(u32));
598
599    memset(&cred, 0, sizeof(cred));
600
601    cred.client = ccache->client;
602    ret = krb5_make_principal(context, &cred.server,
603			      KRB5_REALM_NAME, KRB5_CONF_NAME,
604			      KCM_STATUS_KEY, NULL);
605    if (ret)
606	return ret;
607
608    cred.ticket.data = st;
609    cred.ticket.length = sizeof(st);
610    cred.times.authtime = time(NULL);
611    cred.times.endtime = cred.times.authtime + 3600 * 24 * 30;
612
613    ret = kcm_ccache_store_cred_internal(context, ccache, &cred, NULL, 1);
614
615    krb5_free_principal(context, cred.server);
616
617    return ret;
618}
619
620krb5_error_code
621kcm_ccache_enqueue_default(krb5_context context,
622			   kcm_ccache cache,
623			   krb5_creds *newcred)
624{
625    if (newcred == NULL) {
626
627    } else if (cache->flags & KCM_MASK_KEY_PRESENT) {
628	cache->expire = newcred->times.endtime;
629	kcm_update_renew_time(cache);
630    } else if (newcred->flags.b.renewable) {
631	cache->expire = newcred->times.endtime;
632	kcm_update_renew_time(cache);
633	kcm_update_expire_time(cache, newcred->times.endtime);
634    } else if (newcred->times.endtime > time(NULL)) {
635	cache->expire = newcred->times.endtime;
636	kcm_update_expire_time(cache, newcred->times.endtime);
637    }
638
639    notify_changed_caches();
640
641    return 0;
642}
643
644
645krb5_error_code
646kcm_ccache_remove_creds_internal(krb5_context context,
647				 kcm_ccache ccache)
648{
649    struct kcm_creds *k;
650
651    k = ccache->creds;
652    while (k != NULL) {
653	struct kcm_creds *old;
654
655	krb5_free_cred_contents(context, &k->cred);
656	old = k;
657	k = k->next;
658	free(old);
659    }
660    ccache->creds = NULL;
661
662    notify_changed_caches();
663
664    return 0;
665}
666
667krb5_error_code
668kcm_ccache_remove_creds(krb5_context context,
669			kcm_ccache ccache)
670{
671    krb5_error_code ret;
672
673    KCM_ASSERT_VALID(ccache);
674
675    HEIMDAL_MUTEX_lock(&ccache->mutex);
676    ret = kcm_ccache_remove_creds_internal(context, ccache);
677    HEIMDAL_MUTEX_unlock(&ccache->mutex);
678
679    return ret;
680}
681
682krb5_error_code
683kcm_zero_ccache_data_internal(krb5_context context,
684			      kcm_ccache cache)
685{
686    if (cache->client != NULL) {
687	krb5_free_principal(context, cache->client);
688	cache->client = NULL;
689    }
690
691    if (cache->server != NULL) {
692	krb5_free_principal(context, cache->server);
693	cache->server = NULL;
694    }
695
696    kcm_ccache_remove_creds_internal(context, cache);
697
698    return 0;
699}
700
701krb5_error_code
702kcm_zero_ccache_data(krb5_context context,
703		     kcm_ccache cache)
704{
705    krb5_error_code ret;
706
707    KCM_ASSERT_VALID(cache);
708
709    HEIMDAL_MUTEX_lock(&cache->mutex);
710    ret = kcm_zero_ccache_data_internal(context, cache);
711    HEIMDAL_MUTEX_unlock(&cache->mutex);
712
713    return ret;
714}
715
716krb5_error_code
717kcm_retain_ccache(krb5_context context,
718		  kcm_ccache ccache)
719{
720    KCM_ASSERT_VALID(ccache);
721
722    HEIMDAL_MUTEX_lock(&ccache->mutex);
723    ccache->refcnt++;
724    HEIMDAL_MUTEX_unlock(&ccache->mutex);
725
726    return 0;
727}
728
729static void
730kcm_release_ccache_locked(krb5_context context, kcm_ccache p)
731{
732    if (p->refcnt == 1) {
733	kcm_free_ccache_data_internal(context, p);
734	HEIMDAL_MUTEX_unlock(&p->mutex);
735	HEIMDAL_MUTEX_destroy(&p->mutex);
736	free(p);
737    } else {
738	p->refcnt--;
739	HEIMDAL_MUTEX_unlock(&p->mutex);
740    }
741}
742
743
744krb5_error_code
745kcm_release_ccache(krb5_context context, kcm_ccache c)
746{
747    KCM_ASSERT_VALID(c);
748
749    HEIMDAL_MUTEX_lock(&c->mutex);
750    kcm_release_ccache_locked(context, c);
751
752    return 0;
753}
754
755krb5_error_code
756kcm_ccache_new(krb5_context context,
757	       const char *name,
758	       kcm_ccache *ccache)
759{
760    krb5_error_code ret;
761
762    ret = kcm_ccache_alloc(context, name, ccache);
763    if (ret == 0) {
764	/*
765	 * one reference is held by the linked list,
766	 * one by the caller
767	 */
768	kcm_retain_ccache(context, *ccache);
769    }
770
771    return ret;
772}
773
774krb5_error_code
775kcm_ccache_store_cred(krb5_context context,
776		      kcm_ccache ccache,
777		      krb5_creds *creds,
778		      int copy)
779{
780    krb5_error_code ret;
781
782    KCM_ASSERT_VALID(ccache);
783
784    HEIMDAL_MUTEX_lock(&ccache->mutex);
785    ret = kcm_ccache_store_cred_internal(context, ccache, creds, NULL, copy);
786    HEIMDAL_MUTEX_unlock(&ccache->mutex);
787
788    return ret;
789}
790
791struct kcm_creds *
792kcm_ccache_find_cred_uuid(krb5_context context,
793			  kcm_ccache ccache,
794			  kcmuuid_t uuid)
795{
796    struct kcm_creds *c;
797
798    for (c = ccache->creds; c != NULL; c = c->next)
799	if (memcmp(c->uuid, uuid, sizeof(c->uuid)) == 0)
800	    return c;
801
802    return NULL;
803}
804
805
806
807krb5_error_code
808kcm_ccache_store_cred_internal(krb5_context context,
809			       kcm_ccache ccache,
810			       krb5_creds *creds,
811			       kcmuuid_t uuid,
812			       int copy)
813{
814    struct kcm_creds **c;
815    krb5_error_code ret;
816
817    /*
818     * Remove dup creds and find the end to add new credential.
819     */
820
821    c = &ccache->creds;
822    while (*c != NULL) {
823	if (krb5_compare_creds(context, 0, creds, &(*c)->cred)) {
824	    struct kcm_creds *dup_cred = *c;
825	    *c = dup_cred->next;
826	    krb5_free_cred_contents(context, &dup_cred->cred);
827	    free(dup_cred);
828	} else {
829	    c = &(*c)->next;
830	}
831    }
832
833    *c = (struct kcm_creds *)calloc(1, sizeof(**c));
834    if (*c == NULL)
835	return KRB5_CC_NOMEM;
836
837    if (uuid)
838	memcpy((*c)->uuid, uuid, sizeof((*c)->uuid));
839    else
840	CCRandomCopyBytes(kCCRandomDefault, (*c)->uuid, sizeof((*c)->uuid));
841
842    if (copy) {
843	ret = krb5_copy_creds_contents(context, creds, &(*c)->cred);
844	if (ret) {
845	    free(*c);
846	    *c = NULL;
847	}
848    } else {
849	(*c)->cred = *creds;
850	ret = 0;
851    }
852
853    /*
854     * Only push notification when the krbtgt in the cache changes.
855     */
856    if ((*c)->cred.server && krb5_principal_is_root_krbtgt(context, (*c)->cred.server))
857	notify_changed_caches();
858
859    return ret;
860}
861
862krb5_error_code
863kcm_ccache_remove_cred_internal(krb5_context context,
864				kcm_ccache ccache,
865				krb5_flags whichfields,
866				const krb5_creds *mcreds)
867{
868    krb5_error_code ret;
869    struct kcm_creds **c;
870
871    ret = KRB5_CC_NOTFOUND;
872
873    for (c = &ccache->creds; *c != NULL; c = &(*c)->next) {
874	if (krb5_compare_creds(context, whichfields, mcreds, &(*c)->cred)) {
875	    struct kcm_creds *cred = *c;
876
877	    *c = cred->next;
878	    krb5_free_cred_contents(context, &cred->cred);
879	    free(cred);
880	    ret = 0;
881	    if (*c == NULL)
882		break;
883	}
884    }
885
886    notify_changed_caches();
887
888    return ret;
889}
890
891krb5_error_code
892kcm_ccache_remove_cred(krb5_context context,
893		       kcm_ccache ccache,
894		       krb5_flags whichfields,
895		       const krb5_creds *mcreds)
896{
897    krb5_error_code ret;
898
899    KCM_ASSERT_VALID(ccache);
900
901    HEIMDAL_MUTEX_lock(&ccache->mutex);
902    ret = kcm_ccache_remove_cred_internal(context, ccache, whichfields, mcreds);
903    HEIMDAL_MUTEX_unlock(&ccache->mutex);
904
905    return ret;
906}
907
908krb5_error_code
909kcm_ccache_retrieve_cred_internal(krb5_context context,
910			 	  kcm_ccache ccache,
911			 	  krb5_flags whichfields,
912			 	  const krb5_creds *mcreds,
913			 	  krb5_creds **creds)
914{
915    krb5_boolean match;
916    struct kcm_creds *c;
917    krb5_error_code ret;
918
919    memset(creds, 0, sizeof(*creds));
920
921    ret = KRB5_CC_END;
922
923    match = FALSE;
924    for (c = ccache->creds; c != NULL; c = c->next) {
925	match = krb5_compare_creds(context, whichfields, mcreds, &c->cred);
926	if (match)
927	    break;
928    }
929
930    if (match) {
931	ret = 0;
932	*creds = &c->cred;
933    }
934
935    return ret;
936}
937
938krb5_error_code
939kcm_ccache_retrieve_cred(krb5_context context,
940			 kcm_ccache ccache,
941			 krb5_flags whichfields,
942			 const krb5_creds *mcreds,
943			 krb5_creds **credp)
944{
945    krb5_error_code ret;
946
947    KCM_ASSERT_VALID(ccache);
948
949    HEIMDAL_MUTEX_lock(&ccache->mutex);
950    ret = kcm_ccache_retrieve_cred_internal(context, ccache,
951					    whichfields, mcreds, credp);
952    HEIMDAL_MUTEX_unlock(&ccache->mutex);
953
954    return ret;
955}
956
957char *
958kcm_ccache_first_name(kcm_client *client)
959{
960    kcm_ccache p;
961    char *name = NULL;
962
963    HEIMDAL_MUTEX_lock(&ccache_mutex);
964
965    TAILQ_FOREACH(p, &ccache_head, members) {
966	if (kcm_is_same_session(client, p->uid, p->session))
967	    break;
968    }
969    if (p)
970	name = strdup(p->name);
971    HEIMDAL_MUTEX_unlock(&ccache_mutex);
972    return name;
973}
974
975void
976kcm_cache_remove_session(pid_t session)
977{
978    kcm_ccache p, tempp;
979
980    HEIMDAL_MUTEX_lock(&ccache_mutex);
981
982    TAILQ_FOREACH_SAFE(p, &ccache_head, members, tempp) {
983	if (p->session == session) {
984	    kcm_log(1, "remove credental %s because session %d went away",
985		    p->name, (int)session);
986
987	    TAILQ_REMOVE(&ccache_head, p, members);
988
989	    dispatch_async(dispatch_get_main_queue(), ^{
990		    /* XXX blocking */
991		    HEIMDAL_MUTEX_lock(&p->mutex);
992
993		    heim_ipc_event_cancel(p->renew_event);
994		    heim_ipc_event_free(p->renew_event);
995		    p->renew_event = NULL;
996
997		    heim_ipc_event_cancel(p->expire_event);
998		    heim_ipc_event_free(p->expire_event);
999		    p->expire_event = NULL;
1000
1001		    kcm_release_ccache_locked(kcm_context, p);
1002		});
1003	}
1004    }
1005    HEIMDAL_MUTEX_unlock(&ccache_mutex);
1006}
1007
1008static bool
1009session_exists(pid_t asid)
1010{
1011    auditinfo_addr_t aia;
1012    aia.ai_asid = asid;
1013
1014    if (audit_get_sinfo_addr(&aia, sizeof(aia)) == 0)
1015	return true;
1016    return false;
1017}
1018
1019
1020#define CHECK(s) do { if ((s)) { goto out; } } while(0)
1021
1022#define DUMP_F_SERVER	1
1023#define DUMP_F_PASSWORD	2
1024#define DUMP_F_KEYTAB	4
1025
1026
1027static krb5_error_code
1028parse_krb5_cache(krb5_context context, krb5_storage *sp)
1029{
1030    krb5_error_code ret;
1031    kcm_ccache c = NULL;
1032    char *name = NULL;
1033    uint32_t u32;
1034    int32_t s32;
1035    uint8_t u8;
1036    time_t renew_time;
1037
1038    CHECK(ret = krb5_ret_stringz(sp, &name));
1039    ret = kcm_ccache_new(context, name, &c);
1040    free(name);
1041    CHECK(ret);
1042    CHECK(ret = krb5_ret_uuid(sp, c->uuid));
1043    CHECK(ret = krb5_ret_uint32(sp, &u32));
1044    c->renew_time = renew_time = u32;
1045    CHECK(ret = krb5_ret_uint32(sp, &u32));
1046    c->next_refresh_time = u32;
1047    CHECK(ret = krb5_ret_uint32(sp, &u32));
1048    c->holdcount = u32;
1049    CHECK(ret = krb5_ret_uint32(sp, &u32));
1050    c->flags = u32;
1051    CHECK(ret = krb5_ret_int32(sp, &s32));
1052    c->uid = s32;
1053    CHECK(ret = krb5_ret_int32(sp, &s32));
1054    c->session = s32;
1055
1056    CHECK(ret = krb5_ret_principal(sp, &c->client));
1057
1058    CHECK(ret = krb5_ret_uint32(sp, &u32));
1059
1060    if (u32 & DUMP_F_SERVER)
1061	CHECK(ret = krb5_ret_principal(sp, &c->server));
1062
1063    if (u32 & DUMP_F_PASSWORD)
1064	CHECK(ret = krb5_ret_stringz(sp, &c->password));
1065
1066    if (u32 & DUMP_F_KEYTAB) {
1067	char *keytab;
1068	CHECK(ret = krb5_ret_stringz(sp, &keytab));
1069	CHECK(ret = krb5_kt_resolve(context, keytab, &c->keytab));
1070	free(keytab);
1071    }
1072
1073    while (1) {
1074	krb5_creds cred;
1075	kcmuuid_t uuid;
1076
1077	CHECK(ret = krb5_ret_uint8(sp, &u8));
1078	if (u8 == 0)
1079	    break;
1080
1081	CHECK(ret = krb5_ret_uuid(sp, uuid));
1082	CHECK(ret = krb5_ret_creds(sp, &cred));
1083	ret = kcm_ccache_store_cred_internal(context, c, &cred, uuid, 1);
1084
1085	krb5_free_cred_contents(context, &cred);
1086	CHECK(ret);
1087    }
1088
1089    /* if we have a renew time and its not too far in the past, kick off rewtime again */
1090    if (renew_time && renew_time > time(NULL) - 60) {
1091	kcm_log(1, "re-setting renew time to: %ds (original renew time)", (int)(renew_time - time(NULL)));
1092	heim_ipc_event_set_time(c->renew_event, renew_time);
1093    }
1094    if (c->next_refresh_time)
1095	kcm_update_expire_time(c, c->next_refresh_time);
1096
1097 out:
1098    /* in case of failure, abandon memory (and broken cache) */
1099    if (ret && c) {
1100	TAILQ_REMOVE(&ccache_head, c, members);
1101    }
1102
1103    return ret;
1104}
1105
1106static void
1107update_wakeup(time_t *nextwakeup, time_t t)
1108{
1109    /*
1110     * don't bother wakeing up if its within 30s, to sort of time anyway,
1111     * just let the credential expire
1112     */
1113    if (t < time(NULL) - 30)
1114	return;
1115    if (*nextwakeup == 0 || *nextwakeup > t)
1116	*nextwakeup = t;
1117}
1118
1119static krb5_error_code
1120unparse_krb5_cache(krb5_context context, krb5_storage *sp, kcm_ccache c, time_t *nextwakeup)
1121{
1122    struct kcm_creds *cred;
1123    krb5_error_code ret;
1124    uint32_t sflags;
1125
1126    if (c->renew_time)
1127	update_wakeup(nextwakeup, c->renew_time);
1128
1129    if ((c->flags & KCM_MASK_KEY_PRESENT) && c->next_refresh_time)
1130	update_wakeup(nextwakeup, c->next_refresh_time);
1131
1132    CHECK(ret = krb5_store_stringz(sp, c->name));
1133    CHECK(ret = krb5_store_uuid(sp, c->uuid));
1134    CHECK(ret = krb5_store_uint32(sp, (uint32_t)c->renew_time));
1135    CHECK(ret = krb5_store_uint32(sp, (uint32_t)c->next_refresh_time));
1136    CHECK(ret = krb5_store_uint32(sp, (uint32_t)c->holdcount));
1137    CHECK(ret = krb5_store_uint32(sp, c->flags));
1138    CHECK(ret = krb5_store_int32(sp, c->uid));
1139    CHECK(ret = krb5_store_int32(sp, c->session));
1140
1141    CHECK(ret = krb5_store_principal(sp, c->client));
1142
1143    sflags = 0;
1144    if (c->server) sflags |= DUMP_F_SERVER;
1145    if (c->password) sflags |= DUMP_F_PASSWORD;
1146    if (c->keytab) sflags |= DUMP_F_KEYTAB;
1147    CHECK(ret = krb5_store_uint32(sp, sflags));
1148
1149    if (c->server)
1150	CHECK(ret = krb5_store_principal(sp, c->server));
1151    if (c->password)
1152	CHECK(ret = krb5_store_stringz(sp, c->password));
1153    if (c->keytab) {
1154	char *str;
1155	CHECK(ret = krb5_kt_get_full_name(context, c->keytab, &str));
1156	CHECK(ret = krb5_store_stringz(sp, str));
1157	krb5_xfree(str);
1158    }
1159
1160    for (cred = c->creds; cred != NULL; cred = cred->next) {
1161	CHECK(ret = krb5_store_uint8(sp, 1));
1162	CHECK(ret = krb5_store_uuid(sp, cred->uuid));
1163	CHECK(ret = krb5_store_creds(sp, &cred->cred));
1164    }
1165    CHECK(ret = krb5_store_uint8(sp, 0));
1166 out:
1167    return ret;
1168}
1169
1170static krb5_error_code
1171parse_default_one(krb5_context context, krb5_storage *sp)
1172{
1173    struct kcm_default_cache *c;
1174    krb5_error_code ret;
1175    int32_t s32;
1176    char *str;
1177
1178    c = calloc(1, sizeof(*c));
1179    if (c == NULL)
1180	return ENOMEM;
1181
1182    CHECK(ret = krb5_ret_int32(sp, &s32));
1183    c->uid = s32;
1184    CHECK(ret = krb5_ret_int32(sp, &s32));
1185    c->session = s32;
1186    CHECK(ret = krb5_ret_stringz(sp, &str));
1187    c->name = str;
1188
1189    c->next = default_caches;
1190    default_caches = c;
1191
1192 out:
1193    if (ret)
1194	kcm_log(10, "failed to parse default entry");
1195    return ret;
1196}
1197
1198static krb5_error_code
1199unparse_default_one(krb5_storage *inner, struct kcm_default_cache *c)
1200{
1201    krb5_error_code ret;
1202    CHECK(ret = krb5_store_int32(inner, c->uid));
1203    CHECK(ret = krb5_store_int32(inner, c->session));
1204    CHECK(ret = krb5_store_stringz(inner, c->name));
1205 out:
1206    return ret;
1207}
1208
1209static krb5_error_code
1210unparse_default_all(krb5_context context, krb5_storage *sp)
1211{
1212    struct kcm_default_cache *c;
1213    krb5_error_code r = 0;
1214
1215    for (c = default_caches; r == 0 && c != NULL; c = c->next) {
1216	r = kcm_unparse_wrap(sp, "default-cache", c->session, ^(krb5_storage *inner) {
1217		return unparse_default_one(inner, c);
1218	    });
1219
1220    }
1221
1222    return r;
1223}
1224
1225#define KCM_DUMP_VERSION 2
1226
1227static int
1228kcm_parse_cache_data(krb5_context context, krb5_data *data)
1229{
1230    krb5_error_code ret;
1231    krb5_storage *sp;
1232    char *str;
1233    uint8_t u8;
1234
1235    sp = krb5_storage_from_readonly_mem(data->data, data->length);
1236    if (sp == NULL)
1237	return ENOMEM;
1238
1239    CHECK(ret = krb5_ret_stringz(sp, &str));
1240    if (strcmp(str, "start-dump") != 0) {
1241	free(str);
1242	ret = EINVAL;
1243	goto out;
1244    }
1245    free(str);
1246
1247
1248    CHECK(ret = krb5_ret_uint8(sp, &u8));
1249    if (u8 != KCM_DUMP_VERSION) {
1250	ret = EINVAL;
1251	goto out;
1252    }
1253    CHECK(ret = krb5_ret_uint32(sp, &ccache_nextid));
1254
1255    while(ret == 0) {
1256	int32_t session;
1257	krb5_data idata;
1258	krb5_storage *inner;
1259
1260	CHECK(ret = krb5_ret_stringz(sp, &str));
1261
1262	kcm_log(10, "dump: reading a %s entry", str);
1263
1264	if (strcmp(str, "end-dump") == 0) {
1265	    free(str);
1266	    break;
1267	}
1268
1269	CHECK(ret = krb5_ret_int32(sp, &session));
1270	CHECK(ret = krb5_ret_data(sp, &idata));
1271
1272	inner = krb5_storage_from_data(&idata);
1273	heim_assert(inner, "krb5_storage_from_data");
1274
1275	if (strcmp(str, "ntlm-cache") == 0) {
1276	    ret = kcm_parse_ntlm_challenge_one(context, inner);
1277	} else if (!session_exists(session)) {
1278	    /* do nothing */
1279	} else if (strcmp(str, "krb5-cache") == 0) {
1280	    ret = parse_krb5_cache(context, inner);
1281	} else if (strcmp(str, "digest-cache") == 0) {
1282	    ret = kcm_parse_digest_one(context, inner);
1283	} else if (strcmp(str, "default-cache") == 0) {
1284	    ret = parse_default_one(context, inner);
1285	} else {
1286	    kcm_log(10, "dump: unknown type: %s", str);
1287	    ret = 0;
1288	}
1289	if (ret)
1290	    kcm_log(10, "dump: failed to unparse a %s cache with: %d", str, ret);
1291	free(str);
1292	krb5_storage_free(inner);
1293	krb5_data_free(&idata);
1294    }
1295 out:
1296    krb5_storage_free(sp);
1297    if (ret)
1298	kcm_log(10, "dump: failed to read credential dump: %d", ret);
1299    return ret;
1300}
1301
1302krb5_error_code
1303kcm_unparse_wrap(krb5_storage *sp, char *name, int32_t session, int (^wrapped)(krb5_storage *inner))
1304{
1305    krb5_error_code ret;
1306    krb5_storage *inner = krb5_storage_emem();
1307    krb5_data data;
1308
1309    CHECK(ret = wrapped(inner));
1310    CHECK(ret = krb5_store_stringz(sp, name));
1311    CHECK(ret = krb5_store_int32(sp, session));
1312    CHECK(ret = krb5_storage_to_data(inner, &data));
1313    ret = krb5_store_data(sp, data);
1314    krb5_data_free(&data);
1315
1316 out:
1317    if (ret)
1318	kcm_log(10, "dump: failed to add a %s", name);
1319    krb5_storage_free(inner);
1320    return ret;
1321}
1322
1323void
1324kcm_unparse_cache_data(krb5_context context, krb5_data *data)
1325{
1326    __block time_t nextwakeup = 0;
1327    krb5_error_code ret;
1328    krb5_storage *sp;
1329    kcm_ccache c;
1330
1331    krb5_data_zero(data);
1332
1333    sp = krb5_storage_emem();
1334    if (sp == NULL)
1335	return;
1336
1337    CHECK(ret = krb5_store_stringz(sp, "start-dump"));
1338    CHECK(ret = krb5_store_uint8(sp, KCM_DUMP_VERSION));
1339    CHECK(ret = krb5_store_uint32(sp, ccache_nextid));
1340
1341    HEIMDAL_MUTEX_lock(&ccache_mutex);
1342    TAILQ_FOREACH_REVERSE(c, &ccache_head, ccache_head, members) {
1343	ret = kcm_unparse_wrap(sp, "krb5-cache", c->session, ^(krb5_storage *inner) {
1344		return unparse_krb5_cache(context, inner, c, &nextwakeup);
1345	    });
1346    }
1347    HEIMDAL_MUTEX_unlock(&ccache_mutex);
1348    CHECK(ret);
1349
1350    /* add default cache */
1351    CHECK(ret = unparse_default_all(context, sp));
1352
1353    /* add NTLM/SCRAM */
1354    CHECK(ret = kcm_unparse_digest_all(context, sp));
1355
1356    /* ntlm challenges */
1357    CHECK(ret = kcm_unparse_challenge_all(context, sp));
1358
1359    CHECK(ret = krb5_store_stringz(sp, "end-dump"));
1360
1361    if (nextwakeup) {
1362	int64_t next = nextwakeup - time(NULL);
1363	if (next > 0) {
1364	    vproc_swap_integer(NULL, VPROC_GSK_START_INTERVAL, &next, NULL);
1365	}
1366	kcm_log(1, "next wakup in: %d", (int)next);
1367    }
1368
1369 out:
1370    if (ret == 0) {
1371	ret = krb5_storage_to_data(sp, data);
1372	if (ret)
1373	    kcm_log(1, "dump: failed to create credential data: %d", ret);
1374    }
1375    krb5_storage_free(sp);
1376}
1377
1378static int have_uuid_master = 0;
1379static krb5_uuid uuid_master;
1380static const char *dumpfile = "/var/db/kcm-dump.bin";
1381static const char *keyfile = "/var/db/kcm-dump.uuid";
1382
1383static krb5_error_code
1384kcm_load_key(krb5_context context)
1385{
1386    krb5_error_code ret;
1387    krb5_data enc;
1388    size_t len;
1389    void *p = NULL;
1390
1391    if (have_uuid_master)
1392	return 0;
1393
1394    ret = rk_undumpdata(keyfile, &p, &len);
1395    if (ret != 0)
1396	goto nokey;
1397
1398    if (len != sizeof(uuid_master)) {
1399	free(p);
1400	goto nokey;
1401    }
1402
1403    memcpy(uuid_master, p, sizeof(uuid_master));
1404    free(p);
1405
1406    /* check if key is stale */
1407    ret = kcm_store_io(context, uuid_master, "", 0, &enc, true);
1408    if (ret)
1409	goto nokey;
1410
1411    krb5_data_free(&enc);
1412    have_uuid_master = 1;
1413
1414    return 0;
1415 nokey:
1416
1417    ret = kcm_create_key(uuid_master);
1418    if (ret)
1419	return ret;
1420    rk_dumpdata(keyfile, uuid_master, sizeof(uuid_master));
1421
1422    have_uuid_master = 1;
1423
1424    return ret;
1425}
1426
1427int kcm_data_changed = 0;
1428
1429void
1430kcm_write_dump(krb5_context context)
1431{
1432    uuid_string_t uuidstr;
1433    krb5_data data, enc;
1434    krb5_error_code ret;
1435
1436    ret = kcm_load_key(context);
1437    if (ret) {
1438	unlink(keyfile);
1439	unlink(dumpfile);
1440	return;
1441    }
1442
1443    uuid_unparse(uuid_master, uuidstr);
1444    kcm_log(10, "dump: [masterkey] %s", uuidstr);
1445
1446    kcm_unparse_cache_data(context, &data);
1447    if (data.length == 0)
1448	return;
1449
1450    if (!kcm_data_changed)
1451	return;
1452
1453    ret = kcm_store_io(context, uuid_master, data.data, data.length, &enc, true);
1454    krb5_data_free(&data);
1455    if (ret) {
1456	kcm_log(1, "dump: failed to encrypt credential data %d", ret);
1457	return;
1458    }
1459
1460    rk_dumpdata(dumpfile, enc.data, enc.length);
1461
1462    krb5_data_free(&enc);
1463}
1464
1465void
1466kcm_read_dump(krb5_context context)
1467{
1468    uuid_string_t uuidstr;
1469    krb5_error_code ret;
1470    krb5_data data;
1471    size_t len;
1472    void *p;
1473
1474    ret = kcm_load_key(context);
1475    if (ret)
1476	return;
1477
1478    uuid_unparse(uuid_master, uuidstr);
1479    kcm_log(10, "load: [masterkey] %s", uuidstr);
1480
1481    ret = rk_undumpdata(dumpfile, &p, &len);
1482    if (ret != 0 || len == 0)
1483	return;
1484
1485    ret = kcm_store_io(kcm_context, uuid_master, p, len, &data, false);
1486    if (ret == 0) {
1487	ret = kcm_parse_cache_data(kcm_context, &data);
1488	if (ret)
1489	    unlink(dumpfile);
1490	krb5_data_free(&data);
1491	have_uuid_master = 1;
1492    } else {
1493	unlink(dumpfile);
1494    }
1495    free(p);
1496}
1497
1498