Added LDAP id to structure
authorzev <zev@radensolutions.com>
Thu, 26 May 2016 08:51:44 +0000 (11:51 +0300)
committerzev <zev@radensolutions.com>
Thu, 26 May 2016 11:28:05 +0000 (14:28 +0300)
include/netxmsdb.h
include/nxevent.h
sql/schema.in
sql/setup.in
src/server/core/ldap.cpp
src/server/core/userdb.cpp
src/server/core/userdb_objects.cpp
src/server/include/nms_users.h
src/server/tools/nxdbmgr/upgrade.cpp

index 9431261..1b2086f 100644 (file)
@@ -23,6 +23,6 @@
 #ifndef _netxmsdb_h
 #define _netxmsdb_h
 
-#define DB_FORMAT_VERSION   400
+#define DB_FORMAT_VERSION   401
 
 #endif
index 0ae4340..72d8f0e 100644 (file)
 #define EVENT_IF_IPADDR_DELETED           77
 #define EVENT_MAINTENANCE_MODE_ENTERED    78
 #define EVENT_MAINTENANCE_MODE_LEFT       79
+#define EVENT_LDAP_SYCN_ERROR             80
 
 #define EVENT_SNMP_UNMATCHED_TRAP         500
 #define EVENT_SNMP_COLD_START             501
index c2ad29b..6607a96 100644 (file)
@@ -90,6 +90,7 @@ CREATE TABLE users
   password_history SQL_TEXT null,
   xmpp_id varchar(127) null,
   ldap_dn SQL_TEXT null,
+  ldap_unique_id varchar(64) null,
   PRIMARY KEY(id)
 ) TABLE_TYPE;
 
@@ -128,6 +129,7 @@ CREATE TABLE user_groups
   flags integer not null,
   description varchar(255),
   ldap_dn SQL_TEXT null,
+  ldap_unique_id varchar(63) null,
   PRIMARY KEY(id)
 ) TABLE_TYPE;
 
index d9e4de0..787ed69 100644 (file)
@@ -91,6 +91,7 @@ INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart,data_type,description) VALUES ('JobRetryCount','5',1,0,'I','Maximum mumber of job execution retrys');
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('KeepAliveInterval','60',1,1);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapGroupClass','',1,0);
+INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapGroupUniqueId','',1,0);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapConnectionString','ldap://localhost:389',1,0);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapMappingDescription','',1,0);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapMappingFullName','displayName',1,0);
@@ -103,6 +104,7 @@ INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapSyncUserPassword','',1,0);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapUserClass','displayName',1,0);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapUserDeleteAction','1',1,0);
+INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LdapUserUniqueId','',1,0);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LockTimeout','60000',1,1);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('LogAllSNMPTraps','0',1,1);
 INSERT INTO config (var_name,var_value,is_visible,need_server_restart) VALUES ('MailEncoding','iso-8859-1',1,0);
index 7aa8ac9..cfb256b 100644 (file)
@@ -50,6 +50,7 @@ Entry::Entry()
    m_loginName = NULL;
    m_fullName = NULL;
    m_description = NULL;
+   m_id = NULL;
    m_memberList = new StringSet();
 }
 
@@ -61,6 +62,7 @@ Entry::~Entry()
    free(m_loginName);
    free(m_fullName);
    free(m_description);
+   free(m_id);
    delete m_memberList;
 }
 
