1178825Sdfr/* 2178825Sdfr * Copyright (c) 2005, PADL Software Pty Ltd. 3178825Sdfr * All rights reserved. 4178825Sdfr * 5178825Sdfr * Redistribution and use in source and binary forms, with or without 6178825Sdfr * modification, are permitted provided that the following conditions 7178825Sdfr * are met: 8178825Sdfr * 9178825Sdfr * 1. Redistributions of source code must retain the above copyright 10178825Sdfr * notice, this list of conditions and the following disclaimer. 11178825Sdfr * 12178825Sdfr * 2. Redistributions in binary form must reproduce the above copyright 13178825Sdfr * notice, this list of conditions and the following disclaimer in the 14178825Sdfr * documentation and/or other materials provided with the distribution. 15178825Sdfr * 16178825Sdfr * 3. Neither the name of PADL Software nor the names of its contributors 17178825Sdfr * may be used to endorse or promote products derived from this software 18178825Sdfr * without specific prior written permission. 19178825Sdfr * 20178825Sdfr * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND 21178825Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22178825Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23178825Sdfr * ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE 24178825Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25178825Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26178825Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27178825Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28178825Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29178825Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30178825Sdfr * SUCH DAMAGE. 31178825Sdfr */ 32178825Sdfr 33178825Sdfr#include "kcm_locl.h" 34178825Sdfr 35233294SstasRCSID("$Id$"); 36178825Sdfr 37178825Sdfr/* thread-safe in case we multi-thread later */ 38178825Sdfrstatic HEIMDAL_MUTEX events_mutex = HEIMDAL_MUTEX_INITIALIZER; 39178825Sdfrstatic kcm_event *events_head = NULL; 40178825Sdfrstatic time_t last_run = 0; 41178825Sdfr 42178825Sdfrstatic char *action_strings[] = { 43178825Sdfr "NONE", "ACQUIRE_CREDS", "RENEW_CREDS", 44178825Sdfr "DESTROY_CREDS", "DESTROY_EMPTY_CACHE" }; 45178825Sdfr 46178825Sdfrkrb5_error_code 47178825Sdfrkcm_enqueue_event(krb5_context context, 48178825Sdfr kcm_event *event) 49178825Sdfr{ 50178825Sdfr krb5_error_code ret; 51178825Sdfr 52178825Sdfr if (event->action == KCM_EVENT_NONE) { 53178825Sdfr return 0; 54178825Sdfr } 55178825Sdfr 56178825Sdfr HEIMDAL_MUTEX_lock(&events_mutex); 57178825Sdfr ret = kcm_enqueue_event_internal(context, event); 58178825Sdfr HEIMDAL_MUTEX_unlock(&events_mutex); 59178825Sdfr 60178825Sdfr return ret; 61178825Sdfr} 62178825Sdfr 63178825Sdfrstatic void 64178825Sdfrprint_times(time_t time, char buf[64]) 65178825Sdfr{ 66178825Sdfr if (time) 67178825Sdfr strftime(buf, 64, "%m-%dT%H:%M", gmtime(&time)); 68178825Sdfr else 69178825Sdfr strlcpy(buf, "never", 64); 70178825Sdfr} 71178825Sdfr 72178825Sdfrstatic void 73178825Sdfrlog_event(kcm_event *event, char *msg) 74178825Sdfr{ 75178825Sdfr char fire_time[64], expire_time[64]; 76178825Sdfr 77178825Sdfr print_times(event->fire_time, fire_time); 78178825Sdfr print_times(event->expire_time, expire_time); 79178825Sdfr 80178825Sdfr kcm_log(7, "%s event %08x: fire_time %s fire_count %d expire_time %s " 81178825Sdfr "backoff_time %d action %s cache %s", 82178825Sdfr msg, event, fire_time, event->fire_count, expire_time, 83178825Sdfr event->backoff_time, action_strings[event->action], 84178825Sdfr event->ccache->name); 85178825Sdfr} 86178825Sdfr 87178825Sdfrkrb5_error_code 88178825Sdfrkcm_enqueue_event_internal(krb5_context context, 89178825Sdfr kcm_event *event) 90178825Sdfr{ 91178825Sdfr kcm_event **e; 92178825Sdfr 93178825Sdfr if (event->action == KCM_EVENT_NONE) 94178825Sdfr return 0; 95178825Sdfr 96178825Sdfr for (e = &events_head; *e != NULL; e = &(*e)->next) 97178825Sdfr ; 98178825Sdfr 99178825Sdfr *e = (kcm_event *)malloc(sizeof(kcm_event)); 100178825Sdfr if (*e == NULL) { 101178825Sdfr return KRB5_CC_NOMEM; 102178825Sdfr } 103178825Sdfr 104178825Sdfr (*e)->valid = 1; 105178825Sdfr (*e)->fire_time = event->fire_time; 106178825Sdfr (*e)->fire_count = 0; 107178825Sdfr (*e)->expire_time = event->expire_time; 108178825Sdfr (*e)->backoff_time = event->backoff_time; 109178825Sdfr 110178825Sdfr (*e)->action = event->action; 111178825Sdfr 112178825Sdfr kcm_retain_ccache(context, event->ccache); 113178825Sdfr (*e)->ccache = event->ccache; 114178825Sdfr (*e)->next = NULL; 115178825Sdfr 116178825Sdfr log_event(*e, "enqueuing"); 117178825Sdfr 118178825Sdfr return 0; 119178825Sdfr} 120178825Sdfr 121178825Sdfr/* 122178825Sdfr * Dump events list on SIGUSR2 123178825Sdfr */ 124178825Sdfrkrb5_error_code 125178825Sdfrkcm_debug_events(krb5_context context) 126178825Sdfr{ 127178825Sdfr kcm_event *e; 128178825Sdfr 129178825Sdfr for (e = events_head; e != NULL; e = e->next) 130178825Sdfr log_event(e, "debug"); 131178825Sdfr 132178825Sdfr return 0; 133178825Sdfr} 134178825Sdfr 135178825Sdfrkrb5_error_code 136178825Sdfrkcm_enqueue_event_relative(krb5_context context, 137178825Sdfr kcm_event *event) 138178825Sdfr{ 139178825Sdfr krb5_error_code ret; 140178825Sdfr kcm_event e; 141178825Sdfr 142178825Sdfr e = *event; 143178825Sdfr e.backoff_time = e.fire_time; 144178825Sdfr e.fire_time += time(NULL); 145178825Sdfr 146178825Sdfr ret = kcm_enqueue_event(context, &e); 147178825Sdfr 148178825Sdfr return ret; 149178825Sdfr} 150178825Sdfr 151178825Sdfrstatic krb5_error_code 152178825Sdfrkcm_remove_event_internal(krb5_context context, 153178825Sdfr kcm_event **e) 154178825Sdfr{ 155178825Sdfr kcm_event *next; 156178825Sdfr 157178825Sdfr next = (*e)->next; 158178825Sdfr 159178825Sdfr (*e)->valid = 0; 160178825Sdfr (*e)->fire_time = 0; 161178825Sdfr (*e)->fire_count = 0; 162178825Sdfr (*e)->expire_time = 0; 163178825Sdfr (*e)->backoff_time = 0; 164233294Sstas kcm_release_ccache(context, (*e)->ccache); 165178825Sdfr (*e)->next = NULL; 166178825Sdfr free(*e); 167178825Sdfr 168178825Sdfr *e = next; 169178825Sdfr 170178825Sdfr return 0; 171178825Sdfr} 172178825Sdfr 173178825Sdfrstatic int 174178825Sdfris_primary_credential_p(krb5_context context, 175178825Sdfr kcm_ccache ccache, 176178825Sdfr krb5_creds *newcred) 177178825Sdfr{ 178178825Sdfr krb5_flags whichfields; 179178825Sdfr 180178825Sdfr if (ccache->client == NULL) 181178825Sdfr return 0; 182178825Sdfr 183178825Sdfr if (newcred->client == NULL || 184178825Sdfr !krb5_principal_compare(context, ccache->client, newcred->client)) 185178825Sdfr return 0; 186178825Sdfr 187178825Sdfr /* XXX just checks whether it's the first credential in the cache */ 188178825Sdfr if (ccache->creds == NULL) 189178825Sdfr return 0; 190178825Sdfr 191178825Sdfr whichfields = KRB5_TC_MATCH_KEYTYPE | KRB5_TC_MATCH_FLAGS_EXACT | 192178825Sdfr KRB5_TC_MATCH_TIMES_EXACT | KRB5_TC_MATCH_AUTHDATA | 193178825Sdfr KRB5_TC_MATCH_2ND_TKT | KRB5_TC_MATCH_IS_SKEY; 194178825Sdfr 195178825Sdfr return krb5_compare_creds(context, whichfields, newcred, &ccache->creds->cred); 196178825Sdfr} 197178825Sdfr 198178825Sdfr/* 199178825Sdfr * Setup default events for a new credential 200178825Sdfr */ 201178825Sdfrstatic krb5_error_code 202178825Sdfrkcm_ccache_make_default_event(krb5_context context, 203178825Sdfr kcm_event *event, 204178825Sdfr krb5_creds *newcred) 205178825Sdfr{ 206178825Sdfr krb5_error_code ret = 0; 207178825Sdfr kcm_ccache ccache = event->ccache; 208178825Sdfr 209233294Sstas event->fire_time = 0; 210178825Sdfr event->expire_time = 0; 211178825Sdfr event->backoff_time = KCM_EVENT_DEFAULT_BACKOFF_TIME; 212178825Sdfr 213178825Sdfr if (newcred == NULL) { 214178825Sdfr /* no creds, must be acquire creds request */ 215178825Sdfr if ((ccache->flags & KCM_MASK_KEY_PRESENT) == 0) { 216178825Sdfr kcm_log(0, "Cannot acquire credentials without a key"); 217178825Sdfr return KRB5_FCC_INTERNAL; 218178825Sdfr } 219178825Sdfr 220178825Sdfr event->fire_time = time(NULL); /* right away */ 221178825Sdfr event->action = KCM_EVENT_ACQUIRE_CREDS; 222178825Sdfr } else if (is_primary_credential_p(context, ccache, newcred)) { 223178825Sdfr if (newcred->flags.b.renewable) { 224178825Sdfr event->action = KCM_EVENT_RENEW_CREDS; 225178825Sdfr ccache->flags |= KCM_FLAGS_RENEWABLE; 226178825Sdfr } else { 227178825Sdfr if (ccache->flags & KCM_MASK_KEY_PRESENT) 228178825Sdfr event->action = KCM_EVENT_ACQUIRE_CREDS; 229178825Sdfr else 230178825Sdfr event->action = KCM_EVENT_NONE; 231178825Sdfr ccache->flags &= ~(KCM_FLAGS_RENEWABLE); 232178825Sdfr } 233178825Sdfr /* requeue with some slop factor */ 234178825Sdfr event->fire_time = newcred->times.endtime - KCM_EVENT_QUEUE_INTERVAL; 235178825Sdfr } else { 236178825Sdfr event->action = KCM_EVENT_NONE; 237178825Sdfr } 238178825Sdfr 239178825Sdfr return ret; 240178825Sdfr} 241178825Sdfr 242178825Sdfrkrb5_error_code 243178825Sdfrkcm_ccache_enqueue_default(krb5_context context, 244178825Sdfr kcm_ccache ccache, 245178825Sdfr krb5_creds *newcred) 246178825Sdfr{ 247178825Sdfr kcm_event event; 248178825Sdfr krb5_error_code ret; 249178825Sdfr 250178825Sdfr memset(&event, 0, sizeof(event)); 251178825Sdfr event.ccache = ccache; 252178825Sdfr 253178825Sdfr ret = kcm_ccache_make_default_event(context, &event, newcred); 254178825Sdfr if (ret) 255178825Sdfr return ret; 256178825Sdfr 257178825Sdfr ret = kcm_enqueue_event_internal(context, &event); 258178825Sdfr if (ret) 259178825Sdfr return ret; 260178825Sdfr 261178825Sdfr return 0; 262178825Sdfr} 263178825Sdfr 264178825Sdfrkrb5_error_code 265178825Sdfrkcm_remove_event(krb5_context context, 266178825Sdfr kcm_event *event) 267178825Sdfr{ 268178825Sdfr krb5_error_code ret; 269178825Sdfr kcm_event **e; 270178825Sdfr int found = 0; 271178825Sdfr 272178825Sdfr log_event(event, "removing"); 273178825Sdfr 274178825Sdfr HEIMDAL_MUTEX_lock(&events_mutex); 275178825Sdfr for (e = &events_head; *e != NULL; e = &(*e)->next) { 276178825Sdfr if (event == *e) { 277178825Sdfr *e = event->next; 278178825Sdfr found++; 279178825Sdfr break; 280178825Sdfr } 281178825Sdfr } 282178825Sdfr 283178825Sdfr if (!found) { 284178825Sdfr ret = KRB5_CC_NOTFOUND; 285178825Sdfr goto out; 286178825Sdfr } 287178825Sdfr 288178825Sdfr ret = kcm_remove_event_internal(context, &event); 289178825Sdfr 290178825Sdfrout: 291178825Sdfr HEIMDAL_MUTEX_unlock(&events_mutex); 292178825Sdfr 293178825Sdfr return ret; 294178825Sdfr} 295178825Sdfr 296178825Sdfrkrb5_error_code 297178825Sdfrkcm_cleanup_events(krb5_context context, 298178825Sdfr kcm_ccache ccache) 299178825Sdfr{ 300178825Sdfr kcm_event **e; 301178825Sdfr 302178825Sdfr KCM_ASSERT_VALID(ccache); 303178825Sdfr 304178825Sdfr HEIMDAL_MUTEX_lock(&events_mutex); 305178825Sdfr 306178825Sdfr for (e = &events_head; *e != NULL; e = &(*e)->next) { 307178825Sdfr if ((*e)->valid && (*e)->ccache == ccache) { 308178825Sdfr kcm_remove_event_internal(context, e); 309178825Sdfr } 310178825Sdfr if (*e == NULL) 311178825Sdfr break; 312178825Sdfr } 313178825Sdfr 314178825Sdfr HEIMDAL_MUTEX_unlock(&events_mutex); 315178825Sdfr 316178825Sdfr return 0; 317178825Sdfr} 318178825Sdfr 319178825Sdfrstatic krb5_error_code 320178825Sdfrkcm_fire_event(krb5_context context, 321178825Sdfr kcm_event **e) 322178825Sdfr{ 323178825Sdfr kcm_event *event; 324178825Sdfr krb5_error_code ret; 325178825Sdfr krb5_creds *credp = NULL; 326178825Sdfr int oneshot = 1; 327178825Sdfr 328178825Sdfr event = *e; 329178825Sdfr 330178825Sdfr switch (event->action) { 331178825Sdfr case KCM_EVENT_ACQUIRE_CREDS: 332178825Sdfr ret = kcm_ccache_acquire(context, event->ccache, &credp); 333178825Sdfr oneshot = 0; 334178825Sdfr break; 335178825Sdfr case KCM_EVENT_RENEW_CREDS: 336178825Sdfr ret = kcm_ccache_refresh(context, event->ccache, &credp); 337178825Sdfr if (ret == KRB5KRB_AP_ERR_TKT_EXPIRED) { 338178825Sdfr ret = kcm_ccache_acquire(context, event->ccache, &credp); 339178825Sdfr } 340178825Sdfr oneshot = 0; 341178825Sdfr break; 342178825Sdfr case KCM_EVENT_DESTROY_CREDS: 343178825Sdfr ret = kcm_ccache_destroy(context, event->ccache->name); 344178825Sdfr break; 345178825Sdfr case KCM_EVENT_DESTROY_EMPTY_CACHE: 346178825Sdfr ret = kcm_ccache_destroy_if_empty(context, event->ccache); 347178825Sdfr break; 348178825Sdfr default: 349178825Sdfr ret = KRB5_FCC_INTERNAL; 350178825Sdfr break; 351178825Sdfr } 352178825Sdfr 353178825Sdfr event->fire_count++; 354178825Sdfr 355178825Sdfr if (ret) { 356233294Sstas /* Reschedule failed event for another time */ 357178825Sdfr event->fire_time += event->backoff_time; 358178825Sdfr if (event->backoff_time < KCM_EVENT_MAX_BACKOFF_TIME) 359178825Sdfr event->backoff_time *= 2; 360178825Sdfr 361178825Sdfr /* Remove it if it would never get executed */ 362178825Sdfr if (event->expire_time && 363178825Sdfr event->fire_time > event->expire_time) 364178825Sdfr kcm_remove_event_internal(context, e); 365178825Sdfr } else { 366178825Sdfr if (!oneshot) { 367178825Sdfr char *cpn; 368178825Sdfr 369178825Sdfr if (krb5_unparse_name(context, event->ccache->client, 370178825Sdfr &cpn)) 371178825Sdfr cpn = NULL; 372178825Sdfr 373178825Sdfr kcm_log(0, "%s credentials in cache %s for principal %s", 374178825Sdfr (event->action == KCM_EVENT_ACQUIRE_CREDS) ? 375178825Sdfr "Acquired" : "Renewed", 376178825Sdfr event->ccache->name, 377178825Sdfr (cpn != NULL) ? cpn : "<none>"); 378178825Sdfr 379178825Sdfr if (cpn != NULL) 380178825Sdfr free(cpn); 381178825Sdfr 382178825Sdfr /* Succeeded, but possibly replaced with another event */ 383178825Sdfr ret = kcm_ccache_make_default_event(context, event, credp); 384178825Sdfr if (ret || event->action == KCM_EVENT_NONE) 385178825Sdfr oneshot = 1; 386178825Sdfr else 387178825Sdfr log_event(event, "requeuing"); 388178825Sdfr } 389178825Sdfr if (oneshot) 390178825Sdfr kcm_remove_event_internal(context, e); 391178825Sdfr } 392178825Sdfr 393178825Sdfr return ret; 394178825Sdfr} 395178825Sdfr 396178825Sdfrkrb5_error_code 397233294Sstaskcm_run_events(krb5_context context, time_t now) 398178825Sdfr{ 399178825Sdfr krb5_error_code ret; 400178825Sdfr kcm_event **e; 401178825Sdfr 402178825Sdfr HEIMDAL_MUTEX_lock(&events_mutex); 403178825Sdfr 404178825Sdfr /* Only run event queue every N seconds */ 405178825Sdfr if (now < last_run + KCM_EVENT_QUEUE_INTERVAL) { 406178825Sdfr HEIMDAL_MUTEX_unlock(&events_mutex); 407178825Sdfr return 0; 408178825Sdfr } 409178825Sdfr 410178825Sdfr /* go through events list, fire and expire */ 411178825Sdfr for (e = &events_head; *e != NULL; e = &(*e)->next) { 412178825Sdfr if ((*e)->valid == 0) 413178825Sdfr continue; 414178825Sdfr 415178825Sdfr if (now >= (*e)->fire_time) { 416178825Sdfr ret = kcm_fire_event(context, e); 417178825Sdfr if (ret) { 418178825Sdfr kcm_log(1, "Could not fire event for cache %s: %s", 419178825Sdfr (*e)->ccache->name, krb5_get_err_text(context, ret)); 420178825Sdfr } 421178825Sdfr } else if ((*e)->expire_time && now >= (*e)->expire_time) { 422178825Sdfr ret = kcm_remove_event_internal(context, e); 423178825Sdfr if (ret) { 424178825Sdfr kcm_log(1, "Could not expire event for cache %s: %s", 425178825Sdfr (*e)->ccache->name, krb5_get_err_text(context, ret)); 426178825Sdfr } 427178825Sdfr } 428178825Sdfr 429178825Sdfr if (*e == NULL) 430178825Sdfr break; 431178825Sdfr } 432178825Sdfr 433178825Sdfr last_run = now; 434178825Sdfr 435178825Sdfr HEIMDAL_MUTEX_unlock(&events_mutex); 436178825Sdfr 437178825Sdfr return 0; 438178825Sdfr} 439178825Sdfr 440