diff options
author | Jim McDonough <jmcd@samba.org> | 2004-03-18 19:22:51 +0000 |
---|---|---|
committer | Jim McDonough <jmcd@samba.org> | 2004-03-18 19:22:51 +0000 |
commit | 357998ddbdeb2fae0a30c578e747154fec22c180 (patch) | |
tree | e1d3c687163e603256b60dad9d56333bdc70e5c4 /source3 | |
parent | 5d40499b9f46aff4a70aeda0b8b3b147d2d69c2a (diff) | |
download | samba-357998ddbdeb2fae0a30c578e747154fec22c180.tar.gz samba-357998ddbdeb2fae0a30c578e747154fec22c180.tar.bz2 samba-357998ddbdeb2fae0a30c578e747154fec22c180.zip |
Password lockout for LDAP backend. Caches autolock flag, bad count, and
bad time locally, updating the directory only for hitting the policy limit
or resetting.
This needed to be done at the passdb level rather than auth, because some
of the functions need to be supported from tools such as pdbedit. It was
done at the LDAP backend level instead of generically after discussion,
because of the complexity of inserting it at a higher level.
The login cache read/write/delete is outside of the ldap backend, so it could
easily be called by other backends. tdbsam won't call it for obvious
reasons, and authors of other backends need to decide if they want to
implement it.
(This used to be commit 2a679cbc87a2a9111e9e6cdebbb62dec0ab3a0c0)
Diffstat (limited to 'source3')
-rw-r--r-- | source3/Makefile.in | 3 | ||||
-rw-r--r-- | source3/include/passdb.h | 9 | ||||
-rw-r--r-- | source3/include/smbldap.h | 1 | ||||
-rw-r--r-- | source3/lib/smbldap.c | 2 | ||||
-rw-r--r-- | source3/passdb/login_cache.c | 174 | ||||
-rw-r--r-- | source3/passdb/pdb_ldap.c | 121 |
6 files changed, 308 insertions, 2 deletions
diff --git a/source3/Makefile.in b/source3/Makefile.in index 358d1e144b..843e843a1e 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -290,7 +290,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 9eab46bbff..75c4fd215b 100644 --- a/source3/include/passdb.h +++ b/source3/include/passdb.h @@ -125,6 +125,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 2c76e84254..18979e2f76 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 } }; @@ -1394,3 +1395,4 @@ char *smbldap_get_dn(LDAP *ld, LDAPMessage *entry) ldap_memfree(utf8_dn); return unix_dn; } + 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; } |