fixed compiler warnings; minor refactoring
[public/netxms.git] / src / server / core / userdb.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: userdb.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Password complexity options
27 */
28 #define PSWD_MUST_CONTAIN_DIGITS 0x0001
29 #define PSWD_MUST_CONTAIN_UPPERCASE 0x0002
30 #define PSWD_MUST_CONTAIN_LOWERCASE 0x0004
31 #define PSWD_MUST_CONTAIN_SPECIAL_CHARS 0x0008
32 #define PSWD_FORBID_ALPHABETICAL_SEQUENCE 0x0010
33 #define PSWD_FORBID_KEYBOARD_SEQUENCE 0x0020
34
35 /**
36 * Action done on deleted user/group
37 */
38 #define USER_DELETE 0
39 #define USER_DISABLE 1
40
41 /**
42 * Externals
43 */
44 bool RadiusAuth(const TCHAR *pszLogin, const TCHAR *pszPasswd);
45
46 /**
47 * Static data
48 */
49 static HashMap<UINT32, UserDatabaseObject> s_userDatabase(true);
50 static StringObjectMap<UserDatabaseObject> s_ldapNames(false);
51 static StringObjectMap<User> s_ldapUserId(false);
52 static StringObjectMap<Group> s_ldapGroupId(false);
53 static StringObjectMap<User> s_users(false);
54 static StringObjectMap<Group> s_groups(false);
55 static RWLOCK s_userDatabaseLock = RWLockCreate();
56 static THREAD s_statusUpdateThread = INVALID_THREAD_HANDLE;
57
58 /**
59 * Compare user names
60 */
61 inline bool UserNameEquals(const TCHAR *n1, const TCHAR *n2)
62 {
63 return (g_flags & AF_CASE_INSENSITIVE_LOGINS) ? (_tcsicmp(n1, n2) == 0) : (_tcscmp(n1, n2) == 0);
64 }
65
66 /**
67 * Add user database object
68 */
69 inline void AddDatabaseObject(UserDatabaseObject *object)
70 {
71 s_userDatabase.set(object->getId(), object);
72 if (object->isGroup())
73 s_groups.set(object->getName(), (Group *)object);
74 else
75 s_users.set(object->getName(), (User *)object);
76 if (object->isLDAPUser())
77 {
78 s_ldapNames.set(object->getDn(), object);
79 if(object->getLdapId() != NULL)
80 {
81 if (object->isGroup())
82 s_ldapGroupId.set(object->getLdapId(), (Group *)object);
83 else
84 s_ldapUserId.set(object->getLdapId(), (User *)object);
85 }
86 }
87 }
88
89 /**
90 * Remove user database object
91 */
92 inline void RemoveDatabaseObject(UserDatabaseObject *object)
93 {
94 if (object->isGroup())
95 s_groups.remove(object->getName());
96 else
97 s_users.remove(object->getName());
98 if (object->isLDAPUser())
99 {
100 s_ldapNames.remove(object->getDn());
101 if(object->getLdapId() != NULL)
102 {
103 if (object->isGroup())
104 s_ldapGroupId.remove(object->getLdapId());
105 else
106 s_ldapUserId.remove(object->getLdapId());
107 }
108 }
109 }
110
111 /**
112 * Get effective system rights for user
113 */
114 static UINT64 GetEffectiveSystemRights(User *user)
115 {
116 UINT64 systemRights = user->getSystemRights();
117 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
118 IntegerArray<UINT32> *searchPath = new IntegerArray<UINT32>(16, 16);
119 while(it->hasNext())
120 {
121 UserDatabaseObject *object = it->next();
122
123 // The previous search path is checked to avoid performing a deep membership search again
124 if (object->isGroup() && searchPath->contains(((Group *)object)->getId()))
125 {
126 systemRights |= ((Group *)object)->getSystemRights();
127 continue;
128 }
129 else
130 {
131 searchPath->clear();
132 }
133
134 if (object->isGroup() && (((Group *)object)->isMember(user->getId(), searchPath)))
135 {
136 systemRights |= ((Group *)object)->getSystemRights();
137 }
138 else
139 {
140 searchPath->clear();
141 }
142 }
143 delete searchPath;
144 delete it;
145 return systemRights;
146 }
147
148 /**
149 * Upgrade user accounts status in background
150 */
151 static THREAD_RESULT THREAD_CALL AccountStatusUpdater(void *arg)
152 {
153 ThreadSetName("AccountUpdate");
154 DbgPrintf(2, _T("User account status update thread started"));
155 while(!SleepAndCheckForShutdown(60))
156 {
157 DbgPrintf(8, _T("AccountStatusUpdater: wakeup"));
158
159 time_t blockInactiveAccounts = (time_t)ConfigReadInt(_T("BlockInactiveUserAccounts"), 0) * 86400;
160
161 RWLockWriteLock(s_userDatabaseLock, INFINITE);
162 time_t now = time(NULL);
163 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
164 while(it->hasNext())
165 {
166 UserDatabaseObject *object = it->next();
167 if (object->isDeleted() || object->isGroup())
168 continue;
169
170 User *user = (User *)object;
171
172 if (user->isDisabled() && (user->getReEnableTime() > 0) && (user->getReEnableTime() <= now))
173 {
174 // Re-enable temporary disabled user
175 user->enable();
176 WriteAuditLog(AUDIT_SECURITY, TRUE, user->getId(), NULL, AUDIT_SYSTEM_SID, 0, _T("Temporary disabled user account \"%s\" re-enabled by system"), user->getName());
177 DbgPrintf(3, _T("Temporary disabled user account \"%s\" re-enabled"), user->getName());
178 }
179
180 if (!user->isDisabled() && (blockInactiveAccounts > 0) && (user->getLastLoginTime() > 0) && (user->getLastLoginTime() + blockInactiveAccounts < now))
181 {
182 user->disable();
183 WriteAuditLog(AUDIT_SECURITY, TRUE, user->getId(), NULL, AUDIT_SYSTEM_SID, 0, _T("User account \"%s\" disabled by system due to inactivity"), user->getName());
184 DbgPrintf(3, _T("User account \"%s\" disabled due to inactivity"), user->getName());
185 }
186 }
187 delete it;
188 RWLockUnlock(s_userDatabaseLock);
189 }
190
191 DbgPrintf(2, _T("User account status update thread stopped"));
192 return THREAD_OK;
193 }
194
195 /**
196 * Initialize user handling subsystem
197 */
198 void InitUsers()
199 {
200 s_users.setIgnoreCase((g_flags & AF_CASE_INSENSITIVE_LOGINS) != 0);
201 s_groups.setIgnoreCase((g_flags & AF_CASE_INSENSITIVE_LOGINS) != 0);
202 s_statusUpdateThread = ThreadCreateEx(AccountStatusUpdater, 0, NULL);
203 }
204
205 /**
206 * Cleanup user handling subsystem
207 */
208 void CleanupUsers()
209 {
210 ThreadJoin(s_statusUpdateThread);
211 }
212
213 /**
214 * Load user list from database
215 */
216 BOOL LoadUsers()
217 {
218 int i;
219 DB_RESULT hResult;
220
221 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
222
223 // Load users
224 hResult = DBSelect(hdb,
225 _T("SELECT id,name,system_access,flags,description,guid,ldap_dn,")
226 _T("ldap_unique_id,created,password,full_name,grace_logins,auth_method,")
227 _T("cert_mapping_method,cert_mapping_data,auth_failures,")
228 _T("last_passwd_change,min_passwd_length,disabled_until,")
229 _T("last_login,xmpp_id FROM users"));
230 if (hResult == NULL)
231 {
232 DBConnectionPoolReleaseConnection(hdb);
233 return false;
234 }
235
236 int count = DBGetNumRows(hResult);
237 for(i = 0; i < count; i++)
238 {
239 User *user = new User(hdb, hResult, i);
240 AddDatabaseObject(user);
241 }
242
243 DBFreeResult(hResult);
244
245 // Create system account if it doesn't exist
246 if (!s_userDatabase.contains(0))
247 {
248 User *user = new User();
249 AddDatabaseObject(user);
250 nxlog_write(MSG_SUPERUSER_CREATED, EVENTLOG_WARNING_TYPE, NULL);
251 }
252
253 // Load groups
254 hResult = DBSelect(hdb, _T("SELECT id,name,system_access,flags,description,guid,ldap_dn,ldap_unique_id,created FROM user_groups"));
255 if (hResult == NULL)
256 {
257 DBConnectionPoolReleaseConnection(hdb);
258 return FALSE;
259 }
260
261 count = DBGetNumRows(hResult);
262 for(i = 0; i < count; i++)
263 {
264 Group *group = new Group(hdb, hResult, i);
265 AddDatabaseObject(group);
266 }
267
268 DBFreeResult(hResult);
269
270 // Create everyone group if it doesn't exist
271 if (!s_userDatabase.contains(GROUP_EVERYONE))
272 {
273 Group *group = new Group();
274 group->saveToDatabase(hdb);
275 AddDatabaseObject(group);
276 nxlog_write(MSG_EVERYONE_GROUP_CREATED, EVENTLOG_WARNING_TYPE, NULL);
277 }
278
279 DBConnectionPoolReleaseConnection(hdb);
280 return TRUE;
281 }
282
283 /**
284 * Save user list to database
285 */
286 void SaveUsers(DB_HANDLE hdb, UINT32 watchdogId)
287 {
288 // Save users
289 RWLockWriteLock(s_userDatabaseLock, INFINITE);
290 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
291 while(it->hasNext())
292 {
293 WatchdogNotify(watchdogId);
294 UserDatabaseObject *object = it->next();
295 if (object->isDeleted())
296 {
297 object->deleteFromDatabase(hdb);
298 RemoveDatabaseObject(object);
299 it->remove();
300 }
301 else if (object->isModified())
302 {
303 object->saveToDatabase(hdb);
304 }
305 }
306 delete it;
307 RWLockUnlock(s_userDatabaseLock);
308 }
309
310 /**
311 * Authenticate user
312 * Checks if provided login name and password are correct, and returns RCC_SUCCESS
313 * on success and appropriate RCC otherwise. On success authentication, user's ID is stored
314 * int pdwId. If password authentication is used, dwSigLen should be set to zero.
315 * For non-UNICODE build, password must be UTF-8 encoded. If user already authenticated by
316 * SSO server, ssoAuth must be set to true. Password expiration, change flag and grace
317 * count ignored for SSO logins.
318 */
319 UINT32 AuthenticateUser(const TCHAR *login, const TCHAR *password, size_t sigLen, void *pCert,
320 BYTE *pChallenge, UINT32 *pdwId, UINT64 *pdwSystemRights,
321 bool *pbChangePasswd, bool *pbIntruderLockout, bool *closeOtherSessions,
322 bool ssoAuth, UINT32 *graceLogins)
323 {
324 UINT32 dwResult = RCC_ACCESS_DENIED;
325 BOOL bPasswordValid = FALSE;
326
327 *closeOtherSessions = false;
328 RWLockWriteLock(s_userDatabaseLock, INFINITE);
329 User *user = s_users.get(login);
330 if ((user != NULL) && !user->isDeleted())
331 {
332 *pdwId = user->getId(); // always set user ID for caller so audit log will contain correct user ID on failures as well
333
334 if (user->isLDAPUser())
335 {
336 if (user->isDisabled())
337 {
338 dwResult = RCC_ACCOUNT_DISABLED;
339 goto result;
340 }
341 LDAPConnection conn;
342 dwResult = conn.ldapUserLogin(user->getDn(), password);
343 if (dwResult == RCC_SUCCESS)
344 bPasswordValid = TRUE;
345 goto result;
346 }
347
348 // Determine authentication method to use
349 if (!ssoAuth)
350 {
351 int method = user->getAuthMethod();
352 if ((method == AUTH_CERT_OR_PASSWD) || (method == AUTH_CERT_OR_RADIUS))
353 {
354 if (sigLen > 0)
355 {
356 // certificate auth
357 method = AUTH_CERTIFICATE;
358 }
359 else
360 {
361 method = (method == AUTH_CERT_OR_PASSWD) ? AUTH_NETXMS_PASSWORD : AUTH_RADIUS;
362 }
363 }
364
365 switch(method)
366 {
367 case AUTH_NETXMS_PASSWORD:
368 if (sigLen == 0)
369 {
370 bPasswordValid = user->validatePassword(password);
371 }
372 else
373 {
374 // We got certificate instead of password
375 bPasswordValid = FALSE;
376 }
377 break;
378 case AUTH_RADIUS:
379 if (sigLen == 0)
380 {
381 bPasswordValid = RadiusAuth(login, password);
382 }
383 else
384 {
385 // We got certificate instead of password
386 bPasswordValid = FALSE;
387 }
388 break;
389 case AUTH_CERTIFICATE:
390 if ((sigLen != 0) && (pCert != NULL))
391 {
392 #ifdef _WITH_ENCRYPTION
393 bPasswordValid = ValidateUserCertificate(static_cast<X509*>(pCert), login, pChallenge,
394 reinterpret_cast<const BYTE*>(password), sigLen,
395 user->getCertMappingMethod(),
396 user->getCertMappingData());
397 #else
398 bPasswordValid = FALSE;
399 #endif
400 }
401 else
402 {
403 // We got password instead of certificate
404 bPasswordValid = FALSE;
405 }
406 break;
407 default:
408 nxlog_write(MSG_UNKNOWN_AUTH_METHOD, NXLOG_WARNING, "ds", user->getAuthMethod(), login);
409 bPasswordValid = FALSE;
410 break;
411 }
412 }
413 else
414 {
415 DbgPrintf(4, _T("User \"%s\" already authenticated by SSO server"), user->getName());
416 bPasswordValid = TRUE;
417 }
418
419 result:
420 if (bPasswordValid)
421 {
422 if (!user->isDisabled())
423 {
424 user->resetAuthFailures();
425 if (!ssoAuth)
426 {
427 if (user->getFlags() & UF_CHANGE_PASSWORD)
428 {
429 DbgPrintf(4, _T("Password for user \"%s\" need to be changed"), user->getName());
430 if (user->getId() != 0) // Do not check grace logins for built-in system user
431 {
432 if (user->getGraceLogins() <= 0)
433 {
434 DbgPrintf(4, _T("User \"%s\" has no grace logins left"), user->getName());
435 dwResult = RCC_NO_GRACE_LOGINS;
436 }
437 else
438 {
439 user->decreaseGraceLogins();
440 }
441 }
442 *pbChangePasswd = true;
443 }
444 else
445 {
446 // Check if password was expired
447 int passwordExpirationTime = ConfigReadInt(_T("PasswordExpiration"), 0);
448 if ((user->getAuthMethod() == AUTH_NETXMS_PASSWORD) &&
449 (passwordExpirationTime > 0) &&
450 ((user->getFlags() & UF_PASSWORD_NEVER_EXPIRES) == 0) &&
451 (time(NULL) > user->getPasswordChangeTime() + passwordExpirationTime * 86400))
452 {
453 DbgPrintf(4, _T("Password for user \"%s\" has expired"), user->getName());
454 if (user->getId() != 0) // Do not check grace logins for built-in system user
455 {
456 if (user->getGraceLogins() <= 0)
457 {
458 DbgPrintf(4, _T("User \"%s\" has no grace logins left"), user->getName());
459 dwResult = RCC_NO_GRACE_LOGINS;
460 }
461 else
462 {
463 user->decreaseGraceLogins();
464 }
465 }
466 *pbChangePasswd = true;
467 }
468 else
469 {
470 *pbChangePasswd = false;
471 }
472 }
473 }
474 else
475 {
476 *pbChangePasswd = false;
477 }
478
479 if (dwResult != RCC_NO_GRACE_LOGINS)
480 {
481 *pdwSystemRights = GetEffectiveSystemRights(user);
482 *closeOtherSessions = (user->getFlags() & UF_CLOSE_OTHER_SESSIONS) != 0;
483 *graceLogins = user->getGraceLogins();
484 user->updateLastLogin();
485 dwResult = RCC_SUCCESS;
486 }
487 }
488 else
489 {
490 dwResult = RCC_ACCOUNT_DISABLED;
491 }
492 *pbIntruderLockout = false;
493 }
494 else
495 {
496 user->increaseAuthFailures();
497 *pbIntruderLockout = user->isIntruderLockoutActive();
498 }
499 }
500 RWLockUnlock(s_userDatabaseLock);
501 return dwResult;
502 }
503
504 /**
505 * Check if user is a member of specific child group
506 */
507 bool CheckUserMembershipInternal(UINT32 userId, UINT32 groupId, IntegerArray<UINT32> *searchPath)
508 {
509 Group *group = (Group *)s_userDatabase.get(groupId);
510 if (group != NULL)
511 {
512 return group->isMember(userId, searchPath);
513 }
514 return false;
515 }
516
517 /**
518 * Check if user is a member of specific group
519 */
520 bool NXCORE_EXPORTABLE CheckUserMembership(UINT32 userId, UINT32 groupId)
521 {
522 if (!(groupId & GROUP_FLAG))
523 return false;
524
525 if (groupId == GROUP_EVERYONE)
526 return true;
527
528 bool result = false;
529 IntegerArray<UINT32> searchPath(16, 16);
530
531 RWLockReadLock(s_userDatabaseLock, INFINITE);
532
533 result = CheckUserMembershipInternal(userId, groupId, &searchPath);
534
535 RWLockUnlock(s_userDatabaseLock);
536
537 return result;
538 }
539
540 /**
541 * Fill message with group membership information for given user.
542 * Access to user database must be locked.
543 */
544 void FillGroupMembershipInfo(NXCPMessage *msg, UINT32 userId)
545 {
546 IntegerArray<UINT32> list;
547 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
548 while(it->hasNext())
549 {
550 UserDatabaseObject *object = it->next();
551 if (object->isGroup() && (object->getId() != GROUP_EVERYONE) && ((Group *)object)->isMember(userId))
552 {
553 list.add(object->getId());
554 }
555 }
556 delete it;
557 msg->setField(VID_NUM_GROUPS, (INT32)list.size());
558 if (list.size() > 0)
559 msg->setFieldFromInt32Array(VID_GROUPS, &list);
560 }
561
562 /**
563 * Update group membership for user
564 */
565 void UpdateGroupMembership(UINT32 userId, int numGroups, UINT32 *groups)
566 {
567 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
568 while(it->hasNext())
569 {
570 UserDatabaseObject *object = it->next();
571 if (object->isGroup() && (object->getId() != GROUP_EVERYONE))
572 {
573 bool found = false;
574 for(int j = 0; j < numGroups; j++)
575 {
576 if (object->getId() == groups[j])
577 {
578 found = true;
579 break;
580 }
581 }
582 if (found)
583 {
584 ((Group *)object)->addUser(userId);
585 }
586 else
587 {
588 ((Group *)object)->deleteUser(userId);
589 }
590 }
591 }
592 delete it;
593 }
594
595 /**
596 * Resolve user's ID to login name
597 */
598 bool NXCORE_EXPORTABLE ResolveUserId(UINT32 id, TCHAR *buffer, int bufSize)
599 {
600 RWLockReadLock(s_userDatabaseLock, INFINITE);
601 UserDatabaseObject *object = s_userDatabase.get(id);
602 if (object != NULL)
603 nx_strncpy(buffer, object->getName(), bufSize);
604 RWLockUnlock(s_userDatabaseLock);
605 return object != NULL;
606 }
607
608 /**
609 * Check if provided user name is not used or belongs to given user.
610 * Access to user DB must be locked when this function is called
611 */
612 inline bool UserNameIsUnique(const TCHAR *name, User *user)
613 {
614 User *u = s_users.get(name);
615 return (u == NULL) || ((user != NULL) && (user->getId() == u->getId()));
616 }
617
618 /**
619 * Check if provided group name is not used or belongs to given group.
620 * Access to user DB must be locked when this function is called
621 */
622 inline bool GroupNameIsUnique(const TCHAR *name, Group *group)
623 {
624 Group *g = s_groups.get(name);
625 return (g == NULL) || ((group != NULL) && (group->getId() == g->getId()));
626 }
627
628 /**
629 * Generates unique name for LDAP user
630 */
631 static TCHAR *GenerateUniqueName(const TCHAR *oldName, UINT32 id)
632 {
633 TCHAR *name = (TCHAR *)malloc(sizeof(TCHAR) * 256);
634 _sntprintf(name, 256, _T("%s_LDAP%d"), oldName, id);
635 return name;
636 }
637
638 /**
639 * Update/Add LDAP user
640 */
641 void UpdateLDAPUser(const TCHAR *dn, Entry *obj)
642 {
643 RWLockWriteLock(s_userDatabaseLock, INFINITE);
644
645 bool userModified = false;
646 bool conflict = false;
647 TCHAR description[1024];
648 TCHAR guid[64];
649
650 // Check existing user with same DN
651 UserDatabaseObject *object = NULL;
652 if(obj->m_id != NULL)
653 object = s_ldapUserId.get(obj->m_id);
654 else
655 object = s_ldapNames.get(dn);
656
657 if ((object != NULL) && object->isGroup())
658 {
659 _sntprintf(description, MAX_USER_DESCR, _T("Got user with DN=%s but found existing group %s with same DN"), dn, object->getName());
660 object->getGuidAsText(guid);
661 PostEvent(EVENT_LDAP_SYNC_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), description);
662 DbgPrintf(4, _T("UpdateLDAPUser(): %s"), description);
663 conflict = true;
664 }
665
666 if ((object != NULL) && !conflict)
667 {
668 User *user = (User *)object;
669 if (!user->isDeleted())
670 {
671 user->removeSyncException();
672 if (!UserNameIsUnique(obj->m_loginName, user))
673 {
674 TCHAR *userName = GenerateUniqueName(obj->m_loginName, user->getId());
675 if(_tcscmp(user->getName(), userName))
676 {
677 user->setName(userName);
678 _sntprintf(description, 1024, _T("User with name \"%s\" already exists. Unique user name have been generated: \"%s\""), obj->m_loginName, userName);
679 object->getGuidAsText(guid);
680 PostEvent(EVENT_LDAP_SYNC_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), description);
681 DbgPrintf(4, _T("UpdateLDAPUser(): %s"), description);
682 }
683 user->setFullName(obj->m_fullName);
684 user->setDescription(obj->m_description);
685 if(_tcscmp(user->getDn(), dn))
686 {
687 s_ldapNames.remove(user->getDn());
688 user->setDn(dn);
689 s_ldapNames.set(dn, user);
690 }
691 free(userName);
692 }
693 else
694 {
695 user->setName(obj->m_loginName);
696 user->setFullName(obj->m_fullName);
697 user->setDescription(obj->m_description);
698 if(_tcscmp(user->getDn(), dn))
699 {
700 s_ldapNames.remove(user->getDn());
701 user->setDn(dn);
702 s_ldapNames.set(dn, user);
703 }
704 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));
705 }
706 if (user->isModified())
707 {
708 SendUserDBUpdate(USER_DB_MODIFY, user->getId(), user);
709 }
710 }
711 userModified = true;
712 }
713
714 if (!userModified && !conflict)
715 {
716 if (UserNameIsUnique(obj->m_loginName, NULL))
717 {
718 User *user = new User(CreateUniqueId(IDG_USER), obj->m_loginName);
719 user->setFullName(obj->m_fullName);
720 user->setDescription(obj->m_description);
721 user->setFlags(UF_MODIFIED | UF_LDAP_USER);
722 user->setDn(dn);
723 if(obj->m_id != NULL)
724 user->setLdapId(obj->m_id);
725 AddDatabaseObject(user);
726 SendUserDBUpdate(USER_DB_CREATE, user->getId(), user);
727 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));
728 }
729 else
730 {
731 UINT32 userId = CreateUniqueId(IDG_USER);
732 TCHAR *userName = GenerateUniqueName(obj->m_loginName, userId);
733 _sntprintf(description, MAX_USER_DESCR, _T("User with name \"%s\" already exists. Unique user name have been generated: \"%s\""), obj->m_loginName, userName);
734 DbgPrintf(4, _T("UpdateLDAPUser(): %s"), description);
735 User *user = new User(userId, userName);
736 user->setFullName(obj->m_fullName);
737 user->setDescription(obj->m_description);
738 user->setFlags(UF_MODIFIED | UF_LDAP_USER);
739 user->setDn(dn);
740 if(obj->m_id != NULL)
741 user->setLdapId(obj->m_id);
742 AddDatabaseObject(user);
743 SendUserDBUpdate(USER_DB_CREATE, user->getId(), user);
744 user->getGuidAsText(guid);
745 PostEvent(EVENT_LDAP_SYNC_ERROR ,g_dwMgmtNode, "issss", user->getId(), guid, user->getDn(), user->getName(), description);
746 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));
747 free(userName);
748 }
749 }
750 RWLockUnlock(s_userDatabaseLock);
751 }
752
753 /**
754 * Goes through all existing LDAP entries and check that in newly gotten list they also exist.
755 * If LDAP entries does not exists in new list - it will be disabled or removed depending on action parameter.
756 */
757 void RemoveDeletedLDAPEntries(StringObjectMap<Entry> *entryListDn, StringObjectMap<Entry> *entryListId, UINT32 m_action, bool isUser)
758 {
759 RWLockWriteLock(s_userDatabaseLock, INFINITE);
760 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
761 while(it->hasNext())
762 {
763 UserDatabaseObject *object = it->next();
764 if (!object->isLDAPUser() || object->isDeleted())
765 continue;
766
767 if (isUser ? ((object->getId() & GROUP_FLAG) == 0) : ((object->getId() & GROUP_FLAG) != 0))
768 {
769 if ((object->getLdapId() == NULL || !entryListId->contains(object->getLdapId())) && !entryListDn->contains(object->getDn()))
770 {
771 if (m_action == USER_DELETE)
772 {
773 DbgPrintf(4, _T("RemoveDeletedLDAPEntry(): LDAP %s object %s was removed from user database"), isUser ? _T("user") : _T("group"), object->getDn());
774 DeleteUserDatabaseObject(object->getId(), true);
775 }
776 else if (m_action == USER_DISABLE)
777 {
778 DbgPrintf(4, _T("RemoveDeletedLDAPEntry(): LDAP %s object %s was disabled"), isUser ? _T("user") : _T("group"), object->getDn());
779 object->disable();
780 object->setDescription(_T("LDAP entry was deleted."));
781 }
782 }
783 }
784 }
785 RWLockUnlock(s_userDatabaseLock);
786 }
787
788 /**
789 * Synchronize new user list with old user list of given group. Note: LDAP user will not be changed.
790 */
791 static void SyncGroupMembers(Group *group, Entry *obj)
792 {
793 DbgPrintf(4, _T("SyncGroupMembers(): Sync for LDAP group: %s"), group->getDn());
794
795 StringSet *newMembers = obj->m_memberList;
796 UINT32 *oldMembers = NULL;
797 int count = group->getMembers(&oldMembers);
798
799 /**
800 * Go through existing group member list checking each LDAP user by DN
801 * with new gotten group member list and removing LDAP users not existing in last list.
802 */
803 for(int i = 0; i < count; i++)
804 {
805 UserDatabaseObject *user = s_userDatabase.get(oldMembers[i]);
806 if ((user == NULL) || !user->isLDAPUser())
807 continue;
808
809 if (!newMembers->contains(user->getDn()))
810 {
811 DbgPrintf(4, _T("SyncGroupMembers: Remove from %s group deleted user: %s"), group->getDn(), user->getDn());
812 group->deleteUser(user->getId());
813 count = group->getMembers(&oldMembers);
814 i--;
815 }
816 }
817
818 /**
819 * Go through new gotten group member list checking each LDAP user by DN
820 * with existing group member list and adding users not existing in last list.
821 */
822 Iterator<const TCHAR> *it = newMembers->iterator();
823 while(it->hasNext())
824 {
825 const TCHAR *dn = it->next();
826 UserDatabaseObject *user = s_ldapNames.get(dn);
827 if (user == NULL)
828 continue;
829
830 if (!group->isMember(user->getId()))
831 {
832 DbgPrintf(4, _T("SyncGroupMembers: LDAP user %s added to LDAP group %s"), user->getDn(), group->getDn());
833 group->addUser(user->getId());
834 }
835 }
836 delete it;
837 }
838
839 /**
840 * Update/Add LDAP group
841 */
842 void UpdateLDAPGroup(const TCHAR *dn, Entry *obj) //no full name, add users inside group, and delete removed from the group
843 {
844 RWLockWriteLock(s_userDatabaseLock, INFINITE);
845
846 bool userModified = false;
847 bool conflict = false;
848 TCHAR description[1024];
849 TCHAR guid[64];
850
851 // Check existing user with same DN
852 UserDatabaseObject *object = NULL;
853
854 if(obj->m_id != NULL)
855 object = s_ldapGroupId.get(obj->m_id);
856 else
857 object = s_ldapNames.get(dn);
858 if ((object != NULL) && !object->isGroup())
859 {
860 _sntprintf(description, MAX_USER_DESCR, _T("Got group with DN=%s but found existing group %s with same DN"), dn, object->getName());
861 object->getGuidAsText(guid);
862 PostEvent(EVENT_LDAP_SYNC_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), description);
863 DbgPrintf(4, _T("UpdateLDAPGroup(): %s"), description);
864 conflict = true;
865 }
866
867 if ((object != NULL) && !conflict)
868 {
869 Group *group = (Group *)object;
870 if (!group->isDeleted())
871 {
872 group->removeSyncException();
873 if (!GroupNameIsUnique(obj->m_loginName, group))
874 {
875 TCHAR *groupName = GenerateUniqueName(obj->m_loginName, group->getId());
876 if(_tcscmp(group->getName(), groupName))
877 {
878 group->setName(groupName);
879 _sntprintf(description, MAX_USER_DESCR, _T("Group with name \"%s\" already exists. Unique group name have been generated: \"%s\""), obj->m_loginName, groupName);
880 object->getGuidAsText(guid);
881 PostEvent(EVENT_LDAP_SYNC_ERROR ,g_dwMgmtNode, "issss", object->getId(), guid, object->getDn(), object->getName(), description);
882 DbgPrintf(4, _T("UpdateLDAPGroup(): %s"),description);
883 }
884 group->setDescription(obj->m_description);
885 if(_tcscmp(group->getDn(), dn))
886 {
887 s_ldapNames.remove(group->getDn());
888 group->setDn(dn);
889 s_ldapNames.set(dn, group);
890 }
891 free(groupName);
892 }
893 else
894 {
895 group->setName(obj->m_loginName);
896 group->setDescription(obj->m_description);
897 if(_tcscmp(group->getDn(), dn))
898 {
899 s_ldapNames.remove(group->getDn());
900 group->setDn(dn);
901 s_ldapNames.set(dn, group);
902 }
903 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));
904 }
905 if (group->isModified())
906 {
907 SendUserDBUpdate(USER_DB_MODIFY, group->getId(), group);
908 }
909 SyncGroupMembers(group , obj);
910 }
911 userModified = true;
912 }
913
914 if (!userModified)
915 {
916 if (GroupNameIsUnique(obj->m_loginName, NULL))
917 {
918 Group *group = new Group(CreateUniqueId(IDG_USER_GROUP), obj->m_loginName);
919 group->setDescription(obj->m_description);
920 group->setFlags(UF_MODIFIED | UF_LDAP_USER);
921 group->setDn(dn);
922 if(obj->m_id != NULL)
923 group->setLdapId(obj->m_id);
924 SendUserDBUpdate(USER_DB_CREATE, group->getId(), group);
925 AddDatabaseObject(group);
926 SyncGroupMembers(group , obj);
927 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));
928 }
929 else
930 {
931 UINT32 id = CreateUniqueId(IDG_USER_GROUP);
932 TCHAR *groupName = GenerateUniqueName(obj->m_loginName, id);
933 _sntprintf(description, MAX_USER_DESCR, _T("Group with name \"%s\" already exists. Unique group name have been generated: \"%s\""), obj->m_loginName, groupName);
934 DbgPrintf(4, _T("UpdateLDAPGroup(): %s"),description);
935 Group *group = new Group(id, groupName);
936 group->setDescription(obj->m_description);
937 group->setFlags(UF_MODIFIED | UF_LDAP_USER);
938 group->setDn(dn);
939 if(obj->m_id != NULL)
940 group->setLdapId(obj->m_id);
941 SendUserDBUpdate(USER_DB_CREATE, group->getId(), group);
942 AddDatabaseObject(group);
943 SyncGroupMembers(group , obj);
944 group->getGuidAsText(guid);
945 PostEvent(EVENT_LDAP_SYNC_ERROR ,g_dwMgmtNode, "issss", group->getId(), guid, group->getDn(), group->getName(), description);
946 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));
947 free(groupName);
948 }
949 }
950 RWLockUnlock(s_userDatabaseLock);
951 }
952
953 /**
954 * Dump user list to console
955 *
956 * @param pCtx console context
957 */
958 void DumpUsers(CONSOLE_CTX pCtx)
959 {
960 ConsolePrintf(pCtx, _T("Login name GUID System rights\n")
961 _T("-----------------------------------------------------------------------\n"));
962
963 RWLockReadLock(s_userDatabaseLock, INFINITE);
964 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
965 while(it->hasNext())
966 {
967 UserDatabaseObject *object = it->next();
968 if (!object->isGroup())
969 {
970 TCHAR szGUID[64];
971 UINT64 systemRights = GetEffectiveSystemRights((User *)object);
972 ConsolePrintf(pCtx, _T("%-20s %-36s 0x") UINT64X_FMT(_T("016")) _T("\n"), object->getName(), object->getGuidAsText(szGUID), systemRights);
973 }
974 }
975 delete it;
976 RWLockUnlock(s_userDatabaseLock);
977 ConsolePrintf(pCtx, _T("\n"));
978 }
979
980 /**
981 * Delete user or group
982 *
983 * @param id user database object ID
984 * @return RCC ready to be sent to client
985 */
986 UINT32 NXCORE_EXPORTABLE DeleteUserDatabaseObject(UINT32 id, bool alreadyLocked)
987 {
988 DeleteUserFromAllObjects(id);
989
990 if (!alreadyLocked)
991 RWLockWriteLock(s_userDatabaseLock, INFINITE);
992
993 UserDatabaseObject *object = s_userDatabase.get(id);
994 if (object != NULL)
995 {
996 object->setDeleted();
997 if (!(id & GROUP_FLAG))
998 {
999 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
1000 while(it->hasNext())
1001 {
1002 UserDatabaseObject *group = it->next();
1003 if (group->getId() & GROUP_FLAG)
1004 {
1005 ((Group *)group)->deleteUser(id);
1006 }
1007 }
1008 delete it;
1009 }
1010 }
1011
1012 if (!alreadyLocked)
1013 RWLockUnlock(s_userDatabaseLock);
1014
1015 SendUserDBUpdate(USER_DB_DELETE, id, NULL);
1016 return RCC_SUCCESS;
1017 }
1018
1019 /**
1020 * Create new user or group
1021 */
1022 UINT32 NXCORE_EXPORTABLE CreateNewUser(const TCHAR *name, bool isGroup, UINT32 *id)
1023 {
1024 UINT32 dwResult = RCC_SUCCESS;
1025
1026 RWLockWriteLock(s_userDatabaseLock, INFINITE);
1027
1028 // Check for duplicate name
1029 UserDatabaseObject *object = isGroup ? (UserDatabaseObject *)s_groups.get(name) : (UserDatabaseObject *)s_users.get(name);
1030 if (object != NULL)
1031 {
1032 dwResult = RCC_OBJECT_ALREADY_EXISTS;
1033 }
1034
1035 if (dwResult == RCC_SUCCESS)
1036 {
1037 if (isGroup)
1038 {
1039 object = new Group(CreateUniqueId(IDG_USER_GROUP), name);
1040 }
1041 else
1042 {
1043 object = new User(CreateUniqueId(IDG_USER), name);
1044 }
1045 AddDatabaseObject(object);
1046 SendUserDBUpdate(USER_DB_CREATE, object->getId(), object);
1047 *id = object->getId();
1048 }
1049
1050 RWLockUnlock(s_userDatabaseLock);
1051 return dwResult;
1052 }
1053
1054 /**
1055 * Modify user database object
1056 */
1057 UINT32 NXCORE_EXPORTABLE ModifyUserDatabaseObject(NXCPMessage *msg, json_t **oldData, json_t **newData)
1058 {
1059 UINT32 dwResult = RCC_INVALID_USER_ID;
1060
1061 UINT32 id = msg->getFieldAsUInt32(VID_USER_ID);
1062
1063 RWLockWriteLock(s_userDatabaseLock, INFINITE);
1064
1065 UserDatabaseObject *object = s_userDatabase.get(id);
1066 if (object != NULL)
1067 {
1068 TCHAR name[MAX_USER_NAME], prevName[MAX_USER_NAME];
1069
1070 UINT32 fields = msg->getFieldAsUInt32(VID_FIELDS);
1071 if (fields & USER_MODIFY_LOGIN_NAME)
1072 {
1073 msg->getFieldAsString(VID_USER_NAME, name, MAX_USER_NAME);
1074 if (IsValidObjectName(name))
1075 {
1076 nx_strncpy(prevName, object->getName(), MAX_USER_NAME);
1077 }
1078 else
1079 {
1080 dwResult = RCC_INVALID_OBJECT_NAME;
1081 }
1082 }
1083
1084 if (dwResult != RCC_INVALID_OBJECT_NAME)
1085 {
1086 *oldData = object->toJson();
1087 object->modifyFromMessage(msg);
1088 *newData = object->toJson();
1089 SendUserDBUpdate(USER_DB_MODIFY, id, object);
1090 dwResult = RCC_SUCCESS;
1091 }
1092
1093 if ((dwResult == RCC_SUCCESS) && (fields & USER_MODIFY_LOGIN_NAME))
1094 {
1095 // update login names hash map if login name was modified
1096 if (_tcscmp(prevName, object->getName()))
1097 {
1098 if (object->isGroup())
1099 {
1100 nxlog_debug(4, _T("Group rename: %s -> %s"), prevName, object->getName());
1101 s_groups.remove(prevName);
1102 s_groups.set(object->getName(), (Group *)object);
1103 }
1104 else
1105 {
1106 nxlog_debug(4, _T("User rename: %s -> %s"), prevName, object->getName());
1107 s_users.remove(prevName);
1108 s_users.set(object->getName(), (User *)object);
1109 }
1110 }
1111 }
1112 }
1113
1114 RWLockUnlock(s_userDatabaseLock);
1115 return dwResult;
1116 }
1117
1118 /**
1119 * Modify user database object
1120 */
1121 UINT32 NXCORE_EXPORTABLE DetachLdapUser(UINT32 id)
1122 {
1123 UINT32 dwResult = RCC_INVALID_USER_ID;
1124
1125 RWLockWriteLock(s_userDatabaseLock, INFINITE);
1126
1127 UserDatabaseObject *object = s_userDatabase.get(id);
1128 if (object != NULL)
1129 {
1130 s_ldapNames.remove(object->getDn());
1131 object->detachLdapUser();
1132 SendUserDBUpdate(USER_DB_MODIFY, id, object);
1133 dwResult = RCC_SUCCESS;
1134 }
1135
1136 RWLockUnlock(s_userDatabaseLock);
1137 return dwResult;
1138 }
1139
1140 /**
1141 * Send user DB update for given user ID.
1142 * Access to user database must be already locked.
1143 */
1144 void SendUserDBUpdate(int code, UINT32 id)
1145 {
1146 UserDatabaseObject *object = s_userDatabase.get(id);
1147 if (object != NULL)
1148 {
1149 SendUserDBUpdate(code, id, object);
1150 }
1151 }
1152
1153 /**
1154 * Check if string contains subsequence of given sequence
1155 */
1156 static bool IsStringContainsSubsequence(const TCHAR *str, const TCHAR *sequence, int len)
1157 {
1158 int sequenceLen = (int)_tcslen(sequence);
1159 if ((sequenceLen < len) || (len > 255))
1160 return false;
1161
1162 TCHAR subseq[256];
1163 for(int i = 0; i < sequenceLen - len; i++)
1164 {
1165 nx_strncpy(subseq, &sequence[i], len + 1);
1166 if (_tcsstr(str, subseq) != NULL)
1167 return true;
1168 }
1169
1170 return false;
1171 }
1172
1173 /**
1174 * Check password's complexity
1175 */
1176 static bool CheckPasswordComplexity(const TCHAR *password)
1177 {
1178 int flags = ConfigReadInt(_T("PasswordComplexity"), 0);
1179
1180 if ((flags & PSWD_MUST_CONTAIN_DIGITS) && (_tcspbrk(password, _T("0123456789")) == NULL))
1181 return false;
1182
1183 if ((flags & PSWD_MUST_CONTAIN_UPPERCASE) && (_tcspbrk(password, _T("ABCDEFGHIJKLMNOPQRSTUVWXYZ")) == NULL))
1184 return false;
1185
1186 if ((flags & PSWD_MUST_CONTAIN_LOWERCASE) && (_tcspbrk(password, _T("abcdefghijklmnopqrstuvwxyz")) == NULL))
1187 return false;
1188
1189 if ((flags & PSWD_MUST_CONTAIN_SPECIAL_CHARS) && (_tcspbrk(password, _T("`~!@#$%^&*()_-=+{}[]|\\'\";:,.<>/?")) == NULL))
1190 return false;
1191
1192 if (flags & PSWD_FORBID_ALPHABETICAL_SEQUENCE)
1193 {
1194 if (IsStringContainsSubsequence(password, _T("ABCDEFGHIJKLMNOPQRSTUVWXYZ"), 3))
1195 return false;
1196 if (IsStringContainsSubsequence(password, _T("abcdefghijklmnopqrstuvwxyz"), 3))
1197 return false;
1198 }
1199
1200 if (flags & PSWD_FORBID_KEYBOARD_SEQUENCE)
1201 {
1202 if (IsStringContainsSubsequence(password, _T("~!@#$%^&*()_+"), 3))
1203 return false;
1204 if (IsStringContainsSubsequence(password, _T("1234567890-="), 3))
1205 return false;
1206 if (IsStringContainsSubsequence(password, _T("qwertyuiop[]"), 3))
1207 return false;
1208 if (IsStringContainsSubsequence(password, _T("asdfghjkl;'"), 3))
1209 return false;
1210 if (IsStringContainsSubsequence(password, _T("zxcvbnm,./"), 3))
1211 return false;
1212 if (IsStringContainsSubsequence(password, _T("QWERTYUIOP{}"), 3))
1213 return false;
1214 if (IsStringContainsSubsequence(password, _T("ASDFGHJKL:\""), 3))
1215 return false;
1216 if (IsStringContainsSubsequence(password, _T("ZXCVBNM<>?"), 3))
1217 return false;
1218 }
1219
1220 return true;
1221 }
1222
1223 /**
1224 * Set user's password
1225 * For non-UNICODE build, passwords must be UTF-8 encoded
1226 */
1227 UINT32 NXCORE_EXPORTABLE SetUserPassword(UINT32 id, const TCHAR *newPassword, const TCHAR *oldPassword, bool changeOwnPassword)
1228 {
1229 if (id & GROUP_FLAG)
1230 return RCC_INVALID_USER_ID;
1231
1232 RWLockWriteLock(s_userDatabaseLock, INFINITE);
1233
1234 // Find user
1235 User *user = (User *)s_userDatabase.get(id);
1236 if (user == NULL)
1237 {
1238 RWLockUnlock(s_userDatabaseLock);
1239 return RCC_INVALID_USER_ID;
1240 }
1241
1242 UINT32 dwResult = RCC_INVALID_USER_ID;
1243 if (changeOwnPassword)
1244 {
1245 if (!user->canChangePassword() || !user->validatePassword(oldPassword))
1246 {
1247 dwResult = RCC_ACCESS_DENIED;
1248 goto finish;
1249 }
1250
1251 // Check password length
1252 int minLength = (user->getMinMasswordLength() == -1) ? ConfigReadInt(_T("MinPasswordLength"), 0) : user->getMinMasswordLength();
1253 if ((int)_tcslen(newPassword) < minLength)
1254 {
1255 dwResult = RCC_WEAK_PASSWORD;
1256 goto finish;
1257 }
1258
1259 // Check password complexity
1260 if (!CheckPasswordComplexity(newPassword))
1261 {
1262 dwResult = RCC_WEAK_PASSWORD;
1263 goto finish;
1264 }
1265
1266 // Update password history
1267 int passwordHistoryLength = ConfigReadInt(_T("PasswordHistoryLength"), 0);
1268 if (passwordHistoryLength > 0)
1269 {
1270 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
1271
1272 TCHAR query[8192], *ph = NULL;
1273
1274 _sntprintf(query, 8192, _T("SELECT password_history FROM users WHERE id=%d"), id);
1275 DB_RESULT hResult = DBSelect(hdb, query);
1276 if (hResult != NULL)
1277 {
1278 if (DBGetNumRows(hResult) > 0)
1279 {
1280 ph = DBGetField(hResult, 0, 0, NULL, 0);
1281 }
1282 DBFreeResult(hResult);
1283 }
1284
1285 if (ph != NULL)
1286 {
1287 BYTE newPasswdHash[SHA1_DIGEST_SIZE];
1288 #ifdef UNICODE
1289 char *mb = UTF8StringFromWideString(newPassword);
1290 CalculateSHA1Hash((BYTE *)mb, strlen(mb), newPasswdHash);
1291 free(mb);
1292 #else
1293 CalculateSHA1Hash((BYTE *)newPassword, strlen(newPassword), newPasswdHash);
1294 #endif
1295
1296 int phLen = (int)_tcslen(ph) / (SHA1_DIGEST_SIZE * 2);
1297 if (phLen > passwordHistoryLength)
1298 phLen = passwordHistoryLength;
1299
1300 for(int i = 0; i < phLen; i++)
1301 {
1302 BYTE hash[SHA1_DIGEST_SIZE];
1303 StrToBin(&ph[i * SHA1_DIGEST_SIZE * 2], hash, SHA1_DIGEST_SIZE);
1304 if (!memcmp(hash, newPasswdHash, SHA1_DIGEST_SIZE))
1305 {
1306 dwResult = RCC_REUSED_PASSWORD;
1307 goto finish;
1308 }
1309 }
1310
1311 if (dwResult != RCC_REUSED_PASSWORD)
1312 {
1313 if (phLen == passwordHistoryLength)
1314 {
1315 memmove(ph, &ph[SHA1_DIGEST_SIZE * 2], (phLen - 1) * SHA1_DIGEST_SIZE * 2 * sizeof(TCHAR));
1316 }
1317 else
1318 {
1319 ph = (TCHAR *)realloc(ph, (phLen + 1) * SHA1_DIGEST_SIZE * 2 * sizeof(TCHAR) + sizeof(TCHAR));
1320 phLen++;
1321 }
1322 BinToStr(newPasswdHash, SHA1_DIGEST_SIZE, &ph[(phLen - 1) * SHA1_DIGEST_SIZE * 2]);
1323
1324 _sntprintf(query, 8192, _T("UPDATE users SET password_history='%s' WHERE id=%d"), ph, id);
1325 DBQuery(hdb, query);
1326 }
1327
1328 free(ph);
1329 if (dwResult == RCC_REUSED_PASSWORD)
1330 goto finish;
1331 }
1332 else
1333 {
1334 dwResult = RCC_DB_FAILURE;
1335 goto finish;
1336 }
1337 DBConnectionPoolReleaseConnection(hdb);
1338 }
1339
1340 user->updatePasswordChangeTime();
1341 }
1342 user->setPassword(newPassword, changeOwnPassword);
1343 dwResult = RCC_SUCCESS;
1344
1345 finish:
1346 RWLockUnlock(s_userDatabaseLock);
1347 return dwResult;
1348 }
1349
1350 /**
1351 * Validate user's password
1352 */
1353 UINT32 NXCORE_EXPORTABLE ValidateUserPassword(UINT32 userId, const TCHAR *login, const TCHAR *password, bool *isValid)
1354 {
1355 if (userId & GROUP_FLAG)
1356 return RCC_INVALID_USER_ID;
1357
1358 UINT32 rcc = RCC_INVALID_USER_ID;
1359 RWLockReadLock(s_userDatabaseLock, INFINITE);
1360
1361 // Find user
1362 User *user = (User *)s_userDatabase.get(userId);
1363 if (user != NULL)
1364 {
1365 rcc = RCC_SUCCESS;
1366 if (user->isLDAPUser())
1367 {
1368 if (user->isDisabled())
1369 {
1370 rcc = RCC_ACCOUNT_DISABLED;
1371 }
1372 else
1373 {
1374 LDAPConnection conn;
1375 rcc = conn.ldapUserLogin(user->getDn(), password);
1376 if (rcc == RCC_SUCCESS)
1377 {
1378 *isValid = true;
1379 }
1380 else if (rcc == RCC_ACCESS_DENIED)
1381 {
1382 *isValid = false;
1383 rcc = RCC_SUCCESS;
1384 }
1385 }
1386 }
1387 else
1388 {
1389 switch(user->getAuthMethod())
1390 {
1391 case AUTH_NETXMS_PASSWORD:
1392 case AUTH_CERT_OR_PASSWD:
1393 *isValid = user->validatePassword(password);
1394 break;
1395 case AUTH_RADIUS:
1396 case AUTH_CERT_OR_RADIUS:
1397 *isValid = RadiusAuth(login, password);
1398 break;
1399 default:
1400 rcc = RCC_UNSUPPORTED_AUTH_METHOD;
1401 break;
1402 }
1403 }
1404 }
1405
1406 RWLockUnlock(s_userDatabaseLock);
1407 return rcc;
1408 }
1409
1410 /**
1411 * Open user database
1412 */
1413 Iterator<UserDatabaseObject> NXCORE_EXPORTABLE *OpenUserDatabase()
1414 {
1415 RWLockReadLock(s_userDatabaseLock, INFINITE);
1416 return s_userDatabase.iterator();
1417 }
1418
1419 /**
1420 * Close user database
1421 */
1422 void NXCORE_EXPORTABLE CloseUserDatabase(Iterator<UserDatabaseObject> *it)
1423 {
1424 delete it;
1425 RWLockUnlock(s_userDatabaseLock);
1426 }
1427
1428 /**
1429 * Get custom attribute's value
1430 */
1431 const TCHAR NXCORE_EXPORTABLE *GetUserDbObjectAttr(UINT32 id, const TCHAR *name)
1432 {
1433 const TCHAR *value = NULL;
1434
1435 RWLockReadLock(s_userDatabaseLock, INFINITE);
1436
1437 UserDatabaseObject *object = s_userDatabase.get(id);
1438 if (object != NULL)
1439 value = object->getAttribute(name);
1440
1441 RWLockUnlock(s_userDatabaseLock);
1442 return value;
1443 }
1444
1445 /**
1446 * Get custom attribute's value as unsigned integer
1447 */
1448 UINT32 NXCORE_EXPORTABLE GetUserDbObjectAttrAsULong(UINT32 id, const TCHAR *name)
1449 {
1450 const TCHAR *value = GetUserDbObjectAttr(id, name);
1451 return (value != NULL) ? _tcstoul(value, NULL, 0) : 0;
1452 }
1453
1454 /**
1455 * Set custom attribute's value
1456 */
1457 void NXCORE_EXPORTABLE SetUserDbObjectAttr(UINT32 id, const TCHAR *name, const TCHAR *value)
1458 {
1459 RWLockWriteLock(s_userDatabaseLock, INFINITE);
1460
1461 UserDatabaseObject *object = s_userDatabase.get(id);
1462 if (object != NULL)
1463 {
1464 object->setAttribute(name, value);
1465 }
1466
1467 RWLockUnlock(s_userDatabaseLock);
1468 }
1469
1470 /**
1471 * Authenticate user for XMPP subscription
1472 */
1473 bool AuthenticateUserForXMPPSubscription(const char *xmppId)
1474 {
1475 if (*xmppId == 0)
1476 return false;
1477
1478 bool success = false;
1479
1480 #ifdef UNICODE
1481 WCHAR *_xmppId = WideStringFromUTF8String(xmppId);
1482 #else
1483 char *_xmppId = strdup(xmppId);
1484 #endif
1485
1486 // Remove possible resource at the end
1487 TCHAR *sep = _tcschr(_xmppId, _T('/'));
1488 if (sep != NULL)
1489 *sep = 0;
1490
1491 RWLockReadLock(s_userDatabaseLock, INFINITE);
1492 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
1493 while(it->hasNext())
1494 {
1495 UserDatabaseObject *object = it->next();
1496 if (!object->isGroup() &&
1497 !object->isDisabled() && !object->isDeleted() &&
1498 !_tcsicmp(_xmppId, ((User *)object)->getXmppId()))
1499 {
1500 DbgPrintf(4, _T("User %s authenticated for XMPP subscription"), object->getName());
1501
1502 TCHAR workstation[256];
1503 _tcscpy(workstation, _T("XMPP:"));
1504 nx_strncpy(&workstation[5], _xmppId, 251);
1505 WriteAuditLog(AUDIT_SECURITY, TRUE, object->getId(), workstation, AUDIT_SYSTEM_SID, 0, _T("User authenticated for XMPP subscription"));
1506
1507 success = true;
1508 break;
1509 }
1510 }
1511 delete it;
1512 RWLockUnlock(s_userDatabaseLock);
1513
1514 free(_xmppId);
1515 return success;
1516 }
1517
1518 /**
1519 * Authenticate user for XMPP commands
1520 */
1521 bool AuthenticateUserForXMPPCommands(const char *xmppId)
1522 {
1523 if (*xmppId == 0)
1524 return false;
1525
1526 bool success = false;
1527
1528 #ifdef UNICODE
1529 WCHAR *_xmppId = WideStringFromUTF8String(xmppId);
1530 #else
1531 char *_xmppId = strdup(xmppId);
1532 #endif
1533
1534 // Remove possible resource at the end
1535 TCHAR *sep = _tcschr(_xmppId, _T('/'));
1536 if (sep != NULL)
1537 *sep = 0;
1538
1539 RWLockReadLock(s_userDatabaseLock, INFINITE);
1540 Iterator<UserDatabaseObject> *it = s_userDatabase.iterator();
1541 while(it->hasNext())
1542 {
1543 UserDatabaseObject *object = it->next();
1544 if (!object->isGroup() &&
1545 !object->isDisabled() && !object->isDeleted() &&
1546 !_tcsicmp(_xmppId, ((User *)object)->getXmppId()))
1547 {
1548 UINT64 systemRights = GetEffectiveSystemRights((User *)object);
1549
1550 TCHAR workstation[256];
1551 _tcscpy(workstation, _T("XMPP:"));
1552 nx_strncpy(&workstation[5], _xmppId, 251);
1553
1554 if (systemRights & SYSTEM_ACCESS_XMPP_COMMANDS)
1555 {
1556 DbgPrintf(4, _T("User %s authenticated for XMPP commands"), object->getName());
1557 WriteAuditLog(AUDIT_SECURITY, TRUE, object->getId(), workstation, AUDIT_SYSTEM_SID, 0, _T("User authenticated for XMPP commands"));
1558 success = true;
1559 }
1560 else
1561 {
1562 DbgPrintf(4, _T("Access to XMPP commands denied for user %s"), object->getName());
1563 WriteAuditLog(AUDIT_SECURITY, FALSE, object->getId(), workstation, AUDIT_SYSTEM_SID, 0, _T("Access to XMPP commands denied"));
1564 }
1565 break;
1566 }
1567 }
1568 delete it;
1569 RWLockUnlock(s_userDatabaseLock);
1570
1571 free(_xmppId);
1572 return success;
1573 }