merge from 3.0...LDAP password lockout support
authorJim McDonough <jmcd@samba.org>
Thu, 18 Mar 2004 20:05:00 +0000 (20:05 +0000)
committerJim McDonough <jmcd@samba.org>
Thu, 18 Mar 2004 20:05:00 +0000 (20:05 +0000)
(This used to be commit b627cee3848d73e35181c9e6fdd9931452b28e48)

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 def96051b7ad631b48a315fea728660258f4f923..9b4c8d5c04ff0a3968feab819c7be62d26857737 100644 (file)
@@ -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
index 21feb7208f00c66400e3699a36a95e467dfca4fb..92e4bf3e8dde2baffd3cd246174a74d7b6c1d491 100644 (file)
@@ -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
 {
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 c15ba51306f107f9e1e22f101e894f8ddcc5c72c..20c2163cde6abc38e49669b16703c9745b11804e 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                    }
 };
 
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;
 }