xref: /minix/crypto/external/bsd/heimdal/dist/kcm/events.c (revision 366d18b2)
1 /*	$NetBSD: events.c,v 1.1.1.2 2014/04/24 12:45:27 pettai Exp $	*/
2 
3 /*
4  * Copyright (c) 2005, PADL Software Pty Ltd.
5  * 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 __RCSID("NetBSD");
38 
39 /* thread-safe in case we multi-thread later */
40 static HEIMDAL_MUTEX events_mutex = HEIMDAL_MUTEX_INITIALIZER;
41 static kcm_event *events_head = NULL;
42 static time_t last_run = 0;
43 
44 static char *action_strings[] = {
45 	"NONE", "ACQUIRE_CREDS", "RENEW_CREDS",
46 	"DESTROY_CREDS", "DESTROY_EMPTY_CACHE" };
47 
48 krb5_error_code
49 kcm_enqueue_event(krb5_context context,
50 		  kcm_event *event)
51 {
52     krb5_error_code ret;
53 
54     if (event->action == KCM_EVENT_NONE) {
55 	return 0;
56     }
57 
58     HEIMDAL_MUTEX_lock(&events_mutex);
59     ret = kcm_enqueue_event_internal(context, event);
60     HEIMDAL_MUTEX_unlock(&events_mutex);
61 
62     return ret;
63 }
64 
65 static void
66 print_times(time_t time, char buf[64])
67 {
68     if (time)
69 	strftime(buf, 64, "%m-%dT%H:%M", gmtime(&time));
70     else
71 	strlcpy(buf, "never", 64);
72 }
73 
74 static void
75 log_event(kcm_event *event, char *msg)
76 {
77     char fire_time[64], expire_time[64];
78 
79     print_times(event->fire_time, fire_time);
80     print_times(event->expire_time, expire_time);
81 
82     kcm_log(7, "%s event %08x: fire_time %s fire_count %d expire_time %s "
83 	    "backoff_time %d action %s cache %s",
84 	    msg, event, fire_time, event->fire_count, expire_time,
85 	    event->backoff_time, action_strings[event->action],
86 	    event->ccache->name);
87 }
88 
89 krb5_error_code
90 kcm_enqueue_event_internal(krb5_context context,
91 			   kcm_event *event)
92 {
93     kcm_event **e;
94 
95     if (event->action == KCM_EVENT_NONE)
96 	return 0;
97 
98     for (e = &events_head; *e != NULL; e = &(*e)->next)
99 	;
100 
101     *e = (kcm_event *)malloc(sizeof(kcm_event));
102     if (*e == NULL) {
103 	return KRB5_CC_NOMEM;
104     }
105 
106     (*e)->valid = 1;
107     (*e)->fire_time = event->fire_time;
108     (*e)->fire_count = 0;
109     (*e)->expire_time = event->expire_time;
110     (*e)->backoff_time = event->backoff_time;
111 
112     (*e)->action = event->action;
113 
114     kcm_retain_ccache(context, event->ccache);
115     (*e)->ccache = event->ccache;
116     (*e)->next = NULL;
117 
118     log_event(*e, "enqueuing");
119 
120     return 0;
121 }
122 
123 /*
124  * Dump events list on SIGUSR2
125  */
126 krb5_error_code
127 kcm_debug_events(krb5_context context)
128 {
129     kcm_event *e;
130 
131     for (e = events_head; e != NULL; e = e->next)
132 	log_event(e, "debug");
133 
134     return 0;
135 }
136 
137 krb5_error_code
138 kcm_enqueue_event_relative(krb5_context context,
139 			   kcm_event *event)
140 {
141     krb5_error_code ret;
142     kcm_event e;
143 
144     e = *event;
145     e.backoff_time = e.fire_time;
146     e.fire_time += time(NULL);
147 
148     ret = kcm_enqueue_event(context, &e);
149 
150     return ret;
151 }
152 
153 static krb5_error_code
154 kcm_remove_event_internal(krb5_context context,
155 			  kcm_event **e)
156 {
157     kcm_event *next;
158 
159     next = (*e)->next;
160 
161     (*e)->valid = 0;
162     (*e)->fire_time = 0;
163     (*e)->fire_count = 0;
164     (*e)->expire_time = 0;
165     (*e)->backoff_time = 0;
166     kcm_release_ccache(context, (*e)->ccache);
167     (*e)->next = NULL;
168     free(*e);
169 
170     *e = next;
171 
172     return 0;
173 }
174 
175 static int
176 is_primary_credential_p(krb5_context context,
177 			kcm_ccache ccache,
178 			krb5_creds *newcred)
179 {
180     krb5_flags whichfields;
181 
182     if (ccache->client == NULL)
183 	return 0;
184 
185     if (newcred->client == NULL ||
186 	!krb5_principal_compare(context, ccache->client, newcred->client))
187 	return 0;
188 
189     /* XXX just checks whether it's the first credential in the cache */
190     if (ccache->creds == NULL)
191 	return 0;
192 
193     whichfields = KRB5_TC_MATCH_KEYTYPE | KRB5_TC_MATCH_FLAGS_EXACT |
194 		  KRB5_TC_MATCH_TIMES_EXACT | KRB5_TC_MATCH_AUTHDATA |
195 		  KRB5_TC_MATCH_2ND_TKT | KRB5_TC_MATCH_IS_SKEY;
196 
197     return krb5_compare_creds(context, whichfields, newcred, &ccache->creds->cred);
198 }
199 
200 /*
201  * Setup default events for a new credential
202  */
203 static krb5_error_code
204 kcm_ccache_make_default_event(krb5_context context,
205 			      kcm_event *event,
206 			      krb5_creds *newcred)
207 {
208     krb5_error_code ret = 0;
209     kcm_ccache ccache = event->ccache;
210 
211     event->fire_time = 0;
212     event->expire_time = 0;
213     event->backoff_time = KCM_EVENT_DEFAULT_BACKOFF_TIME;
214 
215     if (newcred == NULL) {
216 	/* no creds, must be acquire creds request */
217 	if ((ccache->flags & KCM_MASK_KEY_PRESENT) == 0) {
218 	    kcm_log(0, "Cannot acquire credentials without a key");
219 	    return KRB5_FCC_INTERNAL;
220 	}
221 
222 	event->fire_time = time(NULL); /* right away */
223 	event->action = KCM_EVENT_ACQUIRE_CREDS;
224     } else if (is_primary_credential_p(context, ccache, newcred)) {
225 	if (newcred->flags.b.renewable) {
226 	    event->action = KCM_EVENT_RENEW_CREDS;
227 	    ccache->flags |= KCM_FLAGS_RENEWABLE;
228 	} else {
229 	    if (ccache->flags & KCM_MASK_KEY_PRESENT)
230 		event->action = KCM_EVENT_ACQUIRE_CREDS;
231 	    else
232 		event->action = KCM_EVENT_NONE;
233 	    ccache->flags &= ~(KCM_FLAGS_RENEWABLE);
234 	}
235 	/* requeue with some slop factor */
236 	event->fire_time = newcred->times.endtime - KCM_EVENT_QUEUE_INTERVAL;
237     } else {
238 	event->action = KCM_EVENT_NONE;
239     }
240 
241     return ret;
242 }
243 
244 krb5_error_code
245 kcm_ccache_enqueue_default(krb5_context context,
246 			   kcm_ccache ccache,
247 			   krb5_creds *newcred)
248 {
249     kcm_event event;
250     krb5_error_code ret;
251 
252     memset(&event, 0, sizeof(event));
253     event.ccache = ccache;
254 
255     ret = kcm_ccache_make_default_event(context, &event, newcred);
256     if (ret)
257 	return ret;
258 
259     ret = kcm_enqueue_event_internal(context, &event);
260     if (ret)
261 	return ret;
262 
263     return 0;
264 }
265 
266 krb5_error_code
267 kcm_remove_event(krb5_context context,
268 		 kcm_event *event)
269 {
270     krb5_error_code ret;
271     kcm_event **e;
272     int found = 0;
273 
274     log_event(event, "removing");
275 
276     HEIMDAL_MUTEX_lock(&events_mutex);
277     for (e = &events_head; *e != NULL; e = &(*e)->next) {
278 	if (event == *e) {
279 	    *e = event->next;
280 	    found++;
281 	    break;
282 	}
283     }
284 
285     if (!found) {
286 	ret = KRB5_CC_NOTFOUND;
287 	goto out;
288     }
289 
290     ret = kcm_remove_event_internal(context, &event);
291 
292 out:
293     HEIMDAL_MUTEX_unlock(&events_mutex);
294 
295     return ret;
296 }
297 
298 krb5_error_code
299 kcm_cleanup_events(krb5_context context,
300 		   kcm_ccache ccache)
301 {
302     kcm_event **e;
303 
304     KCM_ASSERT_VALID(ccache);
305 
306     HEIMDAL_MUTEX_lock(&events_mutex);
307 
308     for (e = &events_head; *e != NULL; e = &(*e)->next) {
309 	if ((*e)->valid && (*e)->ccache == ccache) {
310 	    kcm_remove_event_internal(context, e);
311 	}
312 	if (*e == NULL)
313 	    break;
314     }
315 
316     HEIMDAL_MUTEX_unlock(&events_mutex);
317 
318     return 0;
319 }
320 
321 static krb5_error_code
322 kcm_fire_event(krb5_context context,
323 	       kcm_event **e)
324 {
325     kcm_event *event;
326     krb5_error_code ret;
327     krb5_creds *credp = NULL;
328     int oneshot = 1;
329 
330     event = *e;
331 
332     switch (event->action) {
333     case KCM_EVENT_ACQUIRE_CREDS:
334 	ret = kcm_ccache_acquire(context, event->ccache, &credp);
335 	oneshot = 0;
336 	break;
337     case KCM_EVENT_RENEW_CREDS:
338 	ret = kcm_ccache_refresh(context, event->ccache, &credp);
339 	if (ret == KRB5KRB_AP_ERR_TKT_EXPIRED) {
340 	    ret = kcm_ccache_acquire(context, event->ccache, &credp);
341 	}
342 	oneshot = 0;
343 	break;
344     case KCM_EVENT_DESTROY_CREDS:
345 	ret = kcm_ccache_destroy(context, event->ccache->name);
346 	break;
347     case KCM_EVENT_DESTROY_EMPTY_CACHE:
348 	ret = kcm_ccache_destroy_if_empty(context, event->ccache);
349 	break;
350     default:
351 	ret = KRB5_FCC_INTERNAL;
352 	break;
353     }
354 
355     event->fire_count++;
356 
357     if (ret) {
358 	/* Reschedule failed event for another time */
359 	event->fire_time += event->backoff_time;
360 	if (event->backoff_time < KCM_EVENT_MAX_BACKOFF_TIME)
361 	    event->backoff_time *= 2;
362 
363 	/* Remove it if it would never get executed */
364 	if (event->expire_time &&
365 	    event->fire_time > event->expire_time)
366 	    kcm_remove_event_internal(context, e);
367     } else {
368 	if (!oneshot) {
369 	    char *cpn;
370 
371 	    if (krb5_unparse_name(context, event->ccache->client,
372 				  &cpn))
373 		cpn = NULL;
374 
375 	    kcm_log(0, "%s credentials in cache %s for principal %s",
376 		    (event->action == KCM_EVENT_ACQUIRE_CREDS) ?
377 			"Acquired" : "Renewed",
378 		    event->ccache->name,
379 		    (cpn != NULL) ? cpn : "<none>");
380 
381 	    if (cpn != NULL)
382 		free(cpn);
383 
384 	    /* Succeeded, but possibly replaced with another event */
385 	    ret = kcm_ccache_make_default_event(context, event, credp);
386 	    if (ret || event->action == KCM_EVENT_NONE)
387 		oneshot = 1;
388 	    else
389 		log_event(event, "requeuing");
390 	}
391 	if (oneshot)
392 	    kcm_remove_event_internal(context, e);
393     }
394 
395     return ret;
396 }
397 
398 krb5_error_code
399 kcm_run_events(krb5_context context, time_t now)
400 {
401     krb5_error_code ret;
402     kcm_event **e;
403 
404     HEIMDAL_MUTEX_lock(&events_mutex);
405 
406     /* Only run event queue every N seconds */
407     if (now < last_run + KCM_EVENT_QUEUE_INTERVAL) {
408 	HEIMDAL_MUTEX_unlock(&events_mutex);
409 	return 0;
410     }
411 
412     /* go through events list, fire and expire */
413     for (e = &events_head; *e != NULL; e = &(*e)->next) {
414 	if ((*e)->valid == 0)
415 	    continue;
416 
417 	if (now >= (*e)->fire_time) {
418 	    ret = kcm_fire_event(context, e);
419 	    if (ret) {
420 		kcm_log(1, "Could not fire event for cache %s: %s",
421 			(*e)->ccache->name, krb5_get_err_text(context, ret));
422 	    }
423 	} else if ((*e)->expire_time && now >= (*e)->expire_time) {
424 	    ret = kcm_remove_event_internal(context, e);
425 	    if (ret) {
426 		kcm_log(1, "Could not expire event for cache %s: %s",
427 			(*e)->ccache->name, krb5_get_err_text(context, ret));
428 	    }
429 	}
430 
431 	if (*e == NULL)
432 	    break;
433     }
434 
435     last_run = now;
436 
437     HEIMDAL_MUTEX_unlock(&events_mutex);
438 
439     return 0;
440 }
441 
442