@@ -144,6 +146,14 @@ int ldap_parse_page_control(LDAP *ldap, LDAPControl **controls,
 #endif /* HAVE_LDAP_PARSE_PAGE_CONTROL */
 
 /**
+ * Lists
+ */
+StringObjectMap<Entry> *userIdEntryList;
+StringObjectMap<Entry> *userDnEntryList;
+StringObjectMap<Entry> *groupIdEntryList;
+StringObjectMap<Entry> *groupDnEntryList;
+
+/**
  * Correctly formats ldap connection string (according to OS)
  */
 void LDAPConnection::prepareStringForInit(LDAP_CHAR *connectionLine)
@@ -276,6 +286,8 @@ void LDAPConnection::getAllSyncParameters()
    ConfigReadStrUTF8(_T("LdapMappingName"), m_ldapLoginNameAttr, MAX_CONFIG_VALUE, "");
    ConfigReadStrUTF8(_T("LdapMappingFullName"), m_ldapFullNameAttr, MAX_CONFIG_VALUE, "");
    ConfigReadStrUTF8(_T("LdapMappingDescription"), m_ldapDescriptionAttr, MAX_CONFIG_VALUE, "");
+   ConfigReadStrUTF8(_T("LdapUserUniqueId"), m_ldapUsreIdAttr, MAX_CONFIG_VALUE, "");
+   ConfigReadStrUTF8(_T("LdapGroupUniqueId"), m_ldapGroupIdAttr, MAX_CONFIG_VALUE, "");
    ConfigReadStr(_T("LdapGroupClass"), m_groupClass, MAX_CONFIG_VALUE, _T(""));
    ConfigReadStr(_T("LdapUserClass"), m_userClass, MAX_CONFIG_VALUE, _T(""));
    m_action = ConfigReadInt(_T("LdapUserDeleteAction"), 1); //default value - to disable user(value=1)
@@ -299,8 +311,10 @@ void LDAPConnection::syncUsers()
 
    struct ldap_timeval timeOut = { 10, 0 }; // 10 second connecion/search timeout
    LDAPMessage *searchResult;
-   StringObjectMap<Entry> *userEntryList = new StringObjectMap<Entry>(true); //as unique string ID is used dn
-   StringObjectMap<Entry> *groupEntryList= new StringObjectMap<Entry>(true); //as unique string ID is used dn
+   userDnEntryList = new StringObjectMap<Entry>(true); //as unique string ID is used dn
+   userIdEntryList = new StringObjectMap<Entry>(false); //as unique string ID is used id
+   groupDnEntryList= new StringObjectMap<Entry>(true); //as unique string ID is used dn
+   groupIdEntryList= new StringObjectMap<Entry>(false); //as unique string ID is used id
 
    //Parse search base string. As separater is used ';' if this symbol is not escaped
    LDAP_CHAR *tmp  = ldap_strdup(m_searchBase);
@@ -356,7 +370,7 @@ void LDAPConnection::syncUsers()
       {
          if (rc == LDAP_SIZELIMIT_EXCEEDED)
          {
-            rc = readInPages(userEntryList, groupEntryList, base);
+            rc = readInPages(base);
          }
          else
          {
@@ -367,7 +381,7 @@ void LDAPConnection::syncUsers()
       }
       else
       {
-         fillLists(searchResult, userEntryList, groupEntryList);
+         fillLists(searchResult);
       }
 
       ldap_msgfree(searchResult);
@@ -387,18 +401,20 @@ void LDAPConnection::syncUsers()
    if(rc == LDAP_SUCCESS)
    {
       //compare new LDAP list with old users
-      compareUserLists(userEntryList);
-      compareGroupList(groupEntryList);
+      compareUserLists();
+      compareGroupList();
    }
 
-   delete userEntryList;
-   delete groupEntryList;
+   delete userDnEntryList;
+   delete userIdEntryList;
+   delete groupDnEntryList;
+   delete groupIdEntryList;
 }
 
 /**
  * Reads and process each page
  */
-int LDAPConnection::readInPages(StringObjectMap<Entry> *userEntryList, StringObjectMap<Entry> *groupEntryList, LDAP_CHAR *base)
+int LDAPConnection::readInPages(LDAP_CHAR *base)
 {
    DbgPrintf(7, _T("LDAPConnection::readInPages(): Getting LDAP results as a pages."));
    LDAPControl *pageControl=NULL, *controls[2] = { NULL, NULL };
@@ -432,7 +448,7 @@ int LDAPConnection::readInPages(StringObjectMap<Entry> *userEntryList, StringObj
       /* Insert the control into a list to be passed to the search. */
       controls[0] = pageControl;
 
-      DbgPrintf(6, _T("LDAPConnection::syncUsers(): Search Base DN: %s"), base);
+      DbgPrintf(6, _T("LDAPConnection::syncUsers(): Search Base DN: ") LDAP_TFMT, base);
       /* Search for entries in the directory using the parmeters. */
       rc = ldap_search_ext_s(
                m_ldapConn,             // LDAP session handle
@@ -456,7 +472,7 @@ int LDAPConnection::readInPages(StringObjectMap<Entry> *userEntryList, StringObj
 
       /* Parse the results to retrieve the contols being returned. */
       rc = ldap_parse_result(m_ldapConn, searchResult, NULL, NULL, NULL, NULL, &returnedControls, FALSE);
-      fillLists(searchResult, userEntryList, groupEntryList);
+      fillLists(searchResult);
       ldap_msgfree(searchResult);
 
       /* Clear cookie */
@@ -504,7 +520,7 @@ TCHAR *LDAPConnection::ldap_internal_get_dn(LDAP *conn, LDAPMessage *entry)
 /**
  * Fills lists of users and groups from search results
  */
-void LDAPConnection::fillLists(LDAPMessage *searchResult, StringObjectMap<Entry> *userEntryList, StringObjectMap<Entry> *groupEntryList)
+void LDAPConnection::fillLists(LDAPMessage *searchResult)
 {
    LDAPMessage *entry;
    char *attribute;
@@ -557,6 +573,14 @@ void LDAPConnection::fillLists(LDAPMessage *searchResult, StringObjectMap<Entry>
          {
             newObj->m_description = getAttrValue(entry, attribute);
          }
+         if (m_ldapUsreIdAttr[0] != 0 && !strcmp(attribute, m_ldapUsreIdAttr) && newObj->m_type == LDAP_USER)
+         {
+            //newObj->m_id = getIdAttrValue(entry, attribute);
+         }
+         if (m_ldapGroupIdAttr[0] != 0 && !strcmp(attribute, m_ldapGroupIdAttr) && newObj->m_type == LDAP_GROUP)
+         {
+            newObj->m_id = getIdAttrValue(entry, attribute);
+         }
          if (!strcmp(attribute, "member"))
          {
             i = 0;
@@ -589,12 +613,16 @@ void LDAPConnection::fillLists(LDAPMessage *searchResult, StringObjectMap<Entry>
       if (newObj->m_type == LDAP_USER && newObj->m_loginName != NULL)
       {
          DbgPrintf(4, _T("LDAPConnection::fillLists(): User added: dn: %s, login name: %s, full name: %s, description: %s"), dn, newObj->m_loginName, CHECK_NULL(newObj->m_fullName), CHECK_NULL(newObj->m_description));
-         userEntryList->set(dn, newObj);
+         userDnEntryList->set(dn, newObj);
+         if(m_ldapUsreIdAttr[0] != 0 && newObj->m_id != NULL)
+            userIdEntryList->set(newObj->m_id, newObj);
       }
       else if (newObj->m_type == LDAP_GROUP && newObj->m_loginName != NULL)
       {
          DbgPrintf(4, _T("LDAPConnection::fillLists(): Group added: dn: %s, login name: %s, full name: %s, description: %s"), dn, newObj->m_loginName, CHECK_NULL(newObj->m_fullName), CHECK_NULL(newObj->m_description));
-         groupEntryList->set(dn, newObj);
+         groupDnEntryList->set(dn, newObj);
+         if(m_ldapGroupIdAttr[0] != 0 && newObj->m_id != NULL)
+            groupIdEntryList->set(newObj->m_id, newObj);
       }
       else
       {
@@ -625,6 +653,33 @@ TCHAR *LDAPConnection::getAttrValue(LDAPMessage *entry, const char *attr, UINT32
 }
 
 /**
+ * Get attribute's value
+ */
+TCHAR *LDAPConnection::getIdAttrValue(LDAPMessage *entry, const char *attr)
+{
+   BYTE hash[SHA256_DIGEST_SIZE];
+   BYTE tmp[1024];
+   memset(tmp, 0, 1024);
+   berval **values = ldap_get_values_lenA(m_ldapConn, entry, (char *)attr);   // cast needed for Windows LDAP library
+   int i,pos;
+   for(i = 0, pos = 0; i < ldap_count_values_len(values); i++)
+   {
+      if(pos+values[i]->bv_len > 1024)
+         break;
+      memcpy(tmp+pos,values[i]->bv_val,values[i]->bv_len);
+      pos += values[i]->bv_len;
+   }
+   ldap_value_free_len(values);
+   if(i == 0)
+      return _tcsdup(_T(""));
+
+   CalculateSHA256Hash(tmp, pos, hash);
+   TCHAR *result = (TCHAR *)malloc(SHA256_DIGEST_SIZE * 2 + 1);
+   BinToStr(hash, SHA256_DIGEST_SIZE, result);
+   return result;
+}
+
+/**
  * Parse range information from attribute
  */
 static void ParseRange(const char *attr, int *start, int *end)
@@ -765,10 +820,10 @@ static EnumerationCallbackResult UpdateUserCallback(const TCHAR *key, const void
 /**
  * Updates user list according to newly recievd user list
  */
-void LDAPConnection::compareUserLists(StringObjectMap<Entry> *userEntryList)
+void LDAPConnection::compareUserLists()
 {
-   userEntryList->forEach(UpdateUserCallback, NULL);
-   RemoveDeletedLDAPEntries(userEntryList, m_action, true);
+   userDnEntryList->forEach(UpdateUserCallback, NULL);
+   RemoveDeletedLDAPEntries(userDnEntryList, userIdEntryList, m_action, true);
 }
 
 /**
@@ -783,10 +838,10 @@ static EnumerationCallbackResult UpdateGroupCallback(const TCHAR *key, const voi
 /**
  * Updates group list according to newly recievd user list
  */
-void LDAPConnection::compareGroupList(StringObjectMap<Entry> *groupEntryList)
+void LDAPConnection::compareGroupList()
 {
-   groupEntryList->forEach(UpdateGroupCallback, NULL);
-   RemoveDeletedLDAPEntries(groupEntryList, m_action, false);
+   groupDnEntryList->forEach(UpdateGroupCallback, NULL);
+   RemoveDeletedLDAPEntries(groupDnEntryList, groupIdEntryList, m_action, false);
 }
 
 /**
index b58a1cd..2cb62aa 100644 (file)
@@ -48,6 +48,8 @@ bool RadiusAuth(const TCHAR *pszLogin, const TCHAR *pszPasswd);
  */
 static HashMap<UINT32, UserDatabaseObject> s_userDatabase(true);
 static StringObjectMap<UserDatabaseObject> s_ldapNames(false);
+static StringObjectMap<User> s_ldapUserId(false);
+static StringObjectMap<Group> s_ldapGroupId(false);
 static StringObjectMap<User> s_users(false);
 static StringObjectMap<Group> s_groups(false);
 static RWLOCK s_userDatabaseLock = RWLockCreate();
@@ -72,7 +74,16 @@ inline void AddDatabaseObject(UserDatabaseObject *object)
    else
       s_users.set(object->getName(), (User *)object);
    if (object->isLDAPUser())
+   {
       s_ldapNames.set(object->getDn(), object);
+      if(object->getLdapId() != NULL)
+      {
+         if (object->isGroup())
+            s_ldapGroupId.set(object->getLdapId(), (Group *)object);
+         else
+            s_ldapUserId.set(object->getLdapId(), (User *)object);
+      }
+   }
 }
 
 /**
@@ -85,7 +96,16 @@ inline void RemoveDatabaseObject(UserDatabaseObject *object)
    else
       s_users.remove(object->getName());
    if (object->isLDAPUser())
+   {
       s_ldapNames.remove(object->getDn());
+      if(object->getLdapId() != NULL)
+      {
+         if (object->isGroup())
+            s_ldapGroupId.remove(object->getLdapId());
+         else
+            s_ldapUserId.remove(object->getLdapId());
+      }
+   }
 }
 
 /**
@@ -182,7 +202,7 @@ BOOL LoadUsers()
    // Load users
    hResult = DBSelect(hdb,
                           _T("SELECT id,name,system_access,flags,description,guid,ldap_dn,")
-                                                        _T("password,full_name,grace_logins,auth_method,")
+                                                        _T("ldap_unique_id,password,full_name,grace_logins,auth_method,")
                                                         _T("cert_mapping_method,cert_mapping_data,auth_failures,")
                                                         _T("last_passwd_change,min_passwd_length,disabled_until,")
                                                         _T("last_login,xmpp_id FROM users"));
@@ -210,7 +230,7 @@ BOOL LoadUsers()
    }
 
    // Load groups
-   hResult = DBSelect(hdb, _T("SELECT id,name,system_access,flags,description,guid,ldap_dn FROM user_groups"));
+   hResult = DBSelect(hdb, _T("SELECT id,name,system_access,flags,description,guid,ldap_dn,ldap_unique_id FROM user_groups"));
    if (hResult == NULL)
    {
       DBConnectionPoolReleaseConnection(hdb);
@@ -568,6 +588,16 @@ inline bool GroupNameIsUnique(const TCHAR *name, Group *group)
 }
 
 /**
+ * Generates unique name for LDAP user
+ */
+static TCHAR *GenerateUniqueName(const TCHAR *oldName, UINT32 id)
+{
+   TCHAR *name = (TCHAR *)malloc(sizeof(TCHAR) * 256);
+   _sntprintf(name, 256, _T("%s_LDAP%d"), oldName, id);
+   return name;
+}
+
+/**
  * Update/Add LDAP user
  */
 void UpdateLDAPUser(const TCHAR *dn, Entry *obj)
@@ -576,12 +606,22 @@ void UpdateLDAPUser(const TCHAR *dn, Entry *obj)
 
    bool userModified = false;
    bool conflict = false;
+   TCHAR description[1024];
+   TCHAR guid[64];
 
    // Check existing user with same DN
-   UserDatabaseObject *object = s_ldapNames.get(dn);
+   UserDatabaseObject *object = NULL;
+   if(obj->m_id != NULL)
+      object = s_ldapUserId.get(obj->m_id);
+   else
+      object = s_ldapNames.get(dn);
+
    if ((object != NULL) && object->isGroup())
    {
-      DbgPrintf(4, _T("UpdateLDAPUser(): got user with DN=%s but found existing group %s with same DN"), dn, object->getName());
+      _sntprintf(description, MAX_USER_DESCR, _T("Got user with DN=%s but found existing group %s with same DN"), dn, object->getName());
+      object->getGuidAsText(guid);
+      PostEvent(EVENT_LDAP_SYCN_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), description);
+      DbgPrintf(4,  _T("UpdateLDAPUser(): %s"), description);
       conflict = true;
    }
 
@@ -593,18 +633,31 @@ void UpdateLDAPUser(const TCHAR *dn, Entry *obj)
          user->removeSyncException();
          if (!UserNameIsUnique(obj->m_loginName, user))
          {
-            user->setSyncException();
-            TCHAR conflictDescription[MAX_USER_DESCR];
-            _sntprintf(conflictDescription, MAX_USER_DESCR, _T("UpdateLDAPUser(): LDAP sync error. User with name \"%s\" already exists."), obj->m_loginName);
-            user->setDescription(conflictDescription);
-            DbgPrintf(4, conflictDescription);
+            TCHAR *userName = GenerateUniqueName(obj->m_loginName, user->getId());
+            if(_tcscmp(user->getName(), userName))
+            {
+               user->setSyncException();
+               TCHAR conflictDescription[MAX_USER_DESCR];
+               _sntprintf(conflictDescription, MAX_USER_DESCR, _T("User with name \"%s\" already exists."), obj->m_loginName);
+               user->setDescription(conflictDescription);
+               object->getGuidAsText(guid);
+               PostEvent(EVENT_LDAP_SYCN_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), conflictDescription);
+               DbgPrintf(4,  _T("UpdateLDAPUser(): %s"), conflictDescription);
+            }
+            free(userName);
          }
          else
          {
             user->setName(obj->m_loginName);
             user->setFullName(obj->m_fullName);
             user->setDescription(obj->m_description);
-            DbgPrintf(4, _T("UpdateLDAPUser(): User updated: DN: %s, login name: %s, full name: %s, description: %s"), dn, obj->m_loginName, CHECK_NULL(obj->m_fullName), CHECK_NULL(obj->m_description));
+            if(_tcscmp(user->getDn(), dn))
+            {
+               s_ldapNames.remove(user->getDn());
+               user->setDn(dn);
+               s_ldapNames.set(dn, user);
+            }
+            DbgPrintf(4, _T("UpdateLDAPUser(): User updated: ID: %s DN: %s, login name: %s, full name: %s, description: %s"), CHECK_NULL(obj->m_id), dn, obj->m_loginName, CHECK_NULL(obj->m_fullName), CHECK_NULL(obj->m_description));
          }
          if (user->isModified())
          {
@@ -623,13 +676,31 @@ void UpdateLDAPUser(const TCHAR *dn, Entry *obj)
          user->setDescription(obj->m_description);
          user->setFlags(UF_MODIFIED | UF_LDAP_USER);
          user->setDn(dn);
+         if(obj->m_id != NULL)
+            user->setLdapId(obj->m_id);
          AddDatabaseObject(user);
          SendUserDBUpdate(USER_DB_CREATE, user->getId(), user);
-         DbgPrintf(4, _T("UpdateLDAPUser(): User added: DN: %s, login name: %s, full name: %s, description: %s"), dn, obj->m_loginName, CHECK_NULL(obj->m_fullName), CHECK_NULL(obj->m_description));
+         DbgPrintf(4, _T("UpdateLDAPUser(): User added: ID: %s DN: %s, login name: %s, full name: %s, description: %s"), CHECK_NULL(obj->m_id), dn, obj->m_loginName, CHECK_NULL(obj->m_fullName), CHECK_NULL(obj->m_description));
       }
       else
       {
-         DbgPrintf(4, _T("UpdateLDAPUser(): User with name \"%s\" already exists, but is not an LDAP user. LDAP user won't be created."), obj->m_loginName);
+         UINT32 userId = CreateUniqueId(IDG_USER);
+         TCHAR *userName = GenerateUniqueName(obj->m_loginName, userId);
+         _sntprintf(description, MAX_USER_DESCR, _T("User with name \"%s\" already exists. Unique user name have been generated: %s"), obj->m_loginName, userName);
+         DbgPrintf(4,  _T("UpdateLDAPUser(): %s"), description);
+         User *user = new User(userId, userName);
+         user->setFullName(obj->m_fullName);
+         user->setDescription(obj->m_description);
+         user->setFlags(UF_MODIFIED | UF_LDAP_USER);
+         user->setDn(dn);
+         if(obj->m_id != NULL)
+            user->setLdapId(obj->m_id);
+         AddDatabaseObject(user);
+         SendUserDBUpdate(USER_DB_CREATE, user->getId(), user);
+         user->getGuidAsText(guid);
+         PostEvent(EVENT_LDAP_SYCN_ERROR ,g_dwMgmtNode, "issss", user->getId(), guid, user->getDn(), user->getName(), description);
+         DbgPrintf(4, _T("UpdateLDAPUser(): User added: ID: %s DN: %s, login name: %s, full name: %s, description: %s"), CHECK_NULL(obj->m_id), dn, userName, CHECK_NULL(obj->m_fullName), CHECK_NULL(obj->m_description));
+         free(userName);
       }
    }
    RWLockUnlock(s_userDatabaseLock);
@@ -639,7 +710,7 @@ void UpdateLDAPUser(const TCHAR *dn, Entry *obj)
  * Goes through all existing LDAP entries and check that in newly gotten list they also exist.
  * If LDAP entries does not exists in new list - it will be disabled or removed depending on action parameter.
  */
-void RemoveDeletedLDAPEntries(StringObjectMap<Entry> *entryList, UINT32 m_action, bool isUser)
+void RemoveDeletedLDAPEntries(StringObjectMap<Entry> *entryListDn, StringObjectMap<Entry> *entryListId, UINT32 m_action, bool isUser)
 {
    RWLockWriteLock(s_userDatabaseLock, INFINITE);
    Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
@@ -651,7 +722,7 @@ void RemoveDeletedLDAPEntries(StringObjectMap<Entry> *entryList, UINT32 m_action
 
       if (isUser ? ((object->getId() & GROUP_FLAG) == 0) : ((object->getId() & GROUP_FLAG) != 0))
                {
-         if (!entryList->contains(object->getDn()))
+         if ((object->getLdapId() == NULL || !entryListId->contains(object->getLdapId())) && !entryListDn->contains(object->getDn()))
          {
             if (m_action == USER_DELETE)
             {
@@ -730,12 +801,22 @@ void UpdateLDAPGroup(const TCHAR *dn, Entry *obj) //no full name, add users insi
 
    bool userModified = false;
    bool conflict = false;
+   TCHAR description[1024];
+   TCHAR guid[64];
 
    // Check existing user with same DN
-   UserDatabaseObject *object = s_ldapNames.get(dn);
+   UserDatabaseObject *object = NULL;
+
+   if(obj->m_id != NULL)
+      object = s_ldapGroupId.get(obj->m_id);
+   else
+      object = s_ldapNames.get(dn);
    if ((object != NULL) && !object->isGroup())
    {
-      DbgPrintf(4, _T("UpdateLDAPUser(): got group with DN=%s but found existing user %s with same DN"), dn, object->getName());
+      _sntprintf(description, MAX_USER_DESCR, _T("Got group with DN=%s but found existing group %s with same DN"), dn, object->getName());
+      object->getGuidAsText(guid);
+      PostEvent(EVENT_LDAP_SYCN_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), description);
+      DbgPrintf(4,  _T("UpdateLDAPGroup(): %s"), description);
       conflict = true;
    }
 
@@ -747,17 +828,30 @@ void UpdateLDAPGroup(const TCHAR *dn, Entry *obj) //no full name, add users insi
          group->removeSyncException();
          if (!GroupNameIsUnique(obj->m_loginName, group))
          {
-            group->setSyncException();
-            TCHAR conflictDescription[MAX_USER_DESCR];
-            _sntprintf(conflictDescription, MAX_USER_DESCR, _T("UpdateLDAPGroup(): LDAP sync error. Group with name \"%s\" already exists."), obj->m_loginName);
-            group->setDescription(conflictDescription);
-            DbgPrintf(4, conflictDescription);
+            TCHAR *groupName = GenerateUniqueName(obj->m_loginName, group->getId());
+            if(_tcscmp(group->getName(), groupName))
+            {
+               group->setSyncException();
+               TCHAR conflictDescription[MAX_USER_DESCR];
+               _sntprintf(conflictDescription, MAX_USER_DESCR, _T("Group with name \"%s\" already exists."), obj->m_loginName);
+               group->setDescription(conflictDescription);
+               object->getGuidAsText(guid);
+               PostEvent(EVENT_LDAP_SYCN_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), conflictDescription);
+               DbgPrintf(4,  _T("UpdateLDAPGroup(): %s"),conflictDescription);
+            }
+            free(groupName);
          }
          else
          {
             group->setName(obj->m_loginName);
             group->setDescription(obj->m_description);
-            DbgPrintf(4, _T("UpdateLDAPGroup(): Group updated: DN: %s, login name: %s, description: %s"), dn, obj->m_loginName, CHECK_NULL(obj->m_description));
+            if(_tcscmp(group->getDn(), dn))
+            {
+               s_ldapNames.remove(group->getDn());
+               group->setDn(dn);
+               s_ldapNames.set(dn, group);
+            }
+            DbgPrintf(4, _T("UpdateLDAPGroup(): Group updated: ID: %s DN: %s, login name: %s, description: %s"), CHECK_NULL(obj->m_id), dn, obj->m_loginName, CHECK_NULL(obj->m_description));
          }
          if (group->isModified())
          {
@@ -776,14 +870,32 @@ void UpdateLDAPGroup(const TCHAR *dn, Entry *obj) //no full name, add users insi
          group->setDescription(obj->m_description);
          group->setFlags(UF_MODIFIED | UF_LDAP_USER);
          group->setDn(dn);
+         if(obj->m_id != NULL)
+            group->setLdapId(obj->m_id);
          SendUserDBUpdate(USER_DB_CREATE, group->getId(), group);
          AddDatabaseObject(group);
          SyncGroupMembers(group , obj);
-         DbgPrintf(4, _T("UpdateLDAPGroup(): Group added: DN: %s, login name: %s, description: %s"), dn, obj->m_loginName, CHECK_NULL(obj->m_description));
+         DbgPrintf(4, _T("UpdateLDAPGroup(): Group added: ID: %s DN: %s, login name: %s, description: %s"), CHECK_NULL(obj->m_id), dn, obj->m_loginName, CHECK_NULL(obj->m_description));
       }
       else
       {
-         DbgPrintf(4, _T("UpdateLDAPGroup(): Group with name \"%s\" already exists, but is not an LDAP group. LDAP group won't be created."), obj->m_loginName);
+         UINT32 id = CreateUniqueId(IDG_USER_GROUP);
+         TCHAR *groupName = GenerateUniqueName(obj->m_loginName, id);
+         _sntprintf(description, MAX_USER_DESCR, _T("Group with name \"%s\" already exists. Unique group name have been generated: %s"), obj->m_loginName, groupName);
+         DbgPrintf(4,  _T("UpdateLDAPGroup(): %s"),description);
+         Group *group = new Group(id, groupName);
+         group->setDescription(obj->m_description);
+         group->setFlags(UF_MODIFIED | UF_LDAP_USER);
+         group->setDn(dn);
+         if(obj->m_id != NULL)
+            group->setLdapId(obj->m_id);
+         SendUserDBUpdate(USER_DB_CREATE, group->getId(), group);
+         AddDatabaseObject(group);
+         SyncGroupMembers(group , obj);
+         group->getGuidAsText(guid);
+         PostEvent(EVENT_LDAP_SYCN_ERROR ,g_dwMgmtNode, "issss", group->getId(), guid, group->getDn(), group->getName(), description);
+         DbgPrintf(4, _T("UpdateLDAPGroup(): Group added: ID: %s DN: %s, login name: %s, description: %s"), CHECK_NULL(obj->m_id), dn, obj->m_loginName, CHECK_NULL(obj->m_description));
+         free(groupName);
       }
    }
    RWLockUnlock(s_userDatabaseLock);
index a94ff07..c93e072 100644 (file)
@@ -85,6 +85,7 @@ UserDatabaseObject::UserDatabaseObject(DB_HANDLE hdb, DB_RESULT hResult, int row
        DBGetField(hResult, row, 4, m_description, MAX_USER_DESCR);
        m_guid = DBGetFieldGUID(hResult, row, 5);
        m_userDn = DBGetField(hResult, row, 6, NULL, 0);
+       m_ldapId = DBGetField(hResult, row, 7, NULL, 0);
 }
 
 /**
@@ -96,6 +97,7 @@ UserDatabaseObject::UserDatabaseObject()
    m_guid = uuid::generate();
    m_name[0] = 0;
    m_userDn = NULL;
+   m_ldapId = NULL;
        m_systemRights = 0;
        m_description[0] = 0;
        m_flags = 0;
@@ -113,6 +115,7 @@ UserDatabaseObject::UserDatabaseObject(UINT32 id, const TCHAR *name)
        m_description[0] = 0;
        m_flags = UF_MODIFIED;
        m_userDn = NULL;
+       m_ldapId= NULL;
 }
 
 /**
@@ -121,6 +124,7 @@ UserDatabaseObject::UserDatabaseObject(UINT32 id, const TCHAR *name)
 UserDatabaseObject::~UserDatabaseObject()
 {
    safe_free(m_userDn);
+   safe_free(m_ldapId);
 }
 
 /**
@@ -305,11 +309,24 @@ void UserDatabaseObject::setName(const TCHAR *name)
 
 void UserDatabaseObject::setDn(const TCHAR *dn)
 {
+   if(dn == NULL)
+      return;
+   if(m_userDn != NULL && !_tcscmp(m_userDn, dn))
+      return;
    free(m_userDn);
    m_userDn = _tcsdup_ex(dn);
    m_flags |= UF_MODIFIED;
 }
 
+void UserDatabaseObject::setLdapId(const TCHAR *id)
+{
+   if(m_ldapId != NULL && !_tcscmp(m_ldapId, id))
+      return;
+   free(m_ldapId);
+   m_ldapId = _tcsdup_ex(id);
+   m_flags |= UF_MODIFIED;
+}
+
 /**
  * Disable user account because of sync exception
  */
@@ -364,7 +381,7 @@ User::User(DB_HANDLE hdb, DB_RESULT hResult, int row) : UserDatabaseObject(hdb,
        TCHAR buffer[256];
 
    bool validHash = false;
-   DBGetField(hResult, row, 7, buffer, 256);
+   DBGetField(hResult, row, 8, buffer, 256);
    if (buffer[0] == _T('$'))
    {
       // new format - with hash type indicator
@@ -393,17 +410,17 @@ User::User(DB_HANDLE hdb, DB_RESULT hResult, int row) : UserDatabaseObject(hdb,
       m_flags |= UF_MODIFIED | UF_CHANGE_PASSWORD;
    }
 
-       DBGetField(hResult, row, 8, m_fullName, MAX_USER_FULLNAME);
-       m_graceLogins = DBGetFieldLong(hResult, row, 9);
-       m_authMethod = DBGetFieldLong(hResult, row, 10);
-       m_certMappingMethod = DBGetFieldLong(hResult, row, 11);
-       m_certMappingData = DBGetField(hResult, row, 12, NULL, 0);
-       m_authFailures = DBGetFieldLong(hResult, row, 13);
-       m_lastPasswordChange = (time_t)DBGetFieldLong(hResult, row, 14);
-       m_minPasswordLength = DBGetFieldLong(hResult, row, 15);
-       m_disabledUntil = (time_t)DBGetFieldLong(hResult, row, 16);
-       m_lastLogin = (time_t)DBGetFieldLong(hResult, row, 17);
-   DBGetField(hResult, row, 18, m_xmppId, MAX_XMPP_ID_LEN);
+       DBGetField(hResult, row, 9, m_fullName, MAX_USER_FULLNAME);
+       m_graceLogins = DBGetFieldLong(hResult, row, 10);
+       m_authMethod = DBGetFieldLong(hResult, row, 11);
+       m_certMappingMethod = DBGetFieldLong(hResult, row, 12);
+       m_certMappingData = DBGetField(hResult, row, 13, NULL, 0);
+       m_authFailures = DBGetFieldLong(hResult, row, 14);
+       m_lastPasswordChange = (time_t)DBGetFieldLong(hResult, row, 15);
+       m_minPasswordLength = DBGetFieldLong(hResult, row, 16);
+       m_disabledUntil = (time_t)DBGetFieldLong(hResult, row, 17);
+       m_lastLogin = (time_t)DBGetFieldLong(hResult, row, 18);
+   DBGetField(hResult, row, 19, m_xmppId, MAX_XMPP_ID_LEN);
 
        // Set full system access for superuser
        if (m_id == 0)
@@ -495,14 +512,14 @@ bool User::saveToDatabase(DB_HANDLE hdb)
       hStmt = DBPrepare(hdb,
          _T("UPDATE users SET name=?,password=?,system_access=?,flags=?,full_name=?,description=?,grace_logins=?,guid=?,")
                        _T("  auth_method=?,cert_mapping_method=?,cert_mapping_data=?,auth_failures=?,last_passwd_change=?,")
-         _T("  min_passwd_length=?,disabled_until=?,last_login=?,xmpp_id=?,ldap_dn=? WHERE id=?"));
+         _T("  min_passwd_length=?,disabled_until=?,last_login=?,xmpp_id=?,ldap_dn=?,ldap_unique_id=? WHERE id=?"));
    }
    else
    {
       hStmt = DBPrepare(hdb,
          _T("INSERT INTO users (name,password,system_access,flags,full_name,description,grace_logins,guid,auth_method,")
          _T("  cert_mapping_method,cert_mapping_data,password_history,auth_failures,last_passwd_change,min_passwd_length,")
-         _T("  disabled_until,last_login,xmpp_id,ldap_dn,id) VALUES (?,?,?,?,?,?,?,?,?,?,?,'',?,?,?,?,?,?,?,?)"));
+         _T("  disabled_until,last_login,xmpp_id,ldap_dn,ldap_unique_id,id) VALUES (?,?,?,?,?,?,?,?,?,?,?,'',?,?,?,?,?,?,?,?,?)"));
    }
    if (hStmt == NULL)
       return false;
@@ -525,6 +542,7 @@ bool User::saveToDatabase(DB_HANDLE hdb)
    DBBind(hStmt, 16, DB_SQLTYPE_INTEGER, (UINT32)m_lastLogin);
    DBBind(hStmt, 17, DB_SQLTYPE_VARCHAR, m_xmppId, DB_BIND_STATIC);
    DBBind(hStmt, 18, DB_SQLTYPE_TEXT, m_userDn, DB_BIND_STATIC);
+   DBBind(hStmt, 18, DB_SQLTYPE_TEXT, m_ldapId, DB_BIND_STATIC);
    DBBind(hStmt, 19, DB_SQLTYPE_INTEGER, m_id);
 
    bool success = DBBegin(hdb);
@@ -788,11 +806,11 @@ bool Group::saveToDatabase(DB_HANDLE hdb)
    DB_STATEMENT hStmt;
    if (IsDatabaseRecordExist(hdb, _T("user_groups"), _T("id"), m_id))
    {
-      hStmt = DBPrepare(hdb, _T("UPDATE user_groups SET name=?,system_access=?,flags=?,description=?,guid=?,ldap_dn=? WHERE id=?"));
+      hStmt = DBPrepare(hdb, _T("UPDATE user_groups SET name=?,system_access=?,flags=?,description=?,guid=?,ldap_dn=?,ldap_unique_id=? WHERE id=?"));
    }
    else
    {
-      hStmt = DBPrepare(hdb, _T("INSERT INTO user_groups (name,system_access,flags,description,guid,ldap_dn,id) VALUES (?,?,?,?,?,?,?)"));
+      hStmt = DBPrepare(hdb, _T("INSERT INTO user_groups (name,system_access,flags,description,guid,ldap_dn,ldap_unique_id,id) VALUES (?,?,?,?,?,?,?,?)"));
    }
    if (hStmt == NULL)
       return false;
@@ -803,6 +821,7 @@ bool Group::saveToDatabase(DB_HANDLE hdb)
    DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, m_description, DB_BIND_STATIC);
    DBBind(hStmt, 5, DB_SQLTYPE_VARCHAR, m_guid);
    DBBind(hStmt, 6, DB_SQLTYPE_TEXT, m_userDn, DB_BIND_STATIC);
+   DBBind(hStmt, 6, DB_SQLTYPE_TEXT, m_ldapId, DB_BIND_STATIC);
    DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, m_id);
 
    bool success = DBBegin(hdb);
index c3178a0..723ec09 100644 (file)
@@ -56,6 +56,7 @@ public:
    TCHAR* m_loginName;
    TCHAR* m_fullName;
    TCHAR* m_description;
+   TCHAR* m_id;
    StringSet *m_memberList;
 
    Entry();
@@ -116,6 +117,8 @@ private:
    char m_ldapFullNameAttr[MAX_CONFIG_VALUE];
    char m_ldapLoginNameAttr[MAX_CONFIG_VALUE];
    char m_ldapDescriptionAttr[MAX_CONFIG_VALUE];
+   char m_ldapUsreIdAttr[MAX_CONFIG_VALUE];
+   char m_ldapGroupIdAttr[MAX_CONFIG_VALUE];
    TCHAR m_userClass[MAX_CONFIG_VALUE];
    TCHAR m_groupClass[MAX_CONFIG_VALUE];
    int m_action;
@@ -127,12 +130,13 @@ private:
    UINT32 loginLDAP();
    TCHAR *getErrorString(int code);
    void getAllSyncParameters();
-   void compareGroupList(StringObjectMap<Entry>* groupEntryList);
-   void compareUserLists(StringObjectMap<Entry>* userEntryList);
+   void compareGroupList();
+   void compareUserLists();
    TCHAR *getAttrValue(LDAPMessage *entry, const char *attr, UINT32 i = 0);
+   TCHAR *getIdAttrValue(LDAPMessage *entry, const char *attr);
    void prepareStringForInit(LDAP_CHAR *connectionLine);
-   int readInPages(StringObjectMap<Entry> *userEntryList, StringObjectMap<Entry> *groupEntryList, LDAP_CHAR *base);
-   void fillLists(LDAPMessage *searchResult, StringObjectMap<Entry> *userEntryList, StringObjectMap<Entry> *groupEntryList);
+   int readInPages(LDAP_CHAR *base);
+   void fillLists(LDAPMessage *searchResult);
    TCHAR *ldap_internal_get_dn(LDAP *conn, LDAPMessage *entry);
    void updateMembers(StringSet *memberList, const char *firstAttr, LDAPMessage *firstEntry, const LDAP_CHAR *dn);
 #endif // WITH_LDAP
@@ -186,6 +190,7 @@ protected:
        UINT32 m_flags;
        StringMap m_attributes;         // Custom attributes
    TCHAR *m_userDn;
+   TCHAR *m_ldapId;
 
        bool loadCustomAttributes(DB_HANDLE hdb);
        bool saveCustomAttributes(DB_HANDLE hdb);
@@ -210,6 +215,7 @@ public:
        UINT32 getFlags() const { return m_flags; }
    TCHAR *getGuidAsText(TCHAR *buffer) const { return m_guid.toString(buffer); }
    const TCHAR *getDn() const { return m_userDn; }
+   const TCHAR *getLdapId() const { return m_ldapId; }
 
    bool isGroup() const { return (m_id & GROUP_FLAG) != 0; }
        bool isDeleted() const { return (m_flags & UF_DELETED) ? true : false; }
@@ -231,6 +237,7 @@ public:
        void setName(const TCHAR *name);
        void setDescription(const TCHAR *description);
        void setDn(const TCHAR *dn);
+       void setLdapId(const TCHAR *id);
 };
 
 /**
@@ -404,7 +411,7 @@ UINT32 NXCORE_EXPORTABLE GetUserDbObjectAttrAsULong(UINT32 id, const TCHAR *name
 void NXCORE_EXPORTABLE SetUserDbObjectAttr(UINT32 id, const TCHAR *name, const TCHAR *value);
 bool NXCORE_EXPORTABLE ResolveUserId(UINT32 id, TCHAR *buffer, int bufSize);
 void UpdateLDAPUser(const TCHAR* dn, Entry *obj);
-void RemoveDeletedLDAPEntries(StringObjectMap<Entry>* userEntryList, UINT32 m_action, bool isUser);
+void RemoveDeletedLDAPEntries(StringObjectMap<Entry> *entryListDn, StringObjectMap<Entry> *entryListId, UINT32 m_action, bool isUser);
 void UpdateLDAPGroup(const TCHAR* dn, Entry *obj);
 THREAD_RESULT THREAD_CALL SyncLDAPUsers(void *arg);
 void FillGroupMembershipInfo(NXCPMessage *msg, UINT32 userId);
index 0784aee..de0ad3a 100644 (file)
@@ -664,7 +664,43 @@ static int NextFreeEPPruleID()
        return ruleId;
 }
 
+/**
+ * Upgrade from V400 to V401
+ */
+static BOOL H_UpgradeFromV400(int currVersion, int newVersion)
+{
+   CHK_EXEC(CreateEventTemplate(EVENT_LDAP_SYCN_ERROR, _T("SYS_LDAP_SYCN_ERROR"), SEVERITY_MAJOR, EF_LOG,
+      _T("%5"),
+      _T("Generated when LDAP synchronization error occurs.\r\n")
+      _T("Parameters:\r\n")
+      _T("    1) User ID\r\n")
+      _T("    2) User GUID\r\n")
+      _T("    3) User LDAP DN\r\n")
+      _T("    4) User name\r\n")
+      _T("    5) Problem description")));
+   // Create rule pair in event processing policy
+       int ruleId = 0;
+       DB_RESULT hResult = SQLSelect(_T("SELECT max(rule_id) FROM event_policy"));
+       if (hResult != NULL)
+       {
+               ruleId = DBGetFieldLong(hResult, 0, 0) + 1;
+               DBFreeResult(hResult);
+       }
 
+   TCHAR query[1024];
+       _sntprintf(query, 1024, _T("INSERT INTO event_policy (rule_id,rule_guid,flags,comments,alarm_message,alarm_severity,alarm_key,script,alarm_timeout,alarm_timeout_event,situation_id,situation_instance) VALUES (%d,'417648af-5361-49a5-9471-6ef31e857b2d',7944,'Generate an alarm when error occurred while LDAP sync','%%m',5,'SYS_LDAP_SYCN_ERROR_%%1','',0,%d,0,'')"), ruleId, EVENT_ALARM_TIMEOUT);
+   CHK_EXEC(SQLQuery(query));
+   _sntprintf(query, 1024, _T("INSERT INTO policy_event_list (rule_id,event_code) VALUES (%d,%d)"), ruleId, EVENT_LDAP_SYCN_ERROR);
+   CHK_EXEC(SQLQuery(query));
+
+   CHK_EXEC(SQLQuery(_T("ALTER TABLE users ADD ldap_unique_id varchar(64)")));
+   CHK_EXEC(SQLQuery(_T("ALTER TABLE user_groups ADD ldap_unique_id varchar(64)")));
+   CHK_EXEC(CreateConfigParam(_T("LdapUserUniqueId"), _T(""), true, false, false));
+   CHK_EXEC(CreateConfigParam(_T("LdapGroupUniqueId"), _T(""), true, false, false));
+
+   CHK_EXEC(SQLQuery(_T("UPDATE metadata SET var_value='401' WHERE var_name='SchemaVersion'")));
+   return TRUE;
+}
 
 /**
  * Upgrade from V399 to V400
@@ -10116,6 +10152,7 @@ static struct
    { 397, 398, H_UpgradeFromV397 },
    { 398, 399, H_UpgradeFromV398 },
    { 399, 400, H_UpgradeFromV399 },
+   { 400, 401, H_UpgradeFromV400 },
    { 0, 0, NULL }
 };