tdaq-develop-2025-02-12
WebUsers.cc
1 #include "otsdaq/WebUsersUtilities/WebUsers.h"
2 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
3 
4 #include <openssl/sha.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <cassert>
8 #include <cstdio>
9 #include <cstdlib>
10 #include <iostream>
11 
12 #include <chrono> // std::chrono::seconds
13 #include <thread> // std::this_thread::sleep_for
14 
15 using namespace ots;
16 
17 // clang-format off
18 #define WEB_LOGIN_BKUP_DB_PATH "bkup/"
19 
20 #define SECURITY_FILE_NAME std::string(__ENV__("SERVICE_DATA_PATH")) + "/OtsWizardData/security.dat"
21 
22 #define USERS_ACTIVE_SESSIONS_FILE USERS_DB_PATH + "/activeSessions.sv"
23 
24 #define HASHES_DB_FILE HASHES_DB_PATH + "/hashes.xml"
25 #define USERS_DB_FILE USERS_DB_PATH + "/users.xml"
26 #define USERS_GLOBAL_HISTORY_FILE "__global"
27 #define USERS_LOGIN_HISTORY_FILETYPE "hist"
28 #define USERS_PREFERENCES_FILETYPE "pref"
29 #define SYSTEM_PREFERENCES_PREFIX "system.preset"
30 #define USER_WITH_LOCK_FILE WEB_LOGIN_DB_PATH + "/user_with_lock.dat"
31 #define IP_BLACKLIST_FILE WEB_LOGIN_DB_PATH + "/ip_generated_blacklist.dat"
32 #define IP_REJECT_FILE WEB_LOGIN_DB_PATH + "/ip_reject.dat"
33 #define IP_ACCEPT_FILE WEB_LOGIN_DB_PATH + "/ip_accept.dat"
34 
35 #define SILENCE_ALL_TOOLTIPS_FILENAME "silenceTooltips"
36 
37 #define HASHES_DB_GLOBAL_STRING "hashData"
38 #define HASHES_DB_ENTRY_STRING "hashEntry"
39 #define USERS_DB_GLOBAL_STRING "userData"
40 #define USERS_DB_ENTRY_STRING "userEntry"
41 #define USERS_DB_NEXT_UID_STRING "nextUserId"
42 
44 #define PREF_XML_BGCOLOR_FIELD "pref_bgcolor" // -background color
45 #define PREF_XML_DBCOLOR_FIELD "pref_dbcolor" // -dashboard color
46 #define PREF_XML_WINCOLOR_FIELD "pref_wincolor" // -window color
47 #define PREF_XML_LAYOUT_FIELD "pref_layout" // -3 defaults window layouts(and current)
48 #define PREF_XML_SYSLAYOUT_FIELD "pref_syslayout" // -2 defaults window layouts
49 #define PREF_XML_PERMISSIONS_FIELD "desktop_user_permissions" // 0-255 permissions value (255 is admin super user)
50 #define PREF_XML_USERLOCK_FIELD "username_with_lock" // user with lock (to lockout others)
51 #define PREF_XML_USERNAME_FIELD "pref_username" // user with lock (to lockout others)
52 #define PREF_XML_OTS_OWNER_FIELD "ots_owner" // e.g. the experiment name
53 
54 #define PREF_XML_BGCOLOR_DEFAULT "rgb(0,76,151)" // -background color
55 #define PREF_XML_DBCOLOR_DEFAULT "rgb(0,40,85)" // -dashboard color
56 #define PREF_XML_WINCOLOR_DEFAULT "rgba(196,229,255,0.9)" // -window color
57 #define PREF_XML_LAYOUT_DEFAULT "0;0;0;0" // 3 default window layouts(and current)
58 #define PREF_XML_SYSLAYOUT_DEFAULT "0;0" // 2 system default window layouts
59 
60 #define PREF_XML_ACCOUNTS_FIELD "users_accounts" // user accounts field for super users
61 #define PREF_XML_LOGIN_HISTORY_FIELD "login_entry" // login history field for user login history data
62 
63 const std::string WebUsers::OTS_OWNER = getenv("OTS_OWNER")?getenv("OTS_OWNER"):"";
64 const std::string WebUsers::DEFAULT_ADMIN_USERNAME = "admin";
65 const std::string WebUsers::DEFAULT_ADMIN_DISPLAY_NAME = "Administrator";
66 const std::string WebUsers::DEFAULT_ADMIN_EMAIL = "root@otsdaq.fnal.gov";
67 const std::string WebUsers::DEFAULT_ITERATOR_USERNAME = "iterator";
68 const std::string WebUsers::DEFAULT_STATECHANGER_USERNAME = "statechanger";
69 const std::string WebUsers::DEFAULT_USER_GROUP = "allUsers";
70 
71 const std::string WebUsers::REQ_NO_LOGIN_RESPONSE = "NoLogin";
72 const std::string WebUsers::REQ_NO_PERMISSION_RESPONSE = "NoPermission";
73 const std::string WebUsers::REQ_USER_LOCKOUT_RESPONSE = "UserLockout";
74 const std::string WebUsers::REQ_LOCK_REQUIRED_RESPONSE = "LockRequired";
75 const std::string WebUsers::REQ_ALLOW_NO_USER = "AllowNoUser";
76 
77 const std::string WebUsers::SECURITY_TYPE_NONE = "NoSecurity";
78 const std::string WebUsers::SECURITY_TYPE_DIGEST_ACCESS = "DigestAccessAuthentication";
79 const std::string WebUsers::SECURITY_TYPE_DEFAULT = WebUsers::SECURITY_TYPE_NONE; // default to NO SECURITY
80 
81 const std::vector<std::string> WebUsers::HashesDatabaseEntryFields_ = {"hash","lastAccessTime"};
82 const std::vector<std::string> WebUsers::UsersDatabaseEntryFields_ = {"username","displayName","salt",
83  "uid","permissions","lastLoginAttemptTime","accountCreatedTime",
84  "loginFailureCount","lastModifiedTime","lastModifierUsername","useremail"};
85 
86 #undef __MF_SUBJECT__
87 #define __MF_SUBJECT__ "WebUsers"
88 
89 std::atomic<bool> WebUsers::remoteLoginVerificationEnabled_ = false;
90 volatile bool WebUsers::CareAboutCookieCodes_ = true;
91 
92 // clang-format on
93 
94 WebUsers::WebUsers()
95 {
96  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
97 
98  // deleteUserData(); //leave for debugging to reset user data
99 
100  usersNextUserId_ = 0; // first UID, default to 0 but get from database
101  usersUsernameWithLock_ = ""; // init to no user with lock
102 
103  // define field labels
104  // HashesDatabaseEntryFields.push_back("hash");
105  // HashesDatabaseEntryFields.push_back("lastAccessTime"); // last login month resolution, blurred by 1/2 month
106  //
107  // WebUsers::UsersDatabaseEntryFields_.push_back("username");
108  // WebUsers::UsersDatabaseEntryFields_.push_back("displayName");
109  // WebUsers::UsersDatabaseEntryFields_.push_back("salt");
110  // WebUsers::UsersDatabaseEntryFields_.push_back("uid");
111  // WebUsers::UsersDatabaseEntryFields_.push_back("permissions");
112  // WebUsers::UsersDatabaseEntryFields_.push_back("lastLoginAttemptTime");
113  // WebUsers::UsersDatabaseEntryFields_.push_back("accountCreatedTime");
114  // WebUsers::UsersDatabaseEntryFields_.push_back("loginFailureCount");
115  // WebUsers::UsersDatabaseEntryFields_.push_back("lastModifiedTime");
116  // WebUsers::UsersDatabaseEntryFields_.push_back("lastModifierUsername");
117  // WebUsers::UsersDatabaseEntryFields_.push_back("useremail");
118 
119  // attempt to make directory structure (just in case)
120  mkdir(((std::string)WEB_LOGIN_DB_PATH).c_str(), 0755);
121  mkdir(((std::string)WEB_LOGIN_DB_PATH + "bkup/" + USERS_DB_PATH).c_str(), 0755);
122  mkdir(((std::string)WEB_LOGIN_DB_PATH + HASHES_DB_PATH).c_str(), 0755);
123  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH).c_str(), 0755);
124  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_LOGIN_HISTORY_PATH).c_str(), 0755);
125  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_PREFERENCES_PATH).c_str(), 0755);
126 
127  if(!loadDatabases())
128  __COUT__ << "FATAL USER DATABASE ERROR - failed to load!!!" << __E__;
129 
130  loadSecuritySelection();
131 
132  // print out admin new user code for ease of use
133  uint64_t i;
134  std::string user = DEFAULT_ADMIN_USERNAME;
135  if((i = searchUsersDatabaseForUsername(user)) == NOT_FOUND_IN_DATABASE)
136  {
137  __SS__ << "user: " << user << " is not found. This should be impossible!"
138  << __E__;
139  __COUT_ERR__ << ss.str();
140  __SS_THROW__; // THIS CAN NOT HAPPEN?! There must be an admin user
141  }
142  else if(Users_[i].salt_ ==
143  "" && // admin password not setup, so print out NAC to help out
144  securityType_ == SECURITY_TYPE_DIGEST_ACCESS)
145  {
147  // start thread for notifying the user about the admin new account code
148  // notify for 10 seconds (e.g.)
149  std::thread(
150  [](const std::string& nac, const std::string& user) {
151  WebUsers::NACDisplayThread(nac, user);
152  },
153  Users_[i].getNewAccountCode(),
154  user)
155  .detach();
156  }
157 
158  // attempt to load persistent user sessions
160 
161  // default user with lock to admin and/or try to load last user with lock
162  // Note: this must happen after getting persistent active sessions
163  loadUserWithLock();
164 
165  srand(time(0)); // seed random for hash salt generation
166 
167  __COUT__ << "Done with Web Users initialization!" << __E__;
168 } // end constructor
169 
170 //==============================================================================
175 bool WebUsers::xmlRequestOnGateway(cgicc::Cgicc& cgi,
176  std::ostringstream* out,
177  HttpXmlDocument* xmldoc,
178  WebUsers::RequestUserInfo& userInfo)
179 {
180  std::lock_guard<std::mutex> lock(webUserMutex_);
181 
182  // initialize user info parameters to failed results
184 
185  uint64_t i;
186 
188  userInfo.cookieCode_,
189  &userInfo.groupPermissionLevelMap_,
190  &userInfo.uid_,
191  userInfo.ip_,
192  !userInfo.automatedCommand_ /*refresh cookie*/,
193  userInfo
194  .allowNoUser_ /* do not go to remote verify to avoid hammering remote verify */
195  ,
196  &userInfo.usernameWithLock_,
197  &userInfo.userSessionIndex_))
198  {
199  *out << userInfo.cookieCode_;
200  goto HANDLE_ACCESS_FAILURE; // return false, access failed
201  }
202 
203  // setup userInfo.permissionLevel_ based on userInfo.groupPermissionLevelMap_
204  userInfo.getGroupPermissionLevel();
205 
206  i = searchUsersDatabaseForUserId(userInfo.uid_);
207  if(i >= Users_.size())
208  {
209  __SS__ << "Illegal uid encountered in cookie codes!? " << i << __E__;
210  ss << "User size = " << Users_.size() << __E__;
211  __SS_THROW__;
212  }
213 
214  userInfo.username_ = Users_[i].username_;
215  userInfo.displayName_ = Users_[i].displayName_;
216 
217  if(!WebUsers::checkRequestAccess(cgi, out, xmldoc, userInfo))
218  goto HANDLE_ACCESS_FAILURE; // return false, access failed
219 
220  return true; // access success!
221 
222 HANDLE_ACCESS_FAILURE:
223  // print out return string on failure
224  if(!userInfo.automatedCommand_)
225  __COUT_ERR__ << "Failed request (requestType = " << userInfo.requestType_
226  << "): " << out->str() << __E__;
227  return false; // access failed
228 
229 } // end xmlRequestOnGateway()
230 
231 //==============================================================================
234 void WebUsers::initializeRequestUserInfo(cgicc::Cgicc& cgi,
235  WebUsers::RequestUserInfo& userInfo)
236 {
237  userInfo.ip_ = cgi.getEnvironment().getRemoteAddr();
238 
239  // note if related bools are false, members below may not be set
240  userInfo.username_ = "";
241  userInfo.displayName_ = "";
242  userInfo.usernameWithLock_ = "";
243  userInfo.userSessionIndex_ = NOT_FOUND_IN_DATABASE;
244  userInfo.setGroupPermissionLevels(""); // always init to inactive
245 }
246 
247 //==============================================================================
255 bool WebUsers::checkRequestAccess(cgicc::Cgicc& /*cgi*/,
256  std::ostringstream* out,
257  HttpXmlDocument* xmldoc,
258  WebUsers::RequestUserInfo& userInfo,
259  bool isWizardMode /* = false */,
260  const std::string& wizardModeSequence /* = "" */)
261 {
262  // steps:
263  // - check access based on cookieCode and permission level
264  // - check user lock flags and status
265 
266  if(userInfo.requireSecurity_ && userInfo.permissionsThreshold_ > 1)
267  {
268  // In an attempt to force accountability,..
269  // only allow higher permission threshold requests
270  // if wiz mode with random code, or normal mode with security mode enabled
271 
272  if(isWizardMode && wizardModeSequence.size() < 8)
273  {
274  // force wiz mode sequence to be "random and large"
275  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
276  __COUT__ << "User (@" << userInfo.ip_ << ") has attempted requestType '"
277  << userInfo.requestType_
278  << "' which requires sufficient security enabled. Please enable the "
279  "random wizard mode"
280  " sequence of at least 8 characters."
281  << __E__;
282  return false; // invalid cookie and present sequence, but not correct
283  // sequence
284  }
285  else if(!isWizardMode &&
286  (userInfo.username_ == WebUsers::DEFAULT_ADMIN_USERNAME ||
287  userInfo.username_ == WebUsers::DEFAULT_ITERATOR_USERNAME ||
288  userInfo.username_ == WebUsers::DEFAULT_STATECHANGER_USERNAME))
289  {
290  // force non-admin user, which implies sufficient security
291  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
292  __COUT__ << "User (@" << userInfo.ip_ << ") has attempted requestType '"
293  << userInfo.requestType_
294  << "' which requires sufficient security enabled. Please enable "
295  "individual user "
296  " logins (Note: the user admin is disallowed in an attempt to "
297  "force personal accountability for edits)."
298  << __E__;
299  return false; // invalid cookie and present sequence, but not correct
300  // sequence
301  }
302 
303  } // end security required verification
304 
305  if(!userInfo.automatedCommand_)
306  {
307  __COUTT__ << "requestType ==========>>> " << userInfo.requestType_ << __E__;
308  __COUTTV__((unsigned int)userInfo.permissionLevel_);
309  __COUTTV__((unsigned int)userInfo.permissionsThreshold_);
310  }
311 
312  // second, start check access -------
313  if(!isWizardMode && !userInfo.allowNoUser_ &&
314  userInfo.cookieCode_.length() != WebUsers::COOKIE_CODE_LENGTH &&
315  !(!WebUsers::CareAboutCookieCodes_ && WebUsers::remoteLoginVerificationEnabled_ &&
316  userInfo.cookieCode_ ==
317  WebUsers::
318  REQ_ALLOW_NO_USER)) //ignore case when security disabled at remote subsystem (avoid propagating bad cookieCode to primary Gateway)
319  {
320  __COUT__ << "User (@" << userInfo.ip_
321  << ") has invalid cookie code: " << userInfo.cookieCode_ << std::endl;
322  *out << WebUsers::REQ_NO_LOGIN_RESPONSE;
323  return false; // invalid cookie and sequence present, but not correct sequence
324  }
325 
326  if(!userInfo.allowNoUser_ &&
327  (userInfo.permissionLevel_ == 0 || // reject inactive user permission level
328  userInfo.permissionsThreshold_ == 0 || // reject inactive requests
329  userInfo.permissionLevel_ < userInfo.permissionsThreshold_))
330 
331  {
332  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
333  __COUT_INFO__ << "User (@" << userInfo.ip_
334  << ") has insufficient permissions for requestType '"
335  << userInfo.requestType_ << "' : user level is "
336  << (unsigned int)userInfo.permissionLevel_ << ", "
337  << (unsigned int)userInfo.permissionsThreshold_ << " required."
338  << __E__;
339  return false; // invalid permissions
340  }
341  // end check access -------
342 
343  if(isWizardMode)
344  {
345  userInfo.username_ = WebUsers::DEFAULT_ADMIN_USERNAME;
346  userInfo.displayName_ = "Admin";
347  userInfo.usernameWithLock_ = userInfo.username_;
348  userInfo.userSessionIndex_ = 0;
349  return true; // done, wizard mode access granted
350  }
351  // else, normal gateway verify mode
352 
353  if(xmldoc) // fill with cookie code tag
354  {
355  if(userInfo.allowNoUser_)
356  xmldoc->setHeader(WebUsers::REQ_ALLOW_NO_USER);
357  else
358  xmldoc->setHeader(userInfo.cookieCode_);
359  }
360 
361  if(userInfo.allowNoUser_)
362  {
363  if(userInfo.automatedCommand_)
364  __COUTT__ << "Allowing anonymous access." << __E__;
365 
366  return true; // ignore lock for allow-no-user case
367  }
368 
369  // if(!userInfo.automatedCommand_)
370  // {
371  // __COUTV__(userInfo.username_);
372  // __COUTV__(userInfo.usernameWithLock_);
373  // }
374 
375  if((userInfo.checkLock_ || userInfo.requireLock_) &&
376  userInfo.usernameWithLock_ != "" &&
377  userInfo.usernameWithLock_ != userInfo.username_)
378  {
379  *out << WebUsers::REQ_USER_LOCKOUT_RESPONSE;
380  __COUT_INFO__ << "User '" << userInfo.username_ << "' is locked out. '"
381  << userInfo.usernameWithLock_ << "' has lock." << std::endl;
382  return false; // failed due to another user having lock
383  }
384 
385  if(userInfo.requireLock_ && userInfo.usernameWithLock_ != userInfo.username_)
386  {
387  *out << WebUsers::REQ_LOCK_REQUIRED_RESPONSE;
388  __COUT_INFO__ << "User '" << userInfo.username_
389  << "' must have lock to proceed. ('" << userInfo.usernameWithLock_
390  << "' has lock.)" << std::endl;
391  return false; // failed due to lock being required, and this user does not have
392  // it
393  }
394 
395  return true; // access success!
396 
397 } // end checkRequestAccess()
398 
399 //==============================================================================
403 {
404  std::string fn;
405 
406  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_ACTIVE_SESSIONS_FILE;
407  __COUT__ << fn << __E__;
408 
409  FILE* fp = fopen(fn.c_str(), "w");
410  if(!fp)
411  {
412  __COUT_ERR__ << "Error! Persistent active sessions could not be saved to file: "
413  << fn << __E__;
414  return;
415  }
416 
417  int version = 0;
418  fprintf(fp, "%d\n", version);
419  for(unsigned int i = 0; i < ActiveSessions_.size(); ++i)
420  {
421  // __COUT__ << "SAVE " << ActiveSessionCookieCodeVector[i] << __E__;
422  // __COUT__ << "SAVE " << ActiveSessionIpVector[i] << __E__;
423  // __COUT__ << "SAVE " << ActiveSessionUserIdVector[i] << __E__;
424  // __COUT__ << "SAVE " << ActiveSessionIndex[i] << __E__;
425  // __COUT__ << "SAVE " << ActiveSessionStartTimeVector[i] << __E__;
426 
427  fprintf(fp, "%s\n", ActiveSessions_[i].cookieCode_.c_str());
428  fprintf(fp, "%s\n", ActiveSessions_[i].ip_.c_str());
429  fprintf(fp, "%lu\n", ActiveSessions_[i].userId_);
430  fprintf(fp, "%lu\n", ActiveSessions_[i].sessionIndex_);
431  fprintf(fp, "%ld\n", ActiveSessions_[i].startTime_);
432  }
433 
434  __COUT__ << "Active Sessions saved with size " << ActiveSessions_.size() << __E__;
435 
436  fclose(fp);
437 } // end saveActiveSessions()
438 
439 //====================================================================================================================
443 {
444  std::string fn;
445 
446  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_ACTIVE_SESSIONS_FILE;
447  __COUT__ << fn << __E__;
448  FILE* fp = fopen(fn.c_str(), "r");
449  if(!fp)
450  {
451  __COUT_INFO__
452  << "Persistent active sessions were not found to be loaded at file: " << fn
453  << __E__;
454  return;
455  }
456 
457  int version;
458 
459  const int LINELEN = 1000;
460  char line[LINELEN];
461  fgets(line, LINELEN, fp);
462  sscanf(line, "%d", &version);
463  if(version == 0)
464  {
465  __COUT__ << "Extracting active sessions..." << __E__;
466  }
467  while(fgets(line, LINELEN, fp))
468  {
469  if(strlen(line))
470  line[strlen(line) - 1] = '\0'; // remove new line
471  if(strlen(line) != COOKIE_CODE_LENGTH)
472  {
473  __COUT__ << "Illegal cookie code found: " << line << __E__;
474 
475  fclose(fp);
476  return;
477  }
478  ActiveSessions_.push_back(ActiveSession());
479  ActiveSessions_.back().cookieCode_ = line;
480 
481  fgets(line, LINELEN, fp);
482  if(strlen(line))
483  line[strlen(line) - 1] = '\0'; // remove new line
484  ActiveSessions_.back().ip_ = line;
485 
486  fgets(line, LINELEN, fp);
487  sscanf(line, "%lu", &(ActiveSessions_.back().userId_));
488 
489  fgets(line, LINELEN, fp);
490  sscanf(line, "%lu", &(ActiveSessions_.back().sessionIndex_));
491 
492  fgets(line, LINELEN, fp);
493  sscanf(line, "%ld", &(ActiveSessions_.back().startTime_));
494  }
495 
496  __COUT__ << "Active Sessions loaded with size " << ActiveSessions_.size() << __E__;
497 
498  fclose(fp);
499  // clear file after loading
500  fp = fopen(fn.c_str(), "w");
501  if(fp)
502  fclose(fp);
503 } // end loadActiveSessions()
504 
505 //==============================================================================
509 bool WebUsers::loadDatabases()
510 {
511  std::string fn;
512 
513  FILE* fp;
514  const unsigned int LINE_LEN = 1000;
515  char line[LINE_LEN];
516  unsigned int i, si, c, len, f;
517  // uint64_t tmpInt64;
518 
519  // hashes
520  // File Organization:
521  // <hashData>
522  // <hashEntry><hash>hash0</hash><lastAccessTime>lastAccessTime0</lastAccessTime></hashEntry>
523  // <hashEntry><hash>hash1</hash><lastAccessTime>lastAccessTime1</lastAccessTime></hashEntry>
524  // ..
525  // </hashData>
526 
527  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_FILE;
528  __COUT__ << fn << __E__;
529  fp = fopen(fn.c_str(), "r");
530  if(!fp) // need to create file
531  {
532  mkdir(((std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_PATH).c_str(),
533  0755);
534  __COUT__ << ((std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_PATH).c_str()
535  << __E__;
536  fp = fopen(fn.c_str(), "w");
537  if(!fp)
538  return false;
539  __COUT__ << "Hashes database created: " << fn << __E__;
540 
541  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
542  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
543  fclose(fp);
544  }
545  else // load structures if hashes exists
546  {
547  // for every HASHES_DB_ENTRY_STRING, extract to local vector
548  // trusting file construction, assuming fields based >'s and <'s
549  while(fgets(line, LINE_LEN, fp))
550  {
551  if(strlen(line) < SHA512_DIGEST_LENGTH)
552  continue;
553 
554  c = 0;
555  len =
556  strlen(line); // save len, strlen will change because of \0 manipulations
557  for(i = 0; i < len; ++i)
558  if(line[i] == '>')
559  {
560  ++c; // count >'s
561  if(c != 2 && c != 4)
562  continue; // only proceed for field data
563 
564  si = ++i; // save start index
565  while(i < len && line[i] != '<')
566  ++i;
567  if(i == len)
568  break;
569  line[i] = '\0'; // close std::string
570 
571  //__COUT__ << "Found Hashes field " << c/2 << " " << &line[si] <<
572  //__E__;
573 
574  f = c / 2 - 1;
575  if(f == 0) // hash
576  {
577  Hashes_.push_back(Hash());
578  Hashes_.back().hash_ = &line[si];
579  }
580  else if(f == 1) // lastAccessTime
581  sscanf(&line[si], "%ld", &Hashes_.back().accessTime_);
582  }
583  }
584  __COUT__ << Hashes_.size() << " Hashes found." << __E__;
585 
586  fclose(fp);
587  }
588 
589  // users
590  // File Organization:
591  // <userData>
592  // <nextUserId>...</nextUserId>
593  // <userEntry>...</userEntry>
594  // <userEntry>...</userEntry>
595  // ..
596  // </userData>
597 
598  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_FILE;
599  fp = fopen(fn.c_str(), "r");
600  if(!fp) // need to create file
601  {
602  mkdir(((std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_PATH).c_str(),
603  0755);
604  __COUT__ << ((std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_PATH).c_str()
605  << __E__;
606  fp = fopen(fn.c_str(), "w");
607  if(!fp)
608  return false;
609  __COUT__ << "Users database created: " << fn << __E__;
610 
611  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
612  char nidStr[100];
613  sprintf(nidStr, "%lu", usersNextUserId_);
614  saveToDatabase(fp, USERS_DB_NEXT_UID_STRING, nidStr, DB_SAVE_OPEN_AND_CLOSE);
615  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
616  fclose(fp);
617 
618  createNewAccount(DEFAULT_ADMIN_USERNAME,
619  DEFAULT_ADMIN_DISPLAY_NAME,
620  DEFAULT_ADMIN_EMAIL); // account 0 is always admin
621  }
622  else // extract next user id and user entries if users exists
623  {
624  __COUT__ << "Users database: " << fn << __E__;
625  // for every USERS_DB_ENTRY_STRING, extract to local vector
626  // trusting file construction, assuming fields based >'s and <'s
627 
628  char salt[] = "nextUserId";
629  while(fgets(line, LINE_LEN, fp))
630  {
631  if(strlen(line) < strlen(salt) * 2)
632  continue; // line size should indicate xml tags on same line
633 
634  for(i = 0; i < strlen(salt); ++i) // check for opening tag
635  if(line[i + 1] != salt[i])
636  break;
637 
638  if(i == strlen(salt)) // all salt matched, so found correct line! increment
639  // to get line index
640  {
641  i += 2;
642  si = i;
643  while(i < LINE_LEN && line[i] != '\0' && line[i] != '<')
644  ++i; // find '<'
645  line[i] = '\0'; // close std::string
646  sscanf(&line[si], "%lu", &usersNextUserId_);
647  break; // done with next uid
648  }
649  }
650 
651  __COUT__ << "Found Users database next user Id: " << usersNextUserId_ << __E__;
652 
653  // trusting file construction, assuming fields based >'s and <'s and each entry on
654  // one line
655  while(fgets(line, LINE_LEN, fp))
656  {
657  if(strlen(line) < 30)
658  continue; // rule out header tags
659 
660  c = 0;
661  len =
662  strlen(line); // save len, strlen will change because of \0 manipulations
663  if(len >= LINE_LEN)
664  {
665  __COUT__ << "Line buffer too small: " << len << __E__;
666  break;
667  }
668 
669  // get fields from line
670  f = 0;
671  for(i = 0; i < len; ++i)
672  if(line[i] == '>')
673  {
674  ++c; // count >'s
675  if(c == 0 || c % 2 == 1)
676  continue; // only proceed for field data (even
677 
678  si = ++i; // save start index
679  while(i < len && line[i] != '<')
680  ++i;
681  if(i == len)
682  break;
683  line[i] = '\0'; // close std::string
684  f = c / 2 - 1;
685 
686  //__COUT__ << "Found Users[" <<
687  // Users_.size() << "] field " << f << " " << &line[si] << __E__;
688 
689  if(f == 0) // username
690  {
691  Users_.push_back(User());
692  Users_.back().username_ = &line[si];
693  }
694  else if(f == 1) // displayName
695  Users_.back().displayName_ = &line[si];
696  else if(f == 2) // salt
697  Users_.back().salt_ = &line[si];
698  else if(f == 3) // uid
699  sscanf(&line[si], "%lu", &Users_.back().userId_);
700  else if(f == 4) // permissions
701  {
702  std::map<std::string, permissionLevel_t>& lastPermissionsMap =
703  Users_.back().permissions_;
704  StringMacros::getMapFromString<permissionLevel_t>(
705  &line[si], lastPermissionsMap);
706 
707  //__COUT__ << "User permission levels:" <<
708  // StringMacros::mapToString(lastPermissionsMap) << __E__;
709 
710  // verify 'allUsers' is there
711  // if not, add it as a disabled user (i.e.
712  // WebUsers::PERMISSION_LEVEL_INACTIVE)
713  if(lastPermissionsMap.find(WebUsers::DEFAULT_USER_GROUP) ==
714  lastPermissionsMap.end())
715  {
716  __COUT_INFO__
717  << "User '" << Users_.back().username_
718  << "' is not a member of the default user group '"
719  << WebUsers::DEFAULT_USER_GROUP
720  << ".' Assuming user account is inactive (permission "
721  "level := "
722  << WebUsers::PERMISSION_LEVEL_INACTIVE << ")." << __E__;
723  lastPermissionsMap[WebUsers::DEFAULT_USER_GROUP] =
724  WebUsers::PERMISSION_LEVEL_INACTIVE; // mark inactive
725  }
726 
727  if(Users_.back().username_ == DEFAULT_ADMIN_USERNAME)
728  {
729  // overwrite admin with full permissions (irregardless of corrupt user db situation), never allow to be inactive for example
730 
731  std::map<std::string /*groupName*/,
732  WebUsers::permissionLevel_t>
733  initPermissions = {{WebUsers::DEFAULT_USER_GROUP,
735 
736  Users_.back().permissions_ = initPermissions;
737  }
738  }
739  else if(f == 5) // lastLoginAttemptTime
740  sscanf(&line[si], "%ld", &Users_.back().lastLoginAttempt_);
741  else if(f == 6) // accountCreatedTime
742  sscanf(&line[si], "%ld", &Users_.back().accountCreationTime_);
743  else if(f == 7) // loginFailureCount
744  sscanf(&line[si], "%hhu", &Users_.back().loginFailureCount_);
745  else if(f == 8) // lastModifierTime
746  sscanf(&line[si], "%ld", &Users_.back().accessModifierTime());
747  else if(f == 9) // lastModifierUsername
748  Users_.back().loadModifierUsername(&line[si]);
749  else if(f == 10) // user email
750  Users_.back().email_ = &line[si];
751  }
752 
753  } // end get line loop
754  fclose(fp);
755  }
756 
757  __COUT__ << Users_.size() << " Users found." << __E__;
758  for(size_t ii = 0; ii < Users_.size(); ++ii)
759  {
760  std::cout << // do not send to message facility
761  "User [" << Users_[ii].userId_ << "] \tName: " << std::left
762  << std::setfill(' ') << std::setw(20) << Users_[ii].username_
763  << "\tDisplay Name: " << std::left << std::setfill(' ') << std::setw(30)
764  << Users_[ii].displayName_ << "\tEmail: " << std::left
765  << std::setfill(' ') << std::setw(30) << Users_[ii].email_
766  << "\tNAC: " << std::left << std::setfill(' ') << std::setw(5)
767  << Users_[ii].getNewAccountCode()
768  << "\tFailedCount: " << (int)Users_[ii].loginFailureCount_
769  << "\tPermissions: "
770  << StringMacros::mapToString(Users_[ii].permissions_) <<
771  //"\tSalt: " << Users_[ii].salt_.size() << " " << Users_[ii].salt_ <<
772  __E__;
773  }
774  // __COUT__ << Hashes_.size() << " Hashes found." << __E__;
775  // for(size_t ii = 0; ii < Hashes_.size(); ++ii)
776  // {
777  // std::cout << //do not send to message facility
778  // "Hash [" << ii <<
779  // "]: " << Hashes_[ii].hash_ <<
780  // __E__;
781  // }
782  return true;
783 } // end loadDatabases()
784 
785 //==============================================================================
787 void WebUsers::saveToDatabase(FILE* fp,
788  const std::string& field,
789  const std::string& value,
790  uint8_t type,
791  bool addNewLine)
792 {
793  if(!fp)
794  return;
795 
796  std::string newLine = addNewLine ? "\n" : "";
797 
798  if(type == DB_SAVE_OPEN_AND_CLOSE)
799  fprintf(fp,
800  "<%s>%s</%s>%s",
801  field.c_str(),
802  value.c_str(),
803  field.c_str(),
804  newLine.c_str());
805  else if(type == DB_SAVE_OPEN)
806  fprintf(fp, "<%s>%s%s", field.c_str(), value.c_str(), newLine.c_str());
807  else if(type == DB_SAVE_CLOSE)
808  fprintf(fp, "</%s>%s", field.c_str(), newLine.c_str());
809 } // end saveToDatabase()
810 
811 //==============================================================================
817 bool WebUsers::saveDatabaseToFile(uint8_t db)
818 {
819  //__COUT__ << "Save Database: " << (int)db << __E__;
820 
821  std::string fn =
822  (std::string)WEB_LOGIN_DB_PATH +
823  ((db == DB_USERS) ? (std::string)USERS_DB_FILE : (std::string)HASHES_DB_FILE);
824 
825  __COUT__ << "Save Database Filename: " << fn << __E__;
826 
827  // backup file organized by day
828  if(0)
829  {
830  char dayAppend[20];
831  sprintf(dayAppend, ".%lu.bkup", time(0) / (3600 * 24));
832  std::string bkup_fn = (std::string)WEB_LOGIN_DB_PATH +
833  (std::string)WEB_LOGIN_BKUP_DB_PATH +
834  ((db == DB_USERS) ? (std::string)USERS_DB_FILE
835  : (std::string)HASHES_DB_FILE) +
836  (std::string)dayAppend;
837 
838  __COUT__ << "Backup file: " << bkup_fn << __E__;
839 
840  std::string shell_command = "mv " + fn + " " + bkup_fn;
841  system(shell_command.c_str());
842  }
843 
844  FILE* fp = fopen(fn.c_str(), "wb"); // write in binary mode
845  if(!fp)
846  return false;
847 
848  char fldStr[100];
849 
850  if(db == DB_USERS) // USERS
851  {
852  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
853 
854  sprintf(fldStr, "%lu", usersNextUserId_);
855  saveToDatabase(fp, USERS_DB_NEXT_UID_STRING, fldStr, DB_SAVE_OPEN_AND_CLOSE);
856 
857  __COUT__ << "Saving " << Users_.size() << " Users." << __E__;
858 
859  for(uint64_t i = 0; i < Users_.size(); ++i)
860  {
861  //__COUT__ << "Saving User: " << UsersUsernameVector[i] << __E__;
862 
863  saveToDatabase(fp, USERS_DB_ENTRY_STRING, "", DB_SAVE_OPEN, false);
864 
865  for(unsigned int f = 0; f < WebUsers::UsersDatabaseEntryFields_.size(); ++f)
866  {
867  //__COUT__ << "Saving Field: " << f << __E__;
868  if(f == 0) // username
869  saveToDatabase(fp,
870  WebUsers::UsersDatabaseEntryFields_[f],
871  Users_[i].username_,
872  DB_SAVE_OPEN_AND_CLOSE,
873  false);
874  else if(f == 1) // displayName
875  saveToDatabase(fp,
876  WebUsers::UsersDatabaseEntryFields_[f],
877  Users_[i].displayName_,
878  DB_SAVE_OPEN_AND_CLOSE,
879  false);
880  else if(f == 2) // salt
881  saveToDatabase(fp,
882  WebUsers::UsersDatabaseEntryFields_[f],
883  Users_[i].salt_,
884  DB_SAVE_OPEN_AND_CLOSE,
885  false);
886  else if(f == 3) // uid
887  {
888  sprintf(fldStr, "%lu", Users_[i].userId_);
889  saveToDatabase(fp,
890  WebUsers::UsersDatabaseEntryFields_[f],
891  fldStr,
892  DB_SAVE_OPEN_AND_CLOSE,
893  false);
894  }
895  else if(f == 4) // permissions
896  saveToDatabase(fp,
897  WebUsers::UsersDatabaseEntryFields_[f],
898  StringMacros::mapToString(Users_[i].permissions_,
899  "," /*primary delimeter*/,
900  ":" /*secondary delimeter*/),
901  DB_SAVE_OPEN_AND_CLOSE,
902  false);
903  else if(f == 5) // lastLoginAttemptTime
904  {
905  sprintf(fldStr, "%lu", Users_[i].lastLoginAttempt_);
906  saveToDatabase(fp,
907  WebUsers::UsersDatabaseEntryFields_[f],
908  fldStr,
909  DB_SAVE_OPEN_AND_CLOSE,
910  false);
911  }
912  else if(f == 6) // accountCreatedTime
913  {
914  sprintf(fldStr, "%lu", Users_[i].accountCreationTime_);
915  saveToDatabase(fp,
916  WebUsers::UsersDatabaseEntryFields_[f],
917  fldStr,
918  DB_SAVE_OPEN_AND_CLOSE,
919  false);
920  }
921  else if(f == 7) // loginFailureCount
922  {
923  sprintf(fldStr, "%hhu", Users_[i].loginFailureCount_);
924  saveToDatabase(fp,
925  WebUsers::UsersDatabaseEntryFields_[f],
926  fldStr,
927  DB_SAVE_OPEN_AND_CLOSE,
928  false);
929  }
930  else if(f == 8) // lastModifierTime
931  {
932  sprintf(fldStr, "%lu", Users_[i].getModifierTime());
933  saveToDatabase(fp,
934  WebUsers::UsersDatabaseEntryFields_[f],
935  fldStr,
936  DB_SAVE_OPEN_AND_CLOSE,
937  false);
938  }
939  else if(f == 9) // lastModifierUsername
940  saveToDatabase(fp,
941  WebUsers::UsersDatabaseEntryFields_[f],
942  Users_[i].getModifierUsername(),
943  DB_SAVE_OPEN_AND_CLOSE,
944  false);
945  else if(f == 10) // useremail
946  saveToDatabase(fp,
947  WebUsers::UsersDatabaseEntryFields_[f],
948  Users_[i].email_,
949  DB_SAVE_OPEN_AND_CLOSE,
950  false);
951  }
952 
953  saveToDatabase(fp, USERS_DB_ENTRY_STRING, "", DB_SAVE_CLOSE);
954  }
955 
956  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
957  }
958  else // HASHES
959  {
960  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
961 
962  __COUT__ << "Saving " << Hashes_.size() << " Hashes." << __E__;
963  for(uint64_t i = 0; i < Hashes_.size(); ++i)
964  {
965  __COUT__ << "Saving " << Hashes_[i].hash_ << " Hash." << __E__;
966  saveToDatabase(fp, HASHES_DB_ENTRY_STRING, "", DB_SAVE_OPEN, false);
967  for(unsigned int f = 0; f < WebUsers::HashesDatabaseEntryFields_.size(); ++f)
968  {
969  if(f == 0) // hash
970  saveToDatabase(fp,
971  WebUsers::HashesDatabaseEntryFields_[f],
972  Hashes_[i].hash_,
973  DB_SAVE_OPEN_AND_CLOSE,
974  false);
975  else if(f == 1) // lastAccessTime
976  {
977  sprintf(fldStr, "%lu", Hashes_[i].accessTime_);
978  saveToDatabase(fp,
979  WebUsers::HashesDatabaseEntryFields_[f],
980  fldStr,
981  DB_SAVE_OPEN_AND_CLOSE,
982  false);
983  }
984  }
985  saveToDatabase(fp, HASHES_DB_ENTRY_STRING, "", DB_SAVE_CLOSE);
986  }
987 
988  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
989  }
990 
991  fclose(fp);
992  return true;
993 } // end saveDatabaseToFile()
994 
995 //==============================================================================
1003 void WebUsers::createNewAccount(const std::string& username,
1004  const std::string& displayName,
1005  const std::string& email)
1006 {
1007  __COUT__ << "Creating account: " << username << __E__;
1008  // check if username already exists
1009  uint64_t i;
1010  if((i = searchUsersDatabaseForUsername(username)) != NOT_FOUND_IN_DATABASE ||
1011  username == WebUsers::DEFAULT_ITERATOR_USERNAME ||
1012  username == WebUsers::DEFAULT_STATECHANGER_USERNAME) // prevent reserved usernames
1013  // from being created!
1014  {
1015  __SS__ << "Username '" << username
1016  << "' already exists! Please choose a unique username." << __E__;
1017  __SS_THROW__;
1018  }
1019 
1020  // enforce unique Display Name
1021  if((i = searchUsersDatabaseForDisplayName(displayName)) != NOT_FOUND_IN_DATABASE)
1022  // from being created!
1023  {
1024  __SS__ << "Display Name '" << displayName
1025  << "' already exists! Please choose a unique display name." << __E__;
1026  __SS_THROW__;
1027  }
1028 
1029  // create Users database entry
1030  Users_.push_back(User());
1031 
1032  Users_.back().username_ = username;
1033  Users_.back().displayName_ = displayName;
1034  Users_.back().email_ = email;
1035 
1036  // first user is admin always!
1037  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> initPermissions = {
1038  {WebUsers::DEFAULT_USER_GROUP,
1039  (Users_.size() ? WebUsers::PERMISSION_LEVEL_NOVICE
1041 
1042  Users_.back().permissions_ = initPermissions;
1043  Users_.back().userId_ = usersNextUserId_++;
1044  if(usersNextUserId_ >= ACCOUNT_ERROR_THRESHOLD) // error wrap around case
1045  {
1046  __SS__ << "usersNextUserId_ wrap around!! Too many users??? Notify Admins."
1047  << __E__;
1048  __SS_THROW__;
1049  usersNextUserId_ = 1; // for safety to avoid weird issues at -1 and 0 (if used
1050  // for error indication)
1051  }
1052 
1053  Users_.back().accountCreationTime_ = time(0);
1054 
1055  if(!saveDatabaseToFile(DB_USERS))
1056  {
1057  __SS__ << "Failed to save User DB!" << __E__;
1058  __SS_THROW__;
1059  }
1060 } // end createNewAccount()
1061 
1062 //==============================================================================
1068 bool WebUsers::deleteAccount(const std::string& username, const std::string& displayName)
1069 {
1070  uint64_t i = searchUsersDatabaseForUsername(username);
1071  if(i == NOT_FOUND_IN_DATABASE)
1072  return false;
1073  if(Users_[i].displayName_ != displayName)
1074  return false; // display name does not match
1075 
1076  // delete entry from user database vector
1077 
1078  Users_.erase(Users_.begin() + i);
1079 
1080  // save database
1081  return saveDatabaseToFile(DB_USERS);
1082 } // end deleteAccount()
1083 
1084 //==============================================================================
1085 unsigned int WebUsers::hexByteStrToInt(const char* h)
1086 {
1087  unsigned int rv;
1088  char hs[3] = {h[0], h[1], '\0'};
1089  sscanf(hs, "%X", &rv);
1090  return rv;
1091 } // end hexByteStrToInt()
1092 
1093 //==============================================================================
1094 void WebUsers::intToHexStr(unsigned char i, char* h) { sprintf(h, "%2.2X", i); }
1095 
1096 //==============================================================================
1106 uint64_t WebUsers::attemptActiveSession(const std::string& uuid,
1107  std::string& jumbledUser,
1108  const std::string& jumbledPw,
1109  std::string& newAccountCode,
1110  const std::string& ip)
1111 {
1112  //__COUTV__(ip);
1113  if(!checkIpAccess(ip))
1114  {
1115  __COUT_ERR__ << "rejected ip: " << ip << __E__;
1116  return ACCOUNT_BLACKLISTED;
1117  }
1118 
1119  cleanupExpiredEntries(); // remove expired active and login sessions
1120 
1121  if(!CareAboutCookieCodes_) // NO SECURITY
1122  {
1123  uint64_t uid = getAdminUserID();
1124  jumbledUser = getUsersDisplayName(uid);
1125  newAccountCode = genCookieCode(); // return "dummy" cookie code by reference
1126  return uid;
1127  }
1128 
1129  uint64_t i;
1130 
1131  // search login sessions for uuid
1132  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
1133  {
1134  __COUT_ERR__ << "uuid: " << uuid << " is not found" << __E__;
1135  newAccountCode = "1"; // to indicate uuid was not found
1136 
1137  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1138 
1139  return NOT_FOUND_IN_DATABASE;
1140  }
1141  ++LoginSessions_[i].loginAttempts_;
1142 
1143  std::string user = dejumble(jumbledUser, LoginSessions_[i].id_);
1144  __COUTV__(user);
1145  std::string pw = dejumble(jumbledPw, LoginSessions_[i].id_);
1146 
1147  // search users for username
1148  if((i = searchUsersDatabaseForUsername(user)) == NOT_FOUND_IN_DATABASE)
1149  {
1150  __COUT_ERR__ << "user: " << user << " is not found" << __E__;
1151 
1152  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1153 
1154  return NOT_FOUND_IN_DATABASE;
1155  }
1156  else
1157  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1158 
1159  Users_[i].lastLoginAttempt_ = time(0);
1160 
1161  if(isInactiveForGroup(Users_[i].permissions_))
1162  {
1163  __COUT_ERR__ << "User '" << user
1164  << "' account INACTIVE (could be due to failed logins)" << __E__;
1165  return ACCOUNT_INACTIVE;
1166  }
1167 
1168  if(Users_[i].salt_ == "") // first login
1169  {
1170  __COUT__ << "First login attempt for user: " << user << __E__;
1171 
1172  if(newAccountCode != Users_[i].getNewAccountCode())
1173  {
1174  __COUT__ << "New account code did not match: "
1175  << Users_[i].getNewAccountCode() << " != " << newAccountCode
1176  << __E__;
1177  saveDatabaseToFile(DB_USERS); // users db modified, so save
1178  return NOT_FOUND_IN_DATABASE;
1179  }
1180 
1181  // initial user account setup
1182 
1183  // add until no collision (should 'never' be a collision)
1184  while(!addToHashesDatabase(
1185  sha512(user, pw, Users_[i].salt_))) // sha256 modifies UsersSaltVector[i]
1186  {
1187  // this should never happen, it would mean the user+pw+saltcontext was the
1188  // same
1189  // but if it were to happen, try again...
1190  Users_[i].salt_ = "";
1191  }
1192 
1193  __COUT__ << "\tHash added: " << Hashes_.back().hash_ << __E__;
1194  }
1195  else
1196  {
1197  std::string salt = Users_[i].salt_; // don't want to modify saved salt
1198  //__COUT__ << salt.size() << " " << salt << " " << i << __E__;
1199  if(searchHashesDatabaseForHash(sha512(user, pw, salt)) == NOT_FOUND_IN_DATABASE)
1200  {
1201  __COUT__ << "Failed login for " << user << " with permissions "
1202  << StringMacros::mapToString(Users_[i].permissions_) << __E__;
1203 
1204  // do not allow wrap around
1205  if(++Users_[i].loginFailureCount_ != (unsigned char)-1)
1206  ++Users_[i].loginFailureCount_;
1207 
1208  if(Users_[i].loginFailureCount_ >= USERS_MAX_LOGIN_FAILURES)
1209  Users_[i].permissions_[WebUsers::DEFAULT_USER_GROUP] =
1210  WebUsers::PERMISSION_LEVEL_INACTIVE; // Lock account
1211 
1212  __COUT_INFO__ << "User/pw for user '" << user
1213  << "' was not correct (Failed Attempt #"
1214  << (int)Users_[i].loginFailureCount_ << " of "
1215  << (int)USERS_MAX_LOGIN_FAILURES << " allowed)." << __E__;
1216 
1217  __COUTV__(isInactiveForGroup(Users_[i].permissions_));
1218  if(isInactiveForGroup(Users_[i].permissions_))
1219  __COUT_INFO__ << "Account '" << user
1220  << "' has been marked inactive due to too many failed "
1221  "login attempts (Failed Attempt #"
1222  << (int)Users_[i].loginFailureCount_
1223  << ")! Note only admins can reactivate accounts." << __E__;
1224 
1225  saveDatabaseToFile(DB_USERS); // users db modified, so save
1226  return NOT_FOUND_IN_DATABASE;
1227  }
1228  }
1229 
1230  __COUT_INFO__ << "Login successful for: " << user << __E__;
1231 
1232  Users_[i].loginFailureCount_ = 0;
1233 
1234  // record to login history for user (h==0) and on global server level (h==1)
1235  for(int h = 0; h < 2; ++h)
1236  {
1237  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
1238  (std::string)USERS_LOGIN_HISTORY_PATH +
1239  (h ? USERS_GLOBAL_HISTORY_FILE : Users_[i].username_) + "." +
1240  (std::string)USERS_LOGIN_HISTORY_FILETYPE;
1241 
1242  HttpXmlDocument histXml;
1243 
1244  if(histXml.loadXmlDocument(fn)) // not found
1245  {
1246  while(histXml.getChildrenCount() + 1 >
1247  (h ? USERS_GLOBAL_HISTORY_SIZE : USERS_LOGIN_HISTORY_SIZE))
1248  histXml.removeDataElement();
1249  }
1250  else
1251  __COUT__ << "No previous login history found." << __E__;
1252 
1253  // add new entry to history
1254  char entryStr[500];
1255  if(h)
1256  sprintf(entryStr,
1257  "Time=%lu Username=%s Permissions=%s UID=%lu",
1258  time(0),
1259  Users_[i].username_.c_str(),
1260  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1261  Users_[i].userId_);
1262  else
1263  sprintf(entryStr,
1264  "Time=%lu displayName=%s Permissions=%s UID=%lu",
1265  time(0),
1266  Users_[i].displayName_.c_str(),
1267  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1268  Users_[i].userId_);
1269  histXml.addTextElementToData(PREF_XML_LOGIN_HISTORY_FIELD, entryStr);
1270 
1271  // save file
1272  histXml.saveXmlDocument(fn);
1273  }
1274 
1275  // SUCCESS!!
1276  saveDatabaseToFile(DB_USERS); // users db modified, so save
1277  jumbledUser = Users_[i].displayName_; // pass by reference displayName
1278  newAccountCode = createNewActiveSession(Users_[i].userId_,
1279  ip); // return cookie code by reference
1280 
1281  if(ActiveSessions_.size() ==
1282  1) // if only one user, then attempt to take lock for user friendliness
1283  {
1284  __COUT__ << "Attempting to auto-lock for first login user '"
1285  << Users_[i].username_ << "'... " << __E__;
1286  setUserWithLock(Users_[i].userId_, true /*lock*/, Users_[i].username_);
1287  }
1288 
1289  return Users_[i].userId_; // return user Id
1290 } // end attemptActiveSession()
1291 
1292 //==============================================================================
1298 uint64_t WebUsers::attemptActiveSessionWithCert(const std::string& uuid,
1299  std::string& email,
1300  std::string& cookieCode,
1301  std::string& user,
1302  const std::string& ip)
1303 {
1304  if(!checkIpAccess(ip))
1305  {
1306  __COUT_ERR__ << "rejected ip: " << ip << __E__;
1307  return NOT_FOUND_IN_DATABASE;
1308  }
1309 
1310  cleanupExpiredEntries(); // remove expired active and login sessions
1311 
1312  if(!CareAboutCookieCodes_) // NO SECURITY
1313  {
1314  uint64_t uid = getAdminUserID();
1315  email = getUsersDisplayName(uid);
1316  cookieCode = genCookieCode(); // return "dummy" cookie code by reference
1317  return uid;
1318  }
1319 
1320  if(email == "")
1321  {
1322  __COUT__ << "Rejecting cert logon with blank fingerprint" << __E__;
1323 
1324  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1325 
1326  return NOT_FOUND_IN_DATABASE;
1327  }
1328 
1329  uint64_t i;
1330 
1331  // search login sessions for uuid
1332  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
1333  {
1334  __COUT__ << "uuid: " << uuid << " is not found" << __E__;
1335  cookieCode = "1"; // to indicate uuid was not found
1336 
1337  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1338 
1339  return NOT_FOUND_IN_DATABASE;
1340  }
1341  ++LoginSessions_[i].loginAttempts_;
1342 
1343  email = getUserEmailFromFingerprint(email);
1344  __COUT__ << "DejumbledEmail = " << email << __E__;
1345  if(email == "")
1346  {
1347  __COUT__ << "Rejecting logon with unknown fingerprint" << __E__;
1348 
1349  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1350 
1351  return NOT_FOUND_IN_DATABASE;
1352  }
1353 
1354  // search users for username
1355  if((i = searchUsersDatabaseForUserEmail(email)) == NOT_FOUND_IN_DATABASE)
1356  {
1357  __COUT__ << "email: " << email << " is not found" << __E__;
1358 
1359  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1360 
1361  return NOT_FOUND_IN_DATABASE;
1362  }
1363  else
1364  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1365 
1366  user = getUsersUsername(i);
1367 
1368  Users_[i].lastLoginAttempt_ = time(0);
1369  if(isInactiveForGroup(Users_[i].permissions_))
1370  {
1371  __COUT__ << "User '" << user
1372  << "' account INACTIVE (could be due to failed logins)." << __E__;
1373  return NOT_FOUND_IN_DATABASE;
1374  }
1375 
1376  if(Users_[i].salt_ == "") // Can't be first login
1377  {
1378  return NOT_FOUND_IN_DATABASE;
1379  }
1380 
1381  __COUT__ << "Login successful for: " << user << __E__;
1382 
1383  Users_[i].loginFailureCount_ = 0;
1384 
1385  // record to login history for user (h==0) and on global server level (h==1)
1386  for(int h = 0; h < 2; ++h)
1387  {
1388  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
1389  (std::string)USERS_LOGIN_HISTORY_PATH +
1390  (h ? USERS_GLOBAL_HISTORY_FILE : Users_[i].username_) + "." +
1391  (std::string)USERS_LOGIN_HISTORY_FILETYPE;
1392 
1393  HttpXmlDocument histXml;
1394 
1395  if(histXml.loadXmlDocument(fn)) // not found
1396  {
1397  while(histXml.getChildrenCount() + 1 >
1398  (h ? USERS_GLOBAL_HISTORY_SIZE : USERS_LOGIN_HISTORY_SIZE))
1399  histXml.removeDataElement();
1400  }
1401  else
1402  __COUT__ << "No previous login history found." << __E__;
1403 
1404  // add new entry to history
1405  char entryStr[500];
1406  if(h)
1407  sprintf(entryStr,
1408  "Time=%lu Username=%s Permissions=%s UID=%lu",
1409  time(0),
1410  Users_[i].username_.c_str(),
1411  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1412  Users_[i].userId_);
1413  else
1414  sprintf(entryStr,
1415  "Time=%lu displayName=%s Permissions=%s UID=%lu",
1416  time(0),
1417  Users_[i].displayName_.c_str(),
1418  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1419  Users_[i].userId_);
1420  histXml.addTextElementToData(PREF_XML_LOGIN_HISTORY_FIELD, entryStr);
1421 
1422  // save file
1423  histXml.saveXmlDocument(fn);
1424  }
1425 
1426  // SUCCESS!!
1427  saveDatabaseToFile(DB_USERS); // users db modified, so save
1428  email = Users_[i].displayName_; // pass by reference displayName
1429  cookieCode = createNewActiveSession(Users_[i].userId_,
1430  ip); // return cookie code by reference
1431  return Users_[i].userId_; // return user Id
1432 } // end attemptActiveSessionWithCert()
1433 
1434 //==============================================================================
1437 uint64_t WebUsers::searchActiveSessionDatabaseForCookie(
1438  const std::string& cookieCode) const
1439 {
1440  uint64_t i = 0;
1441  for(; i < ActiveSessions_.size(); ++i)
1442  if(ActiveSessions_[i].cookieCode_ == cookieCode)
1443  break;
1444  return (i == ActiveSessions_.size()) ? NOT_FOUND_IN_DATABASE : i;
1445 } //end searchActiveSessionDatabaseForCookie()
1446 
1451 // {
1452 // for(const auto& remoteSession : RemoteSessions_)
1453 // if(remoteSession.second.second.username_ == username)
1454 // return remoteSession.first;
1455 // return NOT_FOUND_IN_DATABASE;
1456 // } //end searchRemoteSessionDatabaseForUsername()
1457 
1458 //==============================================================================
1462 uint64_t WebUsers::checkRemoteLoginVerification(std::string& cookieCode,
1463  bool refresh,
1464  bool doNotGoRemote,
1465  const std::string& ip)
1466 {
1467  __COUTVS__(2, cookieCode);
1468  remoteLoginVerificationEnabledBlackoutTime_ = 0;
1469  if(!remoteLoginVerificationSocket_) //instantiate socket first time needed
1470  {
1472  {
1473  __SS__
1474  << "Illegal remote login verification port found in remote destination "
1475  << remoteLoginVerificationIP_ << ":" << remoteLoginVerificationPort_
1476  << ". Please check remote settings." << __E__;
1477  __SS_THROW__;
1478  }
1479  __COUT_INFO__ << "Instantiating Remote Gateway login verification socket! "
1480  "Validation requests will go to "
1481  << remoteLoginVerificationIP_ << ":" << remoteLoginVerificationPort_
1482  << __E__;
1483 
1484  remoteLoginVerificationSocket_ =
1485  std::make_unique<TransceiverSocket>(remoteLoginVerificationIP_);
1486  remoteLoginVerificationSocket_->initialize();
1487 
1488  remoteLoginVerificationSocketTarget_ = std::make_unique<Socket>(
1489  remoteLoginVerificationIP_, remoteLoginVerificationPort_);
1490  }
1491 
1492  //check if cookie code is cached locally
1493  cleanupExpiredRemoteEntries(); // remove expired cookies
1494  __COUTTV__(cookieCode);
1495  __COUTTV__(RemoteSessions_.size());
1496  auto it = RemoteSessions_.find(cookieCode);
1497  if(it != RemoteSessions_.end()) //then found cached cookie code
1498  {
1499  __COUTT__ << "cookieCode still active locally!" << __E__;
1500  __COUTV__(it->second.userId_);
1501  return it->second.userId_;
1502  }
1503  //else ask Remote server to verify login
1504 
1505  __COUTTV__(doNotGoRemote);
1506  if(doNotGoRemote)
1507  return NOT_FOUND_IN_DATABASE;
1508 
1509  // Send these parameters:
1510  // command = loginVerify
1511  // parameters.addParameter("CookieCode");
1512  // parameters.addParameter("RefreshOption");
1513  // parameters.addParameter("IPAddress");
1514  // -- Use name to lookup access level conversion for user
1515  // -- if Desktop Icon has a special permission type, then modify userGroupPermissionsMap's allUsers to match
1516  // parameters.addParameter("RemoteGatewaySelfName");
1517 
1518  std::string request = "loginVerify," + cookieCode + "," + (refresh ? "1" : "0") +
1519  "," + ip + "," + remoteGatewaySelfName_;
1520 
1521  __COUTV__(request);
1522  __COUT_TYPE__(TLVL_DEBUG + 40) << __COUT_HDR__ << StringMacros::stackTrace() << __E__;
1523 
1524  std::string requestResponseString = remoteLoginVerificationSocket_->sendAndReceive(
1525  *remoteLoginVerificationSocketTarget_, request, 10 /*timeoutSeconds*/);
1526  __COUTV__(requestResponseString);
1527 
1528  //from response... extract refreshedCookieCode, permissions, userWithLock, username, and display name
1529  std::vector<std::string> rxParams =
1530  StringMacros::getVectorFromString(requestResponseString);
1531  __COUTV__(StringMacros::vectorToString(rxParams));
1532 
1533  if(rxParams.size() != 6)
1534  {
1535  __COUT__ << "Remote login response indicates rejected: " << rxParams.size()
1536  << __E__;
1537  return NOT_FOUND_IN_DATABASE;
1538  }
1539  //else valid remote login! so create active remote session object
1540 
1541  // Receive these parameters
1542  // 0: retParameters.addParameter("CookieCode", cookieCode);
1543  // 1: retParameters.addParameter("Permissions", StringMacros::mapToString(userGroupPermissionsMap).c_str());
1544  // 2: retParameters.addParameter("UserWithLock", userWithLock);
1545  // 3: retParameters.addParameter("Username", theWebUsers_.getUsersUsername(uid));
1546  // 4: retParameters.addParameter("DisplayName", theWebUsers_.getUsersDisplayName(uid));
1547  // 5: retParameters.addParameter("UserSessionIndex", td::to_string(userSessionIndex));
1548 
1549  __COUTTV__(rxParams[2]); //Primary Gateway user-with-lock
1550  __COUTTV__(usersUsernameWithLock_); //Local Gateway user-with-lock
1551 
1552  //search for an existing matching username, otherwise create
1553  std::string username = rxParams[3];
1554  __COUTTV__(username);
1555  uint64_t j = searchUsersDatabaseForUsername(username);
1556  if(j == NOT_FOUND_IN_DATABASE)
1557  {
1558  __COUT_INFO__ << "Creating User entry for remote user '" << username
1559  << "' in local user list to track user preferences." << __E__;
1560 
1561  //Note: createNewAccount will validate username and displayName
1562  createNewAccount(username, rxParams[4] /* displayName */, "" /* email */);
1563  j = Users_.size() - 1;
1564  }
1565 
1566  Users_[j].lastLoginAttempt_ = time(0);
1567  Users_[j].setModifier("REMOTE_GATEWAY");
1568 
1569  //take permissions from remote source always, it overrides existing local user settings (and will force changes to local user db)
1570  __COUTV__(StringMacros::decodeURIComponent(rxParams[1]));
1571  Users_[j]
1572  .permissions_.clear(); //otherwise collissions could occur in getMapFromString()
1574  Users_[j].permissions_);
1575  __COUTV__(StringMacros::mapToString(Users_[j].permissions_));
1576  __COUTV__(Users_[j].username_);
1577  __COUTV__(Users_[j].userId_);
1578 
1579  //fill in Remote Session and User info to cache for next login attempt
1580 
1581  cookieCode = rxParams[0]; //modify cookieCode for response
1582  __COUTTV__(cookieCode);
1583  ActiveSession& newRemoteSession =
1584  RemoteSessions_[cookieCode]; //construct remote ActiveSession
1585  newRemoteSession.cookieCode_ = cookieCode;
1586  newRemoteSession.ip_ = ip;
1587  newRemoteSession.userId_ = Users_[j].userId_;
1588  sscanf(rxParams[5].c_str(), "%lu", &newRemoteSession.sessionIndex_);
1589  newRemoteSession.startTime_ = time(0);
1590 
1591  //handle local user-with-lock
1592  if(!CareAboutCookieCodes_ && refresh &&
1593  usersUsernameWithLock_ == DEFAULT_ADMIN_USERNAME &&
1594  usersUsernameWithLock_ != username)
1595  {
1596  __COUT_INFO__ << "Overriding local user-with-lock '" << usersUsernameWithLock_
1597  << "' with remote user-with-lock 'Remote:" << username << "'"
1598  << __E__;
1599  usersUsernameWithLock_ =
1600  username; //Note: not calling setUserWithLock() because taking lock was incidental (on ots restart, will revert lock to admin still)
1601  addSystemMessage( //broadcast change!
1602  "*",
1603  getUserWithLock() + " has locked REMOTE ots (overriding anonymous " +
1604  DEFAULT_ADMIN_USERNAME + " user).");
1605  }
1606 
1607  __COUTT__ << "Returning remote login success" << __E__;
1608  return Users_[j].userId_;
1609 } //end checkRemoteLoginVerification()
1610 
1611 //==============================================================================
1614 bool WebUsers::isUsernameActive(const std::string& username) const
1615 {
1616  uint64_t u;
1617  if((u = searchUsersDatabaseForUsername(username)) == NOT_FOUND_IN_DATABASE)
1618  return false;
1619  return isUserIdActive(Users_[u].userId_);
1620 } //end isUsernameActive()
1621 
1622 //==============================================================================
1625 bool WebUsers::isUserIdActive(uint64_t uid) const
1626 {
1627  __COUTT__ << "isUserIdActive? " << uid << __E__;
1628  if(remoteLoginVerificationEnabled_) //first check remote sessions
1629  {
1630  for(const auto& remoteSession : RemoteSessions_)
1631  if(remoteSession.second.userId_ == uid)
1632  return true;
1633  } //end remote session checkion
1634 
1635  uint64_t i = 0;
1636  for(; i < ActiveSessions_.size(); ++i)
1637  if(ActiveSessions_[i].userId_ == uid)
1638  return true;
1639  return false;
1640 } // end isUserIdActive()
1641 
1642 //==============================================================================
1645 uint64_t WebUsers::searchUsersDatabaseForUsername(const std::string& username) const
1646 {
1647  uint64_t i = 0;
1648  for(; i < Users_.size(); ++i)
1649  if(Users_[i].username_ == username)
1650  break;
1651  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1652 } // end searchUsersDatabaseForUsername()
1653 
1654 //==============================================================================
1657 uint64_t WebUsers::searchUsersDatabaseForDisplayName(const std::string& displayName) const
1658 {
1659  uint64_t i = 0;
1660  for(; i < Users_.size(); ++i)
1661  if(Users_[i].displayName_ == displayName)
1662  break;
1663  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1664 } // end searchUsersDatabaseForUsername()
1665 
1666 //==============================================================================
1669 uint64_t WebUsers::searchUsersDatabaseForUserEmail(const std::string& useremail) const
1670 {
1671  uint64_t i = 0;
1672  for(; i < Users_.size(); ++i)
1673  if(Users_[i].email_ == useremail)
1674  break;
1675  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1676 } // end searchUsersDatabaseForUserEmail()
1677 
1678 //==============================================================================
1681 uint64_t WebUsers::searchUsersDatabaseForUserId(uint64_t uid) const
1682 {
1683  uint64_t i = 0;
1684  for(; i < Users_.size(); ++i)
1685  if(Users_[i].userId_ == uid)
1686  break;
1687  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1688 } // end searchUsersDatabaseForUserId();
1689 
1690 //==============================================================================
1693 uint64_t WebUsers::searchLoginSessionDatabaseForUUID(const std::string& uuid) const
1694 {
1695  uint64_t i = 0;
1696  for(; i < LoginSessions_.size(); ++i)
1697  if(LoginSessions_[i].uuid_ == uuid)
1698  break;
1699  return (i == LoginSessions_.size()) ? NOT_FOUND_IN_DATABASE : i;
1700 } // end searchLoginSessionDatabaseForUUID()
1701 
1702 //==============================================================================
1705 uint64_t WebUsers::searchHashesDatabaseForHash(const std::string& hash)
1706 {
1707  uint64_t i = 0;
1708  //__COUT__ << i << " " << Hashes_.size() << " " << hash << __E__;
1709  for(; i < Hashes_.size(); ++i)
1710  if(Hashes_[i].hash_ == hash)
1711  break;
1712  // else
1713  // __COUT__ << HashesVector[i] << " ?????? " << __E__;
1714  //__COUT__ << i << __E__;
1715  if(i < Hashes_.size()) // if found, means login successful, so update access time
1716  Hashes_[i].accessTime_ =
1717  ((time(0) + (rand() % 2 ? 1 : -1) * (rand() % 30 * 24 * 60 * 60)) &
1718  0x0FFFFFFFFFE000000);
1719  // else
1720  // __COUT__ << "No matching hash..." << __E__;
1721 
1722  //__COUT__ << i << __E__;
1723  return (i == Hashes_.size()) ? NOT_FOUND_IN_DATABASE : i;
1724 } // end searchHashesDatabaseForHash()
1725 
1726 //==============================================================================
1730 bool WebUsers::addToHashesDatabase(const std::string& hash)
1731 {
1732  if(searchHashesDatabaseForHash(hash) != NOT_FOUND_IN_DATABASE)
1733  {
1734  __COUT__ << "Hash collision: " << hash << __E__;
1735  return false;
1736  }
1737  Hashes_.push_back(Hash());
1738  Hashes_.back().hash_ = hash;
1739  Hashes_.back().accessTime_ =
1740  ((time(0) + (rand() % 2 ? 1 : -1) * (rand() % 30 * 24 * 60 * 60)) &
1741  0x0FFFFFFFFFE000000);
1742  // in seconds, blur by month and mask out changes on year time frame: 0xFFFFFFFF
1743  // FE000000
1744  return saveDatabaseToFile(DB_HASHES);
1745 } // end addToHashesDatabase()
1746 
1747 //==============================================================================
1749 std::string WebUsers::genCookieCode()
1750 {
1751  char hexStr[3];
1752  std::string cc = "";
1753  for(uint32_t i = 0; i < COOKIE_CODE_LENGTH / 2; ++i)
1754  {
1755  intToHexStr(rand(), hexStr);
1756  cc.append(hexStr);
1757  }
1758  return cc;
1759 } // end genCookieCode()
1760 
1761 //==============================================================================
1765 std::string WebUsers::createNewActiveSession(uint64_t uid,
1766  const std::string& ip,
1767  uint64_t asIndex)
1768 {
1769  //__COUTV__(ip);
1770  ActiveSessions_.push_back(ActiveSession());
1771  ActiveSessions_.back().cookieCode_ = genCookieCode();
1772  ActiveSessions_.back().ip_ = ip;
1773  ActiveSessions_.back().userId_ = uid;
1774  ActiveSessions_.back().startTime_ = time(0);
1775 
1776  if(asIndex) // this is a refresh of current active session
1777  ActiveSessions_.back().sessionIndex_ = asIndex;
1778  else
1779  {
1780  // find max(ActiveSessionIndex)
1781  uint64_t max = 0;
1782  for(uint64_t j = 0; j < ActiveSessions_.size(); ++j)
1783  if(ActiveSessions_[j].userId_ == uid &&
1784  max < ActiveSessions_[j].sessionIndex_) // new max
1785  max = ActiveSessions_[j].sessionIndex_;
1786 
1787  ActiveSessions_.back().sessionIndex_ = (max ? max + 1 : 1); // 0 is illegal
1788  }
1789 
1790  return ActiveSessions_.back().cookieCode_;
1791 } // end createNewActiveSession()
1792 
1793 //==============================================================================
1817 std::string WebUsers::refreshCookieCode(unsigned int i, bool enableRefresh)
1818 {
1819  // find most recent cookie for ActiveSessionIndex (should be deepest in vector always)
1820  for(uint64_t j = ActiveSessions_.size() - 1; j != (uint64_t)-1;
1821  --j) // reverse iterate vector
1822  if(ActiveSessions_[j].userId_ == ActiveSessions_[i].userId_ &&
1823  ActiveSessions_[j].sessionIndex_ ==
1824  ActiveSessions_[i].sessionIndex_) // if uid and asIndex match, found match
1825  {
1826  // found!
1827 
1828  // If half of expiration time is up, a new cookie is generated as most recent
1829  if(enableRefresh && (time(0) - ActiveSessions_[j].startTime_ >
1830  ACTIVE_SESSION_EXPIRATION_TIME / 2))
1831  {
1832  // but previous is maintained and start time is changed to accommodate
1833  // overlap time.
1834  ActiveSessions_[j].startTime_ =
1835  time(0) - ACTIVE_SESSION_EXPIRATION_TIME +
1836  ACTIVE_SESSION_COOKIE_OVERLAP_TIME; // give time window for stale
1837  // cookie commands before
1838  // expiring
1839 
1840  // create new active cookieCode with same ActiveSessionIndex, will now be
1841  // found as most recent
1842  return createNewActiveSession(ActiveSessions_[i].userId_,
1843  ActiveSessions_[i].ip_,
1844  ActiveSessions_[i].sessionIndex_);
1845  }
1846 
1847  return ActiveSessions_[j].cookieCode_; // cookieCode is unchanged
1848  }
1849 
1850  return "0"; // failure, should be impossible since i is already validated
1851 } // end refreshCookieCode()
1852 
1853 //==============================================================================
1858 uint64_t WebUsers::isCookieCodeActiveForLogin(const std::string& uuid,
1859  std::string& cookieCode,
1860  std::string& username)
1861 {
1862  if(!CareAboutCookieCodes_)
1863  return getAdminUserID(); // always successful
1864 
1865  // else
1866  // __COUT__ << "I care about
1867  // cookies?!?!?!*************************************************" << __E__;
1868 
1869  if(!ActiveSessions_.size())
1870  return NOT_FOUND_IN_DATABASE; // no active sessions, so do nothing
1871 
1872  uint64_t i, j; // used to iterate and search
1873 
1874  // find uuid in login session database else return "0"
1875  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
1876  {
1877  __COUT__ << "uuid not found: " << uuid << __E__;
1878  return NOT_FOUND_IN_DATABASE;
1879  }
1880 
1881  username =
1882  dejumble(username, LoginSessions_[i].id_); // dejumble user for cookie check
1883 
1884  // search active users for cookie code
1885  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
1886  {
1887  __COUT__ << "Cookie code not found" << __E__;
1888  return NOT_FOUND_IN_DATABASE;
1889  }
1890 
1891  // search users for user id
1892  if((j = searchUsersDatabaseForUserId(ActiveSessions_[i].userId_)) ==
1893  NOT_FOUND_IN_DATABASE)
1894  {
1895  __COUT__ << "User ID not found" << __E__;
1896  return NOT_FOUND_IN_DATABASE;
1897  }
1898 
1899  // match username, with one found
1900  if(Users_[j].username_ != username)
1901  {
1902  __COUT__ << "cookieCode: " << cookieCode << " was.." << __E__;
1903  __COUT__ << "username: " << username << " is not found" << __E__;
1904  return NOT_FOUND_IN_DATABASE;
1905  }
1906 
1907  username = Users_[j].displayName_; // return display name by reference
1908  cookieCode = refreshCookieCode(i); // refresh cookie by reference
1909  return Users_[j].userId_; // return user ID
1910 }
1911 
1912 //==============================================================================
1916 {
1917  bool unique;
1918  std::vector<uint64_t> uniqueAsi; // maintain unique as indices for reference
1919 
1920  uint64_t i, j;
1921  for(i = 0; i < ActiveSessions_.size(); ++i)
1922  if(ActiveSessions_[i].userId_ == uid) // found active session for user
1923  {
1924  // check if ActiveSessionIndex is unique
1925  unique = true;
1926 
1927  for(j = 0; j < uniqueAsi.size(); ++j)
1928  if(uniqueAsi[j] == ActiveSessions_[i].sessionIndex_)
1929  {
1930  unique = false;
1931  break;
1932  }
1933 
1934  if(unique) // unique! so count and save
1935  uniqueAsi.push_back(ActiveSessions_[i].sessionIndex_);
1936  }
1937 
1938  __COUT__ << "Found " << uniqueAsi.size() << " active sessions for uid " << uid
1939  << __E__;
1940 
1941  return uniqueAsi.size();
1942 } // end getActiveSessionCountForUser()
1943 
1944 //==============================================================================
1950 bool WebUsers::checkIpAccess(const std::string& ip)
1951 {
1952  if(ip == "0")
1953  return true; // always accept dummy IP
1954 
1955  __COUTTV__(ip);
1956 
1957  if(time(0) > ipSecurityLastLoadTime_ +
1958  10 * 60 * 60) //every 10 minutes (to allow manual dynamic changes)
1959  {
1960  ipSecurityLastLoadTime_ = time(0);
1961  loadIPAddressSecurity();
1962  }
1963 
1964  for(const auto& acceptIp : ipAccessAccept_)
1965  if(StringMacros::wildCardMatch(ip, acceptIp))
1966  {
1967  __COUTV__(acceptIp);
1968  return true; // found in accept set, so accept
1969  }
1970  for(const auto& rejectIp : ipAccessReject_)
1971  if(StringMacros::wildCardMatch(ip, rejectIp))
1972  {
1973  __COUTV__(rejectIp);
1974  return false; // found in reject file, so reject
1975  }
1976  for(const auto& blacklistIp : ipAccessBlacklist_)
1977  if(StringMacros::wildCardMatch(ip, blacklistIp))
1978  {
1979  __COUTV__(blacklistIp);
1980  return false; // found in blacklist file, so reject
1981  }
1982 
1983  // default to accept if nothing triggered above
1984  return true;
1985 } // end checkIpAccess()
1986 
1987 //==============================================================================
1989 void WebUsers::incrementIpBlacklistCount(const std::string& ip)
1990 {
1991  if(ipAccessBlacklist_.find(ip) != ipAccessBlacklist_.end())
1992  return; //already in IP blacklist
1993 
1994  // increment ip blacklist counter
1995  auto it = ipBlacklistCounts_.find(ip);
1996  if(it == ipBlacklistCounts_.end())
1997  {
1998  __COUT__ << "First error for ip '" << ip << "'" << __E__;
1999  ipBlacklistCounts_[ip] = 1;
2000  }
2001  else
2002  {
2003  ++(it->second);
2004 
2005  if(it->second >= IP_BLACKLIST_COUNT_THRESHOLD)
2006  {
2007  __COUT_WARN__ << "Adding IP '" << ip << "' to blacklist!" << __E__;
2008 
2009  ipAccessBlacklist_.emplace(ip);
2010  __COUTV__(ipAccessBlacklist_.size());
2011 
2012  // append to blacklisted IP to generated IP reject file
2013  FILE* fp = fopen((IP_BLACKLIST_FILE).c_str(), "a");
2014  if(!fp)
2015  {
2016  __COUT_ERR__ << "IP black list file '" << IP_BLACKLIST_FILE
2017  << "' could not be opened." << __E__;
2018  return;
2019  }
2020  fprintf(fp, "%s\n", ip.c_str());
2021  fclose(fp);
2022  }
2023  }
2024 } // end incrementIpBlacklistCount()
2025 
2026 //==============================================================================
2028 std::string WebUsers::getUsersDisplayName(uint64_t uid)
2029 {
2030  uint64_t i;
2031  if((i = searchUsersDatabaseForUserId(uid)) == NOT_FOUND_IN_DATABASE)
2032  return "";
2033  return Users_[i].displayName_;
2034 } // end getUsersDisplayName()
2035 
2036 //==============================================================================
2038 std::string WebUsers::getUsersUsername(uint64_t uid)
2039 {
2040  uint64_t i;
2041  if((i = searchUsersDatabaseForUserId(uid)) == NOT_FOUND_IN_DATABASE)
2042  return "";
2043  return Users_[i].username_;
2044 } // end getUsersUsername()
2045 
2046 //==============================================================================
2057 uint64_t WebUsers::cookieCodeLogout(const std::string& cookieCode,
2058  bool logoutOtherUserSessions,
2059  uint64_t* userId,
2060  const std::string& ip)
2061 {
2062  uint64_t i;
2063 
2064  // search active users for cookie code
2065  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
2066  {
2067  __COUT__ << "Cookie code not found" << __E__;
2068 
2069  incrementIpBlacklistCount(ip); // increment ip blacklist counter
2070 
2071  return NOT_FOUND_IN_DATABASE;
2072  }
2073  else
2074  ipBlacklistCounts_[ip] = 0; // clear blacklist count
2075 
2076  // check ip
2077  if(ActiveSessions_[i].ip_ != ip)
2078  {
2079  __COUT__ << "IP does not match active session" << __E__;
2080  return NOT_FOUND_IN_DATABASE;
2081  }
2082 
2083  // found valid active session i
2084  // if logoutOtherUserSessions
2085  // remove active sessions that match ActiveSessionUserIdVector[i] and
2086  // ActiveSessionIndex[i] else remove active sessions that match
2087  // ActiveSessionUserIdVector[i] but not ActiveSessionIndex[i]
2088 
2089  uint64_t asi = ActiveSessions_[i].sessionIndex_;
2090  uint64_t uid = ActiveSessions_[i].userId_;
2091  if(userId)
2092  *userId = uid; // return uid if requested
2093  uint64_t logoutCount = 0;
2094 
2095  i = 0;
2096  while(i < ActiveSessions_.size())
2097  {
2098  if((logoutOtherUserSessions && ActiveSessions_[i].userId_ == uid &&
2099  ActiveSessions_[i].sessionIndex_ != asi) ||
2100  (!logoutOtherUserSessions && ActiveSessions_[i].userId_ == uid &&
2101  ActiveSessions_[i].sessionIndex_ == asi))
2102  {
2103  __COUT__ << "Logging out of active session " << ActiveSessions_[i].userId_
2104  << "-" << ActiveSessions_[i].sessionIndex_ << __E__;
2105  ActiveSessions_.erase(ActiveSessions_.begin() + i);
2106  ++logoutCount;
2107  }
2108  else // only increment if no delete, for effectively erase rewind
2109  ++i;
2110  } // end cleanup active sessioins loop
2111 
2112  __COUT__ << "Found and removed active session count = " << logoutCount << __E__;
2113 
2114  return logoutCount;
2115 } // end cookieCodeLogout()
2116 
2117 //==============================================================================
2131  std::string& cookieCode,
2132  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>* userPermissions,
2133  uint64_t* uid,
2134  const std::string& ip,
2135  bool refresh,
2136  bool doNotGoRemote,
2137  std::string* userWithLock,
2138  uint64_t* userSessionIndex)
2139 {
2140  //__COUTV__(ip);
2141 
2142  // check ip black list and increment counter if cookie code not found
2143  if(!checkIpAccess(ip))
2144  {
2145  __COUT_ERR__ << "User IP rejected." << __E__;
2146  cookieCode = REQ_NO_LOGIN_RESPONSE;
2147  return false;
2148  }
2149 
2150  cleanupExpiredEntries(); // remove expired cookies
2151 
2152  uint64_t i, j, userId = NOT_FOUND_IN_DATABASE, userSession = NOT_FOUND_IN_DATABASE;
2153 
2154  __COUTTV__(CareAboutCookieCodes_);
2155  __COUTT__ << "refresh=" << refresh << ", doNotGoRemote=" << doNotGoRemote << __E__;
2156  __COUTVS__(2, cookieCode);
2157 
2158  bool localEnableRemoteLogin = WebUsers::
2159  remoteLoginVerificationEnabled_; //cache here so another process does not change mid-function
2160  __COUTTV__(localEnableRemoteLogin);
2161 
2162  //always go remote if enabled
2163  try
2164  {
2165  if(localEnableRemoteLogin &&
2166  time(0) > remoteLoginVerificationEnabledBlackoutTime_ &&
2167  (userId = checkRemoteLoginVerification(
2168  cookieCode, refresh, doNotGoRemote, ip)) != NOT_FOUND_IN_DATABASE)
2169  {
2170  // remote verify success!
2171  __COUTT__ << "Remote login session verified." << __E__;
2172  userSession = RemoteSessions_.at(cookieCode).sessionIndex_;
2173  }
2174  }
2175  catch(...)
2176  {
2177  std::string err = "";
2178  try
2179  {
2180  throw;
2181  }
2182  catch(const std::exception& e)
2183  {
2184  err = e.what();
2185  }
2186 
2187  __COUT_WARN__ << "Ignoring exception during remote login verification. " << err
2188  << __E__;
2189 
2190  //Disable remote login in the case that remote verifier is down
2191  if(!CareAboutCookieCodes_ && localEnableRemoteLogin &&
2192  remoteLoginVerificationEnabledBlackoutTime_ == 0)
2193  {
2194  remoteLoginVerificationEnabled_ = false; //set globally
2195  localEnableRemoteLogin = false; //set locally
2196  remoteLoginVerificationEnabledBlackoutTime_ = time(0) + 10;
2197  __COUT_INFO__ << "Disabled remote login until "
2199  remoteLoginVerificationEnabledBlackoutTime_)
2200  << __E__;
2201  }
2202  }
2203  __COUTTV__(localEnableRemoteLogin);
2204 
2205  if(localEnableRemoteLogin && userId == NOT_FOUND_IN_DATABASE)
2206  __COUTT__ << "Remote login verification failed." << __E__;
2207 
2208  if(!CareAboutCookieCodes_ &&
2209  userId == NOT_FOUND_IN_DATABASE) // No Security, so grant admin
2210  {
2211  if(userPermissions)
2212  *userPermissions =
2213  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>(
2214  {{WebUsers::DEFAULT_USER_GROUP, WebUsers::PERMISSION_LEVEL_ADMIN}});
2215  if(uid)
2216  *uid = getAdminUserID();
2217  if(userWithLock)
2218  *userWithLock = usersUsernameWithLock_;
2219  if(userSessionIndex)
2220  *userSessionIndex = 0;
2221 
2222  if(cookieCode.size() != COOKIE_CODE_LENGTH)
2223  cookieCode = genCookieCode(); // return "dummy" cookie code
2224 
2225  if(localEnableRemoteLogin) //want future login attempts to still go to remote
2226  {
2227  cookieCode = WebUsers::
2228  REQ_ALLOW_NO_USER; //allowNoUser will not overwrite other valid cookieCodes in parent Gateway Desktop
2229  }
2230 
2231  return true;
2232  }
2233  // else using security!
2234 
2235  if(userId == NOT_FOUND_IN_DATABASE) //handle standard active session verify
2236  {
2237  // search active users for cookie code
2238  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) ==
2239  NOT_FOUND_IN_DATABASE)
2240  {
2241  __COUT_ERR__ << "Cookie code not found" << __E__;
2242  cookieCode = REQ_NO_LOGIN_RESPONSE;
2243 
2244  incrementIpBlacklistCount(ip); // increment ip blacklist counter
2245 
2246  return false;
2247  }
2248  else
2249  ipBlacklistCounts_[ip] = 0; // clear blacklist count
2250 
2251  // check ip
2252  if(ip != "0" && ActiveSessions_[i].ip_ != ip)
2253  {
2254  __COUTV__(ActiveSessions_[i].ip_);
2255  __COUTV__(ip);
2256  __COUT_ERR__ << "IP does not match active session." << __E__;
2257  cookieCode = REQ_NO_LOGIN_RESPONSE;
2258  return false;
2259  }
2260 
2261  userId = ActiveSessions_[i].userId_;
2262  userSession = ActiveSessions_[i].sessionIndex_;
2263  cookieCode = refreshCookieCode(i, refresh); // refresh cookie by reference
2264  __COUTT__ << "Login session verified." << __E__;
2265  }
2266 
2267  //at this point userId has been confirmed remotely or locally
2268 
2269  // get Users record
2270  if((j = searchUsersDatabaseForUserId(userId)) == NOT_FOUND_IN_DATABASE)
2271  {
2272  __COUT_ERR__ << "After login verification, User ID not found! Notify admins."
2273  << __E__;
2274  cookieCode = REQ_NO_LOGIN_RESPONSE;
2275  return false;
2276  }
2277 
2278  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> tmpPerm =
2279  getPermissionsForUser(userId);
2280 
2281  if(isInactiveForGroup(tmpPerm)) // Check for inactive for all requests!
2282  {
2283  __COUTT__ << "Inactive user identified." << __E__;
2284  cookieCode = REQ_NO_PERMISSION_RESPONSE;
2285  return false;
2286  }
2287 
2288  // success!
2289  if(userPermissions)
2290  *userPermissions = tmpPerm;
2291  if(uid)
2292  *uid = userId;
2293  if(userWithLock)
2294  *userWithLock = usersUsernameWithLock_;
2295  if(userSessionIndex)
2296  *userSessionIndex = userSession;
2297 
2298  return true;
2299 } // end cookieCodeIsActiveForRequest()
2300 
2301 //==============================================================================
2308 void WebUsers::cleanupExpiredEntries(std::vector<std::string>* loggedOutUsernames)
2309 {
2310  uint64_t i; // used to iterate and search
2311  uint64_t tmpUid;
2312 
2313  if(loggedOutUsernames) // return logged out users this time and clear storage vector
2314  {
2315  for(i = 0; i < UsersLoggedOutUsernames_.size(); ++i)
2316  loggedOutUsernames->push_back(UsersLoggedOutUsernames_[i]);
2317  UsersLoggedOutUsernames_.clear();
2318  }
2319 
2320  // remove expired entries from Login Session
2321  for(i = 0; i < LoginSessions_.size(); ++i)
2322  if(LoginSessions_[i].startTime_ + LOGIN_SESSION_EXPIRATION_TIME <
2323  time(0) || // expired
2324  LoginSessions_[i].loginAttempts_ > LOGIN_SESSION_ATTEMPTS_MAX)
2325  {
2326  __COUT__ << "Found expired login sessions: #" << (i + 1) << " of "
2327  << LoginSessions_.size() << __E__;
2328  //" at time " << LoginSessionStartTimeVector[i] << " with attempts " <<
2329  // LoginSessionAttemptsVector[i] << __E__;
2330 
2331  LoginSessions_.erase(LoginSessions_.begin() + i);
2332  --i; // rewind loop
2333  }
2334 
2335  // declare structures for ascii time
2336  // struct tm * timeinfo;
2337  // time_t tmpt;
2338  // char tstr[200];
2339  // timeinfo = localtime ( &(tmpt=time(0)) );
2340  // sprintf(tstr,"\"%s\"",asctime (timeinfo)); tstr[strlen(tstr)-2] = '\"';
2341  //__COUT__ << "Current time is: " << time(0) << " " << tstr << __E__;
2342 
2343  // remove expired entries from Active Session
2344  for(i = 0; i < ActiveSessions_.size(); ++i)
2345  if(ActiveSessions_[i].startTime_ + ACTIVE_SESSION_EXPIRATION_TIME <=
2346  time(0)) // expired
2347  {
2348  // timeinfo = localtime (&(tmpt=ActiveSessionStartTimeVector[i]));
2349  // sprintf(tstr,"\"%s\"",asctime (timeinfo)); tstr[strlen(tstr)-2] = '\"';
2350  //__COUT__ << "Found expired user: " << ActiveSessionUserIdVector[i] <<
2351  // " start time " << tstr << " i: " << i << " size: " <<
2352  // ActiveSessionStartTimeVector.size()
2353  // << __E__;
2354 
2355  __COUT__ << "Found expired active sessions: #" << (i + 1) << " of "
2356  << ActiveSessions_.size() << __E__;
2357  __COUTTV__(ActiveSessions_[i].cookieCode_);
2358 
2359  tmpUid = ActiveSessions_[i].userId_;
2360  ActiveSessions_.erase(ActiveSessions_.begin() + i);
2361 
2362  if(!isUserIdActive(tmpUid)) // if uid no longer active, then user was
2363  // completely logged out
2364  {
2365  if(loggedOutUsernames) // return logged out users this time
2366  loggedOutUsernames->push_back(
2367  Users_[searchUsersDatabaseForUserId(tmpUid)].username_);
2368  else // store for next time requested as parameter
2369  UsersLoggedOutUsernames_.push_back(
2370  Users_[searchUsersDatabaseForUserId(tmpUid)].username_);
2371  }
2372 
2373  --i; // rewind loop
2374  }
2375  // else
2376  // {
2377  // timeinfo = localtime (&(tmpt=ActiveSessionStartTimeVector[i] +
2378  // ACTIVE_SESSION_EXPIRATION_TIME)); sprintf(tstr,"\"%s\"",asctime
2379  //(timeinfo)); tstr[strlen(tstr)-2] = '\"';
2380  //
2381  // //__COUT__ << "Found user: " << ActiveSessionUserIdVector[i] << "-" <<
2382  // ActiveSessionIndex[i] <<
2383  // // " expires " << tstr <<
2384  // // " sec left " << ActiveSessionStartTimeVector[i] +
2385  // ACTIVE_SESSION_EXPIRATION_TIME - time(0) << __E__;
2386  //
2387  // }
2388 
2389  //__COUT__ << "Found usersUsernameWithLock_: " << usersUsernameWithLock_ << __E__;
2390  // size_t posRemoteFlag = std::string::npos;
2391  if(CareAboutCookieCodes_ && usersUsernameWithLock_ != "" &&
2392  // ((remoteLoginVerificationEnabled_ && //if remote login enabled, check if userWithLock is remote
2393  // (posRemoteFlag = usersUsernameWithLock_.find(REMOTE_USERLOCK_PREFIX)) == 0 &&
2394  // searchRemoteSessionDatabaseForUsername(
2395  // usersUsernameWithLock_.substr(strlen(REMOTE_USERLOCK_PREFIX))) == NOT_FOUND_IN_DATABASE ) ||
2396  // (posRemoteFlag != 0 &&
2397  !isUsernameActive(usersUsernameWithLock_))
2398  //))) // unlock if user no longer logged in
2399  usersUsernameWithLock_ = "";
2400 } // end cleanupExpiredEntries()
2401 
2402 //==============================================================================
2407 {
2408  // remove expired entries from Remote Active Session
2409  std::vector<std::string> toErase;
2410  for(const auto& remoteSession : RemoteSessions_)
2411  if(remoteSession.second.startTime_ + ACTIVE_SESSION_EXPIRATION_TIME / 4 <=
2412  time(0)) // expired
2413  {
2414  __COUT__ << "Found expired remote active sessions: #" << remoteSession.first
2415  << " in " << RemoteSessions_.size() << __E__;
2416  toErase.push_back(remoteSession.first); //mark for erasing
2417  }
2418  for(const auto& eraseId : toErase)
2419  RemoteSessions_.erase(eraseId);
2420 } // end cleanupExpiredRemoteEntries()
2421 
2422 //==============================================================================
2429 std::string WebUsers::createNewLoginSession(const std::string& UUID,
2430  const std::string& ip)
2431 {
2432  __COUTV__(UUID);
2433  //__COUTV__(ip);
2434 
2435  uint64_t i = 0;
2436  for(; i < LoginSessions_.size(); ++i)
2437  if(LoginSessions_[i].uuid_ == UUID)
2438  break;
2439 
2440  if(i != LoginSessions_.size())
2441  {
2442  __COUT_ERR__ << "UUID: " << UUID << " is not unique" << __E__;
2443  return "";
2444  }
2445  // else UUID is unique
2446 
2447  LoginSessions_.push_back(LoginSession());
2448  LoginSessions_.back().uuid_ = UUID;
2449 
2450  // generate sessionId
2451  char hexStr[3];
2452  std::string sid = "";
2453  for(i = 0; i < SESSION_ID_LENGTH / 2; ++i)
2454  {
2455  intToHexStr(rand(), hexStr);
2456  sid.append(hexStr);
2457  }
2458  LoginSessions_.back().id_ = sid;
2459  LoginSessions_.back().ip_ = ip;
2460  LoginSessions_.back().startTime_ = time(0);
2461  LoginSessions_.back().loginAttempts_ = 0;
2462 
2463  return sid;
2464 } // end createNewLoginSession()
2465 
2466 //==============================================================================
2471 std::string WebUsers::sha512(const std::string& user,
2472  const std::string& password,
2473  std::string& salt)
2474 {
2475  SHA512_CTX sha512_context;
2476  char hexStr[3];
2477 
2478  if(salt == "") // generate context
2479  {
2480  SHA512_Init(&sha512_context);
2481 
2482  for(unsigned int i = 0; i < 8; ++i)
2483  sha512_context.h[i] += rand();
2484 
2485  for(unsigned int i = 0; i < sizeof(SHA512_CTX); ++i)
2486  {
2487  intToHexStr((uint8_t)(((uint8_t*)(&sha512_context))[i]), hexStr);
2488 
2489  salt.append(hexStr);
2490  }
2491  //__COUT__ << salt << __E__;
2492  }
2493  else // use existing context
2494  {
2495  //__COUT__ << salt << __E__;
2496 
2497  for(unsigned int i = 0; i < sizeof(SHA512_CTX); ++i)
2498  ((uint8_t*)(&sha512_context))[i] = hexByteStrToInt(&(salt.c_str()[i * 2]));
2499  }
2500 
2501  std::string strToHash = salt + user + password;
2502 
2503  //__COUT__ << salt << __E__;
2504  unsigned char hash[SHA512_DIGEST_LENGTH];
2505  //__COUT__ << salt << __E__;
2506  char retHash[SHA512_DIGEST_LENGTH * 2 + 1];
2507  //__COUT__ << strToHash.length() << " " << strToHash << __E__;
2508 
2509  //__COUT__ << "If crashing occurs here, may be an illegal salt context." << __E__;
2510  SHA512_Update(&sha512_context, strToHash.c_str(), strToHash.length());
2511 
2512  SHA512_Final(hash, &sha512_context);
2513 
2514  //__COUT__ << salt << __E__;
2515  int i = 0;
2516  for(i = 0; i < SHA512_DIGEST_LENGTH; i++)
2517  sprintf(retHash + (i * 2), "%02x", hash[i]);
2518 
2519  //__COUT__ << salt << __E__;
2520  retHash[SHA512_DIGEST_LENGTH * 2] = '\0';
2521 
2522  //__COUT__ << salt << __E__;
2523 
2524  return retHash;
2525 } // end sha512()
2526 
2527 //==============================================================================
2531 std::string WebUsers::dejumble(const std::string& u, const std::string& s)
2532 {
2533  if(s.length() != SESSION_ID_LENGTH)
2534  return ""; // session std::string must be even
2535 
2536  const int ss = s.length() / 2;
2537  int p = hexByteStrToInt(&(s.c_str()[0])) % ss;
2538  int n = hexByteStrToInt(&(s.c_str()[p * 2])) % ss;
2539  int len = (hexByteStrToInt(&(u.c_str()[p * 2])) - p - n + ss * 3) % ss;
2540 
2541  std::vector<bool> x(ss);
2542  for(int i = 0; i < ss; ++i)
2543  x[i] = 0;
2544  x[p] = 1;
2545 
2546  int c = hexByteStrToInt(&(u.c_str()[p * 2]));
2547 
2548  std::string user = "";
2549 
2550  for(int l = 0; l < len; ++l)
2551  {
2552  p = (p + hexByteStrToInt(&(s.c_str()[p * 2]))) % ss;
2553  while(x[p])
2554  p = (p + 1) % ss;
2555  x[p] = 1;
2556  n = hexByteStrToInt(&(s.c_str()[p * 2]));
2557  user.append(1, (hexByteStrToInt(&(u.c_str()[p * 2])) - c - n + ss * 4) % ss);
2558  c = hexByteStrToInt(&(u.c_str()[p * 2]));
2559  }
2560 
2561  return user;
2562 } // end dejumble()
2563 
2564 //==============================================================================
2567 std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>
2569 {
2570  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2571  if(userIndex < Users_.size())
2572  return Users_[userIndex].permissions_;
2573 
2574  // else return all user inactive map
2575  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> retErrorMap;
2576  retErrorMap[WebUsers::DEFAULT_USER_GROUP] = WebUsers::PERMISSION_LEVEL_INACTIVE;
2577  return retErrorMap;
2578 } // end getPermissionsForUser()
2579 
2584 // {
2585 // if(uid == ACCOUNT_REMOTE)
2586 // {
2587 // auto it = RemoteSessions_.find(remoteSessionID);
2588 // if(it == RemoteSessions_.end())
2589 // {
2590 // // else return all user inactive map
2591 // std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> retErrorMap;
2592 // retErrorMap[WebUsers::DEFAULT_USER_GROUP] = WebUsers::PERMISSION_LEVEL_INACTIVE;
2593 // return retErrorMap;
2594 // }
2595 // return it->second.second.permissions_;
2596 // }
2597 
2610 //==============================================================================
2613 WebUsers::permissionLevel_t WebUsers::getPermissionLevelForGroup(
2614  const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap,
2615  const std::string& groupName)
2616 {
2617  auto it = permissionMap.find(groupName);
2618  if(it == permissionMap.end())
2619  {
2620  __COUT__ << "Group name '" << groupName
2621  << "' not found - assuming inactive user in this group." << __E__;
2622  return WebUsers::PERMISSION_LEVEL_INACTIVE;
2623  }
2624  return it->second;
2625 } // end getPermissionLevelForGroup()
2626 
2627 //==============================================================================
2628 bool WebUsers::isInactiveForGroup(
2629  const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap,
2630  const std::string& groupName)
2631 {
2632  return getPermissionLevelForGroup(permissionMap, groupName) ==
2633  WebUsers::PERMISSION_LEVEL_INACTIVE;
2634 }
2635 
2636 //==============================================================================
2637 bool WebUsers::isAdminForGroup(
2638  const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap,
2639  const std::string& groupName)
2640 {
2641  return getPermissionLevelForGroup(permissionMap, groupName) ==
2643 }
2644 
2645 //==============================================================================
2648 std::string WebUsers::getTooltipFilename(const std::string& username,
2649  const std::string& srcFile,
2650  const std::string& srcFunc,
2651  const std::string& srcId)
2652 {
2653  std::string filename = (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH + "/";
2654 
2655  // make tooltip directory if not there
2656  // note: this is static so WebUsers constructor has not necessarily been called
2657  mkdir(((std::string)WEB_LOGIN_DB_PATH).c_str(), 0755);
2658  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH).c_str(), 0755);
2659  mkdir(filename.c_str(), 0755);
2660 
2661  for(const char& c : username)
2662  if( // only keep alpha numeric
2663  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2664  filename += c;
2665  filename += "/";
2666 
2667  // make username tooltip directory if not there
2668  mkdir(filename.c_str(), 0755);
2669 
2670  for(const char& c : srcFile)
2671  if( // only keep alpha numeric
2672  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2673  filename += c;
2674  filename += "_";
2675  for(const char& c : srcFunc)
2676  if( // only keep alpha numeric
2677  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2678  filename += c;
2679  filename += "_";
2680  for(const char& c : srcId)
2681  if( // only keep alpha numeric
2682  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2683  filename += c;
2684  filename += ".tip";
2685  //__COUT__ << "filename " << filename << __E__;
2686  return filename;
2687 }
2688 
2689 std::string ots::WebUsers::getUserEmailFromFingerprint(const std::string& fingerprint)
2690 {
2691  __COUT__ << "Checking if user fingerprint " << fingerprint << " is in memory database"
2692  << __E__;
2693  if(certFingerprints_.count(fingerprint))
2694  {
2695  return certFingerprints_[fingerprint];
2696  }
2697 
2698  __COUT__ << "Going to read credential database " << WEB_LOGIN_CERTDATA_PATH << __E__;
2699  std::ifstream f(WEB_LOGIN_CERTDATA_PATH);
2700  bool open = false;
2701  while(f)
2702  {
2703  open = true;
2704  std::string email;
2705  std::string fp;
2706  f >> email >> fp;
2707  if(fp != "NOKEY" && fp != "")
2708  {
2709  __COUT__ << "Adding user " << email << " to list with fingerprint " << fp
2710  << __E__;
2711  certFingerprints_[fp] = email;
2712  }
2713  }
2714  if(open)
2715  {
2716  f.close();
2717  remove(WEB_LOGIN_CERTDATA_PATH.c_str());
2718  }
2719 
2720  __COUT__ << "Checking again if fingerprint is in memory database" << __E__;
2721  if(certFingerprints_.count(fingerprint))
2722  {
2723  return certFingerprints_[fingerprint];
2724  }
2725 
2726  __COUT__ << "Could not match fingerprint, returning null email" << __E__;
2727  return "";
2728 } // end getUserEmailFromFingerprint()
2729 
2730 //==============================================================================
2733 void WebUsers::tooltipSetNeverShowForUsername(const std::string& username,
2734  HttpXmlDocument* /*xmldoc*/,
2735  const std::string& srcFile,
2736  const std::string& srcFunc,
2737  const std::string& srcId,
2738  bool doNeverShow,
2739  bool temporarySilence)
2740 {
2741  std::string filename;
2742  bool isForAll = (srcFile == "ALL" && srcFunc == "ALL" && srcId == "ALL");
2743 
2744  if(isForAll)
2745  {
2746  __COUT__ << "Disabling ALL tooltips for user '" << username << "' is now set to "
2747  << doNeverShow << __E__;
2748  filename = getTooltipFilename(username, SILENCE_ALL_TOOLTIPS_FILENAME, "", "");
2749  }
2750  else
2751  {
2752  filename = getTooltipFilename(username, srcFile, srcFunc, srcId);
2753  __COUT__ << "Setting tooltip never show for user '" << username << "' to "
2754  << doNeverShow << " (temporarySilence=" << temporarySilence << ")"
2755  << __E__;
2756  }
2757 
2758  FILE* fp = fopen(filename.c_str(), "w");
2759  if(fp)
2760  { // file exists, so do NOT show tooltip
2761  if(temporarySilence)
2762  fprintf(fp,
2763  "%ld",
2764  time(0) + 7 /*days*/ * 24 /*hours*/ * 60 * 60); // mute for a week
2765  else if(!isForAll && doNeverShow && username == WebUsers::DEFAULT_ADMIN_USERNAME)
2766  {
2767  // admin could be shared account, so max out at 30 days
2768  fprintf(fp, "%ld", time(0) + 30 /*days*/ * 24 /*hours*/ * 60 * 60);
2769 
2770  __COUT__ << "User '" << username
2771  << "' may be a shared account, so max silence duration for tooltips "
2772  "is 30 days. Silencing now."
2773  << __E__;
2774  }
2775  else
2776  fputc(doNeverShow ? '1' : '0', fp);
2777  fclose(fp);
2778  }
2779  else // default to show tool tip
2780  __COUT_ERR__ << "Big problem with tooltips! File not accessible: " << filename
2781  << __E__;
2782 } // end tooltipSetNeverShowForUsername()
2783 
2784 //==============================================================================
2791 void WebUsers::tooltipCheckForUsername(const std::string& username,
2792  HttpXmlDocument* xmldoc,
2793  const std::string& srcFile,
2794  const std::string& srcFunc,
2795  const std::string& srcId)
2796 {
2797  if(srcId == "ALWAYS")
2798  {
2799  // ALWAYS shows tool tip
2800  xmldoc->addTextElementToData("ShowTooltip", "1");
2801  return;
2802  }
2803 
2804  // __COUT__ << "username " << username << __E__;
2805  // __COUT__ << "srcFile " << srcFile << __E__;
2806  // __COUT__ << "srcFunc " << srcFunc << __E__;
2807  // __COUT__ << "srcId " << srcId << __E__;
2808  //__COUT__ << "Checking tooltip for user: " << username << __E__;
2809 
2810  // if the silence file exists, silence all tooltips
2811  std::string silencefilename =
2812  getTooltipFilename(username, SILENCE_ALL_TOOLTIPS_FILENAME, "", "");
2813  //__COUTV__(silencefilename);
2814  FILE* silencefp = fopen(silencefilename.c_str(), "r");
2815  if(silencefp != NULL)
2816  {
2817  time_t val;
2818  char line[100];
2819  fgets(line, 100, silencefp);
2820  sscanf(line, "%ld", &val);
2821  fclose(silencefp);
2822  if(val == 1)
2823  {
2824  xmldoc->addTextElementToData("ShowTooltip", "0");
2825  // tooltipSetNeverShowForUsername(username, xmldoc, srcFile, srcFunc, srcId, true, true);
2826  return;
2827  }
2828  }
2829 
2830  std::string filename = getTooltipFilename(username, srcFile, srcFunc, srcId);
2831  FILE* fp = fopen(filename.c_str(), "r");
2832  if(fp)
2833  { // file exists, so do NOT show tooltip
2834  time_t val;
2835  char line[100];
2836  fgets(line, 100, fp);
2837  sscanf(line, "%ld", &val);
2838  fclose(fp);
2839 
2840  __COUT__ << "tooltip value read = " << val << " vs time(0)=" << time(0) << __E__;
2841 
2842  // if first line in file is a 1 then do not show
2843  // else show if current time is greater than value
2844  xmldoc->addTextElementToData("ShowTooltip",
2845  val == 1 ? "0" : (time(0) > val ? "1" : "0"));
2846  }
2847  else // default to show tool tip
2848  {
2849  xmldoc->addTextElementToData("ShowTooltip", "1");
2850  }
2851 
2852 } // end tooltipCheckForUsername();
2853 
2854 //==============================================================================
2856 void WebUsers::resetAllUserTooltips(const std::string& userNeedle)
2857 {
2858  std::system(
2859  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH + "/" + userNeedle)
2860  .c_str());
2861  __COUT__ << "Successfully reset Tooltips for user " << userNeedle << __E__;
2862 } // end of resetAllUserTooltips()
2863 
2864 //==============================================================================
2867 void WebUsers::silenceAllUserTooltips(const std::string& username)
2868 {
2869  std::string silencefilename = getTooltipFilename(
2870  username, SILENCE_ALL_TOOLTIPS_FILENAME, "", ""); // srcFile, srcFunc, srcId);
2871 
2872  __COUTV__(silencefilename);
2873  FILE* silencefp = fopen(silencefilename.c_str(), "w");
2874  if(silencefp != NULL)
2875  {
2876  fputs("1", silencefp);
2877  fclose(silencefp);
2878  }
2879 
2880 } // end of silenceAllUserTooltips()
2881 
2882 //==============================================================================
2903  HttpXmlDocument* xmldoc,
2904  bool includeAccounts)
2905 {
2906  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap =
2907  getPermissionsForUser(uid);
2908 
2909  //__COUTV__(StringMacros::mapToString(permissionMap));
2910  if(isInactiveForGroup(permissionMap))
2911  return; // not an active user
2912 
2913  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2914  __COUT__ << "Gettings settings for user: " << Users_[userIndex].username_ << __E__;
2915 
2916  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
2917  (std::string)USERS_PREFERENCES_PATH + Users_[userIndex].username_ +
2918  "." + (std::string)USERS_PREFERENCES_FILETYPE;
2919 
2920  HttpXmlDocument prefXml;
2921 
2922  __COUT__ << "Preferences file: " << fn << __E__;
2923 
2924  if(!prefXml.loadXmlDocument(fn))
2925  {
2926  __COUT__ << "Preferences are defaults." << __E__;
2927  // insert defaults, no pref document found
2928  xmldoc->addTextElementToData(PREF_XML_BGCOLOR_FIELD, PREF_XML_BGCOLOR_DEFAULT);
2929  xmldoc->addTextElementToData(PREF_XML_DBCOLOR_FIELD, PREF_XML_DBCOLOR_DEFAULT);
2930  xmldoc->addTextElementToData(PREF_XML_WINCOLOR_FIELD, PREF_XML_WINCOLOR_DEFAULT);
2931  xmldoc->addTextElementToData(PREF_XML_LAYOUT_FIELD, PREF_XML_LAYOUT_DEFAULT);
2932  }
2933  else
2934  {
2935  __COUT__ << "Saved Preferences found." << __E__;
2936  xmldoc->copyDataChildren(prefXml);
2937  }
2938 
2939  // add settings if super user
2940  if(includeAccounts && isAdminForGroup(permissionMap))
2941  {
2942  __COUT__ << "Admin on our hands" << __E__;
2943 
2944  xmldoc->addTextElementToData(PREF_XML_ACCOUNTS_FIELD, "");
2945 
2946  if(Users_.size() == 0)
2947  {
2948  __COUT__ << "Missing users? Attempting to load database" << __E__;
2949  loadDatabases();
2950  }
2951 
2952  // get all accounts
2953  for(uint64_t i = 0; i < Users_.size(); ++i)
2954  {
2955  xmldoc->addTextElementToParent(
2956  "username", Users_[i].username_, PREF_XML_ACCOUNTS_FIELD);
2957  xmldoc->addTextElementToParent(
2958  "display_name", Users_[i].displayName_, PREF_XML_ACCOUNTS_FIELD);
2959 
2960  if(Users_[i].email_.size() > i)
2961  {
2962  xmldoc->addTextElementToParent(
2963  "useremail", Users_[i].email_, PREF_XML_ACCOUNTS_FIELD);
2964  }
2965  else
2966  {
2967  xmldoc->addTextElementToParent("useremail", "", PREF_XML_ACCOUNTS_FIELD);
2968  }
2969 
2970  xmldoc->addTextElementToParent(
2971  "permissions",
2972  StringMacros::mapToString(Users_[i].permissions_),
2973  PREF_XML_ACCOUNTS_FIELD);
2974 
2975  xmldoc->addTextElementToParent(
2976  "nac", Users_[i].getNewAccountCode().c_str(), PREF_XML_ACCOUNTS_FIELD);
2977  }
2978  }
2979 
2980  // get system layout defaults
2981  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH +
2982  (std::string)SYSTEM_PREFERENCES_PREFIX + "." +
2983  (std::string)USERS_PREFERENCES_FILETYPE;
2984  if(!prefXml.loadXmlDocument(fn))
2985  {
2986  __COUT__ << "System Preferences are defaults." << __E__;
2987  // insert defaults, no pref document found
2988  xmldoc->addTextElementToData(PREF_XML_SYSLAYOUT_FIELD,
2989  PREF_XML_SYSLAYOUT_DEFAULT);
2990  }
2991  else
2992  {
2993  __COUT__ << "Saved System Preferences found." << __E__;
2994  xmldoc->copyDataChildren(prefXml);
2995  }
2996 
2997  __COUTV__(StringMacros::mapToString(permissionMap));
2998 
2999  // add permissions value
3000  xmldoc->addTextElementToData(PREF_XML_PERMISSIONS_FIELD,
3001  StringMacros::mapToString(permissionMap));
3002 
3003  // add user with lock
3004  xmldoc->addTextElementToData(PREF_XML_USERLOCK_FIELD, usersUsernameWithLock_);
3005 
3006  // add user name
3007  xmldoc->addTextElementToData(PREF_XML_USERNAME_FIELD, getUsersUsername(uid));
3008 
3009  // add ots owner name
3010  xmldoc->addTextElementToData(PREF_XML_OTS_OWNER_FIELD, WebUsers::OTS_OWNER);
3011 
3012  if(WebUsers::remoteLoginVerificationEnabled_) // add remote ots ip:port
3013  xmldoc->addTextElementToData("ots_remote_address",
3014  remoteLoginVerificationIP_ + ":" +
3015  std::to_string(remoteLoginVerificationPort_));
3016 
3017 } // end insertSettingsForUser()
3018 
3019 //==============================================================================
3023  const std::string& preferenceName,
3024  const std::string& preferenceValue)
3025 {
3026  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
3027  //__COUT__ << "setGenericPreference for user: " << UsersUsernameVector[userIndex] <<
3028  //__E__;
3029 
3030  // force alpha-numeric with dash/underscore
3031  std::string safePreferenceName = "";
3032  for(const auto& c : preferenceName)
3033  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
3034  (c >= '-' || c <= '_'))
3035  safePreferenceName += c;
3036 
3037  std::string dir = (std::string)WEB_LOGIN_DB_PATH +
3038  (std::string)USERS_PREFERENCES_PATH + "generic_" +
3039  safePreferenceName + "/";
3040 
3041  // attempt to make directory (just in case)
3042  mkdir(dir.c_str(), 0755);
3043 
3044  std::string fn = Users_[userIndex].username_ + "_" + safePreferenceName + "." +
3045  (std::string)USERS_PREFERENCES_FILETYPE;
3046 
3047  __COUT__ << "Preferences file: " << (dir + fn) << __E__;
3048 
3049  FILE* fp = fopen((dir + fn).c_str(), "w");
3050  if(fp)
3051  {
3052  fprintf(fp, "%s", preferenceValue.c_str());
3053  fclose(fp);
3054  }
3055  else
3056  __COUT_ERR__ << "Preferences file could not be opened for writing!" << __E__;
3057 } // end setGenericPreference()
3058 
3059 //==============================================================================
3063 std::string WebUsers::getGenericPreference(uint64_t uid,
3064  const std::string& preferenceName,
3065  HttpXmlDocument* xmldoc) const
3066 {
3067  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
3068  //__COUT__ << "getGenericPreference for user: " << UsersUsernameVector[userIndex] <<
3069  //__E__;
3070 
3071  // force alpha-numeric with dash/underscore
3072  std::string safePreferenceName = "";
3073  for(const auto& c : preferenceName)
3074  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
3075  (c >= '-' || c <= '_'))
3076  safePreferenceName += c;
3077 
3078  std::string dir = (std::string)WEB_LOGIN_DB_PATH +
3079  (std::string)USERS_PREFERENCES_PATH + "generic_" +
3080  safePreferenceName + "/";
3081 
3082  std::string fn = Users_[userIndex].username_ + "_" + safePreferenceName + "." +
3083  (std::string)USERS_PREFERENCES_FILETYPE;
3084 
3085  __COUT__ << "Preferences file: " << (dir + fn) << __E__;
3086 
3087  // read from preferences file
3088  FILE* fp = fopen((dir + fn).c_str(), "r");
3089  if(fp)
3090  {
3091  fseek(fp, 0, SEEK_END);
3092  const long size = ftell(fp);
3093  char* line = new char
3094  [size +
3095  1]; // std::string with line.reserve(size + 1) does not work for unknown reason
3096  rewind(fp);
3097  fread(line, 1, size, fp);
3098  line[size] = '\0';
3099  fclose(fp);
3100  std::string retVal(line, size);
3101  delete[] line;
3102 
3103  __COUT__ << "Read value (sz = " << retVal.size() << ") " << retVal << __E__;
3104  if(xmldoc)
3105  xmldoc->addTextElementToData(safePreferenceName, retVal);
3106  return retVal;
3107  }
3108  else
3109  __COUT__ << "Using default value." << __E__;
3110 
3111  // default preference is empty string
3112  if(xmldoc)
3113  xmldoc->addTextElementToData(safePreferenceName, "");
3114  return "";
3115 } // end getGenericPreference()
3116 
3117 //==============================================================================
3120  const std::string& bgcolor,
3121  const std::string& dbcolor,
3122  const std::string& wincolor,
3123  const std::string& layout,
3124  const std::string& syslayout)
3125 {
3126  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap =
3127  getPermissionsForUser(uid);
3128  if(isInactiveForGroup(permissionMap))
3129  return; // not an active user
3130 
3131  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
3132  __COUT__ << "Changing settings for user: " << Users_[userIndex].username_ << __E__;
3133 
3134  std::string fn = (std::string)WEB_LOGIN_DB_PATH +
3135  (std::string)USERS_PREFERENCES_PATH + Users_[userIndex].username_ +
3136  "." + (std::string)USERS_PREFERENCES_FILETYPE;
3137 
3138  __COUT__ << "Preferences file: " << fn << __E__;
3139 
3140  HttpXmlDocument prefXml;
3141  prefXml.addTextElementToData(PREF_XML_BGCOLOR_FIELD, bgcolor);
3142  prefXml.addTextElementToData(PREF_XML_DBCOLOR_FIELD, dbcolor);
3143  prefXml.addTextElementToData(PREF_XML_WINCOLOR_FIELD, wincolor);
3144  prefXml.addTextElementToData(PREF_XML_LAYOUT_FIELD, layout);
3145 
3146  prefXml.saveXmlDocument(fn);
3147 
3148  // if admin privilieges set system default layouts
3149  if(!isAdminForGroup(permissionMap))
3150  return; // not admin
3151 
3152  // set system layout defaults
3153  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH +
3154  (std::string)SYSTEM_PREFERENCES_PREFIX + "." +
3155  (std::string)USERS_PREFERENCES_FILETYPE;
3156 
3157  HttpXmlDocument sysPrefXml;
3158  sysPrefXml.addTextElementToData(PREF_XML_SYSLAYOUT_FIELD, syslayout);
3159 
3160  sysPrefXml.saveXmlDocument(fn);
3161 } // end changeSettingsForUser()
3162 
3163 //==============================================================================
3168 bool WebUsers::setUserWithLock(uint64_t actingUid, bool lock, const std::string& username)
3169 {
3170  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap =
3171  getPermissionsForUser(actingUid);
3172 
3173  std::string actingUser = getUsersUsername(actingUid);
3174  bool isUserActive = isUsernameActive(username);
3175 
3176  __COUTV__(actingUser);
3177  __COUT__ << "Permissions: " << StringMacros::mapToString(permissionMap) << __E__;
3178  __COUTV__(usersUsernameWithLock_);
3179  __COUTV__(lock);
3180  __COUTV__(username);
3181  __COUTV__(isUserActive);
3182 
3183  if(lock && (isUserActive || !CareAboutCookieCodes_)) // lock and currently active
3184  {
3185  if(!CareAboutCookieCodes_ && !isUserActive &&
3186  username != DEFAULT_ADMIN_USERNAME) // enforce wiz mode only use admin account
3187  {
3188  __COUT_ERR__
3189  << "User '" << actingUser
3190  << "' tried to lock for a user other than admin in wiz mode. Not allowed."
3191  << __E__;
3192  return false;
3193  }
3194  else if(!isAdminForGroup(permissionMap) &&
3195  actingUser != username) // enforce normal mode admin privleges
3196  {
3197  __COUT_ERR__ << "A non-admin user '" << actingUser
3198  << "' tried to lock for a user other than self. Not allowed."
3199  << __E__;
3200  return false;
3201  }
3202  usersUsernameWithLock_ = username;
3203  }
3204  else if(!lock && usersUsernameWithLock_ == username) // unlock
3205  usersUsernameWithLock_ = "";
3206  else
3207  {
3208  if(!isUserActive)
3209  __COUT_ERR__ << "User '" << username << "' is inactive." << __E__;
3210  __COUT_ERR__ << "Failed to lock for user '" << username << ".'" << __E__;
3211  return false;
3212  }
3213 
3214  __COUT_INFO__ << "User '" << username << "' has locked out the system!" << __E__;
3215 
3216  // save username with lock
3217  {
3218  std::string securityFileName = USER_WITH_LOCK_FILE;
3219  FILE* fp = fopen(securityFileName.c_str(), "w");
3220  if(!fp)
3221  {
3222  __COUT_INFO__ << "USER_WITH_LOCK_FILE " << USER_WITH_LOCK_FILE
3223  << " not found. Ignoring." << __E__;
3224  }
3225  else
3226  {
3227  fprintf(fp, "%s", usersUsernameWithLock_.c_str());
3228  fclose(fp);
3229  }
3230  }
3231  return true;
3232 } // end setUserWithLock()
3233 
3234 //==============================================================================
3236 void WebUsers::modifyAccountSettings(uint64_t actingUid,
3237  uint8_t cmd_type,
3238  const std::string& username,
3239  const std::string& displayname,
3240  const std::string& email,
3241  const std::string& permissions)
3242 {
3243  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap =
3244  getPermissionsForUser(actingUid);
3245  if(!isAdminForGroup(permissionMap))
3246  {
3247  // not an admin
3248  __SS__ << "Only admins can modify user settings." << __E__;
3249  __SS_THROW__;
3250  }
3251 
3252  uint64_t i = searchUsersDatabaseForUserId(actingUid);
3253  uint64_t modi = searchUsersDatabaseForUsername(username);
3254  if(modi == 0)
3255  {
3256  if(i == 0)
3257  {
3258  __COUT_INFO__ << "Admin password reset." << __E__;
3259  Users_[modi].setModifier(Users_[i].username_);
3260  Users_[modi].salt_ = "";
3261  Users_[modi].loginFailureCount_ = 0;
3262  saveDatabaseToFile(DB_USERS);
3263  return;
3264  }
3265  __SS__ << "Cannot modify first user" << __E__;
3266  __SS_THROW__;
3267  }
3268 
3269  if(username.length() < USERNAME_LENGTH)
3270  {
3271  __SS__ << "Invalid Username, must be length " << USERNAME_LENGTH << __E__;
3272  __SS_THROW__;
3273  }
3274  if(displayname.length() < DISPLAY_NAME_LENGTH)
3275  {
3276  __SS__ << "Invalid Display Name; must be length " << DISPLAY_NAME_LENGTH << __E__;
3277  __SS_THROW__;
3278  }
3279 
3280  __COUT__ << "Input Permissions: " << permissions << __E__;
3281  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> newPermissionsMap;
3282 
3283  switch(cmd_type)
3284  {
3285  case MOD_TYPE_UPDATE:
3286 
3287  __COUT__ << "MOD_TYPE_UPDATE " << username << " := " << permissions << __E__;
3288 
3289  if(modi == NOT_FOUND_IN_DATABASE)
3290  {
3291  __SS__ << "User not found!? Should not happen." << __E__;
3292  __SS_THROW__;
3293  }
3294 
3295  // enforce unique Display Name
3296  {
3297  for(uint64_t i = 0; i < Users_.size(); ++i)
3298  if(i == modi)
3299  continue; // skip target user
3300  else if(Users_[i].displayName_ == displayname)
3301  {
3302  __SS__ << "Display Name '" << displayname
3303  << "' already exists! Please choose a unique display name."
3304  << __E__;
3305  __SS_THROW__;
3306  }
3307  }
3308 
3309  Users_[modi].displayName_ = displayname;
3310  Users_[modi].email_ = email;
3311 
3312  { // handle permissions
3313  StringMacros::getMapFromString(permissions, newPermissionsMap);
3314  bool wasInactive = isInactiveForGroup(Users_[modi].permissions_);
3315 
3316  // fix permissions_ if missing default user group
3317  if(newPermissionsMap.size() == 0) // default to inactive
3318  Users_[modi].permissions_[WebUsers::DEFAULT_USER_GROUP] =
3319  std::atoi(permissions.c_str());
3320  else if(newPermissionsMap.size() == 1 &&
3321  newPermissionsMap.find(WebUsers::DEFAULT_USER_GROUP) ==
3322  newPermissionsMap.end())
3323  {
3324  if(newPermissionsMap.begin()->first == "")
3325  Users_[modi].permissions_[WebUsers::DEFAULT_USER_GROUP] =
3326  newPermissionsMap.begin()->second;
3327  else // if a user group attempted, copy settings for default group
3328  {
3329  newPermissionsMap[WebUsers::DEFAULT_USER_GROUP] =
3330  newPermissionsMap.begin()->second;
3331  Users_[modi].permissions_ = newPermissionsMap;
3332  }
3333  }
3334  else
3335  Users_[modi].permissions_ = newPermissionsMap;
3336 
3337  // If account was inactive and re-activating, then reset fail count and
3338  // password. Note: this is the account unlock mechanism.
3339  if(wasInactive && // was inactive
3340  !isInactiveForGroup(Users_[modi].permissions_)) // and re-activating
3341  {
3342  __COUT__ << "Reactivating " << username << __E__;
3343  Users_[modi].loginFailureCount_ = 0;
3344  Users_[modi].salt_ = "";
3345  }
3346  } // end permissions handling
3347 
3348  // save information about modifier
3349  {
3350  if(i == NOT_FOUND_IN_DATABASE)
3351  {
3352  __SS__ << "Master User not found!? Should not happen." << __E__;
3353  __SS_THROW__;
3354  }
3355  Users_[modi].setModifier(Users_[i].username_);
3356  }
3357  break;
3358  case MOD_TYPE_ADD:
3359  // Note: username, userId, AND displayName must be unique!
3360 
3361  __COUT__ << "MOD_TYPE_ADD " << username << " - " << displayname << __E__;
3362 
3363  createNewAccount(username, displayname, email);
3364  // save information about modifier
3365  {
3366  if(i == NOT_FOUND_IN_DATABASE)
3367  {
3368  __SS__ << "Master User not found!? Should not happen." << __E__;
3369  __SS_THROW__;
3370  }
3371  Users_.back().setModifier(Users_[i].username_);
3372  }
3373 
3374  if(permissions.size()) // apply permissions
3375  {
3377  actingUid, MOD_TYPE_UPDATE, username, displayname, email, permissions);
3378  return;
3379  }
3380  break;
3381  case MOD_TYPE_DELETE:
3382  __COUT__ << "MOD_TYPE_DELETE " << username << " - " << displayname << __E__;
3383  deleteAccount(username, displayname);
3384  break;
3385  default:
3386  __SS__ << "Undefined command - do nothing " << username << __E__;
3387  __SS_THROW__;
3388  }
3389 
3390  saveDatabaseToFile(DB_USERS);
3391  loadSecuritySelection(); //give opportunity to dynamically modifiy IP access settings or security settings
3392 } // end modifyAccountSettings()
3393 //==============================================================================
3397 {
3398  std::set<unsigned int> activeUserIndices;
3399  for(uint64_t i = 0; i < ActiveSessions_.size(); ++i)
3400  activeUserIndices.emplace(
3401  searchUsersDatabaseForUserId(ActiveSessions_[i].userId_));
3402  return activeUserIndices.size();
3403 } // end getActiveUserCount()
3404 
3405 //==============================================================================
3409 {
3410  std::set<unsigned int> activeUserIndices;
3411  for(uint64_t i = 0; i < ActiveSessions_.size(); ++i)
3412  activeUserIndices.emplace(
3413  searchUsersDatabaseForUserId(ActiveSessions_[i].userId_));
3414 
3415  std::string activeUsersString = "";
3416  bool addComma = false;
3417  for(const auto& i : activeUserIndices)
3418  {
3419  if(i >= Users_.size())
3420  continue; // skip not found
3421 
3422  if(addComma)
3423  activeUsersString += ",";
3424  else
3425  addComma = true;
3426 
3427  activeUsersString += Users_[i].displayName_;
3428  }
3429  if(activeUserIndices.size() == 0 &&
3431  WebUsers::SECURITY_TYPE_NONE) // assume only admin is active
3432  activeUsersString += WebUsers::DEFAULT_ADMIN_DISPLAY_NAME;
3433 
3434  __COUTVS__(20, activeUsersString);
3435  return activeUsersString;
3436 } // end getActiveUsersString()
3437 
3438 //==============================================================================
3442 {
3443  uint64_t uid = searchUsersDatabaseForUsername(DEFAULT_ADMIN_USERNAME);
3444  return uid;
3445 }
3446 
3447 //==============================================================================
3450 void WebUsers::loadUserWithLock()
3451 {
3452  char username[300] = ""; // assume username is less than 300 chars
3453 
3454  std::string securityFileName = USER_WITH_LOCK_FILE;
3455  FILE* fp = fopen(securityFileName.c_str(), "r");
3456  if(!fp)
3457  {
3458  __COUT_INFO__ << "USER_WITH_LOCK_FILE " << USER_WITH_LOCK_FILE
3459  << " not found. Defaulting to admin lock." << __E__;
3460 
3461  // default to admin lock if no file exists
3462  sprintf(username, "%s", DEFAULT_ADMIN_USERNAME.c_str());
3463  }
3464  else
3465  {
3466  fgets(username, 300, fp);
3467  username[299] =
3468  '\0'; // likely does nothing, but make sure there is closure on string
3469  fclose(fp);
3470  }
3471 
3472  // attempt to set lock
3473  __COUT__ << "Attempting to load username with lock: " << username << __E__;
3474 
3475  if(strlen(username) == 0)
3476  {
3477  __COUT_INFO__ << "Loaded state for user-with-lock is unlocked." << __E__;
3478  return;
3479  }
3480 
3481  uint64_t i = searchUsersDatabaseForUsername(username);
3482  if(i == NOT_FOUND_IN_DATABASE)
3483  {
3484  __COUT_INFO__ << "username " << username << " not found in database. Ignoring."
3485  << __E__;
3486  return;
3487  }
3488  __COUT__ << "Setting lock" << __E__;
3489  setUserWithLock(Users_[i].userId_, true, username);
3490 } // end loadUserWithLock()
3491 
3492 //==============================================================================
3495 void WebUsers::addSystemMessage(const std::string& targetUsersCSV,
3496  const std::string& message)
3497 {
3498  addSystemMessage(targetUsersCSV, "" /*subject*/, message, false /*doEmail*/);
3499 } // end addSystemMessage()
3500 
3501 //==============================================================================
3504 void WebUsers::addSystemMessage(const std::string& targetUsersCSV,
3505  const std::string& subject,
3506  const std::string& message,
3507  bool doEmail)
3508 {
3509  std::vector<std::string> targetUsers;
3510  StringMacros::getVectorFromString(targetUsersCSV, targetUsers);
3511  addSystemMessage(targetUsers, subject, message, doEmail);
3512 } // end addSystemMessage()
3513 
3514 //==============================================================================
3518 void WebUsers::addSystemMessage(const std::vector<std::string>& targetUsers,
3519  const std::string& subject,
3520  const std::string& message,
3521  bool doEmail)
3522 {
3523  systemMessageCleanup();
3524 
3525  std::string fullMessage = StringMacros::encodeURIComponent(
3526  (subject == "" ? "" : (subject + ": ")) + message);
3527 
3528  // Note: do not printout message, because if it was a Console trigger, it will fire repeatedly
3529  std::cout << __COUT_HDR_FL__ << "addSystemMessage() fullMessage: " << fullMessage
3530  << __E__;
3531  __COUTV__(StringMacros::vectorToString(targetUsers));
3532 
3533  std::set<std::string> targetEmails;
3534 
3535  for(const auto& targetUser : targetUsers)
3536  {
3537  // reject if message is a repeat for user
3538 
3539  if(targetUser == "" || (targetUser != "*" && targetUser.size() < 3))
3540  {
3541  __COUT__ << "Illegal username '" << targetUser << "'" << __E__;
3542  continue;
3543  }
3544  __COUTV__(targetUser);
3545  // target user might * or <group name>:<permission threshold> or just <username>
3546 
3547  // do special ALL email handling
3548  if(doEmail && targetUser == "*")
3549  {
3550  // for each user, look up email and append
3551  for(const auto& user : Users_)
3552  {
3553  if(user.email_.size() > 5 && // few simple valid email checks
3554  user.email_.find('@') != std::string::npos &&
3555  user.email_.find('.') != std::string::npos)
3556  {
3557  __COUT__ << "Adding " << user.displayName_
3558  << " email: " << user.email_ << __E__;
3559  targetEmails.emplace(user.email_);
3560  }
3561  } // end add every user loop
3562 
3563  } // end all email handling
3564  else if(targetUser.find(':') != std::string::npos)
3565  {
3566  // special group handling.. convert to individual users
3567  __COUT__ << "Treating as group email target: " << targetUser << __E__;
3568 
3569  std::map<std::string, WebUsers::permissionLevel_t> targetGroupMap;
3570  StringMacros::getMapFromString( // re-factor membership string to map
3571  targetUser,
3572  targetGroupMap);
3573 
3574  __COUTV__(StringMacros::mapToString(targetGroupMap));
3575 
3576  if(targetGroupMap.size() == 1)
3577  {
3578  // add users to targetUsers, so the loop will catch them at end
3579 
3580  // loop through all users, and add users that match group spec
3581  for(const auto& user : Users_)
3582  {
3583  WebUsers::permissionLevel_t userLevel =
3584  getPermissionLevelForGroup(getPermissionsForUser(user.userId_),
3585  targetGroupMap.begin()->first);
3586 
3587  __COUTV__(
3589  __COUTV__((int)userLevel);
3590  __COUTV__(targetGroupMap.begin()->first);
3591 
3592  if(userLevel != WebUsers::PERMISSION_LEVEL_INACTIVE &&
3593  userLevel >= targetGroupMap.begin()->second &&
3594  user.email_.size() > 5 && // few simple valid email checks
3595  user.email_.find('@') != std::string::npos &&
3596  user.email_.find('.') != std::string::npos)
3597  {
3598  if(doEmail)
3599  {
3600  targetEmails.emplace(user.email_);
3601  __COUT__ << "Adding " << user.displayName_
3602  << " email: " << user.email_ << __E__;
3603  }
3604  addSystemMessageToMap(user.displayName_, fullMessage);
3605  }
3606  }
3607  }
3608  else
3609  __COUT__ << "target Group Map from '" << targetUser << "' is empty."
3610  << __E__;
3611 
3612  continue; // proceed with user loop, do not add group target message
3613  }
3614 
3615  // at this point add to system message map (similar to group individual add, but might be '*')
3616 
3617  addSystemMessageToMap(targetUser, fullMessage);
3618 
3619  if(doEmail) // find user for email
3620  {
3621  for(const auto& user : Users_)
3622  {
3623  if(user.displayName_ == targetUser)
3624  {
3625  if(user.email_.size() > 5 && // few simple valid email checks
3626  user.email_.find('@') != std::string::npos &&
3627  user.email_.find('.') != std::string::npos)
3628  {
3629  targetEmails.emplace(user.email_);
3630  __COUT__ << "Adding " << user.displayName_
3631  << " email: " << user.email_ << __E__;
3632  }
3633  break; // user found, exit search loop
3634  }
3635  } // end user search loop
3636  }
3637 
3638  } // end target user message add loop
3639 
3640  __COUTV__(targetEmails.size());
3641 
3642  if(doEmail && targetEmails.size())
3643  {
3644  __COUTV__(StringMacros::setToString(targetEmails));
3645 
3646  std::string toList = "";
3647  bool addComma = false;
3648  for(const auto& email : targetEmails)
3649  {
3650  if(addComma)
3651  toList += ", ";
3652  else
3653  addComma = true;
3654  toList += email;
3655  }
3656 
3657  std::string filename = (std::string)WEB_LOGIN_DB_PATH +
3658  (std::string)USERS_DB_PATH + "/.tmp_email.txt";
3659  FILE* fp = fopen(filename.c_str(), "w");
3660  if(!fp)
3661  {
3662  __SS__ << "Could not open email file: " << filename << __E__;
3663  __SS_THROW__;
3664  }
3665 
3666  fprintf(fp,
3667  "From: %s\n",
3668  (WebUsers::OTS_OWNER == ""
3669  ? "ots"
3671  .c_str());
3672  fprintf(fp, "To: %s\n", toList.c_str());
3673  fprintf(fp, "Subject: %s\n", subject.c_str());
3674  fprintf(fp, "Content-Type: text/html\n");
3675  fprintf(fp, "\n<html><pre>%s</pre></html>", message.c_str());
3676  fclose(fp);
3677 
3678  StringMacros::exec(("sendmail \"" + toList + "\" < " + filename).c_str());
3679  }
3680  else if(doEmail)
3681  __COUT_WARN__ << "Do email was attempted, but no target users had email "
3682  "addresses specified!"
3683  << __E__;
3684 
3685 } // end addSystemMessage()
3686 
3687 //==============================================================================
3691 void WebUsers::addSystemMessageToMap(const std::string& targetUser,
3692  const std::string& fullMessage)
3693 {
3694  // lock for remainder of scope
3695  std::lock_guard<std::mutex> lock(systemMessageLock_);
3696 
3697  __COUT__ << "Before number of users with system messages: " << systemMessages_.size()
3698  << ", first user has "
3699  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3700  << " messages." << __E__;
3701 
3702  auto it = systemMessages_.find(targetUser);
3703 
3704  // check for repeat messages
3705  if(it != systemMessages_.end() && it->second.size() &&
3706  it->second[it->second.size() - 1].message_ == fullMessage)
3707  return; // skip user add
3708 
3709  if(it == systemMessages_.end()) // create first message for target user
3710  {
3711  systemMessages_.emplace(
3712  std::pair<std::string /*toUser*/, std::vector<SystemMessage>>(
3713  targetUser, std::vector<SystemMessage>({SystemMessage(fullMessage)})));
3714  __COUTT__ << targetUser << " Current System Messages count = " << 1 << __E__;
3715  }
3716  else // add message
3717  {
3718  __COUTT__ << __E__;
3719  it->second.push_back(SystemMessage(fullMessage));
3720  __COUTT__ << it->first << " Current System Messages count = " << it->second.size()
3721  << __E__;
3722  }
3723 
3724  __COUT__ << "After number of users with system messages: " << systemMessages_.size()
3725  << ", first user has "
3726  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3727  << " messages." << __E__;
3728 } // end addSystemMessageToMap
3729 
3730 //==============================================================================
3733 std::pair<std::string, time_t> WebUsers::getLastSystemMessage()
3734 {
3735  // lock for remainder of scope
3736  std::lock_guard<std::mutex> lock(systemMessageLock_);
3737 
3738  __COUTT__ << "GetLast number of users with system messages: "
3739  << systemMessages_.size() << ", first user has "
3740  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3741  << " messages." << __E__;
3742 
3743  auto it = systemMessages_.find("*");
3744  if(it == systemMessages_.end() || it->second.size() == 0)
3745  return std::make_pair("", 0);
3746 
3747  return std::make_pair(it->second.back().message_, it->second.back().creationTime_);
3748 } // end getLastSystemMessage()
3749 
3750 //==============================================================================
3755 {
3756  std::string retStr = "";
3757 
3758  // lock for remainder of scope
3759  std::lock_guard<std::mutex> lock(systemMessageLock_);
3760 
3761  for(auto& userSysMessages : systemMessages_)
3762  {
3763  for(auto& userSysMessage : userSysMessages.second)
3764  {
3765  if(userSysMessage.deliveredRemote_)
3766  continue; //skip messages already deivered remote
3767 
3768  if(retStr.size())
3769  retStr += '|';
3770  retStr += userSysMessages.first; //target display name
3771  retStr += "|" + std::to_string(userSysMessage.creationTime_);
3772  retStr += "|" + userSysMessage.message_;
3773  userSysMessage.deliveredRemote_ = true;
3774  }
3775  }
3776  return retStr;
3777 } //end getAllSystemMessages()
3778 
3779 //==============================================================================
3786 std::string WebUsers::getSystemMessage(const std::string& targetUser)
3787 {
3788  __COUT_TYPE__(TLVL_DEBUG + 20)
3789  << __COUT_HDR__ << "Current System Messages: " << targetUser << __E__;
3790  std::string retStr = "";
3791  {
3792  int cnt = 0;
3793  char tmp[32];
3794 
3795  // lock for remainder of scope
3796  std::lock_guard<std::mutex> lock(systemMessageLock_);
3797 
3798  __COUT_TYPE__(TLVL_DEBUG + 20)
3799  << __COUT_HDR__
3800  << "Number of users with system messages: " << systemMessages_.size()
3801  << __E__;
3802 
3803  //do broadcast * messages 1st because the web client will hide all messages before a repeat, so make sure to show user messages
3804  auto it = systemMessages_.find("*");
3805  for(uint64_t i = 0; it != systemMessages_.end() && i < it->second.size(); ++i)
3806  {
3807  // deliver "*" system message
3808  if(cnt)
3809  retStr += "|";
3810  sprintf(tmp, "%lu", it->second[i].creationTime_);
3811  retStr += std::string(tmp) + "|" + it->second[i].message_;
3812 
3813  ++cnt;
3814  }
3815 
3816  //do user messages 2nd because the web client will hide all messages before a repeat, so make sure to show user messages
3817  __COUTVS__(20, targetUser);
3818  it = systemMessages_.find(targetUser);
3819  if(TTEST(20))
3820  {
3821  for(auto systemMessagePair : systemMessages_)
3822  __COUT_TYPE__(TLVL_DEBUG + 20)
3823  << __COUT_HDR__ << systemMessagePair.first << " "
3824  << systemMessagePair.second.size() << " "
3825  << (systemMessagePair.second.size()
3826  ? systemMessagePair.second[0].message_
3827  : "")
3828  << __E__;
3829  }
3830  if(it != systemMessages_.end())
3831  {
3832  __COUT_TYPE__(TLVL_DEBUG + 20)
3833  << __COUT_HDR__ << "Message count: " << it->second.size()
3834  << ", Last Message: "
3835  << (it->second.size() ? it->second.back().message_ : "") << __E__;
3836  }
3837 
3838  for(uint64_t i = 0; it != systemMessages_.end() && i < it->second.size(); ++i)
3839  {
3840  // deliver user specific system message
3841  if(cnt)
3842  retStr += "|";
3843  sprintf(tmp, "%lu", it->second[i].creationTime_);
3844  retStr += std::string(tmp) + "|" + it->second[i].message_;
3845 
3846  it->second[i].delivered_ = true;
3847  ++cnt;
3848  }
3849  } //end mutex scope
3850 
3851  __COUT_TYPE__(TLVL_DEBUG + 20) << __COUT_HDR__ << "retStr: " << retStr << __E__;
3852 
3853  systemMessageCleanup(); //NOTE: also locks mutex within!
3854  return retStr;
3855 } // end getSystemMessage()
3856 
3857 //==============================================================================
3861 void WebUsers::systemMessageCleanup()
3862 {
3863  // lock for remainder of scope
3864  std::lock_guard<std::mutex> lock(systemMessageLock_);
3865 
3866  __COUTT__ << "Before cleanup number of users with system messages: "
3867  << systemMessages_.size() << ", first user has "
3868  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3869  << " messages." << __E__;
3870  for(auto& userMessagesPair : systemMessages_)
3871  {
3872  for(uint64_t i = 0; i < userMessagesPair.second.size(); ++i)
3873  if((userMessagesPair.first != "*" &&
3874  userMessagesPair.second[i].delivered_) || // delivered and != *
3875  userMessagesPair.second[i].creationTime_ + SYS_CLEANUP_WILDCARD_TIME <
3876  time(0)) // expired
3877  {
3878  __COUTT__ << userMessagesPair.first
3879  << " at time: " << userMessagesPair.second[i].creationTime_
3880  << " system messages: " << userMessagesPair.second.size()
3881  << __E__;
3882 
3883  // remove
3884  userMessagesPair.second.erase(userMessagesPair.second.begin() + i);
3885  --i; // rewind
3886  } //end cleanup loop by message
3887 
3888  __COUTT__ << "User '" << userMessagesPair.first
3889  << "' remaining system messages: " << userMessagesPair.second.size()
3890  << __E__;
3891  } //end cleanup loop by user
3892  __COUTT__ << "After cleanup number of users with system messages: "
3893  << systemMessages_.size() << ", first user has "
3894  << (systemMessages_.size() ? systemMessages_.begin()->second.size() : 0)
3895  << " messages." << __E__;
3896 } // end systemMessageCleanup()
3897 
3898 //==============================================================================
3900 const std::string& WebUsers::getSecurity() { return securityType_; }
3901 //==============================================================================
3903 void WebUsers::loadSecuritySelection()
3904 {
3905  std::string securityFileName = SECURITY_FILE_NAME;
3906  FILE* fp = fopen(securityFileName.c_str(), "r");
3907  char line[100] = "";
3908  if(fp)
3909  fgets(line, 100, fp);
3910  unsigned int i = 0;
3911 
3912  // find first character that is not alphabetic
3913  while(i < strlen(line) && line[i] >= 'A' && line[i] <= 'z')
3914  ++i;
3915  line[i] = '\0'; // end string at first illegal character
3916 
3917  if(strcmp(line, SECURITY_TYPE_NONE.c_str()) == 0 ||
3918  strcmp(line, SECURITY_TYPE_DIGEST_ACCESS.c_str()) == 0)
3919  securityType_ = line;
3920  else
3921  securityType_ = SECURITY_TYPE_DEFAULT;
3922 
3923  __COUT__ << "The current security type is " << securityType_ << __E__;
3924 
3925  if(fp)
3926  fclose(fp);
3927 
3928  if(securityType_ == SECURITY_TYPE_NONE)
3929  CareAboutCookieCodes_ = false;
3930  else
3931  CareAboutCookieCodes_ = true;
3932 
3933  __COUT__ << "CareAboutCookieCodes_: " << CareAboutCookieCodes_ << __E__;
3934 
3935  loadIPAddressSecurity();
3936 
3937 } // end loadSecuritySelection()
3938 
3939 //==============================================================================
3941 void WebUsers::loadIPAddressSecurity()
3942 {
3943  ipAccessAccept_.clear();
3944  ipAccessReject_.clear();
3945  ipAccessBlacklist_.clear();
3946 
3947  FILE* fp = fopen((IP_ACCEPT_FILE).c_str(), "r");
3948  char line[300];
3949  size_t len;
3950 
3951  if(fp)
3952  {
3953  while(fgets(line, 300, fp))
3954  {
3955  len = strlen(line);
3956  // remove new line
3957  if(len > 2 && line[len - 1] == '\n')
3958  line[len - 1] = '\0';
3959  ipAccessAccept_.emplace(line);
3960  // if(StringMacros::wildCardMatch(ip, line))
3961  // return true; // found in accept file, so accept
3962  }
3963 
3964  fclose(fp);
3965  }
3966  __COUTV__(ipAccessAccept_.size());
3967 
3968  fp = fopen((IP_REJECT_FILE).c_str(), "r");
3969  if(fp)
3970  {
3971  while(fgets(line, 300, fp))
3972  {
3973  len = strlen(line);
3974  // remove new line
3975  if(len > 2 && line[len - 1] == '\n')
3976  line[len - 1] = '\0';
3977  ipAccessReject_.emplace(line);
3978  // if(StringMacros::wildCardMatch(ip, line))
3979  // return false; // found in reject file, so reject
3980  }
3981 
3982  fclose(fp);
3983  }
3984  __COUTV__(ipAccessReject_.size());
3985 
3986  fp = fopen((IP_BLACKLIST_FILE).c_str(), "r");
3987  if(fp)
3988  {
3989  while(fgets(line, 300, fp))
3990  {
3991  len = strlen(line);
3992  // remove new line
3993  if(len > 2 && line[len - 1] == '\n')
3994  line[len - 1] = '\0';
3995  ipAccessBlacklist_.emplace(line);
3996  // if(StringMacros::wildCardMatch(ip, line))
3997  // return false; // found in blacklist file, so reject
3998  }
3999 
4000  fclose(fp);
4001  }
4002  __COUTV__(ipAccessBlacklist_.size());
4003 } // end loadIPAddressSecurity()
4004 
4005 //==============================================================================
4006 void WebUsers::NACDisplayThread(const std::string& nac, const std::string& user)
4007 {
4008  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
4010  // thread notifying the user about the admin new account code
4011  // notify for 10 seconds (e.g.)
4012 
4013  // child thread
4014  int i = 0;
4015  for(; i < 5; ++i)
4016  {
4017  std::this_thread::sleep_for(std::chrono::seconds(2));
4018  __COUT__
4019  << "\n******************************************************************** "
4020  << __E__;
4021  __COUT__
4022  << "\n******************************************************************** "
4023  << __E__;
4024  __COUT__ << "\n\nNew account code = " << nac << " for user: " << user << "\n"
4025  << __E__;
4026  __COUT__
4027  << "\n******************************************************************** "
4028  << __E__;
4029  __COUT__
4030  << "\n******************************************************************** "
4031  << __E__;
4032  }
4033 } // end NACDisplayThread()
4034 
4035 //==============================================================================
4036 void WebUsers::deleteUserData()
4037 {
4038  __COUT__ << "$$$$$$$$$$$$$$ Deleting ALL service user data... $$$$$$$$$$$$" << __E__;
4039 
4040  // delete Login data
4041  std::system(
4042  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + HASHES_DB_PATH + "/*").c_str());
4043  std::system(
4044  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH + "/*").c_str());
4045  std::system(
4046  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_LOGIN_HISTORY_PATH + "/*")
4047  .c_str());
4048  std::system(
4049  ("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_PREFERENCES_PATH + "/*")
4050  .c_str());
4051  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH).c_str());
4052 
4053  std::string serviceDataPath = __ENV__("SERVICE_DATA_PATH");
4054  // delete macro maker folders
4055  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroData/").c_str());
4056  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroHistory/").c_str());
4057  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroExport/").c_str());
4058 
4059  // delete console folders
4060  std::system(
4061  ("rm -rf " + std::string(serviceDataPath) + "/ConsolePreferences/").c_str());
4062 
4063  // delete code editor folders
4064  std::system(("rm -rf " + std::string(serviceDataPath) + "/CodeEditorData/").c_str());
4065 
4066  // delete wizard folders
4067  std::system(("rm -rf " + std::string(serviceDataPath) + "/OtsWizardData/").c_str());
4068 
4069  // delete progress bar folders
4070  std::system(("rm -rf " + std::string(serviceDataPath) + "/ProgressBarData/").c_str());
4071 
4072  // delete The Supervisor run folders
4073  std::system(("rm -rf " + std::string(serviceDataPath) + "/RunNumber/").c_str());
4074  std::system(("rm -rf " + std::string(serviceDataPath) + "/RunControlData/").c_str());
4075 
4076  // delete Visualizer folders
4077  std::system(("rm -rf " + std::string(serviceDataPath) + "/VisualizerData/").c_str());
4078 
4079  // DO NOT delete active groups file (this messes with people's configuration world,
4080  // which is not expected when "resetting user info") std::system(("rm -rf " +
4081  // std::string(serviceDataPath) + "/ActiveTableGroups.cfg").c_str());
4082 
4083  // delete Logbook folders
4084  std::system(("rm -rf " + std::string(__ENV__("LOGBOOK_DATA_PATH")) + "/").c_str());
4085 
4086  __COUT__ << "$$$$$$$$$$$$$$ Successfully deleted ALL service user data $$$$$$$$$$$$"
4087  << __E__;
4088 } // end deleteUserData()
void copyDataChildren(HttpXmlDocument &document)
void removeDataElement(unsigned int dataChildIndex=0)
default to first child
bool loadXmlDocument(const std::string &filePath)
unsigned int getChildrenCount(xercesc::DOMElement *parent=0)
void addSystemMessage(const std::string &targetUsersCSV, const std::string &message)
Definition: WebUsers.cc:3495
const std::string & getSecurity(void)
WebUsers::getSecurity.
Definition: WebUsers.cc:3900
std::string getGenericPreference(uint64_t uid, const std::string &preferenceName, HttpXmlDocument *xmldoc=0) const
Definition: WebUsers.cc:3063
bool setUserWithLock(uint64_t actingUid, bool lock, const std::string &username)
Definition: WebUsers.cc:3168
static bool checkRequestAccess(cgicc::Cgicc &cgi, std::ostringstream *out, HttpXmlDocument *xmldoc, WebUsers::RequestUserInfo &userInfo, bool isWizardMode=false, const std::string &wizardModeSequence="")
Definition: WebUsers.cc:255
static void silenceAllUserTooltips(const std::string &username)
Definition: WebUsers.cc:2867
size_t getActiveUserCount(void)
Definition: WebUsers.cc:3396
std::map< std::string, WebUsers::permissionLevel_t > getPermissionsForUser(uint64_t uid)
from Gateway, use public version which considers remote users
Definition: WebUsers.cc:2568
uint64_t attemptActiveSession(const std::string &uuid, std::string &jumbledUser, const std::string &jumbledPw, std::string &newAccountCode, const std::string &ip)
Definition: WebUsers.cc:1106
void setGenericPreference(uint64_t uid, const std::string &preferenceName, const std::string &preferenceValue)
Definition: WebUsers.cc:3022
std::string getAllSystemMessages(void)
Definition: WebUsers.cc:3754
void cleanupExpiredEntries(std::vector< std::string > *loggedOutUsernames=0)
Definition: WebUsers.cc:2308
void changeSettingsForUser(uint64_t uid, const std::string &bgcolor, const std::string &dbcolor, const std::string &wincolor, const std::string &layout, const std::string &syslayout)
WebUsers::changeSettingsForUser.
Definition: WebUsers.cc:3119
uint64_t isCookieCodeActiveForLogin(const std::string &uuid, std::string &cookieCode, std::string &username)
Definition: WebUsers.cc:1858
std::string createNewLoginSession(const std::string &uuid, const std::string &ip)
Definition: WebUsers.cc:2429
std::string getActiveUsersString(void)
Definition: WebUsers.cc:3408
void createNewAccount(const std::string &username, const std::string &displayName, const std::string &email)
Definition: WebUsers.cc:1003
void modifyAccountSettings(uint64_t actingUid, uint8_t cmd_type, const std::string &username, const std::string &displayname, const std::string &email, const std::string &permissions)
WebUsers::modifyAccountSettings.
Definition: WebUsers.cc:3236
int remoteLoginVerificationPort_
Port of remote Gateway to be used for login verification.
Definition: WebUsers.h:651
bool isUsernameActive(const std::string &username) const
Definition: WebUsers.cc:1614
bool isUserIdActive(uint64_t uid) const
Definition: WebUsers.cc:1625
void saveActiveSessions(void)
Definition: WebUsers.cc:402
static std::atomic< bool > remoteLoginVerificationEnabled_
true if this supervisor is under control of a remote supervisor
Definition: WebUsers.h:649
uint64_t getAdminUserID(void)
Definition: WebUsers.cc:3441
@ SYS_CLEANUP_WILDCARD_TIME
300 seconds
Definition: WebUsers.h:192
std::string getUsersUsername(uint64_t uid)
from Gateway, use public version which considers remote users
Definition: WebUsers.cc:2038
static void initializeRequestUserInfo(cgicc::Cgicc &cgi, WebUsers::RequestUserInfo &userInfo)
used by gateway and other supervisors to verify requests consistently
Definition: WebUsers.cc:234
bool checkIpAccess(const std::string &ip)
Definition: WebUsers.cc:1950
bool xmlRequestOnGateway(cgicc::Cgicc &cgi, std::ostringstream *out, HttpXmlDocument *xmldoc, WebUsers::RequestUserInfo &userInfo)
Definition: WebUsers.cc:175
uint64_t cookieCodeLogout(const std::string &cookieCode, bool logoutOtherUserSessions, uint64_t *uid=0, const std::string &ip="0")
Definition: WebUsers.cc:2057
std::string getSystemMessage(const std::string &targetUser)
Definition: WebUsers.cc:3786
uint64_t getActiveSessionCountForUser(uint64_t uid)
Definition: WebUsers.cc:1915
static void resetAllUserTooltips(const std::string &userNeedle="*")
WebUsers::resetAllUserTooltips.
Definition: WebUsers.cc:2856
static void tooltipSetNeverShowForUsername(const std::string &username, HttpXmlDocument *xmldoc, const std::string &srcFile, const std::string &srcFunc, const std::string &srcId, bool doNeverShow, bool temporarySilence)
Definition: WebUsers.cc:2733
void cleanupExpiredRemoteEntries(void)
Definition: WebUsers.cc:2406
std::string getUsersDisplayName(uint64_t uid)
from Gateway, use public version which considers remote users
Definition: WebUsers.cc:2028
void loadActiveSessions(void)
Definition: WebUsers.cc:442
std::pair< std::string, time_t > getLastSystemMessage(void)
Definition: WebUsers.cc:3733
uint64_t attemptActiveSessionWithCert(const std::string &uuid, std::string &jumbledEmail, std::string &cookieCode, std::string &username, const std::string &ip)
Definition: WebUsers.cc:1298
static const std::string OTS_OWNER
defined by environment variable, e.g. experiment name
Definition: WebUsers.h:71
static void tooltipCheckForUsername(const std::string &username, HttpXmlDocument *xmldoc, const std::string &srcFile, const std::string &srcFunc, const std::string &srcId)
Definition: WebUsers.cc:2791
std::string remoteGatewaySelfName_
IP of remote Gateway to be used for login verification.
Definition: WebUsers.h:650
bool cookieCodeIsActiveForRequest(std::string &cookieCode, std::map< std::string, WebUsers::permissionLevel_t > *userPermissions=0, uint64_t *uid=0, const std::string &ip="0", bool refresh=true, bool doNotGoRemote=false, std::string *userWithLock=0, uint64_t *userSessionIndex=0)
Definition: WebUsers.cc:2130
void insertSettingsForUser(uint64_t uid, HttpXmlDocument *xmldoc, bool includeAccounts=false)
Definition: WebUsers.cc:2902
@ PERMISSION_LEVEL_ADMIN
max permission level!
Definition: WebUsers.h:64
xercesc::DOMElement * addTextElementToParent(const std::string &childName, const std::string &childText, xercesc::DOMElement *parent)
Definition: XmlDocument.cc:190
void saveXmlDocument(const std::string &filePath)
Definition: XmlDocument.cc:652
void INIT_MF(const char *name)
static std::string getTimestampString(const std::string &linuxTimeInSeconds)
static void getVectorFromString(const std::string &inputString, std::vector< std::string > &listToReturn, const std::set< char > &delimiter={',', '|', '&'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'}, std::vector< char > *listOfDelimiters=0, bool decodeURIComponents=false)
static std::string exec(const char *cmd)
static std::string setToString(const std::set< T > &setToReturn, const std::string &delimeter=", ")
setToString ~
static std::string vectorToString(const std::vector< T > &setToReturn, const std::string &delimeter=", ")
vectorToString ~
static std::string mapToString(const std::map< std::string, T > &mapToReturn, const std::string &primaryDelimeter=", ", const std::string &secondaryDelimeter=": ")
static void getMapFromString(const std::string &inputString, std::map< S, T > &mapToReturn, const std::set< char > &pairPairDelimiter={',', '|', '&'}, const std::set< char > &nameValueDelimiter={'=', ':'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'})
getMapFromString ~
static bool wildCardMatch(const std::string &needle, const std::string &haystack, unsigned int *priorityIndex=0)
Definition: StringMacros.cc:18
static std::string decodeURIComponent(const std::string &data)
static std::string stackTrace(void)
uint64_t userSessionIndex_
can use session index to track a user's session on multiple devices/browsers
Definition: WebUsers.h:362
const WebUsers::permissionLevel_t & getGroupPermissionLevel()
Definition: WebUsers.h:279
bool setGroupPermissionLevels(const std::string &groupPermissionLevelsString)
end setGroupPermissionLevels()
Definition: WebUsers.h:256