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