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