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