a94ff0793874f2d8c79b60b9bbb787f8b9cad710
[public/netxms.git] / src / server / core / userdb_objects.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2012 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_objects.cpp
20 **/
21
22 #include "nxcore.h"
23
24 /**
25 * Compare user IDs (for qsort)
26 */
27 static int CompareUserId(const void *e1, const void *e2)
28 {
29 UINT32 id1 = *((UINT32 *)e1);
30 UINT32 id2 = *((UINT32 *)e2);
31 return (id1 < id2) ? -1 : ((id1 > id2) ? 1 : 0);
32 }
33
34 /**
35 * Generate hash for password
36 */
37 static void CalculatePasswordHash(const TCHAR *password, PasswordHashType type, PasswordHash *ph, const BYTE *salt = NULL)
38 {
39 #ifdef UNICODE
40 char mbPassword[1024];
41 WideCharToMultiByte(CP_UTF8, 0, password, -1, mbPassword, 1024, NULL, NULL);
42 mbPassword[1023] = 0;
43 #else
44 const char *mbPassword = password;
45 #endif
46
47 BYTE buffer[1024];
48
49 memset(ph, 0, sizeof(PasswordHash));
50 ph->hashType = type;
51 switch(type)
52 {
53 case PWD_HASH_SHA1:
54 CalculateSHA1Hash((BYTE *)mbPassword, strlen(mbPassword), ph->hash);
55 break;
56 case PWD_HASH_SHA256:
57 if (salt != NULL)
58 memcpy(buffer, salt, PASSWORD_SALT_LENGTH);
59 else
60 GenerateRandomBytes(buffer, PASSWORD_SALT_LENGTH);
61 strcpy((char *)&buffer[PASSWORD_SALT_LENGTH], mbPassword);
62 CalculateSHA256Hash(buffer, strlen(mbPassword) + PASSWORD_SALT_LENGTH, ph->hash);
63 memcpy(ph->salt, buffer, PASSWORD_SALT_LENGTH);
64 break;
65 default:
66 break;
67 }
68 }
69
70 /*****************************************************************************
71 ** UserDatabaseObject
72 ****************************************************************************/
73
74 /**
75 * Constructor for generic user database object - create from database
76 * Expects fields in the following order:
77 * id,name,system_access,flags,description,guid,ldap_dn
78 */
79 UserDatabaseObject::UserDatabaseObject(DB_HANDLE hdb, DB_RESULT hResult, int row)
80 {
81 m_id = DBGetFieldULong(hResult, row, 0);
82 DBGetField(hResult, row, 1, m_name, MAX_USER_NAME);
83 m_systemRights = DBGetFieldUInt64(hResult, row, 2);
84 m_flags = DBGetFieldULong(hResult, row, 3);
85 DBGetField(hResult, row, 4, m_description, MAX_USER_DESCR);
86 m_guid = DBGetFieldGUID(hResult, row, 5);
87 m_userDn = DBGetField(hResult, row, 6, NULL, 0);
88 }
89
90 /**
91 * Default constructor for generic object
92 */
93 UserDatabaseObject::UserDatabaseObject()
94 {
95 m_id = 0;
96 m_guid = uuid::generate();
97 m_name[0] = 0;
98 m_userDn = NULL;
99 m_systemRights = 0;
100 m_description[0] = 0;
101 m_flags = 0;
102 }
103
104 /**
105 * Constructor for generic object - create new object with given id and name
106 */
107 UserDatabaseObject::UserDatabaseObject(UINT32 id, const TCHAR *name)
108 {
109 m_id = id;
110 m_guid = uuid::generate();
111 nx_strncpy(m_name, name, MAX_USER_NAME);
112 m_systemRights = 0;
113 m_description[0] = 0;
114 m_flags = UF_MODIFIED;
115 m_userDn = NULL;
116 }
117
118 /**
119 * Destructor for generic user database object
120 */
121 UserDatabaseObject::~UserDatabaseObject()
122 {
123 safe_free(m_userDn);
124 }
125
126 /**
127 * Save object to database
128 */
129 bool UserDatabaseObject::saveToDatabase(DB_HANDLE hdb)
130 {
131 return false;
132 }
133
134 /**
135 * Delete object from database
136 */
137 bool UserDatabaseObject::deleteFromDatabase(DB_HANDLE hdb)
138 {
139 return false;
140 }
141
142 /**
143 * Fill NXCP message with object data
144 */
145 void UserDatabaseObject::fillMessage(NXCPMessage *msg)
146 {
147 msg->setField(VID_USER_ID, m_id);
148 msg->setField(VID_USER_NAME, m_name);
149 msg->setField(VID_USER_FLAGS, (WORD)m_flags);
150 msg->setField(VID_USER_SYS_RIGHTS, m_systemRights);
151 msg->setField(VID_USER_DESCRIPTION, m_description);
152 msg->setField(VID_GUID, m_guid);
153 m_attributes.fillMessage(msg, VID_NUM_CUSTOM_ATTRIBUTES, VID_CUSTOM_ATTRIBUTES_BASE);
154 }
155
156 /**
157 * Modify object from NXCP message
158 */
159 void UserDatabaseObject::modifyFromMessage(NXCPMessage *msg)
160 {
161 UINT32 flags, fields;
162
163 fields = msg->getFieldAsUInt32(VID_FIELDS);
164 DbgPrintf(5, _T("UserDatabaseObject::modifyFromMessage(): id=%d fields=%08X"), m_id, fields);
165
166 if (fields & USER_MODIFY_DESCRIPTION)
167 msg->getFieldAsString(VID_USER_DESCRIPTION, m_description, MAX_USER_DESCR);
168 if (fields & USER_MODIFY_LOGIN_NAME)
169 msg->getFieldAsString(VID_USER_NAME, m_name, MAX_USER_NAME);
170
171 // Update custom attributes only if VID_NUM_CUSTOM_ATTRIBUTES exist -
172 // older client versions may not be aware of custom attributes
173 if ((fields & USER_MODIFY_CUSTOM_ATTRIBUTES) || msg->isFieldExist(VID_NUM_CUSTOM_ATTRIBUTES))
174 {
175 UINT32 i, varId, count;
176 TCHAR *name, *value;
177
178 count = msg->getFieldAsUInt32(VID_NUM_CUSTOM_ATTRIBUTES);
179 m_attributes.clear();
180 for(i = 0, varId = VID_CUSTOM_ATTRIBUTES_BASE; i < count; i++)
181 {
182 name = msg->getFieldAsString(varId++);
183 value = msg->getFieldAsString(varId++);
184 m_attributes.setPreallocated((name != NULL) ? name : _tcsdup(_T("")), (value != NULL) ? value : _tcsdup(_T("")));
185 }
186 }
187
188 if ((m_id != 0) && (fields & USER_MODIFY_ACCESS_RIGHTS))
189 m_systemRights = msg->getFieldAsUInt64(VID_USER_SYS_RIGHTS);
190
191 if (fields & USER_MODIFY_FLAGS)
192 {
193 flags = msg->getFieldAsUInt16(VID_USER_FLAGS);
194 // Modify only UF_DISABLED, UF_CHANGE_PASSWORD, UF_CANNOT_CHANGE_PASSWORD and UF_LDAP_USER flags from message
195 // Ignore all but CHANGE_PASSWORD flag for superuser and "everyone" group
196 m_flags &= ~(UF_DISABLED | UF_CHANGE_PASSWORD | UF_CANNOT_CHANGE_PASSWORD);
197 if ((m_id == 0) || (m_id == GROUP_EVERYONE))
198 m_flags |= flags & UF_CHANGE_PASSWORD;
199 else
200 m_flags |= flags & (UF_DISABLED | UF_CHANGE_PASSWORD | UF_CANNOT_CHANGE_PASSWORD);
201
202 }
203
204 m_flags |= UF_MODIFIED;
205 }
206
207 void UserDatabaseObject::detachLdapUser()
208 {
209 m_flags &= ~UF_LDAP_USER;
210 setDn(NULL);
211 m_flags |= UF_MODIFIED;
212 }
213
214 /**
215 * Load custom attributes from database
216 */
217 bool UserDatabaseObject::loadCustomAttributes(DB_HANDLE hdb)
218 {
219 DB_RESULT hResult;
220 TCHAR query[256], *attrName, *attrValue;
221 bool success = false;
222
223 _sntprintf(query, 256, _T("SELECT attr_name,attr_value FROM userdb_custom_attributes WHERE object_id=%d"), m_id);
224 hResult = DBSelect(hdb, query);
225 if (hResult != NULL)
226 {
227 int count = DBGetNumRows(hResult);
228 for(int i = 0; i < count; i++)
229 {
230 attrName = DBGetField(hResult, i, 0, NULL, 0);
231 if (attrName == NULL)
232 attrName = _tcsdup(_T(""));
233
234 attrValue = DBGetField(hResult, i, 1, NULL, 0);
235 if (attrValue == NULL)
236 attrValue = _tcsdup(_T(""));
237
238 m_attributes.setPreallocated(attrName, attrValue);
239 }
240 DBFreeResult(hResult);
241 success = true;
242 }
243 return success;
244 }
245
246 /**
247 * Callback for saving custom attribute in database
248 */
249 static EnumerationCallbackResult SaveAttributeCallback(const TCHAR *key, const void *value, void *data)
250 {
251 DB_STATEMENT hStmt = (DB_STATEMENT)data;
252 DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, key, DB_BIND_STATIC);
253 DBBind(hStmt, 3, DB_SQLTYPE_VARCHAR, (const TCHAR *)value, DB_BIND_STATIC);
254 return DBExecute(hStmt) ? _CONTINUE : _STOP;
255 }
256
257 /**
258 * Save custom attributes to database
259 */
260 bool UserDatabaseObject::saveCustomAttributes(DB_HANDLE hdb)
261 {
262 TCHAR query[256];
263 bool success = false;
264
265 _sntprintf(query, 256, _T("DELETE FROM userdb_custom_attributes WHERE object_id=%d"), m_id);
266 if (DBQuery(hdb, query))
267 {
268 DB_STATEMENT hStmt = DBPrepare(hdb, _T("INSERT INTO userdb_custom_attributes (object_id,attr_name,attr_value) VALUES (?,?,?)"));
269 if (hStmt != NULL)
270 {
271 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
272 success = (m_attributes.forEach(SaveAttributeCallback, hStmt) == _CONTINUE);
273 DBFreeStatement(hStmt);
274 }
275 }
276 return success;
277 }
278
279 /**
280 * Get custom attribute as UINT32
281 */
282 UINT32 UserDatabaseObject::getAttributeAsULong(const TCHAR *name)
283 {
284 const TCHAR *value = getAttribute(name);
285 return (value != NULL) ? _tcstoul(value, NULL, 0) : 0;
286 }
287
288 void UserDatabaseObject::setDescription(const TCHAR *description)
289 {
290 if (_tcscmp(CHECK_NULL_EX(m_description), CHECK_NULL_EX(description)))
291 {
292 nx_strncpy(m_description, CHECK_NULL_EX(description), MAX_USER_DESCR);
293 m_flags |= UF_MODIFIED;
294 }
295 }
296
297 void UserDatabaseObject::setName(const TCHAR *name)
298 {
299 if (_tcscmp(CHECK_NULL_EX(m_name), CHECK_NULL_EX(name)))
300 {
301 nx_strncpy(m_name, CHECK_NULL_EX(name), MAX_USER_NAME);
302 m_flags |= UF_MODIFIED;
303 }
304 }
305
306 void UserDatabaseObject::setDn(const TCHAR *dn)
307 {
308 free(m_userDn);
309 m_userDn = _tcsdup_ex(dn);
310 m_flags |= UF_MODIFIED;
311 }
312
313 /**
314 * Disable user account because of sync exception
315 */
316 void UserDatabaseObject::setSyncException()
317 {
318 m_flags |= UF_SYNC_EXCEPTION | UF_MODIFIED;
319 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
320 }
321
322 void UserDatabaseObject::removeSyncException()
323 {
324 if((m_flags & UF_SYNC_EXCEPTION)> 0)
325 {
326 m_flags &= ~UF_SYNC_EXCEPTION;
327 m_flags |= UF_MODIFIED;
328 }
329 }
330
331 /**
332 * Enable user account
333 */
334 void UserDatabaseObject::enable()
335 {
336 m_flags &= ~(UF_DISABLED);
337 m_flags |= UF_MODIFIED;
338 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
339 }
340
341 /**
342 * Disable user account
343 */
344 void UserDatabaseObject::disable()
345 {
346 m_flags |= UF_DISABLED | UF_MODIFIED;
347 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
348 }
349
350 /*****************************************************************************
351 ** User
352 ****************************************************************************/
353
354 /**
355 * Constructor for user object - create from database
356 * Expects fields in the following order:
357 * id,name,system_access,flags,description,guid,ldap_dn,password,full_name,
358 * grace_logins,auth_method,cert_mapping_method,cert_mapping_data,
359 * auth_failures,last_passwd_change,min_passwd_length,disabled_until,
360 * last_login,xmpp_id
361 */
362 User::User(DB_HANDLE hdb, DB_RESULT hResult, int row) : UserDatabaseObject(hdb, hResult, row)
363 {
364 TCHAR buffer[256];
365
366 bool validHash = false;
367 DBGetField(hResult, row, 7, buffer, 256);
368 if (buffer[0] == _T('$'))
369 {
370 // new format - with hash type indicator
371 if (buffer[1] == 'A')
372 {
373 m_password.hashType = PWD_HASH_SHA256;
374 if (_tcslen(buffer) >= 82)
375 {
376 if ((StrToBin(&buffer[2], m_password.salt, PASSWORD_SALT_LENGTH) == PASSWORD_SALT_LENGTH) &&
377 (StrToBin(&buffer[18], m_password.hash, SHA256_DIGEST_SIZE) == SHA256_DIGEST_SIZE))
378 validHash = true;
379 }
380 }
381 }
382 else
383 {
384 // old format - SHA1 hash without salt
385 m_password.hashType = PWD_HASH_SHA1;
386 if (StrToBin(buffer, m_password.hash, SHA1_DIGEST_SIZE) == SHA1_DIGEST_SIZE)
387 validHash = true;
388 }
389 if (!validHash)
390 {
391 nxlog_write(MSG_INVALID_PASSWORD_HASH, NXLOG_WARNING, "s", m_name);
392 CalculatePasswordHash(_T("netxms"), PWD_HASH_SHA256, &m_password);
393 m_flags |= UF_MODIFIED | UF_CHANGE_PASSWORD;
394 }
395
396 DBGetField(hResult, row, 8, m_fullName, MAX_USER_FULLNAME);
397 m_graceLogins = DBGetFieldLong(hResult, row, 9);
398 m_authMethod = DBGetFieldLong(hResult, row, 10);
399 m_certMappingMethod = DBGetFieldLong(hResult, row, 11);
400 m_certMappingData = DBGetField(hResult, row, 12, NULL, 0);
401 m_authFailures = DBGetFieldLong(hResult, row, 13);
402 m_lastPasswordChange = (time_t)DBGetFieldLong(hResult, row, 14);
403 m_minPasswordLength = DBGetFieldLong(hResult, row, 15);
404 m_disabledUntil = (time_t)DBGetFieldLong(hResult, row, 16);
405 m_lastLogin = (time_t)DBGetFieldLong(hResult, row, 17);
406 DBGetField(hResult, row, 18, m_xmppId, MAX_XMPP_ID_LEN);
407
408 // Set full system access for superuser
409 if (m_id == 0)
410 m_systemRights = SYSTEM_ACCESS_FULL;
411
412 loadCustomAttributes(hdb);
413 }
414
415 /**
416 * Constructor for user object - create default superuser
417 */
418 User::User() : UserDatabaseObject()
419 {
420 m_id = 0;
421 _tcscpy(m_name, _T("admin"));
422 m_flags = UF_MODIFIED | UF_CHANGE_PASSWORD;
423 m_systemRights = SYSTEM_ACCESS_FULL;
424 m_fullName[0] = 0;
425 _tcscpy(m_description, _T("Built-in system administrator account"));
426 CalculatePasswordHash(_T("netxms"), PWD_HASH_SHA256, &m_password);
427 m_graceLogins = MAX_GRACE_LOGINS;
428 m_authMethod = AUTH_NETXMS_PASSWORD;
429 m_certMappingMethod = USER_MAP_CERT_BY_CN;
430 m_certMappingData = NULL;
431 m_authFailures = 0;
432 m_lastPasswordChange = 0;
433 m_minPasswordLength = -1; // Use system-wide default
434 m_disabledUntil = 0;
435 m_lastLogin = 0;
436 m_xmppId[0] = 0;
437
438 }
439
440 /**
441 * Constructor for user object - new user
442 */
443 User::User(UINT32 id, const TCHAR *name) : UserDatabaseObject(id, name)
444 {
445 m_fullName[0] = 0;
446 m_graceLogins = MAX_GRACE_LOGINS;
447 m_authMethod = AUTH_NETXMS_PASSWORD;
448 m_certMappingMethod = USER_MAP_CERT_BY_CN;
449 m_certMappingData = NULL;
450 CalculatePasswordHash(_T(""), PWD_HASH_SHA256, &m_password);
451 m_authFailures = 0;
452 m_lastPasswordChange = 0;
453 m_minPasswordLength = -1; // Use system-wide default
454 m_disabledUntil = 0;
455 m_lastLogin = 0;
456 m_xmppId[0] = 0;
457 }
458
459 /**
460 * Destructor for user object
461 */
462 User::~User()
463 {
464 safe_free(m_certMappingData);
465 }
466
467 /**
468 * Save object to database
469 */
470 bool User::saveToDatabase(DB_HANDLE hdb)
471 {
472 TCHAR password[128];
473
474 // Clear modification flag
475 m_flags &= ~UF_MODIFIED;
476
477 // Create or update record in database
478 switch(m_password.hashType)
479 {
480 case PWD_HASH_SHA1:
481 BinToStr(m_password.hash, SHA1_DIGEST_SIZE, password);
482 break;
483 case PWD_HASH_SHA256:
484 _tcscpy(password, _T("$A"));
485 BinToStr(m_password.salt, PASSWORD_SALT_LENGTH, &password[2]);
486 BinToStr(m_password.hash, SHA256_DIGEST_SIZE, &password[18]);
487 break;
488 default:
489 _tcscpy(password, _T("$$"));
490 break;
491 }
492 DB_STATEMENT hStmt;
493 if (IsDatabaseRecordExist(hdb, _T("users"), _T("id"), m_id))
494 {
495 hStmt = DBPrepare(hdb,
496 _T("UPDATE users SET name=?,password=?,system_access=?,flags=?,full_name=?,description=?,grace_logins=?,guid=?,")
497 _T(" auth_method=?,cert_mapping_method=?,cert_mapping_data=?,auth_failures=?,last_passwd_change=?,")
498 _T(" min_passwd_length=?,disabled_until=?,last_login=?,xmpp_id=?,ldap_dn=? WHERE id=?"));
499 }
500 else
501 {
502 hStmt = DBPrepare(hdb,
503 _T("INSERT INTO users (name,password,system_access,flags,full_name,description,grace_logins,guid,auth_method,")
504 _T(" cert_mapping_method,cert_mapping_data,password_history,auth_failures,last_passwd_change,min_passwd_length,")
505 _T(" disabled_until,last_login,xmpp_id,ldap_dn,id) VALUES (?,?,?,?,?,?,?,?,?,?,?,'',?,?,?,?,?,?,?,?)"));
506 }
507 if (hStmt == NULL)
508 return false;
509
510 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, m_name, DB_BIND_STATIC);
511 DBBind(hStmt, 2, DB_SQLTYPE_VARCHAR, password, DB_BIND_STATIC);
512 DBBind(hStmt, 3, DB_SQLTYPE_BIGINT, m_systemRights);
513 DBBind(hStmt, 4, DB_SQLTYPE_INTEGER, m_flags);
514 DBBind(hStmt, 5, DB_SQLTYPE_VARCHAR, m_fullName, DB_BIND_STATIC);
515 DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, m_description, DB_BIND_STATIC);
516 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, m_graceLogins);
517 DBBind(hStmt, 8, DB_SQLTYPE_VARCHAR, m_guid);
518 DBBind(hStmt, 9, DB_SQLTYPE_INTEGER, m_authMethod);
519 DBBind(hStmt, 10, DB_SQLTYPE_INTEGER, m_certMappingMethod);
520 DBBind(hStmt, 11, DB_SQLTYPE_VARCHAR, m_certMappingData, DB_BIND_STATIC);
521 DBBind(hStmt, 12, DB_SQLTYPE_INTEGER, m_authFailures);
522 DBBind(hStmt, 13, DB_SQLTYPE_INTEGER, (UINT32)m_lastPasswordChange);
523 DBBind(hStmt, 14, DB_SQLTYPE_INTEGER, m_minPasswordLength);
524 DBBind(hStmt, 15, DB_SQLTYPE_INTEGER, (UINT32)m_disabledUntil);
525 DBBind(hStmt, 16, DB_SQLTYPE_INTEGER, (UINT32)m_lastLogin);
526 DBBind(hStmt, 17, DB_SQLTYPE_VARCHAR, m_xmppId, DB_BIND_STATIC);
527 DBBind(hStmt, 18, DB_SQLTYPE_TEXT, m_userDn, DB_BIND_STATIC);
528 DBBind(hStmt, 19, DB_SQLTYPE_INTEGER, m_id);
529
530 bool success = DBBegin(hdb);
531 if (success)
532 {
533 success = DBExecute(hStmt);
534 if (success)
535 {
536 success = saveCustomAttributes(hdb);
537 }
538 if (success)
539 DBCommit(hdb);
540 else
541 DBRollback(hdb);
542 }
543 DBFreeStatement(hStmt);
544 return success;
545 }
546
547 /**
548 * Delete object from database
549 */
550 bool User::deleteFromDatabase(DB_HANDLE hdb)
551 {
552 TCHAR query[256];
553 bool success;
554
555 success = DBBegin(hdb);
556 if (success)
557 {
558 _sntprintf(query, 256, _T("DELETE FROM users WHERE id=%d"), m_id);
559 success = DBQuery(hdb, query);
560 if (success)
561 {
562 _sntprintf(query, 256, _T("DELETE FROM user_profiles WHERE user_id=%d"), m_id);
563 success = DBQuery(hdb, query);
564 if (success)
565 {
566 _sntprintf(query, 256, _T("DELETE FROM userdb_custom_attributes WHERE object_id=%d"), m_id);
567 success = DBQuery(hdb, query);
568 }
569 }
570 if (success)
571 DBCommit(hdb);
572 else
573 DBRollback(hdb);
574 }
575 return success;
576 }
577
578 /**
579 * Validate user's password
580 */
581 bool User::validatePassword(const TCHAR *password)
582 {
583 PasswordHash ph;
584 CalculatePasswordHash(password, m_password.hashType, &ph, m_password.salt);
585 bool success = !memcmp(ph.hash, m_password.hash, PWD_HASH_SIZE(m_password.hashType));
586 if (success && m_password.hashType == PWD_HASH_SHA1)
587 {
588 // regenerate password hash if old format is used
589 CalculatePasswordHash(password, PWD_HASH_SHA256, &m_password);
590 m_flags |= UF_MODIFIED;
591 }
592 return success;
593 }
594
595 /**
596 * Set user's password
597 * For non-UNICODE build, password must be UTF-8 encoded
598 */
599 void User::setPassword(const TCHAR *password, bool clearChangePasswdFlag)
600 {
601 CalculatePasswordHash(password, PWD_HASH_SHA256, &m_password);
602 m_graceLogins = MAX_GRACE_LOGINS;
603 m_flags |= UF_MODIFIED;
604 if (clearChangePasswdFlag)
605 m_flags &= ~UF_CHANGE_PASSWORD;
606 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
607 }
608
609 /**
610 * Fill NXCP message with user data
611 */
612 void User::fillMessage(NXCPMessage *msg)
613 {
614 UserDatabaseObject::fillMessage(msg);
615
616 msg->setField(VID_USER_FULL_NAME, m_fullName);
617 msg->setField(VID_AUTH_METHOD, (WORD)m_authMethod);
618 msg->setField(VID_CERT_MAPPING_METHOD, (WORD)m_certMappingMethod);
619 msg->setField(VID_CERT_MAPPING_DATA, CHECK_NULL_EX(m_certMappingData));
620 msg->setField(VID_LAST_LOGIN, (UINT32)m_lastLogin);
621 msg->setField(VID_LAST_PASSWORD_CHANGE, (UINT32)m_lastPasswordChange);
622 msg->setField(VID_MIN_PASSWORD_LENGTH, (WORD)m_minPasswordLength);
623 msg->setField(VID_DISABLED_UNTIL, (UINT32)m_disabledUntil);
624 msg->setField(VID_AUTH_FAILURES, (UINT32)m_authFailures);
625 msg->setField(VID_XMPP_ID, m_xmppId);
626
627 FillGroupMembershipInfo(msg, m_id);
628 }
629
630 /**
631 * Modify user object from NXCP message
632 */
633 void User::modifyFromMessage(NXCPMessage *msg)
634 {
635 UserDatabaseObject::modifyFromMessage(msg);
636
637 UINT32 fields = msg->getFieldAsUInt32(VID_FIELDS);
638
639 if (fields & USER_MODIFY_FULL_NAME)
640 msg->getFieldAsString(VID_USER_FULL_NAME, m_fullName, MAX_USER_FULLNAME);
641 if (fields & USER_MODIFY_AUTH_METHOD)
642 m_authMethod = msg->getFieldAsUInt16(VID_AUTH_METHOD);
643 if (fields & USER_MODIFY_PASSWD_LENGTH)
644 m_minPasswordLength = msg->getFieldAsUInt16(VID_MIN_PASSWORD_LENGTH);
645 if (fields & USER_MODIFY_TEMP_DISABLE)
646 m_disabledUntil = (time_t)msg->getFieldAsUInt32(VID_DISABLED_UNTIL);
647 if (fields & USER_MODIFY_CERT_MAPPING)
648 {
649 m_certMappingMethod = msg->getFieldAsUInt16(VID_CERT_MAPPING_METHOD);
650 safe_free(m_certMappingData);
651 m_certMappingData = msg->getFieldAsString(VID_CERT_MAPPING_DATA);
652 }
653 if (fields & USER_MODIFY_XMPP_ID)
654 msg->getFieldAsString(VID_XMPP_ID, m_xmppId, MAX_XMPP_ID_LEN);
655 if (fields & USER_MODIFY_GROUP_MEMBERSHIP)
656 {
657 int count = (int)msg->getFieldAsUInt32(VID_NUM_GROUPS);
658 UINT32 *groups = NULL;
659 if (count > 0)
660 {
661 groups = (UINT32 *)malloc(sizeof(UINT32) * count);
662 msg->getFieldAsInt32Array(VID_GROUPS, (UINT32)count, groups);
663 }
664 UpdateGroupMembership(m_id, count, groups);
665 safe_free(groups);
666 }
667
668 // Clear intruder lockout flag if user is not disabled anymore
669 if (!(m_flags & UF_DISABLED))
670 m_flags &= ~UF_INTRUDER_LOCKOUT;
671 }
672
673 /**
674 * Increase auth failures and lockout account if threshold reached
675 */
676 void User::increaseAuthFailures()
677 {
678 m_authFailures++;
679
680 int lockoutThreshold = ConfigReadInt(_T("IntruderLockoutThreshold"), 0);
681 if ((lockoutThreshold > 0) && (m_authFailures >= lockoutThreshold))
682 {
683 m_disabledUntil = time(NULL) + ConfigReadInt(_T("IntruderLockoutTime"), 30) * 60;
684 m_flags |= UF_DISABLED | UF_INTRUDER_LOCKOUT;
685 }
686
687 m_flags |= UF_MODIFIED;
688 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
689 }
690
691 /**
692 * Enable user account
693 */
694 void User::enable()
695 {
696 m_authFailures = 0;
697 m_graceLogins = MAX_GRACE_LOGINS;
698 m_disabledUntil = 0;
699 m_flags &= ~(UF_DISABLED | UF_INTRUDER_LOCKOUT);
700 m_flags |= UF_MODIFIED;
701 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
702 }
703
704 void User::setFullName(const TCHAR *fullName)
705 {
706 if (_tcscmp(CHECK_NULL_EX(m_fullName), CHECK_NULL_EX(fullName)))
707 {
708 nx_strncpy(m_fullName, CHECK_NULL_EX(fullName), MAX_USER_FULLNAME);
709 m_flags |= UF_MODIFIED;
710 }
711 }
712
713 /*****************************************************************************
714 ** Group
715 ****************************************************************************/
716
717 /**
718 * Constructor for group object - create from database
719 * Expects fields in the following order:
720 * id,name,system_access,flags,description,guid,ldap_dn
721 */
722 Group::Group(DB_HANDLE hdb, DB_RESULT hr, int row) : UserDatabaseObject(hdb, hr, row)
723 {
724 DB_RESULT hResult;
725 TCHAR query[256];
726
727 _sntprintf(query, 256, _T("SELECT user_id FROM user_group_members WHERE group_id=%d"), m_id);
728 hResult = DBSelect(hdb, query);
729 if (hResult != NULL)
730 {
731 m_memberCount = DBGetNumRows(hResult);
732 if (m_memberCount > 0)
733 {
734 m_members = (UINT32 *)malloc(sizeof(UINT32) * m_memberCount);
735 for(int i = 0; i < m_memberCount; i++)
736 m_members[i] = DBGetFieldULong(hResult, i, 0);
737 qsort(m_members, m_memberCount, sizeof(UINT32), CompareUserId);
738 }
739 else
740 {
741 m_members = NULL;
742 }
743 DBFreeResult(hResult);
744 }
745
746 loadCustomAttributes(hdb);
747 }
748
749 /**
750 * Constructor for group object - create "Everyone" group
751 */
752 Group::Group() : UserDatabaseObject()
753 {
754 m_id = GROUP_EVERYONE;
755 _tcscpy(m_name, _T("Everyone"));
756 m_flags = UF_MODIFIED;
757 m_systemRights = 0;
758 _tcscpy(m_description, _T("Built-in everyone group"));
759 m_memberCount = 0;
760 m_members = NULL;
761 }
762
763 /**
764 * Constructor for group object - create new group
765 */
766 Group::Group(UINT32 id, const TCHAR *name) : UserDatabaseObject(id, name)
767 {
768 m_memberCount = 0;
769 m_members = NULL;
770 }
771
772 /**
773 * Destructor for group object
774 */
775 Group::~Group()
776 {
777 safe_free(m_members);
778 }
779
780 /**
781 * Save object to database
782 */
783 bool Group::saveToDatabase(DB_HANDLE hdb)
784 {
785 // Clear modification flag
786 m_flags &= ~UF_MODIFIED;
787
788 DB_STATEMENT hStmt;
789 if (IsDatabaseRecordExist(hdb, _T("user_groups"), _T("id"), m_id))
790 {
791 hStmt = DBPrepare(hdb, _T("UPDATE user_groups SET name=?,system_access=?,flags=?,description=?,guid=?,ldap_dn=? WHERE id=?"));
792 }
793 else
794 {
795 hStmt = DBPrepare(hdb, _T("INSERT INTO user_groups (name,system_access,flags,description,guid,ldap_dn,id) VALUES (?,?,?,?,?,?,?)"));
796 }
797 if (hStmt == NULL)
798 return false;
799
800 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, m_name, DB_BIND_STATIC);
801 DBBind(hStmt, 2, DB_SQLTYPE_BIGINT, m_systemRights);
802 DBBind(hStmt, 3, DB_SQLTYPE_INTEGER, m_flags);
803 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, m_description, DB_BIND_STATIC);
804 DBBind(hStmt, 5, DB_SQLTYPE_VARCHAR, m_guid);
805 DBBind(hStmt, 6, DB_SQLTYPE_TEXT, m_userDn, DB_BIND_STATIC);
806 DBBind(hStmt, 7, DB_SQLTYPE_INTEGER, m_id);
807
808 bool success = DBBegin(hdb);
809 if (success)
810 {
811 success = DBExecute(hStmt);
812 if (success)
813 {
814 DBFreeStatement(hStmt);
815 hStmt = DBPrepare(hdb, _T("DELETE FROM user_group_members WHERE group_id=?"));
816 if (hStmt != NULL)
817 {
818 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
819 success = DBExecute(hStmt);
820 }
821 else
822 {
823 success = false;
824 }
825
826 if (success && (m_memberCount > 0))
827 {
828 DBFreeStatement(hStmt);
829 hStmt = DBPrepare(hdb, _T("INSERT INTO user_group_members (group_id,user_id) VALUES (?,?)"));
830 if (hStmt != NULL)
831 {
832 DBBind(hStmt, 1, DB_SQLTYPE_INTEGER, m_id);
833 for(int i = 0; (i < m_memberCount) && success; i++)
834 {
835 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, m_members[i]);
836 success = DBExecute(hStmt);
837 }
838 }
839 else
840 {
841 success = false;
842 }
843 }
844
845 if (success)
846 {
847 success = saveCustomAttributes(hdb);
848 }
849 }
850 if (success)
851 DBCommit(hdb);
852 else
853 DBRollback(hdb);
854 }
855
856 if (hStmt != NULL)
857 DBFreeStatement(hStmt);
858
859 return success;
860 }
861
862 /**
863 * Delete object from database
864 */
865 bool Group::deleteFromDatabase(DB_HANDLE hdb)
866 {
867 TCHAR query[256];
868 bool success;
869
870 success = DBBegin(hdb);
871 if (success)
872 {
873 _sntprintf(query, 256, _T("DELETE FROM user_groups WHERE id=%d"), m_id);
874 success = DBQuery(hdb, query);
875 if (success)
876 {
877 _sntprintf(query, 256, _T("DELETE FROM user_group_members WHERE group_id=%d"), m_id);
878 success = DBQuery(hdb, query);
879 if (success)
880 {
881 _sntprintf(query, 256, _T("DELETE FROM userdb_custom_attributes WHERE object_id=%d"), m_id);
882 success = DBQuery(hdb, query);
883 }
884 }
885 if (success)
886 DBCommit(hdb);
887 else
888 DBRollback(hdb);
889 }
890 return success;
891 }
892
893 /**
894 * Check if given user is a member
895 */
896 bool Group::isMember(UINT32 userId)
897 {
898 if (m_id == GROUP_EVERYONE)
899 return true;
900
901 // This is to avoid applying disabled group rights on enabled user
902 if ((m_flags & UF_SYNC_EXCEPTION) || (m_flags & UF_DISABLED))
903 return false;
904
905 return bsearch(&userId, m_members, m_memberCount, sizeof(UINT32), CompareUserId) != NULL;
906 }
907
908 /**
909 * Add user to group
910 */
911 void Group::addUser(UINT32 userId)
912 {
913 if (bsearch(&userId, m_members, m_memberCount, sizeof(UINT32), CompareUserId) != NULL)
914 return; // already added
915
916 // Not in group, add it
917 m_memberCount++;
918 m_members = (UINT32 *)realloc(m_members, sizeof(UINT32) * m_memberCount);
919 m_members[m_memberCount - 1] = userId;
920 qsort(m_members, m_memberCount, sizeof(UINT32), CompareUserId);
921
922 m_flags |= UF_MODIFIED;
923
924 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
925 }
926
927 /**
928 * Delete user from group
929 */
930 void Group::deleteUser(UINT32 userId)
931 {
932 UINT32 *e = (UINT32 *)bsearch(&userId, m_members, m_memberCount, sizeof(UINT32), CompareUserId);
933 if (e == NULL)
934 return; // not a member
935
936 int index = (int)((char *)e - (char *)m_members) / sizeof(UINT32);
937 m_memberCount--;
938 memmove(&m_members[index], &m_members[index + 1], sizeof(UINT32) * (m_memberCount - index));
939 m_flags |= UF_MODIFIED;
940 SendUserDBUpdate(USER_DB_MODIFY, m_id, this);
941 }
942
943 /**
944 * Get group members.
945 */
946 int Group::getMembers(UINT32 **members)
947 {
948 *members = m_members;
949 return m_memberCount;
950 }
951
952 /**
953 * Fill NXCP message with user group data
954 */
955 void Group::fillMessage(NXCPMessage *msg)
956 {
957 UINT32 varId;
958 int i;
959
960 UserDatabaseObject::fillMessage(msg);
961
962 msg->setField(VID_NUM_MEMBERS, (UINT32)m_memberCount);
963 for(i = 0, varId = VID_GROUP_MEMBER_BASE; i < m_memberCount; i++, varId++)
964 msg->setField(varId, m_members[i]);
965 }
966
967 /**
968 * Modify group object from NXCP message
969 */
970 void Group::modifyFromMessage(NXCPMessage *msg)
971 {
972 int i;
973 UINT32 varId, fields;
974
975 UserDatabaseObject::modifyFromMessage(msg);
976
977 fields = msg->getFieldAsUInt32(VID_FIELDS);
978 if (fields & USER_MODIFY_MEMBERS)
979 {
980 UINT32 *members = m_members;
981 int count = m_memberCount;
982 m_memberCount = msg->getFieldAsUInt32(VID_NUM_MEMBERS);
983 if (m_memberCount > 0)
984 {
985 m_members = (UINT32 *)malloc(sizeof(UINT32) * m_memberCount);
986 for(i = 0, varId = VID_GROUP_MEMBER_BASE; i < m_memberCount; i++, varId++)
987 {
988 m_members[i] = msg->getFieldAsUInt32(varId);
989
990 // check if new member
991 UINT32 *e = (UINT32 *)bsearch(&m_members[i], members, count, sizeof(UINT32), CompareUserId);
992 if (e != NULL)
993 {
994 *((UINT32 *)e) = 0xFFFFFFFF; // mark as found
995 }
996 else
997 {
998 SendUserDBUpdate(USER_DB_MODIFY, m_members[i]); // new member added
999 }
1000 }
1001 for(i = 0; i < count; i++)
1002 if (members[i] != 0xFFFFFFFF) // not present in new list
1003 SendUserDBUpdate(USER_DB_MODIFY, members[i]);
1004 qsort(m_members, m_memberCount, sizeof(UINT32), CompareUserId);
1005 }
1006 else
1007 {
1008 m_members = NULL;
1009
1010 // notify change for all old members
1011 for(i = 0; i < count; i++)
1012 SendUserDBUpdate(USER_DB_MODIFY, members[i]);
1013 }
1014 free(members);
1015 }
1016 }