events.c revision 178825
1251875Speter/*
2251875Speter * Copyright (c) 2005, PADL Software Pty Ltd.
3251875Speter * All rights reserved.
4251875Speter *
5251875Speter * Redistribution and use in source and binary forms, with or without
6251875Speter * modification, are permitted provided that the following conditions
7251875Speter * are met:
8251875Speter *
9251875Speter * 1. Redistributions of source code must retain the above copyright
10251875Speter *    notice, this list of conditions and the following disclaimer.
11251875Speter *
12251875Speter * 2. Redistributions in binary form must reproduce the above copyright
13251875Speter *    notice, this list of conditions and the following disclaimer in the
14251875Speter *    documentation and/or other materials provided with the distribution.
15251875Speter *
16251875Speter * 3. Neither the name of PADL Software nor the names of its contributors
17251875Speter *    may be used to endorse or promote products derived from this software
18251875Speter *    without specific prior written permission.
19251875Speter *
20251875Speter * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
21251875Speter * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22251875Speter * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23251875Speter * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
24251875Speter * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25251875Speter * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26251875Speter * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27251875Speter * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28251875Speter * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29251875Speter * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30251875Speter * SUCH DAMAGE.
31251875Speter */
32251875Speter
33251875Speter#include "kcm_locl.h"
34251875Speter
35251875SpeterRCSID("$Id: events.c 15294 2005-05-30 01:43:23Z lukeh $");
36251875Speter
37251875Speter/* thread-safe in case we multi-thread later */
38251875Speterstatic HEIMDAL_MUTEX events_mutex = HEIMDAL_MUTEX_INITIALIZER;
39251875Speterstatic kcm_event *events_head = NULL;
40251875Speterstatic time_t last_run = 0;
41251875Speter
42251875Speterstatic char *action_strings[] = {
43251875Speter	"NONE", "ACQUIRE_CREDS", "RENEW_CREDS",
44251875Speter	"DESTROY_CREDS", "DESTROY_EMPTY_CACHE" };
45251875Speter
46251875Speterkrb5_error_code
47251875Speterkcm_enqueue_event(krb5_context context,
48251875Speter		  kcm_event *event)
49251875Speter{
50251875Speter    krb5_error_code ret;
51251875Speter
52251875Speter    if (event->action == KCM_EVENT_NONE) {
53251875Speter	return 0;
54251875Speter    }
55251875Speter
56251875Speter    HEIMDAL_MUTEX_lock(&events_mutex);
57251875Speter    ret = kcm_enqueue_event_internal(context, event);
58251875Speter    HEIMDAL_MUTEX_unlock(&events_mutex);
59251875Speter
60251875Speter    return ret;
61251875Speter}
62251875Speter
63251875Speterstatic void
64251875Speterprint_times(time_t time, char buf[64])
65251875Speter{
66251875Speter    if (time)
67251875Speter	strftime(buf, 64, "%m-%dT%H:%M", gmtime(&time));
68251875Speter    else
69251875Speter	strlcpy(buf, "never", 64);
70251875Speter}
71251875Speter
72251875Speterstatic void
73251875Speterlog_event(kcm_event *event, char *msg)
74251875Speter{
75251875Speter    char fire_time[64], expire_time[64];
76251875Speter
77251875Speter    print_times(event->fire_time, fire_time);
78251875Speter    print_times(event->expire_time, expire_time);
79251875Speter
80251875Speter    kcm_log(7, "%s event %08x: fire_time %s fire_count %d expire_time %s "
81251875Speter	    "backoff_time %d action %s cache %s",
82251875Speter	    msg, event, fire_time, event->fire_count, expire_time,
83251875Speter	    event->backoff_time, action_strings[event->action],
84251875Speter	    event->ccache->name);
85251875Speter}
86251875Speter
87251875Speterkrb5_error_code
88251875Speterkcm_enqueue_event_internal(krb5_context context,
89251875Speter			   kcm_event *event)
90251875Speter{
91251875Speter    kcm_event **e;
92251875Speter
93251875Speter    if (event->action == KCM_EVENT_NONE)
94251875Speter	return 0;
95251875Speter
96251875Speter    for (e = &events_head; *e != NULL; e = &(*e)->next)
97251875Speter	;
98251875Speter
99251875Speter    *e = (kcm_event *)malloc(sizeof(kcm_event));
100251875Speter    if (*e == NULL) {
101251875Speter	return KRB5_CC_NOMEM;
102251875Speter    }
103251875Speter
104251875Speter    (*e)->valid = 1;
105251875Speter    (*e)->fire_time = event->fire_time;
106251875Speter    (*e)->fire_count = 0;
107251875Speter    (*e)->expire_time = event->expire_time;
108251875Speter    (*e)->backoff_time = event->backoff_time;
109251875Speter
110251875Speter    (*e)->action = event->action;
111251875Speter
112251875Speter    kcm_retain_ccache(context, event->ccache);
113251875Speter    (*e)->ccache = event->ccache;
114251875Speter    (*e)->next = NULL;
115251875Speter
116251875Speter    log_event(*e, "enqueuing");
117251875Speter
118251875Speter    return 0;
119251875Speter}
120251875Speter
121251875Speter/*
122251875Speter * Dump events list on SIGUSR2
123251875Speter */
124251875Speterkrb5_error_code
125251875Speterkcm_debug_events(krb5_context context)
126251875Speter{
127251875Speter    kcm_event *e;
128251875Speter
129251875Speter    for (e = events_head; e != NULL; e = e->next)
130251875Speter	log_event(e, "debug");
131251875Speter
132251875Speter    return 0;
133251875Speter}
134251875Speter
135251875Speterkrb5_error_code
136251875Speterkcm_enqueue_event_relative(krb5_context context,
137251875Speter			   kcm_event *event)
138251875Speter{
139251875Speter    krb5_error_code ret;
140251875Speter    kcm_event e;
141251875Speter
142251875Speter    e = *event;
143251875Speter    e.backoff_time = e.fire_time;
144251875Speter    e.fire_time += time(NULL);
145251875Speter
146251875Speter    ret = kcm_enqueue_event(context, &e);
147251875Speter
148251875Speter    return ret;
149251875Speter}
150251875Speter
151251875Speterstatic krb5_error_code
152251875Speterkcm_remove_event_internal(krb5_context context,
153251875Speter			  kcm_event **e)
154251875Speter{
155251875Speter    kcm_event *next;
156251875Speter
157251875Speter    next = (*e)->next;
158251875Speter
159251875Speter    (*e)->valid = 0;
160251875Speter    (*e)->fire_time = 0;
161251875Speter    (*e)->fire_count = 0;
162251875Speter    (*e)->expire_time = 0;
163251875Speter    (*e)->backoff_time = 0;
164251875Speter    kcm_release_ccache(context, &(*e)->ccache);
165251875Speter    (*e)->next = NULL;
166251875Speter    free(*e);
167251875Speter
168251875Speter    *e = next;
169251875Speter
170251875Speter    return 0;
171251875Speter}
172251875Speter
173251875Speterstatic int
174251875Speteris_primary_credential_p(krb5_context context,
175251875Speter			kcm_ccache ccache,
176251875Speter			krb5_creds *newcred)
177251875Speter{
178251875Speter    krb5_flags whichfields;
179251875Speter
180251875Speter    if (ccache->client == NULL)
181251875Speter	return 0;
182251875Speter
183251875Speter    if (newcred->client == NULL ||
184251875Speter	!krb5_principal_compare(context, ccache->client, newcred->client))
185251875Speter	return 0;
186251875Speter
187251875Speter    /* XXX just checks whether it's the first credential in the cache */
188251875Speter    if (ccache->creds == NULL)
189251875Speter	return 0;
190251875Speter
191251875Speter    whichfields = KRB5_TC_MATCH_KEYTYPE | KRB5_TC_MATCH_FLAGS_EXACT |
192251875Speter		  KRB5_TC_MATCH_TIMES_EXACT | KRB5_TC_MATCH_AUTHDATA |
193251875Speter		  KRB5_TC_MATCH_2ND_TKT | KRB5_TC_MATCH_IS_SKEY;
194251875Speter
195251875Speter    return krb5_compare_creds(context, whichfields, newcred, &ccache->creds->cred);
196251875Speter}
197251875Speter
198251875Speter/*
199251875Speter * Setup default events for a new credential
200251875Speter */
201251875Speterstatic krb5_error_code
202251875Speterkcm_ccache_make_default_event(krb5_context context,
203251875Speter			      kcm_event *event,
204251875Speter			      krb5_creds *newcred)
205251875Speter{
206251875Speter    krb5_error_code ret = 0;
207251875Speter    kcm_ccache ccache = event->ccache;
208251875Speter
209251875Speter    event->fire_time = 0;
210251875Speter    event->expire_time = 0;
211251875Speter    event->backoff_time = KCM_EVENT_DEFAULT_BACKOFF_TIME;
212251875Speter
213251875Speter    if (newcred == NULL) {
214251875Speter	/* no creds, must be acquire creds request */
215251875Speter	if ((ccache->flags & KCM_MASK_KEY_PRESENT) == 0) {
216251875Speter	    kcm_log(0, "Cannot acquire credentials without a key");
217251875Speter	    return KRB5_FCC_INTERNAL;
218251875Speter	}
219251875Speter
220251875Speter	event->fire_time = time(NULL); /* right away */
221251875Speter	event->action = KCM_EVENT_ACQUIRE_CREDS;
222251875Speter    } else if (is_primary_credential_p(context, ccache, newcred)) {
223251875Speter	if (newcred->flags.b.renewable) {
224251875Speter	    event->action = KCM_EVENT_RENEW_CREDS;
225251875Speter	    ccache->flags |= KCM_FLAGS_RENEWABLE;
226251875Speter	} else {
227251875Speter	    if (ccache->flags & KCM_MASK_KEY_PRESENT)
228251875Speter		event->action = KCM_EVENT_ACQUIRE_CREDS;
229251875Speter	    else
230251875Speter		event->action = KCM_EVENT_NONE;
231251875Speter	    ccache->flags &= ~(KCM_FLAGS_RENEWABLE);
232251875Speter	}
233251875Speter	/* requeue with some slop factor */
234251875Speter	event->fire_time = newcred->times.endtime - KCM_EVENT_QUEUE_INTERVAL;
235251875Speter    } else {
236251875Speter	event->action = KCM_EVENT_NONE;
237251875Speter    }
238251875Speter
239251875Speter    return ret;
240251875Speter}
241251875Speter
242251875Speterkrb5_error_code
243251875Speterkcm_ccache_enqueue_default(krb5_context context,
244251875Speter			   kcm_ccache ccache,
245251875Speter			   krb5_creds *newcred)
246251875Speter{
247251875Speter    kcm_event event;
248251875Speter    krb5_error_code ret;
249251875Speter
250251875Speter    memset(&event, 0, sizeof(event));
251251875Speter    event.ccache = ccache;
252251875Speter
253251875Speter    ret = kcm_ccache_make_default_event(context, &event, newcred);
254251875Speter    if (ret)
255251875Speter	return ret;
256251875Speter
257251875Speter    ret = kcm_enqueue_event_internal(context, &event);
258251875Speter    if (ret)
259251875Speter	return ret;
260251875Speter
261251875Speter    return 0;
262251875Speter}
263251875Speter
264251875Speterkrb5_error_code
265251875Speterkcm_remove_event(krb5_context context,
266251875Speter		 kcm_event *event)
267251875Speter{
268251875Speter    krb5_error_code ret;
269251875Speter    kcm_event **e;
270251875Speter    int found = 0;
271251875Speter
272251875Speter    log_event(event, "removing");
273251875Speter
274251875Speter    HEIMDAL_MUTEX_lock(&events_mutex);
275251875Speter    for (e = &events_head; *e != NULL; e = &(*e)->next) {
276251875Speter	if (event == *e) {
277251875Speter	    *e = event->next;
278251875Speter	    found++;
279251875Speter	    break;
280251875Speter	}
281251875Speter    }
282251875Speter
283251875Speter    if (!found) {
284251875Speter	ret = KRB5_CC_NOTFOUND;
285251875Speter	goto out;
286251875Speter    }
287251875Speter
288251875Speter    ret = kcm_remove_event_internal(context, &event);
289251875Speter
290251875Speterout:
291251875Speter    HEIMDAL_MUTEX_unlock(&events_mutex);
292251875Speter
293251875Speter    return ret;
294251875Speter}
295251875Speter
296251875Speterkrb5_error_code
297251875Speterkcm_cleanup_events(krb5_context context,
298251875Speter		   kcm_ccache ccache)
299251875Speter{
300251875Speter    kcm_event **e;
301251875Speter
302251875Speter    KCM_ASSERT_VALID(ccache);
303251875Speter
304251875Speter    HEIMDAL_MUTEX_lock(&events_mutex);
305251875Speter
306251875Speter    for (e = &events_head; *e != NULL; e = &(*e)->next) {
307251875Speter	if ((*e)->valid && (*e)->ccache == ccache) {
308251875Speter	    kcm_remove_event_internal(context, e);
309251875Speter	}
310251875Speter	if (*e == NULL)
311251875Speter	    break;
312251875Speter    }
313251875Speter
314251875Speter    HEIMDAL_MUTEX_unlock(&events_mutex);
315251875Speter
316251875Speter    return 0;
317251875Speter}
318251875Speter
319251875Speterstatic krb5_error_code
320251875Speterkcm_fire_event(krb5_context context,
321251875Speter	       kcm_event **e)
322251875Speter{
323251875Speter    kcm_event *event;
324251875Speter    krb5_error_code ret;
325251875Speter    krb5_creds *credp = NULL;
326251875Speter    int oneshot = 1;
327251875Speter
328251875Speter    event = *e;
329251875Speter
330251875Speter    switch (event->action) {
331251875Speter    case KCM_EVENT_ACQUIRE_CREDS:
332251875Speter	ret = kcm_ccache_acquire(context, event->ccache, &credp);
333251875Speter	oneshot = 0;
334251875Speter	break;
335251875Speter    case KCM_EVENT_RENEW_CREDS:
336251875Speter	ret = kcm_ccache_refresh(context, event->ccache, &credp);
337251875Speter	if (ret == KRB5KRB_AP_ERR_TKT_EXPIRED) {
338251875Speter	    ret = kcm_ccache_acquire(context, event->ccache, &credp);
339251875Speter	}
340251875Speter	oneshot = 0;
341251875Speter	break;
342251875Speter    case KCM_EVENT_DESTROY_CREDS:
343251875Speter	ret = kcm_ccache_destroy(context, event->ccache->name);
344251875Speter	break;
345251875Speter    case KCM_EVENT_DESTROY_EMPTY_CACHE:
346251875Speter	ret = kcm_ccache_destroy_if_empty(context, event->ccache);
347251875Speter	break;
348251875Speter    default:
349251875Speter	ret = KRB5_FCC_INTERNAL;
350251875Speter	break;
351251875Speter    }
352251875Speter
353251875Speter    event->fire_count++;
354251875Speter
355251875Speter    if (ret) {
356251875Speter	/* Reschedule failed event for another time */
357251875Speter	event->fire_time += event->backoff_time;
358251875Speter	if (event->backoff_time < KCM_EVENT_MAX_BACKOFF_TIME)
359251875Speter	    event->backoff_time *= 2;
360251875Speter
361251875Speter	/* Remove it if it would never get executed */
362251875Speter	if (event->expire_time &&
363251875Speter	    event->fire_time > event->expire_time)
364251875Speter	    kcm_remove_event_internal(context, e);
365251875Speter    } else {
366251875Speter	if (!oneshot) {
367251875Speter	    char *cpn;
368251875Speter
369251875Speter	    if (krb5_unparse_name(context, event->ccache->client,
370251875Speter				  &cpn))
371251875Speter		cpn = NULL;
372251875Speter
373251875Speter	    kcm_log(0, "%s credentials in cache %s for principal %s",
374251875Speter		    (event->action == KCM_EVENT_ACQUIRE_CREDS) ?
375251875Speter			"Acquired" : "Renewed",
376251875Speter		    event->ccache->name,
377251875Speter		    (cpn != NULL) ? cpn : "<none>");
378251875Speter
379251875Speter	    if (cpn != NULL)
380251875Speter		free(cpn);
381251875Speter
382251875Speter	    /* Succeeded, but possibly replaced with another event */
383251875Speter	    ret = kcm_ccache_make_default_event(context, event, credp);
384251875Speter	    if (ret || event->action == KCM_EVENT_NONE)
385251875Speter		oneshot = 1;
386251875Speter	    else
387251875Speter		log_event(event, "requeuing");
388251875Speter	}
389251875Speter	if (oneshot)
390251875Speter	    kcm_remove_event_internal(context, e);
391251875Speter    }
392251875Speter
393251875Speter    return ret;
394251875Speter}
395251875Speter
396251875Speterkrb5_error_code
397251875Speterkcm_run_events(krb5_context context,
398251875Speter	       time_t now)
399251875Speter{
400251875Speter    krb5_error_code ret;
401251875Speter    kcm_event **e;
402251875Speter
403251875Speter    HEIMDAL_MUTEX_lock(&events_mutex);
404251875Speter
405251875Speter    /* Only run event queue every N seconds */
406251875Speter    if (now < last_run + KCM_EVENT_QUEUE_INTERVAL) {
407251875Speter	HEIMDAL_MUTEX_unlock(&events_mutex);
408251875Speter	return 0;
409251875Speter    }
410251875Speter
411251875Speter    /* go through events list, fire and expire */
412251875Speter    for (e = &events_head; *e != NULL; e = &(*e)->next) {
413251875Speter	if ((*e)->valid == 0)
414251875Speter	    continue;
415251875Speter
416251875Speter	if (now >= (*e)->fire_time) {
417251875Speter	    ret = kcm_fire_event(context, e);
418251875Speter	    if (ret) {
419251875Speter		kcm_log(1, "Could not fire event for cache %s: %s",
420251875Speter			(*e)->ccache->name, krb5_get_err_text(context, ret));
421251875Speter	    }
422251875Speter	} else if ((*e)->expire_time && now >= (*e)->expire_time) {
423251875Speter	    ret = kcm_remove_event_internal(context, e);
424251875Speter	    if (ret) {
425251875Speter		kcm_log(1, "Could not expire event for cache %s: %s",
426251875Speter			(*e)->ccache->name, krb5_get_err_text(context, ret));
427251875Speter	    }
428251875Speter	}
429251875Speter
430251875Speter	if (*e == NULL)
431251875Speter	    break;
432251875Speter    }
433251875Speter
434251875Speter    last_run = now;
435251875Speter
436251875Speter    HEIMDAL_MUTEX_unlock(&events_mutex);
437251875Speter
438251875Speter    return 0;
439251875Speter}
440251875Speter
441251875Speter