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