From 778b58dd80e8fd0fe9f0487d219c0fde48b2a6dc Mon Sep 17 00:00:00 2001 From: David Mulder Date: Wed, 10 Jan 2018 10:00:27 -0700 Subject: [PATCH] winbind: account locked when using winbind refresh tickets Users were being locked out by machines they hadn't recently authenticated to. The source of the problem is in krb5_ticket_refresh_handler(), when users are password kinit'd after a long period of being offline using invalid passwords. BUG: https://bugzilla.samba.org/show_bug.cgi?id=13212 Signed-off-by: David Mulder --- source3/libads/ads_ldap_protos.h | 2 + source3/libads/ldap.c | 27 ++++++++ source3/winbindd/winbindd.h | 1 + source3/winbindd/winbindd_cred_cache.c | 109 +++++++++++++++++++++++++++++++-- source3/winbindd/winbindd_pam.c | 7 ++- source3/winbindd/winbindd_proto.h | 3 +- 6 files changed, 142 insertions(+), 7 deletions(-) diff --git a/source3/libads/ads_ldap_protos.h b/source3/libads/ads_ldap_protos.h index b063815678a..e1ad7f30666 100644 --- a/source3/libads/ads_ldap_protos.h +++ b/source3/libads/ads_ldap_protos.h @@ -52,6 +52,8 @@ char **ads_pull_strings_range(ADS_STRUCT *ads, bool *more_strings); bool ads_pull_uint32(ADS_STRUCT *ads, LDAPMessage *msg, const char *field, uint32_t *v); +bool ads_pull_uint64(ADS_STRUCT *ads, LDAPMessage *msg, const char *field, + uint64_t *v); bool ads_pull_guid(ADS_STRUCT *ads, LDAPMessage *msg, struct GUID *guid); bool ads_pull_sid(ADS_STRUCT *ads, LDAPMessage *msg, const char *field, struct dom_sid *sid); diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c index c18837cc524..0720cd65362 100644 --- a/source3/libads/ldap.c +++ b/source3/libads/ldap.c @@ -2743,6 +2743,33 @@ int ads_count_replies(ADS_STRUCT *ads, void *res) } /** + * pull a single uint64_t from a ADS result + * @param ads connection to ads server + * @param msg Results of search + * @param field Attribute to retrieve + * @param v Pointer to int to store result + * @return boolean inidicating success +*/ +bool ads_pull_uint64(ADS_STRUCT *ads, LDAPMessage *msg, const char *field, + uint64_t *v) +{ + char **values; + + values = ldap_get_values(ads->ldap.ld, msg, field); + if (!values) { + return False; + } + if (!values[0]) { + ldap_value_free(values); + return False; + } + + *v = atoll(values[0]); + ldap_value_free(values); + return True; +} + +/** * pull a single objectGUID from an ADS result * @param ads connection to ADS server * @param msg results of search diff --git a/source3/winbindd/winbindd.h b/source3/winbindd/winbindd.h index 396f7f7946e..10057757b0f 100644 --- a/source3/winbindd/winbindd.h +++ b/source3/winbindd/winbindd.h @@ -347,6 +347,7 @@ struct WINBINDD_CCACHE_ENTRY { time_t renew_until; time_t refresh_time; struct tevent_timer *event; + NTTIME last_password_change; }; #include "winbindd/winbindd_proto.h" diff --git a/source3/winbindd/winbindd_cred_cache.c b/source3/winbindd/winbindd_cred_cache.c index 5e019dfce0c..eb34c35aea7 100644 --- a/source3/winbindd/winbindd_cred_cache.c +++ b/source3/winbindd/winbindd_cred_cache.c @@ -26,6 +26,9 @@ #include "../libcli/auth/libcli_auth.h" #include "smb_krb5.h" #include "libads/kerberos_proto.h" +#include "ads.h" +#include "secrets.h" +#include "flags.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_WINBIND @@ -100,6 +103,87 @@ void ccache_remove_all_after_fork(void) return; } +static uint64_t fetch_last_password_change(struct WINBINDD_CCACHE_ENTRY *entry) +{ + TALLOC_CTX *ctx = talloc_new(entry); + ADS_STATUS status; + ADS_STRUCT *ads; + LDAPMessage *res = NULL; + char *ldap_exp; + char *passwd = NULL; + uint64_t ret = 0; + const char *attr_list[] = { + "pwdLastSet", + NULL + }; + char *sam = NULL, *at_ptr; + + if ((at_ptr = strchr(entry->principal_name, '@')) != NULL) { + int strlen = at_ptr-entry->principal_name; + sam = talloc_zero_size(ctx, strlen+1); + strncpy(sam, entry->principal_name, strlen); + } else { + DEBUG(5,("Could not determine samAccountName from %s\n", + entry->principal_name)); + goto fail; + } + + if ((ldap_exp = talloc_asprintf(ctx, + "(samAccountName=%s)", + sam)) == NULL) { + goto fail; + } + + ads = ads_init(entry->realm, lp_workgroup(), + saf_fetch(ctx, entry->realm)); + if (ads == NULL) { + goto fail; + } + + ads->auth.user_name = talloc_asprintf(ctx, "%s$", lp_netbios_name()); + if (!ads->auth.user_name) { + goto fail; + } + + ads->auth.flags |= ADS_AUTH_USER_CREDS; + if (!secrets_init()) { + goto fail; + } + + passwd = secrets_fetch_machine_password(ads->server.workgroup, NULL, + NULL); + if (passwd == NULL) { + goto fail; + } + ads->auth.password = smb_xstrdup(passwd); + ads->auth.realm = smb_xstrdup(ads->server.realm); + if (!strupper_m(ads->auth.realm)) { + goto fail; + } + + status = ads_connect(ads); + if (!ADS_ERR_OK(status)) { + goto fail; + } + + status = ads_do_search_all(ads, ads->config.bind_path, + LDAP_SCOPE_SUBTREE, ldap_exp, + attr_list, &res); + if (ADS_ERR_OK(status)) { + if (ads_count_replies(ads, res) != 1) { + goto fail; + } + if (!ads_pull_uint64(ads, res, "pwdLastSet", &ret)) { + goto fail; + } + } + +fail: + SAFE_FREE(passwd); + TALLOC_FREE(ctx); + return ret; +} + /**************************************************************** Do the work of refreshing the ticket. ****************************************************************/ @@ -128,9 +212,15 @@ static void krb5_ticket_refresh_handler(struct tevent_context *event_ctx, /* Kinit again if we have the user password and we can't renew the old * tgt anymore * NB - * This happens when machine are put to sleep for a very long time. */ - - if (entry->renew_until < time(NULL)) { + * This happens when machine are put to sleep for a very long time. + * + * Using cached passwords to kinit can lockout a user account if their + * password has changed. Check the user pwdLastSet attribute and ignore + * the kinit if it has changed. + */ + + if (entry->renew_until < time(NULL) && + fetch_last_password_change(entry) <= entry->last_password_change) { rekinit: if (cred_ptr && cred_ptr->pass) { @@ -331,6 +421,15 @@ static void krb5_ticket_gain_handler(struct tevent_context *event_ctx, goto retry_later; } + /* Using cached passwords to kinit can lockout a user account if their + * password has changed. Check the user pwdLastSet attribute and ignore + * the kinit if it has changed. + */ + if (fetch_last_password_change(entry) > entry->last_password_change) { + DEBUG(10,("krb5_ticket_gain_handler: old cached password\n")); + return; + } + set_effective_uid(entry->uid); ret = kerberos_kinit_password_ext(entry->principal_name, @@ -494,7 +593,8 @@ NTSTATUS add_ccache_to_list(const char *princ_name, time_t create_time, time_t ticket_end, time_t renew_until, - bool postponed_request) + bool postponed_request, + NTTIME last_password_change) { struct WINBINDD_CCACHE_ENTRY *entry = NULL; struct timeval t; @@ -625,6 +725,7 @@ NTSTATUS add_ccache_to_list(const char *princ_name, entry->renew_until = renew_until; entry->uid = uid; entry->ref_count = 1; + entry->last_password_change = last_password_change; if (!lp_winbind_refresh_tickets() || renew_until <= 0) { goto add_entry; diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c index abd208a515f..19efca272e6 100644 --- a/source3/winbindd/winbindd_pam.c +++ b/source3/winbindd/winbindd_pam.c @@ -617,6 +617,7 @@ static NTSTATUS winbindd_raw_kerberos_login(TALLOC_CTX *mem_ctx, const char *local_service; uint32_t i; struct netr_SamInfo3 *info3_copy = NULL; + struct netr_SamBaseInfo *info_base = NULL; *info3 = NULL; @@ -749,6 +750,7 @@ static NTSTATUS winbindd_raw_kerberos_login(TALLOC_CTX *mem_ctx, if (!NT_STATUS_IS_OK(result)) { goto failed; } + info_base = &(info3_copy->base); /* if we had a user's ccache then return that string for the pam * environment */ @@ -767,7 +769,8 @@ static NTSTATUS winbindd_raw_kerberos_login(TALLOC_CTX *mem_ctx, time(NULL), ticket_lifetime, renewal_until, - false); + false, + info_base->last_password_change); if (!NT_STATUS_IS_OK(result)) { DEBUG(10,("winbindd_raw_kerberos_login: failed to add ccache to list: %s\n", @@ -1103,7 +1106,7 @@ static NTSTATUS winbindd_dual_pam_auth_cached(struct winbindd_domain *domain, time(NULL), time(NULL) + lp_winbind_cache_time(), time(NULL) + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME, - true); + true, 0); if (!NT_STATUS_IS_OK(result)) { DEBUG(10,("winbindd_dual_pam_auth_cached: failed " diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h index cf01337aaad..b3bd1b6deee 100644 --- a/source3/winbindd/winbindd_proto.h +++ b/source3/winbindd/winbindd_proto.h @@ -232,7 +232,8 @@ NTSTATUS add_ccache_to_list(const char *princ_name, time_t create_time, time_t ticket_end, time_t renew_until, - bool postponed_request); + bool postponed_request, + NTTIME last_password_change); NTSTATUS remove_ccache(const char *username); struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username); NTSTATUS winbindd_add_memory_creds(const char *username, -- 2.13.6