diff options
author | Bo Yang <boyang@novell.com> | 2009-01-04 16:45:30 +0800 |
---|---|---|
committer | Stefan Metzmacher <metze@samba.org> | 2009-01-05 15:07:31 +0100 |
commit | f389b97c698aff9888ae9cdde9eb10e8e9fab4c7 (patch) | |
tree | 7d52f18e246f820f03f6a234079f63f3cf17dccd /source3/winbindd | |
parent | 022e2f81991f12637ca0eeb3a030d4cec69d6fb9 (diff) | |
download | samba-f389b97c698aff9888ae9cdde9eb10e8e9fab4c7.tar.gz samba-f389b97c698aff9888ae9cdde9eb10e8e9fab4c7.tar.bz2 samba-f389b97c698aff9888ae9cdde9eb10e8e9fab4c7.zip |
Fix broken krb5 refresh chain
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Diffstat (limited to 'source3/winbindd')
-rw-r--r-- | source3/winbindd/winbindd_cm.c | 2 | ||||
-rw-r--r-- | source3/winbindd/winbindd_cred_cache.c | 252 | ||||
-rw-r--r-- | source3/winbindd/winbindd_dual.c | 21 |
3 files changed, 236 insertions, 39 deletions
diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c index 14890683bc..cecfdb3848 100644 --- a/source3/winbindd/winbindd_cm.c +++ b/source3/winbindd/winbindd_cm.c @@ -426,6 +426,8 @@ static void set_domain_online(struct winbindd_domain *domain) GetTimeOfDay(&now); set_event_dispatch_time(winbind_event_context(), "krb5_ticket_gain_handler", now); + set_event_dispatch_time(winbind_event_context(), + "krb5_ticket_refresh_handler", now); /* Ok, we're out of any startup mode now... */ domain->startup = False; diff --git a/source3/winbindd/winbindd_cred_cache.c b/source3/winbindd/winbindd_cred_cache.c index 311b1d1822..e9e9e4dd12 100644 --- a/source3/winbindd/winbindd_cred_cache.c +++ b/source3/winbindd/winbindd_cred_cache.c @@ -34,6 +34,10 @@ #define MAX_CCACHES 100 static struct WINBINDD_CCACHE_ENTRY *ccache_list; +static void krb5_ticket_gain_handler(struct event_context *, + struct timed_event *, + const struct timeval *, + void *); /* The Krb5 ticket refresh handler should be scheduled at one-half of the period from now till the tkt @@ -85,6 +89,7 @@ static void krb5_ticket_refresh_handler(struct event_context *event_ctx, #ifdef HAVE_KRB5 int ret; time_t new_start; + time_t expire_time = 0; struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr; #endif @@ -97,44 +102,84 @@ static void krb5_ticket_refresh_handler(struct event_context *event_ctx, #ifdef HAVE_KRB5 /* Kinit again if we have the user password and we can't renew the old - * tgt anymore */ - - if ((entry->renew_until < time(NULL)) && cred_ptr && cred_ptr->pass) { - - set_effective_uid(entry->uid); - - ret = kerberos_kinit_password_ext(entry->principal_name, - cred_ptr->pass, - 0, /* hm, can we do time correction here ? */ - &entry->refresh_time, - &entry->renew_until, - entry->ccname, - False, /* no PAC required anymore */ - True, - WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, - NULL); - gain_root_privilege(); - - if (ret) { - DEBUG(3,("krb5_ticket_refresh_handler: " - "could not re-kinit: %s\n", - error_message(ret))); - TALLOC_FREE(entry->event); - return; - } - - DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit " - "for: %s in ccache: %s\n", - entry->principal_name, entry->ccname)); + * tgt anymore + * NB + * This happens when machine are put to sleep for a very long time. */ + + if (entry->renew_until < time(NULL)) { +rekinit: + if (cred_ptr && cred_ptr->pass) { + + set_effective_uid(entry->uid); + + ret = kerberos_kinit_password_ext(entry->principal_name, + cred_ptr->pass, + 0, /* hm, can we do time correction here ? */ + &entry->refresh_time, + &entry->renew_until, + entry->ccname, + False, /* no PAC required anymore */ + True, + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, + NULL); + gain_root_privilege(); + + if (ret) { + DEBUG(3,("krb5_ticket_refresh_handler: " + "could not re-kinit: %s\n", + error_message(ret))); + /* destroy the ticket because we cannot rekinit + * it, ignore error here */ + ads_kdestroy(entry->ccname); + + /* Don't break the ticket refresh chain: retry + * refreshing ticket sometime later when KDC is + * unreachable -- BoYang + * */ + + if ((ret == KRB5_KDC_UNREACH) + || (ret == KRB5_REALM_CANT_RESOLVE)) { +#if defined(DEBUG_KRB5_TKT_RENEWAL) + new_start = time(NULL) + 30; +#else + new_start = time(NULL) + + MAX(30, lp_winbind_cache_time()); +#endif + /* try to regain ticket here */ + entry->event = event_add_timed(winbind_event_context(), + entry, + timeval_set(new_start, 0), + "krb5_ticket_gain_handler", + krb5_ticket_gain_handler, + entry); + return; + } + TALLOC_FREE(entry->event); + return; + } + + DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit " + "for: %s in ccache: %s\n", + entry->principal_name, entry->ccname)); #if defined(DEBUG_KRB5_TKT_RENEWAL) - new_start = time(NULL) + 30; + new_start = time(NULL) + 30; #else - /* The tkt should be refreshed at one-half the period - from now to the expiration time */ - new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time); + /* The tkt should be refreshed at one-half the period + from now to the expiration time */ + expire_time = entry->refresh_time; + new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time); #endif - goto done; + goto done; + } else { + /* can this happen? + * No cached credentials + * destroy ticket and refresh chain + * */ + ads_kdestroy(entry->ccname); + TALLOC_FREE(entry->event); + return; + } } set_effective_uid(entry->uid); @@ -146,6 +191,7 @@ static void krb5_ticket_refresh_handler(struct event_context *event_ctx, #if defined(DEBUG_KRB5_TKT_RENEWAL) new_start = time(NULL) + 30; #else + expire_time = new_start; new_start = KRB5_EVENT_REFRESH_TIME(new_start); #endif @@ -157,20 +203,69 @@ static void krb5_ticket_refresh_handler(struct event_context *event_ctx, error_message(ret))); /* maybe we are beyond the renewing window */ + /* evil rises here, we refresh ticket failed, + * but the ticket might be expired. Therefore, + * When we refresh ticket failed, destory the + * ticket */ + + ads_kdestroy(entry->ccname); + /* avoid breaking the renewal chain: retry in * lp_winbind_cache_time() seconds when the KDC was not - * available right now. */ + * available right now. + * the return code can be KRB5_REALM_CANT_RESOLVE*/ - if (ret == KRB5_KDC_UNREACH) { + if ((ret == KRB5_KDC_UNREACH) + || (ret == KRB5_REALM_CANT_RESOLVE)) { +#if defined(DEBUG_KRB5_TKT_RENEWAL) + new_start = time(NULL) + 30; +#else new_start = time(NULL) + MAX(30, lp_winbind_cache_time()); - goto done; +#endif + /* ticket is destroyed here, we have to regain it + * if it is possible */ + entry->event = event_add_timed(winbind_event_context(), + entry, + timeval_set(new_start, 0), + "krb5_ticket_gain_handler", + krb5_ticket_gain_handler, + entry); + return; } + /* This is evil, if the ticket was already expired. + * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED. + * But there is still a chance that we can rekinit it. + * + * This happens when user login in online mode, and then network + * down or something cause winbind goes offline for a very long time, + * and then goes online again. ticket expired, renew failed. + * This happens when machine are put to sleep for a long time, + * but shorter than entry->renew_util. + * NB + * Looks like the KDC is reachable, we want to rekinit as soon as + * possible instead of waiting some time later. */ + if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED) + || (ret == KRB5_FCC_NOFILE)) goto rekinit; + return; } done: + /* in cases that ticket will be unrenewable soon, we don't try to renew ticket + * but try to regain ticket if it is possible */ + if (entry->renew_until && expire_time + && (entry->renew_until <= expire_time)) { + /* try to regain ticket 10 seconds beforre expiration */ + expire_time -= 10; + entry->event = event_add_timed(winbind_event_context(), entry, + timeval_set(expire_time, 0), + "krb5_ticket_gain_handler", + krb5_ticket_gain_handler, + entry); + return; + } entry->event = event_add_timed(winbind_event_context(), entry, timeval_set(new_start, 0), @@ -239,6 +334,9 @@ static void krb5_ticket_gain_handler(struct event_context *event_ctx, DEBUG(3,("krb5_ticket_gain_handler: " "could not kinit: %s\n", error_message(ret))); + /* evil. If we cannot do it, destroy any the __maybe__ + * __existing__ ticket */ + ads_kdestroy(entry->ccname); goto retry_later; } @@ -249,8 +347,12 @@ static void krb5_ticket_gain_handler(struct event_context *event_ctx, goto got_ticket; retry_later: - + +#if defined(DEBUG_KRB5_TKT_REGAIN) + t = timeval_set(time(NULL) + 30, 0); +#else t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0); +#endif entry->event = event_add_timed(winbind_event_context(), entry, @@ -331,6 +433,10 @@ NTSTATUS add_ccache_to_list(const char *princ_name, { struct WINBINDD_CCACHE_ENTRY *entry = NULL; struct timeval t; + NTSTATUS ntret; +#ifdef HAVE_KRB5 + int ret; +#endif if ((username == NULL && princ_name == NULL) || ccname == NULL || uid < 0) { @@ -343,6 +449,28 @@ NTSTATUS add_ccache_to_list(const char *princ_name, return NT_STATUS_NO_MORE_ENTRIES; } + /* If it is cached login, destroy krb5 ticket + * to avoid surprise. */ +#ifdef HAVE_KRB5 + if (postponed_request) { + /* ignore KRB5_FCC_NOFILE error here */ + ret = ads_kdestroy(ccname); + if (ret == KRB5_FCC_NOFILE) { + ret = 0; + } + if (ret) { + DEBUG(0, ("add_ccache_to_list: failed to destroy " + "user krb5 ccache %s with %s\n", ccname, + error_message(ret))); + return krb5_to_nt_status(ret); + } else { + DEBUG(10, ("add_ccache_to_list: successfully destroyed " + "krb5 ccache %s for user %s\n", ccname, + username)); + } + } +#endif + /* Reference count old entries */ entry = get_ccache_by_username(username); if (entry) { @@ -355,7 +483,53 @@ NTSTATUS add_ccache_to_list(const char *princ_name, "ref count on entry %s is now %d\n", username, entry->ref_count)); /* FIXME: in this case we still might want to have a krb5 cred - * event handler created - gd*/ + * event handler created - gd + * Add ticket refresh handler here */ + + if (!lp_winbind_refresh_tickets() || renew_until <= 0) { + return NT_STATUS_OK; + } + + if (!entry->event) { + if (postponed_request) { + t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0); + entry->event = event_add_timed(winbind_event_context(), + entry, + t, + "krb5_ticket_gain_handler", + krb5_ticket_gain_handler, + entry); + } else { + /* Renew at 1/2 the ticket expiration time */ +#if defined(DEBUG_KRB5_TKT_RENEWAL) + t = timeval_set(time(NULL)+30, 0); +#else + t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0); +#endif + entry->event = event_add_timed(winbind_event_context(), + entry, + t, + "krb5_ticket_refresh_handler", + krb5_ticket_refresh_handler, + entry); + } + + if (!entry->event) { + ntret = remove_ccache(username); + if (!NT_STATUS_IS_OK(ntret)) { + DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 " + "ccache %s for user %s\n", entry->ccname, + entry->username)); + DEBUG(0, ("add_ccache_to_list: error is %s\n", + nt_errstr(ntret))); + return ntret; + } + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n")); + } + return NT_STATUS_OK; } diff --git a/source3/winbindd/winbindd_dual.c b/source3/winbindd/winbindd_dual.c index 169a80d701..42e43e8cf3 100644 --- a/source3/winbindd/winbindd_dual.c +++ b/source3/winbindd/winbindd_dual.c @@ -985,6 +985,7 @@ static void child_msg_offline(struct messaging_context *msg, DATA_BLOB *data) { struct winbindd_domain *domain; + struct winbindd_domain *primary_domain = NULL; const char *domainname = (const char *)data->data; if (data->data == NULL || data->length == 0) { @@ -998,6 +999,8 @@ static void child_msg_offline(struct messaging_context *msg, return; } + primary_domain = find_our_domain(); + /* Mark the requested domain offline. */ for (domain = domain_list(); domain; domain = domain->next) { @@ -1007,6 +1010,11 @@ static void child_msg_offline(struct messaging_context *msg, if (strequal(domain->name, domainname)) { DEBUG(5,("child_msg_offline: marking %s offline.\n", domain->name)); set_domain_offline(domain); + /* we are in the trusted domain, set the primary domain + * offline too */ + if (domain != primary_domain) { + set_domain_offline(primary_domain); + } } } } @@ -1020,6 +1028,7 @@ static void child_msg_online(struct messaging_context *msg, DATA_BLOB *data) { struct winbindd_domain *domain; + struct winbindd_domain *primary_domain = NULL; const char *domainname = (const char *)data->data; if (data->data == NULL || data->length == 0) { @@ -1033,6 +1042,8 @@ static void child_msg_online(struct messaging_context *msg, return; } + primary_domain = find_our_domain(); + /* Set our global state as online. */ set_global_winbindd_state_online(); @@ -1047,6 +1058,16 @@ static void child_msg_online(struct messaging_context *msg, DEBUG(5,("child_msg_online: requesting %s to go online.\n", domain->name)); winbindd_flush_negative_conn_cache(domain); set_domain_online_request(domain); + + /* we can be in trusted domain, which will contact primary domain + * we have to bring primary domain online in trusted domain process + * see, winbindd_dual_pam_auth() --> winbindd_dual_pam_auth_samlogon() + * --> contact_domain = find_our_domain() + * */ + if (domain != primary_domain) { + winbindd_flush_negative_conn_cache(primary_domain); + set_domain_online_request(primary_domain); + } } } } |