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