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