From 2332bebd9fabc32dbc2756347338dbecc06e527b Mon Sep 17 00:00:00 2001 From: Jim McDonough Date: Thu, 18 Mar 2004 20:05:00 +0000 Subject: merge from 3.0...LDAP password lockout support (This used to be commit b627cee3848d73e35181c9e6fdd9931452b28e48) --- source3/Makefile.in | 3 +- source3/include/passdb.h | 9 +++ source3/include/smbldap.h | 1 + source3/lib/smbldap.c | 1 + source3/passdb/login_cache.c | 174 +++++++++++++++++++++++++++++++++++++++++++ source3/passdb/pdb_ldap.c | 121 +++++++++++++++++++++++++++++- 6 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 source3/passdb/login_cache.c diff --git a/source3/Makefile.in b/source3/Makefile.in index def96051b7..9b4c8d5c04 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -297,7 +297,8 @@ PASSDB_GET_SET_OBJ = passdb/pdb_get_set.o PASSDB_OBJ = $(PASSDB_GET_SET_OBJ) passdb/passdb.o passdb/pdb_interface.o \ passdb/util_sam_sid.o passdb/pdb_compat.o \ - passdb/privileges.o passdb/lookup_sid.o @PDB_STATIC@ passdb/pdb_sql.o + passdb/privileges.o passdb/lookup_sid.o \ + passdb/login_cache.o @PDB_STATIC@ passdb/pdb_sql.o XML_OBJ = passdb/pdb_xml.o MYSQL_OBJ = passdb/pdb_mysql.o diff --git a/source3/include/passdb.h b/source3/include/passdb.h index 21feb7208f..92e4bf3e8d 100644 --- a/source3/include/passdb.h +++ b/source3/include/passdb.h @@ -134,6 +134,15 @@ enum pdb_value_state { #define IS_SAM_SET(x, flag) (pdb_get_init_flags(x, flag) == PDB_SET) #define IS_SAM_CHANGED(x, flag) (pdb_get_init_flags(x, flag) == PDB_CHANGED) #define IS_SAM_DEFAULT(x, flag) (pdb_get_init_flags(x, flag) == PDB_DEFAULT) + +/* cache for bad password lockout data, to be used on replicated SAMs */ +typedef struct logon_cache_struct +{ + time_t entry_timestamp; + uint16 acct_ctrl; + uint16 bad_password_count; + time_t bad_password_time; +} LOGIN_CACHE; typedef struct sam_passwd { diff --git a/source3/include/smbldap.h b/source3/include/smbldap.h index 2f71f971d9..68a2c00afe 100644 --- a/source3/include/smbldap.h +++ b/source3/include/smbldap.h @@ -92,6 +92,7 @@ #define LDAP_ATTR_LOGON_COUNT 36 #define LDAP_ATTR_MUNGED_DIAL 37 #define LDAP_ATTR_BAD_PASSWORD_TIME 38 +#define LDAP_ATTR_MOD_TIMESTAMP 39 typedef struct _attrib_map_entry { int attrib; diff --git a/source3/lib/smbldap.c b/source3/lib/smbldap.c index c15ba51306..20c2163cde 100644 --- a/source3/lib/smbldap.c +++ b/source3/lib/smbldap.c @@ -100,6 +100,7 @@ ATTRIB_MAP_ENTRY attrib_map_v30[] = { { LDAP_ATTR_MUNGED_DIAL, "sambaMungedDial" }, { LDAP_ATTR_BAD_PASSWORD_COUNT, "sambaBadPasswordCount" }, { LDAP_ATTR_BAD_PASSWORD_TIME, "sambaBadPasswordTime" }, + { LDAP_ATTR_MOD_TIMESTAMP, "modifyTimestamp" }, { LDAP_ATTR_LIST_END, NULL } }; diff --git a/source3/passdb/login_cache.c b/source3/passdb/login_cache.c new file mode 100644 index 0000000000..4b760172ec --- /dev/null +++ b/source3/passdb/login_cache.c @@ -0,0 +1,174 @@ +/* + Unix SMB/CIFS implementation. + SAM_ACCOUNT local cache for + Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2004. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_PASSDB + +#define LOGIN_CACHE_FILE "login_cache.tdb" + +#define SAM_CACHE_FORMAT "dwwd" + +static TDB_CONTEXT *cache; + +BOOL login_cache_init(void) +{ + char* cache_fname = NULL; + + /* skip file open if it's already opened */ + if (cache) return True; + + asprintf(&cache_fname, "%s/%s", lp_lockdir(), LOGIN_CACHE_FILE); + if (cache_fname) + DEBUG(5, ("Opening cache file at %s\n", cache_fname)); + else { + DEBUG(0, ("Filename allocation failed.\n")); + return False; + } + + cache = tdb_open_log(cache_fname, 0, TDB_DEFAULT, + O_RDWR|O_CREAT, 0644); + + if (!cache) + DEBUG(5, ("Attempt to open %s failed.\n", cache_fname)); + + SAFE_FREE(cache_fname); + + return (cache ? True : False); +} + +BOOL login_cache_shutdown(void) +{ + /* tdb_close routine returns -1 on error */ + if (!cache) return False; + DEBUG(5, ("Closing cache file\n")); + return tdb_close(cache) != -1; +} + +/* if we can't read the cache, oh well, no need to return anything */ +LOGIN_CACHE * login_cache_read(SAM_ACCOUNT *sampass) +{ + TDB_DATA keybuf, databuf; + LOGIN_CACHE *entry; + + if (!login_cache_init()) + return NULL; + + keybuf.dptr = strdup(pdb_get_nt_username(sampass)); + if (!keybuf.dptr || !strlen(keybuf.dptr)) { + SAFE_FREE(keybuf.dptr); + return NULL; + } + keybuf.dsize = strlen(keybuf.dptr) + 1; + + DEBUG(7, ("Looking up login cache for user %s\n", + keybuf.dptr)); + databuf = tdb_fetch(cache, keybuf); + SAFE_FREE(keybuf.dptr); + + if (!(entry = malloc(sizeof(LOGIN_CACHE)))) { + DEBUG(1, ("Unable to allocate cache entry buffer!\n")); + SAFE_FREE(databuf.dptr); + return NULL; + } + + if (tdb_unpack (databuf.dptr, databuf.dsize, SAM_CACHE_FORMAT, + &entry->entry_timestamp, &entry->acct_ctrl, + &entry->bad_password_count, + &entry->bad_password_time) == -1) { + DEBUG(7, ("No cache entry found\n")); + SAFE_FREE(databuf.dptr); + return NULL; + } + + DEBUG(5, ("Found login cache entry: timestamp %12u, flags 0x%x, count %d, time %12u\n", + entry->entry_timestamp, entry->acct_ctrl, + entry->bad_password_count, entry->bad_password_time)); + return entry; +} + +BOOL login_cache_write(const SAM_ACCOUNT *sampass, LOGIN_CACHE entry) +{ + + TDB_DATA keybuf, databuf; + BOOL ret; + + + keybuf.dptr = strdup(pdb_get_nt_username(sampass)); + if (!keybuf.dptr || !strlen(keybuf.dptr)) { + SAFE_FREE(keybuf.dptr); + return False; + } + keybuf.dsize = strlen(keybuf.dptr) + 1; + + entry.entry_timestamp = time(NULL); + + databuf.dsize = + tdb_pack(NULL, 0, SAM_CACHE_FORMAT, + entry.entry_timestamp, + entry.acct_ctrl, + entry.bad_password_count, + entry.bad_password_time); + databuf.dptr = malloc(databuf.dsize); + if (!databuf.dptr) { + SAFE_FREE(keybuf.dptr); + return False; + } + + if (tdb_pack(databuf.dptr, databuf.dsize, SAM_CACHE_FORMAT, + entry.entry_timestamp, + entry.acct_ctrl, + entry.bad_password_count, + entry.bad_password_time) + != databuf.dsize) { + SAFE_FREE(keybuf.dptr); + SAFE_FREE(databuf.dptr); + return False; + } + + ret = tdb_store(cache, keybuf, databuf, 0); + SAFE_FREE(keybuf.dptr); + SAFE_FREE(databuf.dptr); + return ret == 0; +} + +BOOL login_cache_delentry(const SAM_ACCOUNT *sampass) +{ + int ret; + TDB_DATA keybuf; + + if (!login_cache_init()) + return False; + + keybuf.dptr = strdup(pdb_get_nt_username(sampass)); + if (!keybuf.dptr || !strlen(keybuf.dptr)) { + SAFE_FREE(keybuf.dptr); + return False; + } + keybuf.dsize = strlen(keybuf.dptr) + 1; + DEBUG(9, ("About to delete entry for %s\n", keybuf.dptr)); + ret = tdb_delete(cache, keybuf); + DEBUG(9, ("tdb_delete returned %d\n", ret)); + + SAFE_FREE(keybuf.dptr); + return ret == 0; +} + diff --git a/source3/passdb/pdb_ldap.c b/source3/passdb/pdb_ldap.c index 0ebb63b3fb..b7e28a9e99 100644 --- a/source3/passdb/pdb_ldap.c +++ b/source3/passdb/pdb_ldap.c @@ -391,6 +391,25 @@ static BOOL get_unix_attributes (struct ldapsam_privates *ldap_state, #endif +static time_t ldapsam_get_entry_timestamp( + struct ldapsam_privates *ldap_state, + LDAPMessage * entry) +{ + pstring temp; + struct tm tm; + + if (!smbldap_get_single_pstring( + ldap_state->smbldap_state->ldap_struct, entry, + get_userattr_key2string(ldap_state->schema_ver, + LDAP_ATTR_MOD_TIMESTAMP), + temp)) + return (time_t) 0; + + strptime(temp, "%Y%m%d%H%M%SZ", &tm); + tzset(); + return (mktime(&tm) - timezone); +} + /********************************************************************** Initialize SAM_ACCOUNT from an LDAP query. (Based on init_sam_from_buffer in pdb_tdb.c) @@ -405,7 +424,9 @@ static BOOL init_sam_from_ldap (struct ldapsam_privates *ldap_state, kickoff_time, pass_last_set_time, pass_can_change_time, - pass_must_change_time; + pass_must_change_time, + ldap_entry_time, + bad_password_time; pstring username, domain, nt_username, @@ -427,6 +448,7 @@ static BOOL init_sam_from_ldap (struct ldapsam_privates *ldap_state, uint32 hours_len; uint8 hours[MAX_HOURS_LEN]; pstring temp; + LOGIN_CACHE *cache_entry = NULL; /* * do a little initialization @@ -720,6 +742,15 @@ static BOOL init_sam_from_ldap (struct ldapsam_privates *ldap_state, pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET); } + if (!smbldap_get_single_pstring(ldap_state->smbldap_state->ldap_struct, entry, + get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_BAD_PASSWORD_TIME), temp)) { + /* leave as default */ + } else { + bad_password_time = (time_t) atol(temp); + pdb_set_bad_password_time(sampass, bad_password_time, PDB_SET); + } + + if (!smbldap_get_single_pstring(ldap_state->smbldap_state->ldap_struct, entry, get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LOGON_COUNT), temp)) { /* leave as default */ @@ -732,6 +763,43 @@ static BOOL init_sam_from_ldap (struct ldapsam_privates *ldap_state, pdb_set_hours(sampass, hours, PDB_SET); + /* check the timestamp of the cache vs ldap entry */ + if (!(ldap_entry_time = ldapsam_get_entry_timestamp(ldap_state, + entry))) + return True; + + /* see if we have newer updates */ + if (!(cache_entry = login_cache_read(sampass))) { + DEBUG (9, ("No cache entry, bad count = %d, bad time = %d\n", + pdb_get_bad_password_count(sampass), + pdb_get_bad_password_time(sampass))); + return True; + } + + DEBUG(7, ("ldap time is %d, cache time is %d, bad time = %d\n", + ldap_entry_time, cache_entry->entry_timestamp, + cache_entry->bad_password_time)); + + if (ldap_entry_time > cache_entry->entry_timestamp) { + /* cache is older than directory , so + we need to delete the entry but allow the + fields to be written out */ + login_cache_delentry(sampass); + } else { + /* read cache in */ + pdb_set_acct_ctrl(sampass, + pdb_get_acct_ctrl(sampass) | + (cache_entry->acct_ctrl & ACB_AUTOLOCK), + PDB_SET); + pdb_set_bad_password_count(sampass, + cache_entry->bad_password_count, + PDB_SET); + pdb_set_bad_password_time(sampass, + cache_entry->bad_password_time, + PDB_SET); + } + + SAFE_FREE(cache_entry); return True; } @@ -907,6 +975,7 @@ static BOOL init_ldap_from_sam (struct ldapsam_privates *ldap_state, smbldap_make_mod(ldap_state->smbldap_state->ldap_struct, existing, mods, get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_MUST_CHANGE), temp); + if ((pdb_get_acct_ctrl(sampass)&(ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST)) || (lp_ldap_passwd_sync()!=LDAP_PASSWD_SYNC_ONLY)) { @@ -954,6 +1023,56 @@ static BOOL init_ldap_from_sam (struct ldapsam_privates *ldap_state, get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_ACB_INFO), pdb_encode_acct_ctrl (pdb_get_acct_ctrl(sampass), NEW_PW_FORMAT_SPACE_PADDED_LEN)); + /* password lockout cache: + - If we are now autolocking or clearing, we write to ldap + - If we are clearing, we delete the cache entry + - If the count is > 0, we update the cache + + This even means when autolocking, we cache, just in case the + update doesn't work, and we have to cache the autolock flag */ + + if (need_update(sampass, PDB_BAD_PASSWORD_COUNT)) /* && + need_update(sampass, PDB_BAD_PASSWORD_TIME)) */ { + uint16 badcount = pdb_get_bad_password_count(sampass); + time_t badtime = pdb_get_bad_password_time(sampass); + uint32 pol; + account_policy_get(AP_BAD_ATTEMPT_LOCKOUT, &pol); + + DEBUG(3, ("updating bad password fields, policy=%d, count=%d, time=%d\n", pol, badcount, badtime)); + + if ((badcount >= pol) || (badcount == 0)) { + DEBUG(7, ("making mods to update ldap, count=%d, time=%d\n", badcount, badtime)); + slprintf (temp, sizeof (temp) - 1, "%li", badcount); + smbldap_make_mod( + ldap_state->smbldap_state->ldap_struct, + existing, mods, + get_userattr_key2string( + ldap_state->schema_ver, + LDAP_ATTR_BAD_PASSWORD_COUNT), + temp); + + slprintf (temp, sizeof (temp) - 1, "%li", badtime); + smbldap_make_mod( + ldap_state->smbldap_state->ldap_struct, + existing, mods, + get_userattr_key2string( + ldap_state->schema_ver, + LDAP_ATTR_BAD_PASSWORD_TIME), + temp); + } + if (badcount == 0) { + DEBUG(7, ("bad password count is reset, deleting login cache entry for %s\n", pdb_get_nt_username(sampass))); + login_cache_delentry(sampass); + } else { + LOGIN_CACHE cache_entry ={time(NULL), + pdb_get_acct_ctrl(sampass), + badcount, badtime}; + DEBUG(7, ("Updating bad password count and time in login cache\n")); + login_cache_write(sampass, cache_entry); + } + + } + return True; } -- cgit