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