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