Password lockout for LDAP backend. Caches autolock flag, bad count, and
authorJim McDonough <jmcd@samba.org>
Thu, 18 Mar 2004 19:22:51 +0000 (19:22 +0000)
committerJim McDonough <jmcd@samba.org>
Thu, 18 Mar 2004 19:22:51 +0000 (19:22 +0000)
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)

source3/Makefile.in
source3/include/passdb.h
source3/include/smbldap.h
source3/lib/smbldap.c
source3/passdb/login_cache.c [new file with mode: 0644]
source3/passdb/pdb_ldap.c

index 358d1e144be84780a79518d2ebe28aa99eafd751..843e843a1efca1a4bf6b1703b4605b6c01487341 100644 (file)
@@ -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
index 9eab46bbffc5390fddaa838296d391fba19e3daf..75c4fd215bffeab9745e86402b5300e2fdaa45de 100644 (file)
@@ -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
 {
index 2f71f971d92c2389c3285a4016c355f10189d9c4..68a2c00afe07ec11bdc8d2be0bb733eb05ae6597 100644 (file)
@@ -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;
index 2c76e842548921b85e2601b94406f4292115af08..18979e2f76fafd87f375cf3973d665efb8d06b85 100644 (file)
@@ -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 (file)
index 0000000..4b76017
--- /dev/null
@@ -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;
+}
+
index 0ebb63b3fbb819141a6be248c49f670247755d58..b7e28a9e9903fb811cfc536032d7b4d5494ce72c 100644 (file)
@@ -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;
 }