otsdaq  v2_05_02_indev
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 
43 // defines for user preferences
44 #define PREF_XML_BGCOLOR_FIELD "pref_bgcolor" // -background color
45 #define PREF_XML_DBCOLOR_FIELD "pref_dbcolor" // -dashboard color
46 #define PREF_XML_WINCOLOR_FIELD "pref_wincolor" // -window color
47 #define PREF_XML_LAYOUT_FIELD "pref_layout" // -3 defaults window layouts(and current)
48 #define PREF_XML_SYSLAYOUT_FIELD "pref_syslayout" // -2 defaults window layouts
49 #define PREF_XML_PERMISSIONS_FIELD "desktop_user_permissions" // 0-255 permissions value (255 is admin super user)
50 #define PREF_XML_USERLOCK_FIELD "username_with_lock" // user with lock (to lockout others)
51 #define PREF_XML_USERNAME_FIELD "pref_username" // user with lock (to lockout others)
52 #define PREF_XML_OTS_OWNER_FIELD "ots_owner" // e.g. the experiment name
53 
54 #define PREF_XML_BGCOLOR_DEFAULT "rgb(0,76,151)" // -background color
55 #define PREF_XML_DBCOLOR_DEFAULT "rgb(0,40,85)" // -dashboard color
56 #define PREF_XML_WINCOLOR_DEFAULT "rgba(196,229,255,0.9)" // -window color
57 #define PREF_XML_LAYOUT_DEFAULT "0;0;0;0" // 3 default window layouts(and current)
58 #define PREF_XML_SYSLAYOUT_DEFAULT "0;0" // 2 system default window layouts
59 
60 #define PREF_XML_ACCOUNTS_FIELD "users_accounts" // user accounts field for super users
61 #define PREF_XML_LOGIN_HISTORY_FIELD "login_entry" // login history field for user login history data
62 
63 const std::string WebUsers::OTS_OWNER = getenv("OTS_OWNER")?getenv("OTS_OWNER"):"";
64 const std::string WebUsers::DEFAULT_ADMIN_USERNAME = "admin";
65 const std::string WebUsers::DEFAULT_ADMIN_DISPLAY_NAME = "Administrator";
66 const std::string WebUsers::DEFAULT_ADMIN_EMAIL = "root@otsdaq.fnal.gov";
67 const std::string WebUsers::DEFAULT_ITERATOR_USERNAME = "iterator";
68 const std::string WebUsers::DEFAULT_STATECHANGER_USERNAME = "statechanger";
69 const std::string WebUsers::DEFAULT_USER_GROUP = "allUsers";
70 
71 const std::string WebUsers::REQ_NO_LOGIN_RESPONSE = "NoLogin";
72 const std::string WebUsers::REQ_NO_PERMISSION_RESPONSE = "NoPermission";
73 const std::string WebUsers::REQ_USER_LOCKOUT_RESPONSE = "UserLockout";
74 const std::string WebUsers::REQ_LOCK_REQUIRED_RESPONSE = "LockRequired";
75 const std::string WebUsers::REQ_ALLOW_NO_USER = "AllowNoUser";
76 
77 const std::string WebUsers::SECURITY_TYPE_NONE = "NoSecurity";
78 const std::string WebUsers::SECURITY_TYPE_DIGEST_ACCESS = "DigestAccessAuthentication";
79 const std::string WebUsers::SECURITY_TYPE_DEFAULT = WebUsers::SECURITY_TYPE_NONE; // default to NO SECURITY
80 
81 const std::vector<std::string> WebUsers::HashesDatabaseEntryFields_ = {"hash","lastAccessTime"};
82 const std::vector<std::string> WebUsers::UsersDatabaseEntryFields_ = {"username","displayName","salt",
83  "uid","permissions","lastLoginAttemptTime","accountCreatedTime",
84  "loginFailureCount","lastModifiedTime","lastModifierUsername","useremail"};
85 
86 #undef __MF_SUBJECT__
87 #define __MF_SUBJECT__ "WebUsers"
88 
89 // clang-format on
90 
91 WebUsers::WebUsers()
92 {
93  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
94 
95  // deleteUserData(); //leave for debugging to reset user data
96 
97  usersNextUserId_ = 0; // first UID, default to 0 but get from database
98  usersUsernameWithLock_ = ""; // init to no user with lock
99 
100  // define field labels
101  // HashesDatabaseEntryFields.push_back("hash");
102  // HashesDatabaseEntryFields.push_back("lastAccessTime"); // last login month resolution, blurred by 1/2 month
103  //
104  // WebUsers::UsersDatabaseEntryFields_.push_back("username");
105  // WebUsers::UsersDatabaseEntryFields_.push_back("displayName");
106  // WebUsers::UsersDatabaseEntryFields_.push_back("salt");
107  // WebUsers::UsersDatabaseEntryFields_.push_back("uid");
108  // WebUsers::UsersDatabaseEntryFields_.push_back("permissions");
109  // WebUsers::UsersDatabaseEntryFields_.push_back("lastLoginAttemptTime");
110  // WebUsers::UsersDatabaseEntryFields_.push_back("accountCreatedTime");
111  // WebUsers::UsersDatabaseEntryFields_.push_back("loginFailureCount");
112  // WebUsers::UsersDatabaseEntryFields_.push_back("lastModifiedTime");
113  // WebUsers::UsersDatabaseEntryFields_.push_back("lastModifierUsername");
114  // WebUsers::UsersDatabaseEntryFields_.push_back("useremail");
115 
116  // attempt to make directory structure (just in case)
117  mkdir(((std::string)WEB_LOGIN_DB_PATH).c_str(), 0755);
118  mkdir(((std::string)WEB_LOGIN_DB_PATH + "bkup/" + USERS_DB_PATH).c_str(), 0755);
119  mkdir(((std::string)WEB_LOGIN_DB_PATH + HASHES_DB_PATH).c_str(), 0755);
120  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH).c_str(), 0755);
121  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_LOGIN_HISTORY_PATH).c_str(), 0755);
122  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_PREFERENCES_PATH).c_str(), 0755);
123 
124  if(!loadDatabases())
125  __COUT__ << "FATAL USER DATABASE ERROR - failed to load!!!" << __E__;
126 
127  loadSecuritySelection();
128 
129  // print out admin new user code for ease of use
130  uint64_t i;
131  std::string user = DEFAULT_ADMIN_USERNAME;
132  if((i = searchUsersDatabaseForUsername(user)) == NOT_FOUND_IN_DATABASE)
133  {
134  __SS__ << "user: " << user << " is not found. This should be impossible!" << __E__;
135  __COUT_ERR__ << ss.str();
136  __SS_THROW__; // THIS CAN NOT HAPPEN?! There must be an admin user
137  }
138  else if(Users_[i].salt_ == "" && // admin password not setup, so print out NAC to help out
139  securityType_ == SECURITY_TYPE_DIGEST_ACCESS)
140  {
142  // start thread for notifying the user about the admin new account code
143  // notify for 10 seconds (e.g.)
144  std::thread([](const std::string& nac, const std::string& user) { WebUsers::NACDisplayThread(nac, user); }, Users_[i].getNewAccountCode(), user)
145  .detach();
146  }
147 
148  // attempt to load persistent user sessions
149  loadActiveSessions();
150 
151  // default user with lock to admin and/or try to load last user with lock
152  // Note: this must happen after getting persistent active sessions
153  loadUserWithLock();
154 
155  srand(time(0)); // seed random for hash salt generation
156 
157  __COUT__ << "Done with Web Users initialization!" << __E__;
158 } // end constructor
159 
160 //==============================================================================
161 // xmlRequestOnGateway
162 // check the validity of an xml request at the server side, i.e. at the Gateway
163 // supervisor, which is the owner of the web users instance. if false, gateway
164 // request code should just return.. out is handled on false; on true, out is untouched
165 bool WebUsers::xmlRequestOnGateway(cgicc::Cgicc& cgi, std::ostringstream* out, HttpXmlDocument* xmldoc, WebUsers::RequestUserInfo& userInfo)
166 {
167  std::lock_guard<std::mutex> lock(webUserMutex_);
168 
169  // initialize user info parameters to failed results
170  WebUsers::initializeRequestUserInfo(cgi, userInfo);
171 
172  uint64_t i;
173 
174  if(!cookieCodeIsActiveForRequest(userInfo.cookieCode_,
175  &userInfo.groupPermissionLevelMap_,
176  &userInfo.uid_,
177  userInfo.ip_,
178  !userInfo.automatedCommand_ /*refresh cookie*/,
179  &userInfo.usernameWithLock_,
180  &userInfo.activeUserSessionIndex_))
181  {
182  *out << userInfo.cookieCode_;
183  goto HANDLE_ACCESS_FAILURE; // return false, access failed
184  }
185 
186  // setup userInfo.permissionLevel_ based on userInfo.groupPermissionLevelMap_
187  userInfo.getGroupPermissionLevel();
188 
189  i = searchUsersDatabaseForUserId(userInfo.uid_);
190  if(i >= Users_.size())
191  {
192  __SS__ << "Illegal uid encountered in cookie codes!? " << i << __E__;
193  ss << "User size = " << Users_.size() << __E__;
194  __SS_THROW__;
195  }
196 
197  userInfo.username_ = Users_[i].username_;
198  userInfo.displayName_ = Users_[i].displayName_;
199 
200  if(!WebUsers::checkRequestAccess(cgi, out, xmldoc, userInfo))
201  goto HANDLE_ACCESS_FAILURE; // return false, access failed
202 
203  return true; // access success!
204 
205 HANDLE_ACCESS_FAILURE:
206  // print out return string on failure
207  if(!userInfo.automatedCommand_)
208  __COUT_ERR__ << "Failed request (requestType = " << userInfo.requestType_ << "): " << out->str() << __E__;
209  return false; // access failed
210 
211 } // end xmlRequestOnGateway()
212 
213 //==============================================================================
214 // initializeRequestUserInfo
215 // initialize user info parameters to failed results
216 void WebUsers::initializeRequestUserInfo(cgicc::Cgicc& cgi, WebUsers::RequestUserInfo& userInfo)
217 {
218  userInfo.ip_ = cgi.getEnvironment().getRemoteAddr();
219 
220  // note if related bools are false, members below may not be set
221  userInfo.username_ = "";
222  userInfo.displayName_ = "";
223  userInfo.usernameWithLock_ = "";
224  userInfo.activeUserSessionIndex_ = -1;
225  userInfo.setGroupPermissionLevels(""); // always init to inactive
226 }
227 
228 //==============================================================================
229 // checkRequestAccess
230 // check user permission parameters based on cookie code, user permission level
231 //(extracted previous from group membership) Note: assumes
232 // userInfo.groupPermissionLevelMap_ and userInfo.permissionLevel_ are properly setup
233 // by either calling userInfo.setGroupPermissionLevels() or
234 // userInfo.getGroupPermissionLevel()
235 bool WebUsers::checkRequestAccess(cgicc::Cgicc& /*cgi*/,
236  std::ostringstream* out,
237  HttpXmlDocument* xmldoc,
238  WebUsers::RequestUserInfo& userInfo,
239  bool isWizardMode /* = false */,
240  const std::string& wizardModeSequence /* = "" */)
241 {
242  // steps:
243  // - check access based on cookieCode and permission level
244  // - check user lock flags and status
245 
246  if(userInfo.requireSecurity_)
247  {
248  // only allow if wiz mode with random code, or normal mode with security mode
249  // enabled
250 
251  if(isWizardMode && wizardModeSequence.size() < 8)
252  {
253  // force wiz mode sequence to be "random and large"
254  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
255  __COUT__ << "User (@" << userInfo.ip_ << ") has attempted requestType '" << userInfo.requestType_
256  << "' which requires sufficient security enabled. Please enable the "
257  "random wizard mode"
258  " sequence of at least 8 characters."
259  << __E__;
260  return false; // invalid cookie and present sequence, but not correct
261  // sequence
262  }
263  else if(!isWizardMode && (userInfo.username_ == WebUsers::DEFAULT_ADMIN_USERNAME || userInfo.username_ == WebUsers::DEFAULT_ITERATOR_USERNAME ||
264  userInfo.username_ == WebUsers::DEFAULT_STATECHANGER_USERNAME))
265  {
266  // force non-admin user, which implies sufficient security
267  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
268  __COUT__ << "User (@" << userInfo.ip_ << ") has attempted requestType '" << userInfo.requestType_
269  << "' which requires sufficient security enabled. Please enable "
270  "individual user "
271  " logins (Note: the user admin is disallowed in an attempt to force personal accountability for edits)."
272  << __E__;
273  return false; // invalid cookie and present sequence, but not correct
274  // sequence
275  }
276 
277  } // end security required verification
278 
279  if(!userInfo.automatedCommand_)
280  {
281  __COUTT__ << "requestType ==========>>> " << userInfo.requestType_ << __E__;
282  __COUTTV__((unsigned int)userInfo.permissionLevel_);
283  __COUTTV__((unsigned int)userInfo.permissionsThreshold_);
284  }
285 
286  // second, start check access -------
287  if(!isWizardMode && !userInfo.allowNoUser_ && userInfo.cookieCode_.length() != WebUsers::COOKIE_CODE_LENGTH)
288  {
289  __COUT__ << "User (@" << userInfo.ip_ << ") has invalid cookie code: " << userInfo.cookieCode_ << std::endl;
290  *out << WebUsers::REQ_NO_LOGIN_RESPONSE;
291  return false; // invalid cookie and present sequence, but not correct sequence
292  }
293 
294  if(!userInfo.allowNoUser_ && (userInfo.permissionLevel_ == 0 || // reject inactive permission level
295  userInfo.permissionLevel_ < userInfo.permissionsThreshold_))
296  {
297  *out << WebUsers::REQ_NO_PERMISSION_RESPONSE;
298  __COUT__ << "User (@" << userInfo.ip_ << ") has insufficient permissions for requestType '" << userInfo.requestType_
299  << "' : " << (unsigned int)userInfo.permissionLevel_ << "<" << (unsigned int)userInfo.permissionsThreshold_ << std::endl;
300  return false; // invalid cookie and present sequence, but not correct sequence
301  }
302  // end check access -------
303 
304  if(isWizardMode)
305  {
306  userInfo.username_ = WebUsers::DEFAULT_ADMIN_USERNAME;
307  userInfo.displayName_ = "Admin";
308  userInfo.usernameWithLock_ = userInfo.username_;
309  userInfo.activeUserSessionIndex_ = 0;
310  return true; // done, wizard mode access granted
311  }
312  // else, normal gateway verify mode
313 
314  if(xmldoc) // fill with cookie code tag
315  {
316  if(userInfo.allowNoUser_)
317  xmldoc->setHeader(WebUsers::REQ_ALLOW_NO_USER);
318  else
319  xmldoc->setHeader(userInfo.cookieCode_);
320  }
321 
322  if(userInfo.allowNoUser_)
323  {
324  if(userInfo.automatedCommand_)
325  __COUT__ << "Allowing anonymous access." << __E__;
326 
327  return true; // ignore lock for allow-no-user case
328  }
329 
330  // if(!userInfo.automatedCommand_)
331  // {
332  // __COUTV__(userInfo.username_);
333  // __COUTV__(userInfo.usernameWithLock_);
334  // }
335 
336  if((userInfo.checkLock_ || userInfo.requireLock_) && userInfo.usernameWithLock_ != "" && userInfo.usernameWithLock_ != userInfo.username_)
337  {
338  *out << WebUsers::REQ_USER_LOCKOUT_RESPONSE;
339  __COUT__ << "User '" << userInfo.username_ << "' is locked out. '" << userInfo.usernameWithLock_ << "' has lock." << std::endl;
340  return false; // failed due to another user having lock
341  }
342 
343  if(userInfo.requireLock_ && userInfo.usernameWithLock_ != userInfo.username_)
344  {
345  *out << WebUsers::REQ_LOCK_REQUIRED_RESPONSE;
346  __COUT__ << "User '" << userInfo.username_ << "' must have lock to proceed. ('" << userInfo.usernameWithLock_ << "' has lock.)" << std::endl;
347  return false; // failed due to lock being required, and this user does not have
348  // it
349  }
350 
351  return true; // access success!
352 
353 } // end checkRequestAccess()
354 
355 //==============================================================================
356 // saveActiveSessions
357 // save active sessions structure so that they can survive restart
358 void WebUsers::saveActiveSessions()
359 {
360  std::string fn;
361 
362  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_ACTIVE_SESSIONS_FILE;
363  __COUT__ << fn << __E__;
364 
365  FILE* fp = fopen(fn.c_str(), "w");
366  if(!fp)
367  {
368  __COUT_ERR__ << "Error! Persistent active sessions could not be saved to file: " << fn << __E__;
369  return;
370  }
371 
372  int version = 0;
373  fprintf(fp, "%d\n", version);
374  for(unsigned int i = 0; i < ActiveSessions_.size(); ++i)
375  {
376  // __COUT__ << "SAVE " << ActiveSessionCookieCodeVector[i] << __E__;
377  // __COUT__ << "SAVE " << ActiveSessionIpVector[i] << __E__;
378  // __COUT__ << "SAVE " << ActiveSessionUserIdVector[i] << __E__;
379  // __COUT__ << "SAVE " << ActiveSessionIndex[i] << __E__;
380  // __COUT__ << "SAVE " << ActiveSessionStartTimeVector[i] << __E__;
381 
382  fprintf(fp, "%s\n", ActiveSessions_[i].cookieCode_.c_str());
383  fprintf(fp, "%s\n", ActiveSessions_[i].ip_.c_str());
384  fprintf(fp, "%lu\n", ActiveSessions_[i].userId_);
385  fprintf(fp, "%lu\n", ActiveSessions_[i].sessionIndex_);
386  fprintf(fp, "%ld\n", ActiveSessions_[i].startTime_);
387  }
388 
389  __COUT__ << "Active Sessions saved with size " << ActiveSessions_.size() << __E__;
390 
391  fclose(fp);
392 } // end saveActiveSessions()
393 
394 //====================================================================================================================
395 // loadActiveSessions
396 // load active sessions structure so that they can survive restart
397 void WebUsers::loadActiveSessions()
398 {
399  std::string fn;
400 
401  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_ACTIVE_SESSIONS_FILE;
402  __COUT__ << fn << __E__;
403  FILE* fp = fopen(fn.c_str(), "r");
404  if(!fp)
405  {
406  __COUT_INFO__ << "Persistent active sessions were not found to be loaded at file: " << fn << __E__;
407  return;
408  }
409 
410  int version;
411 
412  const int LINELEN = 1000;
413  char line[LINELEN];
414  fgets(line, LINELEN, fp);
415  sscanf(line, "%d", &version);
416  if(version == 0)
417  {
418  __COUT__ << "Extracting active sessions..." << __E__;
419  }
420  unsigned int i = 0;
421  while(fgets(line, LINELEN, fp))
422  {
423  if(strlen(line))
424  line[strlen(line) - 1] = '\0'; // remove new line
425  if(strlen(line) != COOKIE_CODE_LENGTH)
426  {
427  __COUT__ << "Illegal cookie code found: " << line << __E__;
428 
429  fclose(fp);
430  return;
431  }
432  ActiveSessions_.push_back(ActiveSession());
433  ActiveSessions_.back().cookieCode_ = line;
434 
435  fgets(line, LINELEN, fp);
436  if(strlen(line))
437  line[strlen(line) - 1] = '\0'; // remove new line
438  ActiveSessions_.back().ip_ = line;
439 
440  fgets(line, LINELEN, fp);
441  sscanf(line, "%lu", &(ActiveSessions_.back().userId_));
442 
443  fgets(line, LINELEN, fp);
444  sscanf(line, "%lu", &(ActiveSessions_.back().sessionIndex_));
445 
446  fgets(line, LINELEN, fp);
447  sscanf(line, "%ld", &(ActiveSessions_.back().startTime_));
448 
449  ++i;
450  }
451 
452  __COUT__ << "Active Sessions loaded with size " << ActiveSessions_.size() << __E__;
453 
454  fclose(fp);
455  // clear file after loading
456  fp = fopen(fn.c_str(), "w");
457  if(fp)
458  fclose(fp);
459 } // end loadActiveSessions()
460 
461 //==============================================================================
462 // loadDatabaseFromFile
463 // load Hashes and Users from file
464 // create database if non-existent
465 bool WebUsers::loadDatabases()
466 {
467  std::string fn;
468 
469  FILE* fp;
470  const unsigned int LINE_LEN = 1000;
471  char line[LINE_LEN];
472  unsigned int i, si, c, len, f;
473  //uint64_t tmpInt64;
474 
475  // hashes
476  // File Organization:
477  // <hashData>
478  // <hashEntry><hash>hash0</hash><lastAccessTime>lastAccessTime0</lastAccessTime></hashEntry>
479  // <hashEntry><hash>hash1</hash><lastAccessTime>lastAccessTime1</lastAccessTime></hashEntry>
480  // ..
481  // </hashData>
482 
483  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_FILE;
484  __COUT__ << fn << __E__;
485  fp = fopen(fn.c_str(), "r");
486  if(!fp) // need to create file
487  {
488  mkdir(((std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_PATH).c_str(), 0755);
489  __COUT__ << ((std::string)WEB_LOGIN_DB_PATH + (std::string)HASHES_DB_PATH).c_str() << __E__;
490  fp = fopen(fn.c_str(), "w");
491  if(!fp)
492  return false;
493  __COUT__ << "Hashes database created: " << fn << __E__;
494 
495  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
496  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
497  fclose(fp);
498  }
499  else // load structures if hashes exists
500  {
501  // for every HASHES_DB_ENTRY_STRING, extract to local vector
502  // trusting file construction, assuming fields based >'s and <'s
503  while(fgets(line, LINE_LEN, fp))
504  {
505  if(strlen(line) < SHA512_DIGEST_LENGTH)
506  continue;
507 
508  c = 0;
509  len = strlen(line); // save len, strlen will change because of \0 manipulations
510  for(i = 0; i < len; ++i)
511  if(line[i] == '>')
512  {
513  ++c; // count >'s
514  if(c != 2 && c != 4)
515  continue; // only proceed for field data
516 
517  si = ++i; // save start index
518  while(i < len && line[i] != '<')
519  ++i;
520  if(i == len)
521  break;
522  line[i] = '\0'; // close std::string
523 
524  //__COUT__ << "Found Hashes field " << c/2 << " " << &line[si] <<
525  //__E__;
526 
527  f = c / 2 - 1;
528  if(f == 0) // hash
529  {
530  Hashes_.push_back(Hash());
531  Hashes_.back().hash_ = &line[si];
532  }
533  else if(f == 1) // lastAccessTime
534  sscanf(&line[si], "%ld", &Hashes_.back().accessTime_);
535  }
536  }
537  __COUT__ << Hashes_.size() << " Hashes found." << __E__;
538 
539  fclose(fp);
540  }
541 
542  // users
543  // File Organization:
544  // <userData>
545  // <nextUserId>...</nextUserId>
546  // <userEntry>...</userEntry>
547  // <userEntry>...</userEntry>
548  // ..
549  // </userData>
550 
551  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_FILE;
552  fp = fopen(fn.c_str(), "r");
553  if(!fp) // need to create file
554  {
555  mkdir(((std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_PATH).c_str(), 0755);
556  __COUT__ << ((std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_PATH).c_str() << __E__;
557  fp = fopen(fn.c_str(), "w");
558  if(!fp)
559  return false;
560  __COUT__ << "Users database created: " << fn << __E__;
561 
562  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
563  char nidStr[100];
564  sprintf(nidStr, "%lu", usersNextUserId_);
565  saveToDatabase(fp, USERS_DB_NEXT_UID_STRING, nidStr, DB_SAVE_OPEN_AND_CLOSE);
566  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
567  fclose(fp);
568 
569  createNewAccount(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_DISPLAY_NAME,
570  DEFAULT_ADMIN_EMAIL); // account 0 is always admin
571  }
572  else // extract next user id and user entries if users exists
573  {
574  __COUT__ << "Users database: " << fn << __E__;
575  // for every USERS_DB_ENTRY_STRING, extract to local vector
576  // trusting file construction, assuming fields based >'s and <'s
577 
578  char salt[] = "nextUserId";
579  while(fgets(line, LINE_LEN, fp))
580  {
581  if(strlen(line) < strlen(salt) * 2)
582  continue; // line size should indicate xml tags on same line
583 
584  for(i = 0; i < strlen(salt); ++i) // check for opening tag
585  if(line[i + 1] != salt[i])
586  break;
587 
588  if(i == strlen(salt)) // all salt matched, so found correct line! increment
589  // to get line index
590  {
591  i += 2;
592  si = i;
593  while(i < LINE_LEN && line[i] != '\0' && line[i] != '<')
594  ++i; // find '<'
595  line[i] = '\0'; // close std::string
596  sscanf(&line[si], "%lu", &usersNextUserId_);
597  break; // done with next uid
598  }
599  }
600 
601  __COUT__ << "Found Users database next user Id: " << usersNextUserId_ << __E__;
602 
603  // trusting file construction, assuming fields based >'s and <'s and each entry on
604  // one line
605  while(fgets(line, LINE_LEN, fp))
606  {
607  if(strlen(line) < 30)
608  continue; // rule out header tags
609 
610  c = 0;
611  len = strlen(line); // save len, strlen will change because of \0 manipulations
612  if(len >= LINE_LEN)
613  {
614  __COUT__ << "Line buffer too small: " << len << __E__;
615  break;
616  }
617 
618  // get fields from line
619  f = 0;
620  for(i = 0; i < len; ++i)
621  if(line[i] == '>')
622  {
623  ++c; // count >'s
624  if(c == 0 || c % 2 == 1)
625  continue; // only proceed for field data (even
626 
627  si = ++i; // save start index
628  while(i < len && line[i] != '<')
629  ++i;
630  if(i == len)
631  break;
632  line[i] = '\0'; // close std::string
633  f = c / 2 - 1;
634 
635  //__COUT__ << "Found Users[" <<
636  // Users_.size() << "] field " << f << " " << &line[si] << __E__;
637 
638  if(f == 0) // username
639  {
640  Users_.push_back(User());
641  Users_.back().username_ = &line[si];
642  }
643  else if(f == 1) // displayName
644  Users_.back().displayName_ = &line[si];
645  else if(f == 2) // salt
646  Users_.back().salt_ = &line[si];
647  else if(f == 3) // uid
648  sscanf(&line[si], "%lu", &Users_.back().userId_);
649  else if(f == 4) // permissions
650  {
651  std::map<std::string, permissionLevel_t>& lastPermissionsMap = Users_.back().permissions_;
652  StringMacros::getMapFromString<permissionLevel_t>(&line[si], lastPermissionsMap);
653 
654  //__COUT__ << "User permission levels:" <<
655  // StringMacros::mapToString(lastPermissionsMap) << __E__;
656 
657  // verify 'allUsers' is there
658  // if not, add it as a disabled user (i.e.
659  // WebUsers::PERMISSION_LEVEL_INACTIVE)
660  if(lastPermissionsMap.find(WebUsers::DEFAULT_USER_GROUP) == lastPermissionsMap.end())
661  {
662  __MCOUT_INFO__("User '" << Users_.back().username_ << "' is not a member of the default user group '"
663  << WebUsers::DEFAULT_USER_GROUP
664  << ".' Assuming user account is inactive (permission "
665  "level := "
666  << WebUsers::PERMISSION_LEVEL_INACTIVE << ")." << __E__);
667  lastPermissionsMap[WebUsers::DEFAULT_USER_GROUP] = WebUsers::PERMISSION_LEVEL_INACTIVE; // mark inactive
668  }
669 
670  if(Users_.back().username_ == DEFAULT_ADMIN_USERNAME)
671  {
672  // overwrite admin with full permissions (irregardless of corrupt user db situation), never allow to be inactive for example
673 
674  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> initPermissions = {
675  {WebUsers::DEFAULT_USER_GROUP, WebUsers::PERMISSION_LEVEL_ADMIN}};
676 
677  Users_.back().permissions_ = initPermissions;
678  }
679  }
680  else if(f == 5) // lastLoginAttemptTime
681  sscanf(&line[si], "%ld", &Users_.back().lastLoginAttempt_);
682  else if(f == 6) // accountCreatedTime
683  sscanf(&line[si], "%ld", &Users_.back().accountCreationTime_);
684  else if(f == 7) // loginFailureCount
685  sscanf(&line[si], "%hhu", &Users_.back().loginFailureCount_);
686  else if(f == 8) // lastModifierTime
687  sscanf(&line[si], "%ld", &Users_.back().accessModifierTime());
688  else if(f == 9) // lastModifierUsername
689  Users_.back().loadModifierUsername(&line[si]);
690  else if(f == 10) // user email
691  Users_.back().email_ = &line[si];
692  }
693 
694  } // end get line loop
695  fclose(fp);
696  }
697 
698  __COUT__ << Users_.size() << " Users found." << __E__;
699  for(size_t ii = 0; ii < Users_.size(); ++ii)
700  {
701  std::cout << // do not send to message facility
702  "User [" << Users_[ii].userId_ << "] \tName: " << std::left << std::setfill(' ') << std::setw(20) << Users_[ii].username_
703  << "\tDisplay Name: " << std::left << std::setfill(' ') << std::setw(30) << Users_[ii].displayName_ << "\tEmail: " << std::left
704  << std::setfill(' ') << std::setw(30) << Users_[ii].email_ << "\tNAC: " << std::left << std::setfill(' ') << std::setw(5)
705  << Users_[ii].getNewAccountCode() << "\tFailedCount: " << (int)Users_[ii].loginFailureCount_
706  << "\tPermissions: " << StringMacros::mapToString(Users_[ii].permissions_) <<
707  //"\tSalt: " << Users_[ii].salt_.size() << " " << Users_[ii].salt_ <<
708  __E__;
709  }
710  // __COUT__ << Hashes_.size() << " Hashes found." << __E__;
711  // for(size_t ii = 0; ii < Hashes_.size(); ++ii)
712  // {
713  // std::cout << //do not send to message facility
714  // "Hash [" << ii <<
715  // "]: " << Hashes_[ii].hash_ <<
716  // __E__;
717  // }
718  return true;
719 } // end loadDatabases()
720 
721 //==============================================================================
722 // saveToDatabase
723 void WebUsers::saveToDatabase(FILE* fp, const std::string& field, const std::string& value, uint8_t type, bool addNewLine)
724 {
725  if(!fp)
726  return;
727 
728  std::string newLine = addNewLine ? "\n" : "";
729 
730  if(type == DB_SAVE_OPEN_AND_CLOSE)
731  fprintf(fp, "<%s>%s</%s>%s", field.c_str(), value.c_str(), field.c_str(), newLine.c_str());
732  else if(type == DB_SAVE_OPEN)
733  fprintf(fp, "<%s>%s%s", field.c_str(), value.c_str(), newLine.c_str());
734  else if(type == DB_SAVE_CLOSE)
735  fprintf(fp, "</%s>%s", field.c_str(), newLine.c_str());
736 } // end saveToDatabase()
737 
738 //==============================================================================
739 // saveDatabaseToFile
740 // returns true if saved database successfully
741 // db: DB_USERS or DB_HASHES
742 // else false
743 
744 bool WebUsers::saveDatabaseToFile(uint8_t db)
745 {
746  //__COUT__ << "Save Database: " << (int)db << __E__;
747 
748  std::string fn = (std::string)WEB_LOGIN_DB_PATH + ((db == DB_USERS) ? (std::string)USERS_DB_FILE : (std::string)HASHES_DB_FILE);
749 
750  __COUT__ << "Save Database Filename: " << fn << __E__;
751 
752  // backup file organized by day
753  if(0)
754  {
755  char dayAppend[20];
756  sprintf(dayAppend, ".%lu.bkup", time(0) / (3600 * 24));
757  std::string bkup_fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)WEB_LOGIN_BKUP_DB_PATH +
758  ((db == DB_USERS) ? (std::string)USERS_DB_FILE : (std::string)HASHES_DB_FILE) + (std::string)dayAppend;
759 
760  __COUT__ << "Backup file: " << bkup_fn << __E__;
761 
762  std::string shell_command = "mv " + fn + " " + bkup_fn;
763  system(shell_command.c_str());
764  }
765 
766  FILE* fp = fopen(fn.c_str(), "wb"); // write in binary mode
767  if(!fp)
768  return false;
769 
770  char fldStr[100];
771 
772  if(db == DB_USERS) // USERS
773  {
774  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
775 
776  sprintf(fldStr, "%lu", usersNextUserId_);
777  saveToDatabase(fp, USERS_DB_NEXT_UID_STRING, fldStr, DB_SAVE_OPEN_AND_CLOSE);
778 
779  __COUT__ << "Saving " << Users_.size() << " Users." << __E__;
780 
781  for(uint64_t i = 0; i < Users_.size(); ++i)
782  {
783  //__COUT__ << "Saving User: " << UsersUsernameVector[i] << __E__;
784 
785  saveToDatabase(fp, USERS_DB_ENTRY_STRING, "", DB_SAVE_OPEN, false);
786 
787  for(unsigned int f = 0; f < WebUsers::UsersDatabaseEntryFields_.size(); ++f)
788  {
789  //__COUT__ << "Saving Field: " << f << __E__;
790  if(f == 0) // username
791  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], Users_[i].username_, DB_SAVE_OPEN_AND_CLOSE, false);
792  else if(f == 1) // displayName
793  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], Users_[i].displayName_, DB_SAVE_OPEN_AND_CLOSE, false);
794  else if(f == 2) // salt
795  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], Users_[i].salt_, DB_SAVE_OPEN_AND_CLOSE, false);
796  else if(f == 3) // uid
797  {
798  sprintf(fldStr, "%lu", Users_[i].userId_);
799  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], fldStr, DB_SAVE_OPEN_AND_CLOSE, false);
800  }
801  else if(f == 4) // permissions
802  saveToDatabase(fp,
803  WebUsers::UsersDatabaseEntryFields_[f],
804  StringMacros::mapToString(Users_[i].permissions_, "," /*primary delimeter*/, ":" /*secondary delimeter*/),
805  DB_SAVE_OPEN_AND_CLOSE,
806  false);
807  else if(f == 5) // lastLoginAttemptTime
808  {
809  sprintf(fldStr, "%lu", Users_[i].lastLoginAttempt_);
810  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], fldStr, DB_SAVE_OPEN_AND_CLOSE, false);
811  }
812  else if(f == 6) // accountCreatedTime
813  {
814  sprintf(fldStr, "%lu", Users_[i].accountCreationTime_);
815  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], fldStr, DB_SAVE_OPEN_AND_CLOSE, false);
816  }
817  else if(f == 7) // loginFailureCount
818  {
819  sprintf(fldStr, "%hhu", Users_[i].loginFailureCount_);
820  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], fldStr, DB_SAVE_OPEN_AND_CLOSE, false);
821  }
822  else if(f == 8) // lastModifierTime
823  {
824  sprintf(fldStr, "%lu", Users_[i].getModifierTime());
825  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], fldStr, DB_SAVE_OPEN_AND_CLOSE, false);
826  }
827  else if(f == 9) // lastModifierUsername
828  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], Users_[i].getModifierUsername(), DB_SAVE_OPEN_AND_CLOSE, false);
829  else if(f == 10) // useremail
830  saveToDatabase(fp, WebUsers::UsersDatabaseEntryFields_[f], Users_[i].email_, DB_SAVE_OPEN_AND_CLOSE, false);
831  }
832 
833  saveToDatabase(fp, USERS_DB_ENTRY_STRING, "", DB_SAVE_CLOSE);
834  }
835 
836  saveToDatabase(fp, USERS_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
837  }
838  else // HASHES
839  {
840  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_OPEN);
841 
842  __COUT__ << "Saving " << Hashes_.size() << " Hashes." << __E__;
843  for(uint64_t i = 0; i < Hashes_.size(); ++i)
844  {
845  __COUT__ << "Saving " << Hashes_[i].hash_ << " Hash." << __E__;
846  saveToDatabase(fp, HASHES_DB_ENTRY_STRING, "", DB_SAVE_OPEN, false);
847  for(unsigned int f = 0; f < WebUsers::HashesDatabaseEntryFields_.size(); ++f)
848  {
849  if(f == 0) // hash
850  saveToDatabase(fp, WebUsers::HashesDatabaseEntryFields_[f], Hashes_[i].hash_, DB_SAVE_OPEN_AND_CLOSE, false);
851  else if(f == 1) // lastAccessTime
852  {
853  sprintf(fldStr, "%lu", Hashes_[i].accessTime_);
854  saveToDatabase(fp, WebUsers::HashesDatabaseEntryFields_[f], fldStr, DB_SAVE_OPEN_AND_CLOSE, false);
855  }
856  }
857  saveToDatabase(fp, HASHES_DB_ENTRY_STRING, "", DB_SAVE_CLOSE);
858  }
859 
860  saveToDatabase(fp, HASHES_DB_GLOBAL_STRING, "", DB_SAVE_CLOSE);
861  }
862 
863  fclose(fp);
864  return true;
865 } // end saveDatabaseToFile()
866 
867 //==============================================================================
868 // createNewAccount
869 // adds a new valid user to database
870 // inputs: username and name to display
871 // initializes database entry with minimal permissions
872 // and salt starts as "" until password is set
873 // Special case if first user name!! max permissions given (super user made)
874 // //Note: username, userId, AND displayName must be unique!
875 void WebUsers::createNewAccount(const std::string& username, const std::string& displayName, const std::string& email)
876 {
877  __COUT__ << "Creating account: " << username << __E__;
878  // check if username already exists
879  uint64_t i;
880  if((i = searchUsersDatabaseForUsername(username)) != NOT_FOUND_IN_DATABASE || username == WebUsers::DEFAULT_ITERATOR_USERNAME ||
881  username == WebUsers::DEFAULT_STATECHANGER_USERNAME) // prevent reserved usernames
882  // from being created!
883  {
884  __SS__ << "Username '" << username << "' already exists! Please choose a unique username." << __E__;
885  __SS_THROW__;
886  }
887 
888  //enforce unique Display Name
889  if((i = searchUsersDatabaseForDisplayName(displayName)) != NOT_FOUND_IN_DATABASE)
890  // from being created!
891  {
892  __SS__ << "Display Name '" << displayName << "' already exists! Please choose a unique display name." << __E__;
893  __SS_THROW__;
894  }
895 
896  // create Users database entry
897  Users_.push_back(User());
898 
899  Users_.back().username_ = username;
900  Users_.back().displayName_ = displayName;
901  Users_.back().email_ = email;
902 
903  // first user is admin always!
904  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> initPermissions = {
905  {WebUsers::DEFAULT_USER_GROUP, (Users_.size() ? WebUsers::PERMISSION_LEVEL_NOVICE : WebUsers::PERMISSION_LEVEL_ADMIN)}};
906 
907  Users_.back().permissions_ = initPermissions;
908  Users_.back().userId_ = usersNextUserId_++;
909  if(usersNextUserId_ >= ACCOUNT_ERROR_THRESHOLD) // error wrap around case
910  {
911  __SS__ << "usersNextUserId_ wrap around!! Too many users??? Notify Admins." << __E__;
912  __SS_THROW__;
913  usersNextUserId_ = 1; // for safety to avoid weird issues at -1 and 0 (if used
914  // for error indication)
915  }
916 
917  Users_.back().accountCreationTime_ = time(0);
918 
919  if(!saveDatabaseToFile(DB_USERS))
920  {
921  __SS__ << "Failed to save User DB!" << __E__;
922  __SS_THROW__;
923  }
924 } // end createNewAccount()
925 
926 //==============================================================================
927 // deleteAccount
928 // private function, deletes user account
929 // inputs: username and name to display
930 // if username and display name match account found, then account is deleted and true
931 // returned else false
932 bool WebUsers::deleteAccount(const std::string& username, const std::string& displayName)
933 {
934  uint64_t i = searchUsersDatabaseForUsername(username);
935  if(i == NOT_FOUND_IN_DATABASE)
936  return false;
937  if(Users_[i].displayName_ != displayName)
938  return false; // display name does not match
939 
940  // delete entry from user database vector
941 
942  Users_.erase(Users_.begin() + i);
943 
944  // save database
945  return saveDatabaseToFile(DB_USERS);
946 } // end deleteAccount()
947 
948 //==============================================================================
949 unsigned int WebUsers::hexByteStrToInt(const char* h)
950 {
951  unsigned int rv;
952  char hs[3] = {h[0], h[1], '\0'};
953  sscanf(hs, "%X", &rv);
954  return rv;
955 } // end hexByteStrToInt()
956 
957 //==============================================================================
958 void WebUsers::intToHexStr(unsigned char i, char* h) { sprintf(h, "%2.2X", i); }
959 
960 //==============================================================================
961 // WebUsers::attemptActiveSession ---
962 // Attempts login.
963 //
964 // If new login, then new account code must match account creation time and account is
965 // made with pw
966 //
967 // if old login, password is checked
968 // returns User Id, cookieCode in newAccountCode, and displayName in jumbledUser on
969 // success else returns -1 and cookieCode "0"
970 uint64_t WebUsers::attemptActiveSession(
971  const std::string& uuid, std::string& jumbledUser, const std::string& jumbledPw, std::string& newAccountCode, const std::string& ip)
972 {
973  //__COUTV__(ip);
974  if(!checkIpAccess(ip))
975  {
976  __COUT_ERR__ << "rejected ip: " << ip << __E__;
977  return ACCOUNT_BLACKLISTED;
978  }
979 
980  cleanupExpiredEntries(); // remove expired active and login sessions
981 
982  if(!CareAboutCookieCodes_) // NO SECURITY
983  {
984  uint64_t uid = getAdminUserID();
985  jumbledUser = getUsersDisplayName(uid);
986  newAccountCode = genCookieCode(); // return "dummy" cookie code by reference
987  return uid;
988  }
989 
990  uint64_t i;
991 
992  // search login sessions for uuid
993  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
994  {
995  __COUT_ERR__ << "uuid: " << uuid << " is not found" << __E__;
996  newAccountCode = "1"; // to indicate uuid was not found
997 
998  incrementIpBlacklistCount(ip); // increment ip blacklist counter
999 
1000  return NOT_FOUND_IN_DATABASE;
1001  }
1002  ++LoginSessions_[i].loginAttempts_;
1003 
1004  std::string user = dejumble(jumbledUser, LoginSessions_[i].id_);
1005  __COUTV__(user);
1006  std::string pw = dejumble(jumbledPw, LoginSessions_[i].id_);
1007 
1008  // search users for username
1009  if((i = searchUsersDatabaseForUsername(user)) == NOT_FOUND_IN_DATABASE)
1010  {
1011  __COUT_ERR__ << "user: " << user << " is not found" << __E__;
1012 
1013  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1014 
1015  return NOT_FOUND_IN_DATABASE;
1016  }
1017  else
1018  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1019 
1020  Users_[i].lastLoginAttempt_ = time(0);
1021 
1022  if(isInactiveForGroup(Users_[i].permissions_))
1023  {
1024  __MCOUT_ERR__("User '" << user << "' account INACTIVE (could be due to failed logins)" << __E__);
1025  return ACCOUNT_INACTIVE;
1026  }
1027 
1028  if(Users_[i].salt_ == "") // first login
1029  {
1030  __MCOUT__("First login attempt for user: " << user << __E__);
1031 
1032  if(newAccountCode != Users_[i].getNewAccountCode())
1033  {
1034  __COUT__ << "New account code did not match: " << Users_[i].getNewAccountCode() << " != " << newAccountCode << __E__;
1035  saveDatabaseToFile(DB_USERS); // users db modified, so save
1036  return NOT_FOUND_IN_DATABASE;
1037  }
1038 
1039  // initial user account setup
1040 
1041  // add until no collision (should 'never' be a collision)
1042  while(!addToHashesDatabase(sha512(user, pw, Users_[i].salt_))) // sha256 modifies UsersSaltVector[i]
1043  {
1044  // this should never happen, it would mean the user+pw+saltcontext was the
1045  // same
1046  // but if it were to happen, try again...
1047  Users_[i].salt_ = "";
1048  }
1049 
1050  __COUT__ << "\tHash added: " << Hashes_.back().hash_ << __E__;
1051  }
1052  else
1053  {
1054  std::string salt = Users_[i].salt_; // don't want to modify saved salt
1055  //__COUT__ << salt.size() << " " << salt << " " << i << __E__;
1056  if(searchHashesDatabaseForHash(sha512(user, pw, salt)) == NOT_FOUND_IN_DATABASE)
1057  {
1058  __COUT__ << "Failed login for " << user << " with permissions " << StringMacros::mapToString(Users_[i].permissions_) << __E__;
1059 
1060  // do not allow wrap around
1061  if(++Users_[i].loginFailureCount_ != (unsigned char)-1)
1062  ++Users_[i].loginFailureCount_;
1063 
1064  if(Users_[i].loginFailureCount_ >= USERS_MAX_LOGIN_FAILURES)
1065  Users_[i].permissions_[WebUsers::DEFAULT_USER_GROUP] = WebUsers::PERMISSION_LEVEL_INACTIVE; // Lock account
1066 
1067  __COUT_INFO__ << "User/pw for user '" << user << "' was not correct (Failed Attempt #" << (int)Users_[i].loginFailureCount_ << " of "
1068  << (int)USERS_MAX_LOGIN_FAILURES << " allowed)." << __E__;
1069 
1070  __COUTV__(isInactiveForGroup(Users_[i].permissions_));
1071  if(isInactiveForGroup(Users_[i].permissions_))
1072  __MCOUT_INFO__("Account '" << user
1073  << "' has been marked inactive due to too many failed "
1074  "login attempts (Failed Attempt #"
1075  << (int)Users_[i].loginFailureCount_ << ")! Note only admins can reactivate accounts." << __E__);
1076 
1077  saveDatabaseToFile(DB_USERS); // users db modified, so save
1078  return NOT_FOUND_IN_DATABASE;
1079  }
1080  }
1081 
1082  __MCOUT_INFO__("Login successful for: " << user << __E__);
1083 
1084  Users_[i].loginFailureCount_ = 0;
1085 
1086  // record to login history for user (h==0) and on global server level (h==1)
1087  for(int h = 0; h < 2; ++h)
1088  {
1089  std::string fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_LOGIN_HISTORY_PATH + (h ? USERS_GLOBAL_HISTORY_FILE : Users_[i].username_) + "." +
1090  (std::string)USERS_LOGIN_HISTORY_FILETYPE;
1091 
1092  HttpXmlDocument histXml;
1093 
1094  if(histXml.loadXmlDocument(fn)) // not found
1095  {
1096  while(histXml.getChildrenCount() + 1 > (h ? USERS_GLOBAL_HISTORY_SIZE : USERS_LOGIN_HISTORY_SIZE))
1097  histXml.removeDataElement();
1098  }
1099  else
1100  __COUT__ << "No previous login history found." << __E__;
1101 
1102  // add new entry to history
1103  char entryStr[500];
1104  if(h)
1105  sprintf(entryStr,
1106  "Time=%lu Username=%s Permissions=%s UID=%lu",
1107  time(0),
1108  Users_[i].username_.c_str(),
1109  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1110  Users_[i].userId_);
1111  else
1112  sprintf(entryStr,
1113  "Time=%lu displayName=%s Permissions=%s UID=%lu",
1114  time(0),
1115  Users_[i].displayName_.c_str(),
1116  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1117  Users_[i].userId_);
1118  histXml.addTextElementToData(PREF_XML_LOGIN_HISTORY_FIELD, entryStr);
1119 
1120  // save file
1121  histXml.saveXmlDocument(fn);
1122  }
1123 
1124  // SUCCESS!!
1125  saveDatabaseToFile(DB_USERS); // users db modified, so save
1126  jumbledUser = Users_[i].displayName_; // pass by reference displayName
1127  newAccountCode = createNewActiveSession(Users_[i].userId_,
1128  ip); // return cookie code by reference
1129 
1130  if(ActiveSessions_.size() == 1) //if only one user, then attempt to take lock for user friendliness
1131  {
1132  __COUT__ << "Attempting to auto-lock for first login user '" <<
1133  Users_[i].username_ << "'... " << __E__;
1134  setUserWithLock(Users_[i].userId_, true /*lock*/, Users_[i].username_);
1135  }
1136 
1137  return Users_[i].userId_; // return user Id
1138 } // end attemptActiveSession()
1139 
1140 //==============================================================================
1141 // WebUsers::attemptActiveSessionWithCert ---
1142 // Attempts login using certificate.
1143 //
1144 // returns User Id, cookieCode, and displayName in jumbledEmail on success
1145 // else returns -1 and cookieCode "0"
1146 uint64_t WebUsers::attemptActiveSessionWithCert(const std::string& uuid, std::string& email, std::string& cookieCode, std::string& user, const std::string& ip)
1147 {
1148  if(!checkIpAccess(ip))
1149  {
1150  __COUT_ERR__ << "rejected ip: " << ip << __E__;
1151  return NOT_FOUND_IN_DATABASE;
1152  }
1153 
1154  cleanupExpiredEntries(); // remove expired active and login sessions
1155 
1156  if(!CareAboutCookieCodes_) // NO SECURITY
1157  {
1158  uint64_t uid = getAdminUserID();
1159  email = getUsersDisplayName(uid);
1160  cookieCode = genCookieCode(); // return "dummy" cookie code by reference
1161  return uid;
1162  }
1163 
1164  if(email == "")
1165  {
1166  __COUT__ << "Rejecting cert logon with blank fingerprint" << __E__;
1167 
1168  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1169 
1170  return NOT_FOUND_IN_DATABASE;
1171  }
1172 
1173  uint64_t i;
1174 
1175  // search login sessions for uuid
1176  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
1177  {
1178  __COUT__ << "uuid: " << uuid << " is not found" << __E__;
1179  cookieCode = "1"; // to indicate uuid was not found
1180 
1181  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1182 
1183  return NOT_FOUND_IN_DATABASE;
1184  }
1185  ++LoginSessions_[i].loginAttempts_;
1186 
1187  email = getUserEmailFromFingerprint(email);
1188  __COUT__ << "DejumbledEmail = " << email << __E__;
1189  if(email == "")
1190  {
1191  __COUT__ << "Rejecting logon with unknown fingerprint" << __E__;
1192 
1193  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1194 
1195  return NOT_FOUND_IN_DATABASE;
1196  }
1197 
1198  // search users for username
1199  if((i = searchUsersDatabaseForUserEmail(email)) == NOT_FOUND_IN_DATABASE)
1200  {
1201  __COUT__ << "email: " << email << " is not found" << __E__;
1202 
1203  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1204 
1205  return NOT_FOUND_IN_DATABASE;
1206  }
1207  else
1208  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1209 
1210  user = getUsersUsername(i);
1211 
1212  Users_[i].lastLoginAttempt_ = time(0);
1213  if(isInactiveForGroup(Users_[i].permissions_))
1214  {
1215  __MCOUT__("User '" << user << "' account INACTIVE (could be due to failed logins)." << __E__);
1216  return NOT_FOUND_IN_DATABASE;
1217  }
1218 
1219  if(Users_[i].salt_ == "") // Can't be first login
1220  {
1221  return NOT_FOUND_IN_DATABASE;
1222  }
1223 
1224  __MCOUT__("Login successful for: " << user << __E__);
1225 
1226  Users_[i].loginFailureCount_ = 0;
1227 
1228  // record to login history for user (h==0) and on global server level (h==1)
1229  for(int h = 0; h < 2; ++h)
1230  {
1231  std::string fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_LOGIN_HISTORY_PATH + (h ? USERS_GLOBAL_HISTORY_FILE : Users_[i].username_) + "." +
1232  (std::string)USERS_LOGIN_HISTORY_FILETYPE;
1233 
1234  HttpXmlDocument histXml;
1235 
1236  if(histXml.loadXmlDocument(fn)) // not found
1237  {
1238  while(histXml.getChildrenCount() + 1 > (h ? USERS_GLOBAL_HISTORY_SIZE : USERS_LOGIN_HISTORY_SIZE))
1239  histXml.removeDataElement();
1240  }
1241  else
1242  __COUT__ << "No previous login history found." << __E__;
1243 
1244  // add new entry to history
1245  char entryStr[500];
1246  if(h)
1247  sprintf(entryStr,
1248  "Time=%lu Username=%s Permissions=%s UID=%lu",
1249  time(0),
1250  Users_[i].username_.c_str(),
1251  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1252  Users_[i].userId_);
1253  else
1254  sprintf(entryStr,
1255  "Time=%lu displayName=%s Permissions=%s UID=%lu",
1256  time(0),
1257  Users_[i].displayName_.c_str(),
1258  StringMacros::mapToString(Users_[i].permissions_).c_str(),
1259  Users_[i].userId_);
1260  histXml.addTextElementToData(PREF_XML_LOGIN_HISTORY_FIELD, entryStr);
1261 
1262  // save file
1263  histXml.saveXmlDocument(fn);
1264  }
1265 
1266  // SUCCESS!!
1267  saveDatabaseToFile(DB_USERS); // users db modified, so save
1268  email = Users_[i].displayName_; // pass by reference displayName
1269  cookieCode = createNewActiveSession(Users_[i].userId_,
1270  ip); // return cookie code by reference
1271  return Users_[i].userId_; // return user Id
1272 } // end attemptActiveSessionWithCert()
1273 
1274 //==============================================================================
1275 // WebUsers::searchActiveSessionDatabaseForUID ---
1276 // returns index if found, else -1
1277 uint64_t WebUsers::searchActiveSessionDatabaseForCookie(const std::string& cookieCode) const
1278 {
1279  uint64_t i = 0;
1280  for(; i < ActiveSessions_.size(); ++i)
1281  if(ActiveSessions_[i].cookieCode_ == cookieCode)
1282  break;
1283  return (i == ActiveSessions_.size()) ? NOT_FOUND_IN_DATABASE : i;
1284 }
1285 
1286 //==============================================================================
1287 // WebUsers::isUsernameActive ---
1288 // returns true if found, else false
1289 bool WebUsers::isUsernameActive(const std::string& username) const
1290 {
1291  uint64_t u;
1292  if((u = searchUsersDatabaseForUsername(username)) == NOT_FOUND_IN_DATABASE)
1293  return false;
1294  return isUserIdActive(Users_[u].userId_);
1295 }
1296 
1297 //==============================================================================
1298 // WebUsers::isUserIdActive ---
1299 // returns true if found, else false
1300 bool WebUsers::isUserIdActive(uint64_t uid) const
1301 {
1302  uint64_t i = 0;
1303  for(; i < ActiveSessions_.size(); ++i)
1304  if(ActiveSessions_[i].userId_ == uid)
1305  return true;
1306  return false;
1307 } // end isUserIdActive()
1308 
1309 //==============================================================================
1310 // WebUsers::searchUsersDatabaseForUsername ---
1311 // returns index if found, else -1
1312 uint64_t WebUsers::searchUsersDatabaseForUsername(const std::string& username) const
1313 {
1314  uint64_t i = 0;
1315  for(; i < Users_.size(); ++i)
1316  if(Users_[i].username_ == username)
1317  break;
1318  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1319 } // end searchUsersDatabaseForUsername()
1320 
1321 //==============================================================================
1322 // WebUsers::searchUsersDatabaseForDisplayName ---
1323 // returns index if found, else -1
1324 uint64_t WebUsers::searchUsersDatabaseForDisplayName(const std::string& displayName) const
1325 {
1326  uint64_t i = 0;
1327  for(; i < Users_.size(); ++i)
1328  if(Users_[i].displayName_ == displayName)
1329  break;
1330  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1331 } // end searchUsersDatabaseForUsername()
1332 
1333 //==============================================================================
1334 // WebUsers::searchUsersDatabaseForUserEmail ---
1335 // returns index if found, else -1
1336 uint64_t WebUsers::searchUsersDatabaseForUserEmail(const std::string& useremail) const
1337 {
1338  uint64_t i = 0;
1339  for(; i < Users_.size(); ++i)
1340  if(Users_[i].email_ == useremail)
1341  break;
1342  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1343 } // end searchUsersDatabaseForUserEmail()
1344 
1345 //==============================================================================
1346 // WebUsers::searchUsersDatabaseForUserId ---
1347 // returns index if found, else -1
1348 uint64_t WebUsers::searchUsersDatabaseForUserId(uint64_t uid) const
1349 {
1350  uint64_t i = 0;
1351  for(; i < Users_.size(); ++i)
1352  if(Users_[i].userId_ == uid)
1353  break;
1354  return (i == Users_.size()) ? NOT_FOUND_IN_DATABASE : i;
1355 } // end searchUsersDatabaseForUserId();
1356 
1357 //==============================================================================
1358 // WebUsers::searchLoginSessionDatabaseForUUID ---
1359 // returns index if found, else -1
1360 uint64_t WebUsers::searchLoginSessionDatabaseForUUID(const std::string& uuid) const
1361 {
1362  uint64_t i = 0;
1363  for(; i < LoginSessions_.size(); ++i)
1364  if(LoginSessions_[i].uuid_ == uuid)
1365  break;
1366  return (i == LoginSessions_.size()) ? NOT_FOUND_IN_DATABASE : i;
1367 } // end searchLoginSessionDatabaseForUUID()
1368 
1369 //==============================================================================
1370 // WebUsers::searchHashesDatabaseForHash ---
1371 // returns index if found, else -1
1372 uint64_t WebUsers::searchHashesDatabaseForHash(const std::string& hash)
1373 {
1374  uint64_t i = 0;
1375  //__COUT__ << i << " " << Hashes_.size() << " " << hash << __E__;
1376  for(; i < Hashes_.size(); ++i)
1377  if(Hashes_[i].hash_ == hash)
1378  break;
1379  // else
1380  // __COUT__ << HashesVector[i] << " ?????? " << __E__;
1381  //__COUT__ << i << __E__;
1382  if(i < Hashes_.size()) // if found, means login successful, so update access time
1383  Hashes_[i].accessTime_ = ((time(0) + (rand() % 2 ? 1 : -1) * (rand() % 30 * 24 * 60 * 60)) & 0x0FFFFFFFFFE000000);
1384  // else
1385  // __COUT__ << "No matching hash..." << __E__;
1386 
1387  //__COUT__ << i << __E__;
1388  return (i == Hashes_.size()) ? NOT_FOUND_IN_DATABASE : i;
1389 } // end searchHashesDatabaseForHash()
1390 
1391 //==============================================================================
1392 // WebUsers::addToHashesDatabase ---
1393 // returns false if hash already exists
1394 // else true for success
1395 bool WebUsers::addToHashesDatabase(const std::string& hash)
1396 {
1397  if(searchHashesDatabaseForHash(hash) != NOT_FOUND_IN_DATABASE)
1398  {
1399  __COUT__ << "Hash collision: " << hash << __E__;
1400  return false;
1401  }
1402  Hashes_.push_back(Hash());
1403  Hashes_.back().hash_ = hash;
1404  Hashes_.back().accessTime_ = ((time(0) + (rand() % 2 ? 1 : -1) * (rand() % 30 * 24 * 60 * 60)) & 0x0FFFFFFFFFE000000);
1405  // in seconds, blur by month and mask out changes on year time frame: 0xFFFFFFFF
1406  // FE000000
1407  return saveDatabaseToFile(DB_HASHES);
1408 } // end addToHashesDatabase()
1409 
1410 //==============================================================================
1411 // WebUsers::genCookieCode ---
1412 std::string WebUsers::genCookieCode()
1413 {
1414  char hexStr[3];
1415  std::string cc = "";
1416  for(uint32_t i = 0; i < COOKIE_CODE_LENGTH / 2; ++i)
1417  {
1418  intToHexStr(rand(), hexStr);
1419  cc.append(hexStr);
1420  }
1421  return cc;
1422 } // end genCookieCode()
1423 
1424 //==============================================================================
1425 // WebUsers::createNewActiveSession ---
1426 // if asIndex is not specified (0), new session receives max(ActiveSessionIndex) for user
1427 //+1.. always skipping 0. In this ActiveSessionIndex should link a thread of cookieCodes
1428 std::string WebUsers::createNewActiveSession(uint64_t uid, const std::string& ip, uint64_t asIndex)
1429 {
1430  //__COUTV__(ip);
1431  ActiveSessions_.push_back(ActiveSession());
1432  ActiveSessions_.back().cookieCode_ = genCookieCode();
1433  ActiveSessions_.back().ip_ = ip;
1434  ActiveSessions_.back().userId_ = uid;
1435  ActiveSessions_.back().startTime_ = time(0);
1436 
1437  if(asIndex) // this is a refresh of current active session
1438  ActiveSessions_.back().sessionIndex_ = asIndex;
1439  else
1440  {
1441  // find max(ActiveSessionIndex)
1442  uint64_t max = 0;
1443  for(uint64_t j = 0; j < ActiveSessions_.size(); ++j)
1444  if(ActiveSessions_[j].userId_ == uid && max < ActiveSessions_[j].sessionIndex_) // new max
1445  max = ActiveSessions_[j].sessionIndex_;
1446 
1447  ActiveSessions_.back().sessionIndex_ = (max ? max + 1 : 1); // 0 is illegal
1448  }
1449 
1450  return ActiveSessions_.back().cookieCode_;
1451 } // end createNewActiveSession()
1452 
1453 //==============================================================================
1454 // WebUsers::refreshCookieCode ---
1455 // Basic idea is to return valid cookieCode to user for future commands
1456 // There are two issues that arise due to "same user - multiple location":
1457 // 1. Multiple Tabs Scenario (same browser cookie)
1458 // 2. Multiple Browser Scenario (separate login chain)
1459 // We want to allow both modes of operation.
1460 //
1461 // Solution to 1. : long expiration and overlap times
1462 // return most recent cookie for ActiveSessionIndex (should be deepest in vector always)
1463 // - If half of expiration time is up, a new cookie is generated as most recent
1464 // but previous is maintained and start time is changed to accommodate overlap time.
1465 // - Overlap time should be enough to allow other tabs to take an action and
1466 // receive the new cookie code.
1467 //
1468 // Solution to 2. : ActiveSessionIndex
1469 // return most recent cookie for ActiveSessionIndex (should be deepest in vector always)
1470 // - Independent browsers will have independent cookie chains for same user
1471 // based on ActiveSessionIndex.
1472 // - Can use ActiveSessionIndex to detect old logins and log them out.
1473 //
1474 // enableRefresh added for automatic actions that take place, that should still get
1475 // the most recent code, but should not generate new codes (set enableRefresh =
1476 // false).
1477 std::string WebUsers::refreshCookieCode(unsigned int i, bool enableRefresh)
1478 {
1479  // find most recent cookie for ActiveSessionIndex (should be deepest in vector always)
1480  for(uint64_t j = ActiveSessions_.size() - 1; j != (uint64_t)-1; --j) // reverse iterate vector
1481  if(ActiveSessions_[j].userId_ == ActiveSessions_[i].userId_ &&
1482  ActiveSessions_[j].sessionIndex_ == ActiveSessions_[i].sessionIndex_) // if uid and asIndex match, found match
1483  {
1484  // found!
1485 
1486  // If half of expiration time is up, a new cookie is generated as most recent
1487  if(enableRefresh && (time(0) - ActiveSessions_[j].startTime_ > ACTIVE_SESSION_EXPIRATION_TIME / 2))
1488  {
1489  // but previous is maintained and start time is changed to accommodate
1490  // overlap time.
1491  ActiveSessions_[j].startTime_ = time(0) - ACTIVE_SESSION_EXPIRATION_TIME + ACTIVE_SESSION_COOKIE_OVERLAP_TIME; // give time window for stale
1492  // cookie commands before
1493  // expiring
1494 
1495  // create new active cookieCode with same ActiveSessionIndex, will now be
1496  // found as most recent
1497  return createNewActiveSession(ActiveSessions_[i].userId_, ActiveSessions_[i].ip_, ActiveSessions_[i].sessionIndex_);
1498  }
1499 
1500  return ActiveSessions_[j].cookieCode_; // cookieCode is unchanged
1501  }
1502 
1503  return "0"; // failure, should be impossible since i is already validated
1504 } // end refreshCookieCode()
1505 
1506 //==============================================================================
1507 // WebUsers::IsCookieActive ---
1508 // returns User Id on success, returns by reference refreshed cookieCode and displayName
1509 // if cookieCode/user combo is still active displayName is returned in username
1510 // std::string else returns -1
1511 uint64_t WebUsers::isCookieCodeActiveForLogin(const std::string& uuid, std::string& cookieCode, std::string& username)
1512 {
1513  if(!CareAboutCookieCodes_)
1514  return getAdminUserID(); // always successful
1515 
1516  // else
1517  // __COUT__ << "I care about
1518  // cookies?!?!?!*************************************************" << __E__;
1519 
1520  if(!ActiveSessions_.size())
1521  return NOT_FOUND_IN_DATABASE; // no active sessions, so do nothing
1522 
1523  uint64_t i, j; // used to iterate and search
1524 
1525  // find uuid in login session database else return "0"
1526  if((i = searchLoginSessionDatabaseForUUID(uuid)) == NOT_FOUND_IN_DATABASE)
1527  {
1528  __COUT__ << "uuid not found: " << uuid << __E__;
1529  return NOT_FOUND_IN_DATABASE;
1530  }
1531 
1532  username = dejumble(username, LoginSessions_[i].id_); // dejumble user for cookie check
1533 
1534  // search active users for cookie code
1535  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
1536  {
1537  __COUT__ << "Cookie code not found" << __E__;
1538  return NOT_FOUND_IN_DATABASE;
1539  }
1540 
1541  // search users for user id
1542  if((j = searchUsersDatabaseForUserId(ActiveSessions_[i].userId_)) == NOT_FOUND_IN_DATABASE)
1543  {
1544  __COUT__ << "User ID not found" << __E__;
1545  return NOT_FOUND_IN_DATABASE;
1546  }
1547 
1548  // match username, with one found
1549  if(Users_[j].username_ != username)
1550  {
1551  __COUT__ << "cookieCode: " << cookieCode << " was.." << __E__;
1552  __COUT__ << "username: " << username << " is not found" << __E__;
1553  return NOT_FOUND_IN_DATABASE;
1554  }
1555 
1556  username = Users_[j].displayName_; // return display name by reference
1557  cookieCode = refreshCookieCode(i); // refresh cookie by reference
1558  return Users_[j].userId_; // return user ID
1559 }
1560 
1561 //==============================================================================
1562 // WebUsers::getActiveSessionCountForUser ---
1563 // Returns count of unique ActiveSessionIndex entries for user's uid
1564 uint64_t WebUsers::getActiveSessionCountForUser(uint64_t uid)
1565 {
1566  bool unique;
1567  std::vector<uint64_t> uniqueAsi; // maintain unique as indices for reference
1568 
1569  uint64_t i, j;
1570  for(i = 0; i < ActiveSessions_.size(); ++i)
1571  if(ActiveSessions_[i].userId_ == uid) // found active session for user
1572  {
1573  // check if ActiveSessionIndex is unique
1574  unique = true;
1575 
1576  for(j = 0; j < uniqueAsi.size(); ++j)
1577  if(uniqueAsi[j] == ActiveSessions_[i].sessionIndex_)
1578  {
1579  unique = false;
1580  break;
1581  }
1582 
1583  if(unique) // unique! so count and save
1584  uniqueAsi.push_back(ActiveSessions_[i].sessionIndex_);
1585  }
1586 
1587  __COUT__ << "Found " << uniqueAsi.size() << " active sessions for uid " << uid << __E__;
1588 
1589  return uniqueAsi.size();
1590 } // end getActiveSessionCountForUser()
1591 
1592 //==============================================================================
1593 // WebUsers::checkIpAccess ---
1594 // checks user defined accept,
1595 // then checks reject IP file
1596 // then checks blacklist file
1597 // return true if ip is accepted, and false if rejected
1598 bool WebUsers::checkIpAccess(const std::string& ip)
1599 {
1600  if(ip == "0")
1601  return true; // always accept dummy IP
1602 
1603  FILE* fp = fopen((IP_ACCEPT_FILE).c_str(), "r");
1604  char line[300];
1605  size_t len;
1606 
1607  if(fp)
1608  {
1609  while(fgets(line, 300, fp))
1610  {
1611  len = strlen(line);
1612  // remove new line
1613  if(len > 2 && line[len - 1] == '\n')
1614  line[len - 1] = '\0';
1615  if(StringMacros::wildCardMatch(ip, line))
1616  return true; // found in accept file, so accept
1617  }
1618 
1619  fclose(fp);
1620  }
1621 
1622  fp = fopen((IP_REJECT_FILE).c_str(), "r");
1623  if(fp)
1624  {
1625  while(fgets(line, 300, fp))
1626  {
1627  len = strlen(line);
1628  // remove new line
1629  if(len > 2 && line[len - 1] == '\n')
1630  line[len - 1] = '\0';
1631  if(StringMacros::wildCardMatch(ip, line))
1632  return false; // found in reject file, so reject
1633  }
1634 
1635  fclose(fp);
1636  }
1637 
1638  fp = fopen((IP_BLACKLIST_FILE).c_str(), "r");
1639  if(fp)
1640  {
1641  while(fgets(line, 300, fp))
1642  {
1643  len = strlen(line);
1644  // remove new line
1645  if(len > 2 && line[len - 1] == '\n')
1646  line[len - 1] = '\0';
1647  if(StringMacros::wildCardMatch(ip, line))
1648  return false; // found in blacklist file, so reject
1649  }
1650 
1651  fclose(fp);
1652  }
1653 
1654  // default to accept if nothing triggered above
1655  return true;
1656 } // end checkIpAccess()
1657 
1658 //==============================================================================
1659 // WebUsers::incrementIpBlacklistCount ---
1660 void WebUsers::incrementIpBlacklistCount(const std::string& ip)
1661 {
1662  // increment ip blacklist counter
1663  auto it = ipBlacklistCounts_.find(ip);
1664  if(it == ipBlacklistCounts_.end())
1665  {
1666  __COUT__ << "First error for ip '" << ip << "'" << __E__;
1667  ipBlacklistCounts_[ip] = 1;
1668  }
1669  else
1670  {
1671  ++(it->second);
1672 
1673  if(it->second >= IP_BLACKLIST_COUNT_THRESHOLD)
1674  {
1675  __MCOUT__("Adding IP '" << ip << "' to blacklist!" << __E__);
1676 
1677  // append to blacklisted IP to generated IP reject file
1678  FILE* fp = fopen((IP_BLACKLIST_FILE).c_str(), "a");
1679  if(!fp)
1680  {
1681  __SS__ << "IP black list file '" << IP_BLACKLIST_FILE << "' could not be opened." << __E__;
1682  __MCOUT_ERR__(ss.str());
1683  return;
1684  }
1685  fprintf(fp, "%s\n", ip.c_str());
1686  fclose(fp);
1687  }
1688  }
1689 } // end incrementIpBlacklistCount()
1690 
1691 //==============================================================================
1692 // WebUsers::getUsersDisplayName ---
1693 std::string WebUsers::getUsersDisplayName(uint64_t uid)
1694 {
1695  uint64_t i;
1696  if((i = searchUsersDatabaseForUserId(uid)) == NOT_FOUND_IN_DATABASE)
1697  return "";
1698  return Users_[i].displayName_;
1699 } // end getUsersDisplayName()
1700 
1701 //==============================================================================
1702 // WebUsers::getUsersUsername ---
1703 std::string WebUsers::getUsersUsername(uint64_t uid)
1704 {
1705  uint64_t i;
1706  if((i = searchUsersDatabaseForUserId(uid)) == NOT_FOUND_IN_DATABASE)
1707  return "";
1708  return Users_[i].username_;
1709 } // end getUsersUsername()
1710 
1711 //==============================================================================
1712 // WebUsers::cookieCodeLogout ---
1713 // Used to logout user based on cookieCode and ActiveSessionIndex
1714 // logoutOtherUserSessions true logs out all of user's other sessions by uid
1715 // Note: when true, user will remain logged in to current active session
1716 // logoutOtherUserSessions false logs out only this cookieCode/ActiveSessionIndex
1717 // Note: when false, user will remain logged in other locations based different
1718 // ActiveSessionIndex
1719 //
1720 // on failure, returns -1
1721 // on success returns number of active sessions that were removed
1722 uint64_t WebUsers::cookieCodeLogout(const std::string& cookieCode, bool logoutOtherUserSessions, uint64_t* userId, const std::string& ip)
1723 {
1724  uint64_t i;
1725 
1726  // search active users for cookie code
1727  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
1728  {
1729  __COUT__ << "Cookie code not found" << __E__;
1730 
1731  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1732 
1733  return NOT_FOUND_IN_DATABASE;
1734  }
1735  else
1736  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1737 
1738  // check ip
1739  if(ActiveSessions_[i].ip_ != ip)
1740  {
1741  __COUT__ << "IP does not match active session" << __E__;
1742  return NOT_FOUND_IN_DATABASE;
1743  }
1744 
1745  // found valid active session i
1746  // if logoutOtherUserSessions
1747  // remove active sessions that match ActiveSessionUserIdVector[i] and
1748  // ActiveSessionIndex[i] else remove active sessions that match
1749  // ActiveSessionUserIdVector[i] but not ActiveSessionIndex[i]
1750 
1751  uint64_t asi = ActiveSessions_[i].sessionIndex_;
1752  uint64_t uid = ActiveSessions_[i].userId_;
1753  if(userId)
1754  *userId = uid; // return uid if requested
1755  uint64_t logoutCount = 0;
1756 
1757  i = 0;
1758  while(i < ActiveSessions_.size())
1759  {
1760  if((logoutOtherUserSessions && ActiveSessions_[i].userId_ == uid && ActiveSessions_[i].sessionIndex_ != asi) ||
1761  (!logoutOtherUserSessions && ActiveSessions_[i].userId_ == uid && ActiveSessions_[i].sessionIndex_ == asi))
1762  {
1763  __COUT__ << "Logging out of active session " << ActiveSessions_[i].userId_ << "-" << ActiveSessions_[i].sessionIndex_ << __E__;
1764  ActiveSessions_.erase(ActiveSessions_.begin() + i);
1765  ++logoutCount;
1766  }
1767  else // only increment if no delete, for effectively erase rewind
1768  ++i;
1769  } //end cleanup active sessioins loop
1770 
1771  __COUT__ << "Found and removed active session count = " << logoutCount << __E__;
1772 
1773  return logoutCount;
1774 } // end cookieCodeLogout()
1775 
1776 //==============================================================================
1777 // WebUsers::getUserInfoForCookie ---
1778 bool WebUsers::getUserInfoForCookie(std::string& cookieCode, std::string* userName, std::string* displayName, uint64_t* activeSessionIndex)
1779 {
1780  if(userName)
1781  *userName = "";
1782  if(displayName)
1783  *displayName = "";
1784 
1785  if(!CareAboutCookieCodes_) // NO SECURITY, return admin
1786  {
1787  uint64_t uid = getAdminUserID();
1788  if(userName)
1789  *userName = getUsersUsername(uid);
1790  if(displayName)
1791  *displayName = getUsersDisplayName(uid);
1792  if(activeSessionIndex)
1793  *activeSessionIndex = -1;
1794  return true;
1795  }
1796 
1797  uint64_t i, j;
1798 
1799  // search active users for cookie code
1800  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
1801  {
1802  __COUT__ << "cookieCode NOT_FOUND_IN_DATABASE" << __E__;
1803  return false;
1804  }
1805 
1806  // get Users record
1807  if((j = searchUsersDatabaseForUserId(ActiveSessions_[i].userId_)) == NOT_FOUND_IN_DATABASE)
1808  {
1809  __COUT__ << "ActiveSession UserId NOT_FOUND_IN_DATABASE" << __E__;
1810  return false;
1811  }
1812 
1813  if(userName)
1814  *userName = Users_[j].username_;
1815  if(displayName)
1816  *displayName = Users_[j].displayName_;
1817  if(activeSessionIndex)
1818  *activeSessionIndex = ActiveSessions_[i].sessionIndex_;
1819  return true;
1820 } // end getUserInfoForCookie()
1821 
1822 //==============================================================================
1823 // WebUsers::isCookieCodeActiveForRequest ---
1824 // Used to verify cookie code for all general user requests
1825 // cookieCode/ip must be active to pass
1826 //
1827 // cookieCode is passed by reference. It is refreshed, if refresh=true on success and may
1828 // be modified.
1829 // on success, if userPermissions and/or uid are not null, the permissions and uid
1830 // are returned
1831 // on failure, cookieCode contains error message to return to client
1832 //
1833 // If do NOT care about cookie code, then returns uid 0 (admin)
1834 // and grants full permissions
1835 bool WebUsers::cookieCodeIsActiveForRequest(std::string& cookieCode,
1836  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>* userPermissions,
1837  uint64_t* uid,
1838  const std::string& ip,
1839  bool refresh,
1840  std::string* userWithLock,
1841  uint64_t* activeUserSessionIndex)
1842 {
1843  //__COUTV__(ip);
1844 
1845  // check ip black list and increment counter if cookie code not found
1846  if(!checkIpAccess(ip))
1847  {
1848  __COUT_ERR__ << "User IP rejected." << __E__;
1849  cookieCode = REQ_NO_LOGIN_RESPONSE;
1850  return false;
1851  }
1852 
1853  cleanupExpiredEntries(); // remove expired cookies
1854 
1855  uint64_t i, j;
1856 
1857  //__COUT__ << "I care about cookie codes: " << CareAboutCookieCodes_ << __E__;
1858  //__COUT__ << "refresh cookie " << refresh << __E__;
1859 
1860  if(!CareAboutCookieCodes_) // No Security, so grant admin
1861  {
1862  if(userPermissions)
1863  *userPermissions =
1864  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>({{WebUsers::DEFAULT_USER_GROUP, WebUsers::PERMISSION_LEVEL_ADMIN}});
1865  if(uid)
1866  *uid = getAdminUserID();
1867  if(userWithLock)
1868  *userWithLock = usersUsernameWithLock_;
1869  if(activeUserSessionIndex)
1870  *activeUserSessionIndex = -1;
1871 
1872  if(cookieCode.size() != COOKIE_CODE_LENGTH)
1873  cookieCode = genCookieCode(); // return "dummy" cookie code
1874 
1875  return true;
1876  }
1877  // else using security!
1878 
1879  // search active users for cookie code
1880  if((i = searchActiveSessionDatabaseForCookie(cookieCode)) == NOT_FOUND_IN_DATABASE)
1881  {
1882  __COUT_ERR__ << "Cookie code not found" << __E__;
1883  cookieCode = REQ_NO_LOGIN_RESPONSE;
1884 
1885  incrementIpBlacklistCount(ip); // increment ip blacklist counter
1886 
1887  return false;
1888  }
1889  else
1890  ipBlacklistCounts_[ip] = 0; // clear blacklist count
1891 
1892  // check ip
1893  if(ip != "0" && ActiveSessions_[i].ip_ != ip)
1894  {
1895  __COUTV__(ActiveSessions_[i].ip_);
1896  //__COUTV__(ip);
1897  __COUT_ERR__ << "IP does not match active session." << __E__;
1898  cookieCode = REQ_NO_LOGIN_RESPONSE;
1899  return false;
1900  }
1901 
1902  // get Users record
1903  if((j = searchUsersDatabaseForUserId(ActiveSessions_[i].userId_)) == NOT_FOUND_IN_DATABASE)
1904  {
1905  __COUT_ERR__ << "User ID not found" << __E__;
1906  cookieCode = REQ_NO_LOGIN_RESPONSE;
1907  return false;
1908  }
1909 
1910  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> tmpPerm = getPermissionsForUser(Users_[j].userId_);
1911 
1912  if(isInactiveForGroup(tmpPerm)) // Check for inactive for all requests!
1913  {
1914  cookieCode = REQ_NO_PERMISSION_RESPONSE;
1915  return false;
1916  }
1917 
1918  // success!
1919  if(userPermissions)
1920  *userPermissions = tmpPerm;
1921  if(uid)
1922  *uid = Users_[j].userId_;
1923  if(userWithLock)
1924  *userWithLock = usersUsernameWithLock_;
1925  if(activeUserSessionIndex)
1926  *activeUserSessionIndex = ActiveSessions_[i].sessionIndex_;
1927 
1928  cookieCode = refreshCookieCode(i, refresh); // refresh cookie by reference
1929 
1930  return true;
1931 } // end cookieCodeIsActiveForRequest()
1932 
1933 //==============================================================================
1934 // WebUsers::cleanupExpiredEntries ---
1935 // cleanup expired entries form Login Session and Active Session databases
1936 // check if usersUsernameWithLock_ is still active
1937 // return the vector of logged out user names if a parameter
1938 // if not a parameter, store logged out user names for next time called with
1939 // parameter
1940 void WebUsers::cleanupExpiredEntries(std::vector<std::string>* loggedOutUsernames)
1941 {
1942  uint64_t i; // used to iterate and search
1943  uint64_t tmpUid;
1944 
1945  if(loggedOutUsernames) // return logged out users this time and clear storage vector
1946  {
1947  for(i = 0; i < UsersLoggedOutUsernames_.size(); ++i)
1948  loggedOutUsernames->push_back(UsersLoggedOutUsernames_[i]);
1949  UsersLoggedOutUsernames_.clear();
1950  }
1951 
1952  // remove expired entries from Login Session
1953  for(i = 0; i < LoginSessions_.size(); ++i)
1954  if(LoginSessions_[i].startTime_ + LOGIN_SESSION_EXPIRATION_TIME < time(0) || // expired
1955  LoginSessions_[i].loginAttempts_ > LOGIN_SESSION_ATTEMPTS_MAX)
1956  {
1957  __COUT__ << "Found expired login sessions: " << i << " of " << LoginSessions_.size() << __E__;
1958  //" at time " << LoginSessionStartTimeVector[i] << " with attempts " <<
1959  //LoginSessionAttemptsVector[i] << __E__;
1960 
1961  LoginSessions_.erase(LoginSessions_.begin() + i);
1962  --i; // rewind loop
1963  }
1964 
1965  // declare structures for ascii time
1966  // struct tm * timeinfo;
1967  // time_t tmpt;
1968  // char tstr[200];
1969  // timeinfo = localtime ( &(tmpt=time(0)) );
1970  // sprintf(tstr,"\"%s\"",asctime (timeinfo)); tstr[strlen(tstr)-2] = '\"';
1971  //__COUT__ << "Current time is: " << time(0) << " " << tstr << __E__;
1972 
1973  // remove expired entries from Active Session
1974  for(i = 0; i < ActiveSessions_.size(); ++i)
1975  if(ActiveSessions_[i].startTime_ + ACTIVE_SESSION_EXPIRATION_TIME <= time(0)) // expired
1976  {
1977  // timeinfo = localtime (&(tmpt=ActiveSessionStartTimeVector[i]));
1978  // sprintf(tstr,"\"%s\"",asctime (timeinfo)); tstr[strlen(tstr)-2] = '\"';
1979  //__COUT__ << "Found expired user: " << ActiveSessionUserIdVector[i] <<
1980  // " start time " << tstr << " i: " << i << " size: " <<
1981  // ActiveSessionStartTimeVector.size()
1982  // << __E__;
1983 
1984 
1985  __COUT__ << "Found expired active sessions: " << i << " of " << ActiveSessions_.size() << __E__;
1986 
1987  tmpUid = ActiveSessions_[i].userId_;
1988  ActiveSessions_.erase(ActiveSessions_.begin() + i);
1989 
1990  if(!isUserIdActive(tmpUid)) // if uid no longer active, then user was
1991  // completely logged out
1992  {
1993  if(loggedOutUsernames) // return logged out users this time
1994  loggedOutUsernames->push_back(Users_[searchUsersDatabaseForUserId(tmpUid)].username_);
1995  else // store for next time requested as parameter
1996  UsersLoggedOutUsernames_.push_back(Users_[searchUsersDatabaseForUserId(tmpUid)].username_);
1997  }
1998 
1999  --i; // rewind loop
2000  }
2001  // else
2002  // {
2003  // timeinfo = localtime (&(tmpt=ActiveSessionStartTimeVector[i] +
2004  // ACTIVE_SESSION_EXPIRATION_TIME)); sprintf(tstr,"\"%s\"",asctime
2005  //(timeinfo)); tstr[strlen(tstr)-2] = '\"';
2006  //
2007  // //__COUT__ << "Found user: " << ActiveSessionUserIdVector[i] << "-" <<
2008  // ActiveSessionIndex[i] <<
2009  // // " expires " << tstr <<
2010  // // " sec left " << ActiveSessionStartTimeVector[i] +
2011  // ACTIVE_SESSION_EXPIRATION_TIME - time(0) << __E__;
2012  //
2013  // }
2014 
2015  //__COUT__ << "Found usersUsernameWithLock_: " << usersUsernameWithLock_ << __E__;
2016  if(CareAboutCookieCodes_ && !isUsernameActive(usersUsernameWithLock_)) // unlock if user no longer logged in
2017  usersUsernameWithLock_ = "";
2018 } // end cleanupExpiredEntries()
2019 
2020 //==============================================================================
2021 // createNewLoginSession
2022 // adds a new login session id to database
2023 // inputs: UUID
2024 // checks that UUID is unique
2025 // initializes database entry and returns sessionId std::string
2026 // return "" on failure
2027 std::string WebUsers::createNewLoginSession(const std::string& UUID, const std::string& ip)
2028 {
2029  __COUTV__(UUID);
2030  //__COUTV__(ip);
2031 
2032  uint64_t i = 0;
2033  for(; i < LoginSessions_.size(); ++i)
2034  if(LoginSessions_[i].uuid_ == UUID)
2035  break;
2036 
2037  if(i != LoginSessions_.size())
2038  {
2039  __COUT_ERR__ << "UUID: " << UUID << " is not unique" << __E__;
2040  return "";
2041  }
2042  // else UUID is unique
2043 
2044  LoginSessions_.push_back(LoginSession());
2045  LoginSessions_.back().uuid_ = UUID;
2046 
2047  // generate sessionId
2048  char hexStr[3];
2049  std::string sid = "";
2050  for(i = 0; i < SESSION_ID_LENGTH / 2; ++i)
2051  {
2052  intToHexStr(rand(), hexStr);
2053  sid.append(hexStr);
2054  }
2055  LoginSessions_.back().id_ = sid;
2056  LoginSessions_.back().ip_ = ip;
2057  LoginSessions_.back().startTime_ = time(0);
2058  LoginSessions_.back().loginAttempts_ = 0;
2059 
2060  return sid;
2061 } // end createNewLoginSession()
2062 
2063 //==============================================================================
2064 // WebUsers::sha512
2065 // performs SHA-512 encoding using openssl linux library crypto on context+user+password
2066 // if context is empty std::string "", context is generated and returned by reference
2067 // hashed result is returned
2068 std::string WebUsers::sha512(const std::string& user, const std::string& password, std::string& salt)
2069 {
2070  SHA512_CTX sha512_context;
2071  char hexStr[3];
2072 
2073  if(salt == "") // generate context
2074  {
2075  SHA512_Init(&sha512_context);
2076 
2077  for(unsigned int i = 0; i < 8; ++i)
2078  sha512_context.h[i] += rand();
2079 
2080  for(unsigned int i = 0; i < sizeof(SHA512_CTX); ++i)
2081  {
2082  intToHexStr((uint8_t)(((uint8_t*)(&sha512_context))[i]), hexStr);
2083 
2084  salt.append(hexStr);
2085  }
2086  //__COUT__ << salt << __E__;
2087  }
2088  else // use existing context
2089  {
2090  //__COUT__ << salt << __E__;
2091 
2092  for(unsigned int i = 0; i < sizeof(SHA512_CTX); ++i)
2093  ((uint8_t*)(&sha512_context))[i] = hexByteStrToInt(&(salt.c_str()[i * 2]));
2094  }
2095 
2096  std::string strToHash = salt + user + password;
2097 
2098  //__COUT__ << salt << __E__;
2099  unsigned char hash[SHA512_DIGEST_LENGTH];
2100  //__COUT__ << salt << __E__;
2101  char retHash[SHA512_DIGEST_LENGTH * 2 + 1];
2102  //__COUT__ << strToHash.length() << " " << strToHash << __E__;
2103 
2104  //__COUT__ << "If crashing occurs here, may be an illegal salt context." << __E__;
2105  SHA512_Update(&sha512_context, strToHash.c_str(), strToHash.length());
2106 
2107  SHA512_Final(hash, &sha512_context);
2108 
2109  //__COUT__ << salt << __E__;
2110  int i = 0;
2111  for(i = 0; i < SHA512_DIGEST_LENGTH; i++)
2112  sprintf(retHash + (i * 2), "%02x", hash[i]);
2113 
2114  //__COUT__ << salt << __E__;
2115  retHash[SHA512_DIGEST_LENGTH * 2] = '\0';
2116 
2117  //__COUT__ << salt << __E__;
2118 
2119  return retHash;
2120 } // end sha512()
2121 
2122 //==============================================================================
2123 // WebUsers::dejumble
2124 // the client sends username and pw jumbled for http transmission
2125 // this function dejumbles
2126 std::string WebUsers::dejumble(const std::string& u, const std::string& s)
2127 {
2128  if(s.length() != SESSION_ID_LENGTH)
2129  return ""; // session std::string must be even
2130 
2131  const int ss = s.length() / 2;
2132  int p = hexByteStrToInt(&(s.c_str()[0])) % ss;
2133  int n = hexByteStrToInt(&(s.c_str()[p * 2])) % ss;
2134  int len = (hexByteStrToInt(&(u.c_str()[p * 2])) - p - n + ss * 3) % ss;
2135 
2136  std::vector<bool> x(ss);
2137  for(int i = 0; i < ss; ++i)
2138  x[i] = 0;
2139  x[p] = 1;
2140 
2141  int c = hexByteStrToInt(&(u.c_str()[p * 2]));
2142 
2143  std::string user = "";
2144 
2145  for(int l = 0; l < len; ++l)
2146  {
2147  p = (p + hexByteStrToInt(&(s.c_str()[p * 2]))) % ss;
2148  while(x[p])
2149  p = (p + 1) % ss;
2150  x[p] = 1;
2151  n = hexByteStrToInt(&(s.c_str()[p * 2]));
2152  user.append(1, (hexByteStrToInt(&(u.c_str()[p * 2])) - c - n + ss * 4) % ss);
2153  c = hexByteStrToInt(&(u.c_str()[p * 2]));
2154  }
2155 
2156  return user;
2157 } // end dejumble()
2158 
2159 //==============================================================================
2160 // WebUsers::getPermissionForUser
2161 // return WebUsers::PERMISSION_LEVEL_INACTIVE if invalid index
2162 std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> WebUsers::getPermissionsForUser(uint64_t uid)
2163 {
2164  //__COUTV__(uid);
2165  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2166  //__COUTV__(userIndex); __COUTV__(UsersPermissionsVector.size());
2167  if(userIndex < Users_.size())
2168  return Users_[userIndex].permissions_;
2169 
2170  // else return all user inactive map
2171  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> retErrorMap;
2172  retErrorMap[WebUsers::DEFAULT_USER_GROUP] = WebUsers::PERMISSION_LEVEL_INACTIVE;
2173  return retErrorMap;
2174 } // end getPermissionsForUser()
2175 
2176 //==============================================================================
2177 // WebUsers::getPermissionLevelForGroup
2178 // return WebUsers::PERMISSION_LEVEL_INACTIVE if group not found in permission map
2179 WebUsers::permissionLevel_t WebUsers::getPermissionLevelForGroup(const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap,
2180  const std::string& groupName)
2181 {
2182  auto it = permissionMap.find(groupName);
2183  if(it == permissionMap.end())
2184  {
2185  __COUT__ << "Group name '" << groupName << "' not found - assuming inactive user in this group." << __E__;
2186  return WebUsers::PERMISSION_LEVEL_INACTIVE;
2187  }
2188  return it->second;
2189 } // end getPermissionLevelForGroup()
2190 
2191 //==============================================================================
2192 bool WebUsers::isInactiveForGroup(const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap, const std::string& groupName)
2193 {
2194  return getPermissionLevelForGroup(permissionMap, groupName) == WebUsers::PERMISSION_LEVEL_INACTIVE;
2195 }
2196 
2197 //==============================================================================
2198 bool WebUsers::isAdminForGroup(const std::map<std::string /*groupName*/, WebUsers::permissionLevel_t>& permissionMap, const std::string& groupName)
2199 {
2200  return getPermissionLevelForGroup(permissionMap, groupName) == WebUsers::PERMISSION_LEVEL_ADMIN;
2201 }
2202 
2203 //==============================================================================
2204 // WebUsers::getPermissionForUser
2205 // return 0 if invalid index
2206 std::string WebUsers::getTooltipFilename(const std::string& username, const std::string& srcFile, const std::string& srcFunc, const std::string& srcId)
2207 {
2208  std::string filename = (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH + "/";
2209 
2210  // make tooltip directory if not there
2211  // note: this is static so WebUsers constructor has not necessarily been called
2212  mkdir(((std::string)WEB_LOGIN_DB_PATH).c_str(), 0755);
2213  mkdir(((std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH).c_str(), 0755);
2214  mkdir(filename.c_str(), 0755);
2215 
2216  for(const char& c : username)
2217  if( // only keep alpha numeric
2218  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2219  filename += c;
2220  filename += "/";
2221 
2222  // make username tooltip directory if not there
2223  mkdir(filename.c_str(), 0755);
2224 
2225  for(const char& c : srcFile)
2226  if( // only keep alpha numeric
2227  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2228  filename += c;
2229  filename += "_";
2230  for(const char& c : srcFunc)
2231  if( // only keep alpha numeric
2232  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2233  filename += c;
2234  filename += "_";
2235  for(const char& c : srcId)
2236  if( // only keep alpha numeric
2237  (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
2238  filename += c;
2239  filename += ".tip";
2240  //__COUT__ << "filename " << filename << __E__;
2241  return filename;
2242 }
2243 
2244 std::string ots::WebUsers::getUserEmailFromFingerprint(const std::string& fingerprint)
2245 {
2246  __COUT__ << "Checking if user fingerprint " << fingerprint << " is in memory database" << __E__;
2247  if(certFingerprints_.count(fingerprint)) { return certFingerprints_[fingerprint]; }
2248 
2249  __COUT__ << "Going to read credential database " << WEB_LOGIN_CERTDATA_PATH << __E__;
2250  std::ifstream f(WEB_LOGIN_CERTDATA_PATH);
2251  bool open =false;
2252  while(f)
2253  {
2254  open = true;
2255  std::string email;
2256  std::string fp;
2257  f >> email >> fp;
2258  if(fp != "NOKEY" && fp != "") {
2259  __COUT__ << "Adding user " << email << " to list with fingerprint " << fp << __E__;
2260  certFingerprints_[fp] = email; }
2261  }
2262  if(open) {
2263  f.close();
2264  remove(WEB_LOGIN_CERTDATA_PATH.c_str());
2265  }
2266 
2267  __COUT__ << "Checking again if fingerprint is in memory database" << __E__;
2268  if(certFingerprints_.count(fingerprint)) { return certFingerprints_[fingerprint]; }
2269 
2270  __COUT__ << "Could not match fingerprint, returning null email" << __E__;
2271  return "";
2272 } // end getUserEmailFromFingerprint()
2273 
2274 //==============================================================================
2275 // WebUsers::tooltipSetNeverShowForUsername
2276 // temporarySilence has priority over the neverShow setting
2277 void WebUsers::tooltipSetNeverShowForUsername(const std::string& username,
2278  HttpXmlDocument* /*xmldoc*/,
2279  const std::string& srcFile,
2280  const std::string& srcFunc,
2281  const std::string& srcId,
2282  bool doNeverShow,
2283  bool temporarySilence)
2284 {
2285  __COUT__ << "Setting tooltip never show for user '" << username << "' to " << doNeverShow << " (temporarySilence=" << temporarySilence << ")" << __E__;
2286 
2287  std::string filename = getTooltipFilename(username, srcFile, srcFunc, srcId);
2288  FILE* fp = fopen(filename.c_str(), "w");
2289  if(fp)
2290  { // file exists, so do NOT show tooltip
2291  if(temporarySilence)
2292  fprintf(fp, "%ld", time(0) + 7 /*days*/ * 24 /*hours*/ * 60 * 60); // mute for a week
2293  else if(doNeverShow && username == WebUsers::DEFAULT_ADMIN_USERNAME)
2294  {
2295  // admin could be shared account, so max out at 30 days
2296  fprintf(fp, "%ld", time(0) + 30 /*days*/ * 24 /*hours*/ * 60 * 60);
2297 
2298  __COUT__ << "User '" << username
2299  << "' may be a shared account, so max silence duration for tooltips "
2300  "is 30 days. Silencing now."
2301  << __E__;
2302  }
2303  else
2304  fputc(doNeverShow ? '1' : '0', fp);
2305  fclose(fp);
2306  }
2307  else // default to show tool tip
2308  __COUT_ERR__ << "Big problem with tooltips! File not accessible: " << filename << __E__;
2309 } // end tooltipSetNeverShowForUsername()
2310 
2311 //==============================================================================
2312 // WebUsers::tooltipCheckForUsername
2313 // read file for tooltip
2314 // if not 1 then never show
2315 // if 0 then "always show"
2316 // if other then treat as temporary mute..
2317 // i.e. if time(0) > val show
2318 void WebUsers::tooltipCheckForUsername(
2319  const std::string& username, HttpXmlDocument* xmldoc, const std::string& srcFile, const std::string& srcFunc, const std::string& srcId)
2320 {
2321  if(srcId == "ALWAYS")
2322  {
2323  // ALWAYS shows tool tip
2324  xmldoc->addTextElementToData("ShowTooltip", "1");
2325  return;
2326  }
2327 
2328  // __COUT__ << "username " << username << __E__;
2329  // __COUT__ << "srcFile " << srcFile << __E__;
2330  // __COUT__ << "srcFunc " << srcFunc << __E__;
2331  // __COUT__ << "srcId " << srcId << __E__;
2332  //__COUT__ << "Checking tooltip for user: " << username << __E__;
2333 
2334  // if the silence file exists, silence all tooltips
2335  std::string silencefilename = getTooltipFilename(username, SILENCE_ALL_TOOLTIPS_FILENAME, "", "");
2336  //__COUTV__(silencefilename);
2337  FILE* silencefp = fopen(silencefilename.c_str(), "r");
2338  if(silencefp != NULL)
2339  {
2340  xmldoc->addTextElementToData("ShowTooltip", "0");
2341  tooltipSetNeverShowForUsername(username, xmldoc, srcFile, srcFunc, srcId, true, true);
2342  return;
2343  }
2344 
2345  std::string filename = getTooltipFilename(username, srcFile, srcFunc, srcId);
2346  FILE* fp = fopen(filename.c_str(), "r");
2347  if(fp)
2348  { // file exists, so do NOT show tooltip
2349  time_t val;
2350  char line[100];
2351  fgets(line, 100, fp);
2352  // int val = fgetc(fp);
2353  sscanf(line, "%ld", &val);
2354  fclose(fp);
2355 
2356  __COUT__ << "tooltip value read = " << val << " vs time(0)=" << time(0) << __E__;
2357 
2358  // if first line in file is a 1 then do not show
2359  // else show if current time is greater than value
2360  xmldoc->addTextElementToData("ShowTooltip", val == 1 ? "0" : (time(0) > val ? "1" : "0"));
2361  }
2362  else // default to show tool tip
2363  {
2364  xmldoc->addTextElementToData("ShowTooltip", "1");
2365  }
2366 
2367 } // end tooltipCheckForUsername();
2368 
2369 //==============================================================================
2370 // WebUsers::resetAllUserTooltips
2371 void WebUsers::resetAllUserTooltips(const std::string& userNeedle)
2372 {
2373  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH + "/" + userNeedle).c_str());
2374  __COUT__ << "Successfully reset Tooltips for user " << userNeedle << __E__;
2375 } // end of resetAllUserTooltips()
2376 
2377 //==============================================================================
2378 // WebUsers::silenceAllUserTooltips
2379 // creates a file
2380 void WebUsers::silenceAllUserTooltips(const std::string& username)
2381 {
2382  std::string silencefilename = getTooltipFilename(username, SILENCE_ALL_TOOLTIPS_FILENAME, "", ""); // srcFile, srcFunc, srcId);
2383  FILE* silencefp = fopen(silencefilename.c_str(), "w");
2384  if(silencefp != NULL)
2385  {
2386  fputs("mute tool tips", silencefp);
2387  fclose(silencefp);
2388  }
2389 
2390 } // end of silenceAllUserTooltips()
2391 
2392 //==============================================================================
2393 // WebUsers::insertGetSettingsResponse
2394 // add settings to xml document
2395 // all active users have permissions of at least 1 so have web preferences:
2396 // -background color
2397 // -dashboard color
2398 // -window color
2399 // -3 user defaults for window layouts(and current), can set current as one of
2400 // defaults
2401 // super users have account controls:
2402 // -list of user accounts to edit permissions, display name, or delete account
2403 // -add new account
2404 // ...and super users have system default window layout
2405 // -2 system defaults for window layouts
2406 //
2407 // layout settings explanation
2408 // 0 = no windows, never set, empty desktop
2409 // example 2 layouts set, 2 not,
2410 // [<win name>, <win subname>, <win url>, <x>, <y>, <w>, <h>]; [<win name>, <win
2411 // subname>, <win url>, <x>, <y>, <w>, <h>]...];0;0
2412 void WebUsers::insertSettingsForUser(uint64_t uid, HttpXmlDocument* xmldoc, bool includeAccounts)
2413 {
2414  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap = getPermissionsForUser(uid);
2415 
2416  //__COUTV__(StringMacros::mapToString(permissionMap));
2417  if(isInactiveForGroup(permissionMap))
2418  return; // not an active user
2419 
2420  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2421  __COUT__ << "Gettings settings for user: " << Users_[userIndex].username_ << __E__;
2422 
2423  std::string fn =
2424  (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH + Users_[userIndex].username_ + "." + (std::string)USERS_PREFERENCES_FILETYPE;
2425 
2426  HttpXmlDocument prefXml;
2427 
2428  __COUT__ << "Preferences file: " << fn << __E__;
2429 
2430  if(!prefXml.loadXmlDocument(fn))
2431  {
2432  __COUT__ << "Preferences are defaults." << __E__;
2433  // insert defaults, no pref document found
2434  xmldoc->addTextElementToData(PREF_XML_BGCOLOR_FIELD, PREF_XML_BGCOLOR_DEFAULT);
2435  xmldoc->addTextElementToData(PREF_XML_DBCOLOR_FIELD, PREF_XML_DBCOLOR_DEFAULT);
2436  xmldoc->addTextElementToData(PREF_XML_WINCOLOR_FIELD, PREF_XML_WINCOLOR_DEFAULT);
2437  xmldoc->addTextElementToData(PREF_XML_LAYOUT_FIELD, PREF_XML_LAYOUT_DEFAULT);
2438  }
2439  else
2440  {
2441  __COUT__ << "Saved Preferences found." << __E__;
2442  xmldoc->copyDataChildren(prefXml);
2443  }
2444 
2445  // add settings if super user
2446  if(includeAccounts && isAdminForGroup(permissionMap))
2447  {
2448  __COUT__ << "Admin on our hands" << __E__;
2449 
2450  xmldoc->addTextElementToData(PREF_XML_ACCOUNTS_FIELD, "");
2451 
2452  if(Users_.size() == 0)
2453  {
2454  __COUT__ << "Missing users? Attempting to load database" << __E__;
2455  loadDatabases();
2456  }
2457 
2458  // get all accounts
2459  for(uint64_t i = 0; i < Users_.size(); ++i)
2460  {
2461  xmldoc->addTextElementToParent("username", Users_[i].username_, PREF_XML_ACCOUNTS_FIELD);
2462  xmldoc->addTextElementToParent("display_name", Users_[i].displayName_, PREF_XML_ACCOUNTS_FIELD);
2463 
2464  if(Users_[i].email_.size() > i)
2465  {
2466  xmldoc->addTextElementToParent("useremail", Users_[i].email_, PREF_XML_ACCOUNTS_FIELD);
2467  }
2468  else
2469  {
2470  xmldoc->addTextElementToParent("useremail", "", PREF_XML_ACCOUNTS_FIELD);
2471  }
2472 
2473  xmldoc->addTextElementToParent("permissions", StringMacros::mapToString(Users_[i].permissions_), PREF_XML_ACCOUNTS_FIELD);
2474 
2475  xmldoc->addTextElementToParent("nac", Users_[i].getNewAccountCode().c_str(), PREF_XML_ACCOUNTS_FIELD);
2476  }
2477  }
2478 
2479  // get system layout defaults
2480  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH + (std::string)SYSTEM_PREFERENCES_PREFIX + "." +
2481  (std::string)USERS_PREFERENCES_FILETYPE;
2482  if(!prefXml.loadXmlDocument(fn))
2483  {
2484  __COUT__ << "System Preferences are defaults." << __E__;
2485  // insert defaults, no pref document found
2486  xmldoc->addTextElementToData(PREF_XML_SYSLAYOUT_FIELD, PREF_XML_SYSLAYOUT_DEFAULT);
2487  }
2488  else
2489  {
2490  __COUT__ << "Saved System Preferences found." << __E__;
2491  xmldoc->copyDataChildren(prefXml);
2492  }
2493 
2494  __COUTV__(StringMacros::mapToString(permissionMap));
2495 
2496  // add permissions value
2497  xmldoc->addTextElementToData(PREF_XML_PERMISSIONS_FIELD, StringMacros::mapToString(permissionMap));
2498 
2499  // add user with lock
2500  xmldoc->addTextElementToData(PREF_XML_USERLOCK_FIELD, usersUsernameWithLock_);
2501 
2502  // add user name
2503  xmldoc->addTextElementToData(PREF_XML_USERNAME_FIELD, getUsersUsername(uid));
2504 
2505  // add ots owner name
2506  xmldoc->addTextElementToData(PREF_XML_OTS_OWNER_FIELD, WebUsers::OTS_OWNER);
2507 
2508 } // end insertSettingsForUser()
2509 
2510 //==============================================================================
2511 // WebUsers::setGenericPreference
2512 // each generic preference has its own directory, and each user has their own file
2513 void WebUsers::setGenericPreference(uint64_t uid, const std::string& preferenceName, const std::string& preferenceValue)
2514 {
2515  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2516  //__COUT__ << "setGenericPreference for user: " << UsersUsernameVector[userIndex] <<
2517  //__E__;
2518 
2519  // force alpha-numeric with dash/underscore
2520  std::string safePreferenceName = "";
2521  for(const auto& c : preferenceName)
2522  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c >= '-' || c <= '_'))
2523  safePreferenceName += c;
2524 
2525  std::string dir = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH + "generic_" + safePreferenceName + "/";
2526 
2527  // attempt to make directory (just in case)
2528  mkdir(dir.c_str(), 0755);
2529 
2530  std::string fn = Users_[userIndex].username_ + "_" + safePreferenceName + "." + (std::string)USERS_PREFERENCES_FILETYPE;
2531 
2532  __COUT__ << "Preferences file: " << (dir + fn) << __E__;
2533 
2534  FILE* fp = fopen((dir + fn).c_str(), "w");
2535  if(fp)
2536  {
2537  fprintf(fp, "%s", preferenceValue.c_str());
2538  fclose(fp);
2539  }
2540  else
2541  __COUT_ERR__ << "Preferences file could not be opened for writing!" << __E__;
2542 } // end setGenericPreference()
2543 
2544 //==============================================================================
2545 // WebUsers::getGenericPreference
2546 // each generic preference has its own directory, and each user has their own file
2547 // default preference is empty string.
2548 std::string WebUsers::getGenericPreference(uint64_t uid, const std::string& preferenceName, HttpXmlDocument* xmldoc) const
2549 {
2550  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2551  //__COUT__ << "getGenericPreference for user: " << UsersUsernameVector[userIndex] <<
2552  //__E__;
2553 
2554  // force alpha-numeric with dash/underscore
2555  std::string safePreferenceName = "";
2556  for(const auto& c : preferenceName)
2557  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c >= '-' || c <= '_'))
2558  safePreferenceName += c;
2559 
2560  std::string dir = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH + "generic_" + safePreferenceName + "/";
2561 
2562  std::string fn = Users_[userIndex].username_ + "_" + safePreferenceName + "." + (std::string)USERS_PREFERENCES_FILETYPE;
2563 
2564  __COUT__ << "Preferences file: " << (dir + fn) << __E__;
2565 
2566  // read from preferences file
2567  FILE* fp = fopen((dir + fn).c_str(), "rb");
2568  if(fp)
2569  {
2570  fseek(fp, 0, SEEK_END);
2571  long size = ftell(fp);
2572  std::string line;
2573  line.reserve(size + 1);
2574  rewind(fp);
2575  fgets(&line[0], size + 1, fp);
2576  fclose(fp);
2577 
2578  __COUT__ << "Read value " << line << __E__;
2579  if(xmldoc)
2580  xmldoc->addTextElementToData(safePreferenceName, line);
2581  return line;
2582  }
2583  else
2584  __COUT__ << "Using default value." << __E__;
2585 
2586  // default preference is empty string
2587  if(xmldoc)
2588  xmldoc->addTextElementToData(safePreferenceName, "");
2589  return "";
2590 } // end getGenericPreference()
2591 
2592 //==============================================================================
2593 // WebUsers::changeSettingsForUser
2594 void WebUsers::changeSettingsForUser(
2595  uint64_t uid, const std::string& bgcolor, const std::string& dbcolor, const std::string& wincolor, const std::string& layout, const std::string& syslayout)
2596 {
2597  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap = getPermissionsForUser(uid);
2598  if(isInactiveForGroup(permissionMap))
2599  return; // not an active user
2600 
2601  uint64_t userIndex = searchUsersDatabaseForUserId(uid);
2602  __COUT__ << "Changing settings for user: " << Users_[userIndex].username_ << __E__;
2603 
2604  std::string fn =
2605  (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH + Users_[userIndex].username_ + "." + (std::string)USERS_PREFERENCES_FILETYPE;
2606 
2607  __COUT__ << "Preferences file: " << fn << __E__;
2608 
2609  HttpXmlDocument prefXml;
2610  prefXml.addTextElementToData(PREF_XML_BGCOLOR_FIELD, bgcolor);
2611  prefXml.addTextElementToData(PREF_XML_DBCOLOR_FIELD, dbcolor);
2612  prefXml.addTextElementToData(PREF_XML_WINCOLOR_FIELD, wincolor);
2613  prefXml.addTextElementToData(PREF_XML_LAYOUT_FIELD, layout);
2614 
2615  prefXml.saveXmlDocument(fn);
2616 
2617  // if admin privilieges set system default layouts
2618  if(!isAdminForGroup(permissionMap))
2619  return; // not admin
2620 
2621  // set system layout defaults
2622  fn = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_PREFERENCES_PATH + (std::string)SYSTEM_PREFERENCES_PREFIX + "." +
2623  (std::string)USERS_PREFERENCES_FILETYPE;
2624 
2625  HttpXmlDocument sysPrefXml;
2626  sysPrefXml.addTextElementToData(PREF_XML_SYSLAYOUT_FIELD, syslayout);
2627 
2628  sysPrefXml.saveXmlDocument(fn);
2629 } // end changeSettingsForUser()
2630 
2631 //==============================================================================
2632 // WebUsers::setUserWithLock
2633 // if lock is true, set lock user specified
2634 // if lock is false, attempt to unlock user specified
2635 // return true on success
2636 bool WebUsers::setUserWithLock(uint64_t actingUid, bool lock, const std::string& username)
2637 {
2638  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap = getPermissionsForUser(actingUid);
2639 
2640  std::string actingUser = getUsersUsername(actingUid);
2641 
2642  __COUTV__(actingUser);
2643  __COUT__ << "Permissions: " << StringMacros::mapToString(permissionMap) << __E__;
2644  __COUTV__(usersUsernameWithLock_);
2645  __COUTV__(lock);
2646  __COUTV__(username);
2647  __COUTV__(isUsernameActive(username));
2648 
2649  if(lock && (isUsernameActive(username) || !CareAboutCookieCodes_)) // lock and currently active
2650  {
2651  if(!CareAboutCookieCodes_ && username != DEFAULT_ADMIN_USERNAME) // enforce wiz mode only use admin account
2652  {
2653  __MCOUT_ERR__("User '" << actingUser << "' tried to lock for a user other than admin in wiz mode. Not allowed." << __E__);
2654  return false;
2655  }
2656  else if(!isAdminForGroup(permissionMap) && actingUser != username) // enforce normal mode admin privleges
2657  {
2658  __MCOUT_ERR__("A non-admin user '" << actingUser << "' tried to lock for a user other than self. Not allowed." << __E__);
2659  return false;
2660  }
2661  usersUsernameWithLock_ = username;
2662  }
2663  else if(!lock && usersUsernameWithLock_ == username) // unlock
2664  usersUsernameWithLock_ = "";
2665  else
2666  {
2667  if(!isUsernameActive(username))
2668  __MCOUT_ERR__("User '" << username << "' is inactive." << __E__);
2669  __MCOUT_ERR__("Failed to lock for user '" << username << ".'" << __E__);
2670  return false;
2671  }
2672 
2673  __MCOUT_INFO__("User '" << username << "' has locked out the system!" << __E__);
2674 
2675  // save username with lock
2676  {
2677  std::string securityFileName = USER_WITH_LOCK_FILE;
2678  FILE* fp = fopen(securityFileName.c_str(), "w");
2679  if(!fp)
2680  {
2681  __COUT_INFO__ << "USER_WITH_LOCK_FILE " << USER_WITH_LOCK_FILE << " not found. Ignoring." << __E__;
2682  }
2683  else
2684  {
2685  fprintf(fp, "%s", usersUsernameWithLock_.c_str());
2686  fclose(fp);
2687  }
2688  }
2689  return true;
2690 } // end setUserWithLock()
2691 
2692 //==============================================================================
2693 // WebUsers::modifyAccountSettings
2694 void WebUsers::modifyAccountSettings(
2695  uint64_t actingUid, uint8_t cmd_type, const std::string& username, const std::string& displayname, const std::string& email, const std::string& permissions)
2696 {
2697  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> permissionMap = getPermissionsForUser(actingUid);
2698  if(!isAdminForGroup(permissionMap))
2699  {
2700  // not an admin
2701  __SS__ << "Only admins can modify user settings." << __E__;
2702  __SS_THROW__;
2703  }
2704 
2705  uint64_t i = searchUsersDatabaseForUserId(actingUid);
2706  uint64_t modi = searchUsersDatabaseForUsername(username);
2707  if(modi == 0)
2708  {
2709  if(i == 0)
2710  {
2711  __COUT_INFO__ << "Admin password reset." << __E__;
2712  Users_[modi].setModifier(Users_[i].username_);
2713  Users_[modi].salt_ = "";
2714  Users_[modi].loginFailureCount_ = 0;
2715  saveDatabaseToFile(DB_USERS);
2716  return;
2717  }
2718  __SS__ << "Cannot modify first user" << __E__;
2719  __SS_THROW__;
2720  }
2721 
2722  if(username.length() < USERNAME_LENGTH)
2723  {
2724  __SS__ << "Invalid Username, must be length " << USERNAME_LENGTH << __E__;
2725  __SS_THROW__;
2726  }
2727  if(displayname.length() < DISPLAY_NAME_LENGTH)
2728  {
2729  __SS__ << "Invalid Display Name; must be length " << DISPLAY_NAME_LENGTH << __E__;
2730  __SS_THROW__;
2731  }
2732 
2733  __COUT__ << "Input Permissions: " << permissions << __E__;
2734  std::map<std::string /*groupName*/, WebUsers::permissionLevel_t> newPermissionsMap;
2735 
2736  switch(cmd_type)
2737  {
2738  case MOD_TYPE_UPDATE:
2739 
2740  __COUT__ << "MOD_TYPE_UPDATE " << username << " := " << permissions << __E__;
2741 
2742  if(modi == NOT_FOUND_IN_DATABASE)
2743  {
2744  __SS__ << "User not found!? Should not happen." << __E__;
2745  __SS_THROW__;
2746  }
2747 
2748  //enforce unique Display Name
2749  {
2750  for(uint64_t i=0; i < Users_.size(); ++i)
2751  if(i == modi) continue; //skip target user
2752  else if(Users_[i].displayName_ == displayname)
2753  {
2754  __SS__ << "Display Name '" << displayname << "' already exists! Please choose a unique display name." << __E__;
2755  __SS_THROW__;
2756  }
2757  }
2758 
2759  Users_[modi].displayName_ = displayname;
2760  Users_[modi].email_ = email;
2761 
2762  { // handle permissions
2763  StringMacros::getMapFromString(permissions, newPermissionsMap);
2764  bool wasInactive = isInactiveForGroup(Users_[modi].permissions_);
2765 
2766  // fix permissions_ if missing default user group
2767  if(newPermissionsMap.size() == 0) // default to inactive
2768  Users_[modi].permissions_[WebUsers::DEFAULT_USER_GROUP] = std::atoi(permissions.c_str());
2769  else if(newPermissionsMap.size() == 1 && newPermissionsMap.find(WebUsers::DEFAULT_USER_GROUP) == newPermissionsMap.end())
2770  {
2771  if(newPermissionsMap.begin()->first == "")
2772  Users_[modi].permissions_[WebUsers::DEFAULT_USER_GROUP] = newPermissionsMap.begin()->second;
2773  else // if a user group attempted, copy settings for default group
2774  {
2775  newPermissionsMap[WebUsers::DEFAULT_USER_GROUP] = newPermissionsMap.begin()->second;
2776  Users_[modi].permissions_ = newPermissionsMap;
2777  }
2778  }
2779  else
2780  Users_[modi].permissions_ = newPermissionsMap;
2781 
2782  // If account was inactive and re-activating, then reset fail count and
2783  // password. Note: this is the account unlock mechanism.
2784  if(wasInactive && // was inactive
2785  !isInactiveForGroup(Users_[modi].permissions_)) // and re-activating
2786  {
2787  __COUT__ << "Reactivating " << username << __E__;
2788  Users_[modi].loginFailureCount_ = 0;
2789  Users_[modi].salt_ = "";
2790  }
2791  } // end permissions handling
2792 
2793  // save information about modifier
2794  {
2795  if(i == NOT_FOUND_IN_DATABASE)
2796  {
2797  __SS__ << "Master User not found!? Should not happen." << __E__;
2798  __SS_THROW__;
2799  }
2800  Users_[modi].setModifier(Users_[i].username_);
2801  }
2802  break;
2803  case MOD_TYPE_ADD:
2804  //Note: username, userId, AND displayName must be unique!
2805 
2806  __COUT__ << "MOD_TYPE_ADD " << username << " - " << displayname << __E__;
2807 
2808  createNewAccount(username, displayname, email);
2809  // save information about modifier
2810  {
2811  if(i == NOT_FOUND_IN_DATABASE)
2812  {
2813  __SS__ << "Master User not found!? Should not happen." << __E__;
2814  __SS_THROW__;
2815  }
2816  Users_.back().setModifier(Users_[i].username_);
2817  }
2818 
2819  if(permissions.size()) // apply permissions
2820  {
2821  modifyAccountSettings(actingUid, MOD_TYPE_UPDATE, username, displayname, email, permissions);
2822  return;
2823  }
2824  break;
2825  case MOD_TYPE_DELETE:
2826  __COUT__ << "MOD_TYPE_DELETE " << username << " - " << displayname << __E__;
2827  deleteAccount(username, displayname);
2828  break;
2829  default:
2830  __SS__ << "Undefined command - do nothing " << username << __E__;
2831  __SS_THROW__;
2832  }
2833 
2834  saveDatabaseToFile(DB_USERS);
2835 } // end modifyAccountSettings()
2836 
2837 //==============================================================================
2838 // WebUsers::getActiveUsersString
2839 // return comma separated list of active Display Names
2840 std::string WebUsers::getActiveUsersString()
2841 {
2842  std::set<unsigned int> activeUserIndices;
2843  for(uint64_t i = 0; i < ActiveSessions_.size(); ++i)
2844  activeUserIndices.emplace(searchUsersDatabaseForUserId(ActiveSessions_[i].userId_));
2845 
2846  std::string ret = "";
2847  bool addComma = false;
2848  for(const auto& i:activeUserIndices)
2849  {
2850  if(i >= Users_.size()) continue; //skip not found
2851 
2852  if(addComma) ret += ",";
2853  else addComma = true;
2854 
2855  ret += Users_[i].displayName_;
2856  }
2857  __COUTV__(ret);
2858  return ret;
2859 } // end getActiveUsersString()
2860 
2861 //==============================================================================
2862 // WebUsers::getAdminUserID
2863 //
2864 uint64_t WebUsers::getAdminUserID()
2865 {
2866  uint64_t uid = searchUsersDatabaseForUsername(DEFAULT_ADMIN_USERNAME);
2867  return uid;
2868 }
2869 
2870 //==============================================================================
2871 // WebUsers::loadUserWithLock
2872 // //load username with lock from file
2873 void WebUsers::loadUserWithLock()
2874 {
2875  char username[300] = ""; // assume username is less than 300 chars
2876 
2877  std::string securityFileName = USER_WITH_LOCK_FILE;
2878  FILE* fp = fopen(securityFileName.c_str(), "r");
2879  if(!fp)
2880  {
2881  __COUT_INFO__ << "USER_WITH_LOCK_FILE " << USER_WITH_LOCK_FILE << " not found. Defaulting to admin lock." << __E__;
2882 
2883  // default to admin lock if no file exists
2884  sprintf(username, "%s", DEFAULT_ADMIN_USERNAME.c_str());
2885  }
2886  else
2887  {
2888  fgets(username, 300, fp);
2889  username[299] = '\0'; // likely does nothing, but make sure there is closure on string
2890  fclose(fp);
2891  }
2892 
2893  // attempt to set lock
2894  __COUT__ << "Attempting to load username with lock: " << username << __E__;
2895 
2896  if(strlen(username) == 0)
2897  {
2898  __COUT_INFO__ << "Loaded state for user-with-lock is unlocked." << __E__;
2899  return;
2900  }
2901 
2902  uint64_t i = searchUsersDatabaseForUsername(username);
2903  if(i == NOT_FOUND_IN_DATABASE)
2904  {
2905  __COUT_INFO__ << "username " << username << " not found in database. Ignoring." << __E__;
2906  return;
2907  }
2908  __COUT__ << "Setting lock" << __E__;
2909  setUserWithLock(Users_[i].userId_, true, username);
2910 } // end loadUserWithLock()
2911 
2912 
2913 //==============================================================================
2914 // addSystemMessage
2915 // targetUser can be "*" for all users
2916 void WebUsers::addSystemMessage(const std::string& targetUsersCSV,const std::string& message)
2917 {
2918  addSystemMessage(targetUsersCSV,"" /*subject*/,message,false /*doEmail*/);
2919 } //end addSystemMessage()
2920 
2921 //==============================================================================
2922 // addSystemMessage
2923 // targetUser can be "*" for all users
2924 void WebUsers::addSystemMessage(const std::string& targetUsersCSV, const std::string& subject, const std::string& message, bool doEmail)
2925 {
2926  std::vector<std::string> targetUsers;
2927  StringMacros::getVectorFromString(targetUsersCSV,targetUsers);
2928  addSystemMessage(targetUsers,subject,message,doEmail);
2929 } //end addSystemMessage()
2930 
2931 //==============================================================================
2932 // addSystemMessage
2933 // targetUser can be "*" for all users
2934 void WebUsers::addSystemMessage(const std::vector<std::string>& targetUsers,
2935  const std::string& subject, const std::string& message, bool doEmail)
2936 {
2937  __COUT__ << "Before number of users with system messages: " << systemMessages_.size() << __E__;
2938 
2939  //lock for remainder of scope
2940  std::lock_guard<std::mutex> lock(systemMessageLock_);
2941 
2942  systemMessageCleanup();
2943 
2944  std::string fullMessage = StringMacros::encodeURIComponent((subject == ""?"":(subject + ": ")) +
2945  message);
2946 
2947  __COUTV__(fullMessage);
2948  __COUTV__(StringMacros::vectorToString(targetUsers));
2949 
2950  std::set<std::string> targetEmails;
2951 
2952  for(const auto& targetUser : targetUsers)
2953  {
2954  // reject if message is a repeat for user
2955 
2956  if(targetUser == "" || (targetUser != "*" && targetUser.size() < 3))
2957  {
2958  __COUT__ << "Illegal username '" << targetUser << "'" << __E__;
2959  continue;
2960  }
2961  __COUTV__(targetUser);
2962  //target user might * or <group name>:<permission threshold> or just <username>
2963 
2964 
2965  //do special ALL email handling
2966  if(doEmail && targetUser == "*")
2967  {
2968  //for each user, look up email and append
2969  for(const auto& user : Users_)
2970  {
2971  if(user.email_.size() > 5 && //few simple valid email checks
2972  user.email_.find('@') != std::string::npos &&
2973  user.email_.find('.') != std::string::npos)
2974  {
2975  __COUT__ << "Adding " << user.displayName_ << " email: " << user.email_ << __E__;
2976  targetEmails.emplace(user.email_);
2977  }
2978  } //end add every user loop
2979 
2980  } //end all email handling
2981  else if(targetUser.find(':') != std::string::npos)
2982  {
2983  //special group handling.. convert to individual users
2984  __COUT__ << "Treating as group email target: " << targetUser << __E__;
2985 
2986  std::map<std::string, WebUsers::permissionLevel_t> targetGroupMap;
2987  StringMacros::getMapFromString( // re-factor membership string to map
2988  targetUser,
2989  targetGroupMap);
2990 
2991  __COUTV__(StringMacros::mapToString(targetGroupMap));
2992 
2993  if(targetGroupMap.size() == 1)
2994  {
2995  //add users to targetUsers, so the loop will catch them at end
2996 
2997  //loop through all users, and add users that match group spec
2998  for(const auto& user : Users_)
2999  {
3000  WebUsers::permissionLevel_t userLevel =
3001  getPermissionLevelForGroup(getPermissionsForUser(user.userId_),
3002  targetGroupMap.begin()->first);
3003 
3004  __COUTV__(StringMacros::mapToString(getPermissionsForUser(user.userId_)));
3005  __COUTV__((int)userLevel);
3006  __COUTV__(targetGroupMap.begin()->first);
3007 
3008  if(userLevel != WebUsers::PERMISSION_LEVEL_INACTIVE &&
3009  userLevel >= targetGroupMap.begin()->second &&
3010  user.email_.size() > 5 && //few simple valid email checks
3011  user.email_.find('@') != std::string::npos &&
3012  user.email_.find('.') != std::string::npos)
3013  {
3014  if(doEmail)
3015  {
3016  targetEmails.emplace(user.email_);
3017  __COUT__ << "Adding " << user.displayName_ << " email: " << user.email_ << __E__;
3018  }
3019  addSystemMessageToMap(user.displayName_,fullMessage);
3020  }
3021  }
3022  }
3023  else
3024  __COUT__ << "target Group Map from '" << targetUser << "' is empty." << __E__;
3025 
3026  continue; //proceed with user loop, do not add group target message
3027  }
3028 
3029  //at this point add to system message map (similar to group individual add, but might be '*')
3030 
3031  addSystemMessageToMap(targetUser,fullMessage);
3032 
3033  if(doEmail)//find user for email
3034  {
3035  for(const auto& user : Users_)
3036  {
3037  if(user.displayName_ == targetUser)
3038  {
3039  if(user.email_.size() > 5 && //few simple valid email checks
3040  user.email_.find('@') != std::string::npos &&
3041  user.email_.find('.') != std::string::npos)
3042  {
3043  targetEmails.emplace(user.email_);
3044  __COUT__ << "Adding " << user.displayName_ << " email: " << user.email_ << __E__;
3045  }
3046  break; //user found, exit search loop
3047  }
3048  } //end user search loop
3049  }
3050 
3051  } //end target user message add loop
3052 
3053  __COUT__ << "After number of users with system messages: " << systemMessages_.size() << __E__;
3054  __COUTV__(targetEmails.size());
3055 
3056  if(doEmail && targetEmails.size())
3057  {
3058  __COUTV__(StringMacros::setToString(targetEmails));
3059 
3060  std::string toList = "";
3061  bool addComma = false;
3062  for(const auto& email : targetEmails)
3063  {
3064  if(addComma) toList += ", ";
3065  else addComma = true;
3066  toList += email;
3067  }
3068 
3069  std::string filename = (std::string)WEB_LOGIN_DB_PATH + (std::string)USERS_DB_PATH + "/.tmp_email.txt";
3070  FILE* fp = fopen(filename.c_str(),"w");
3071  if(!fp)
3072  {
3073  __SS__ << "Could not open email file: " << filename << __E__;
3074  __SS_THROW__;
3075  }
3076 
3077  fprintf(fp,"From: %s\n",(WebUsers::OTS_OWNER==""?"ots":(
3078  StringMacros::decodeURIComponent(WebUsers::OTS_OWNER) + "_ots")).c_str());
3079  fprintf(fp,"To: %s\n",toList.c_str());
3080  fprintf(fp,"Subject: %s\n",subject.c_str());
3081  fprintf(fp,"Content-Type: text/html\n");
3082  fprintf(fp,"\n<html><pre>%s</pre></html>",message.c_str());
3083  fclose(fp);
3084 
3085  StringMacros::exec(("sendmail \"" + toList + "\" < " + filename).c_str());
3086  }
3087  else if(doEmail)
3088  __COUT_WARN__ << "Do email was attempted, but no target users had email addresses specified!" << __E__;
3089 
3090 } //end addSystemMessage()
3091 
3092 //==============================================================================
3093 // addSystemMessageToMap
3094 // Manages map and adds message for user, does not add repeat messages for target user.
3095 // targetUser should be display name of user or "*"
3096 void WebUsers::addSystemMessageToMap(const std::string& targetUser, const std::string& fullMessage)
3097 {
3098  auto it = systemMessages_.find(targetUser);
3099 
3100  //check for repeat messages
3101  if(it != systemMessages_.end() &&
3102  it->second.size() &&
3103  it->second[it->second.size() - 1].message_ == fullMessage)
3104  return; //skip user add
3105 
3106  if(it == systemMessages_.end()) //create first message for target user
3107  {
3108  systemMessages_.emplace(
3109  std::pair<std::string /*toUser*/,std::vector<SystemMessage>>(
3110  targetUser,
3111  std::vector<SystemMessage>({SystemMessage(fullMessage)})
3112  ));
3113  __COUT__ << targetUser << " Current System Messages count = " << 1 << __E__;
3114  }
3115  else //add message
3116  {
3117  __COUT__ << __E__;
3118  it->second.push_back(SystemMessage(fullMessage));
3119  __COUT__ << it->first << " Current System Messages count = " << it->second.size() << __E__;
3120  }
3121 } //end addSystemMessageToMap
3122 
3123 //==============================================================================
3124 // getSystemMessage
3125 // Deliver | separated system messages (time | msg | time | msg...etc),
3126 // if there is any in vector set for user or for wildcard *
3127 // Empty std::string "" returned if no message for targetUser
3128 // Note: | is an illegal character and will cause GUI craziness
3129 // Note: targetUser is by display name
3130 std::string WebUsers::getSystemMessage(const std::string& targetUser)
3131 {
3132  //__COUT__ << "Number of users with system messages: " << systemMessages_.size() << __E__;
3133 
3134  //lock for remainder of scope
3135  std::lock_guard<std::mutex> lock(systemMessageLock_);
3136 
3137  // __COUT__ << "Current System Messages: " << targetUser <<
3138  // std::endl << std::endl;
3139 
3140  std::string retStr = "";
3141  int cnt = 0;
3142  char tmp[32];
3143 
3144  //__COUTV__(targetUser);
3145  auto it = systemMessages_.find(targetUser);
3146  //for(auto systemMessagePair:systemMessages_)
3147  // __COUT__ << systemMessagePair.first << " " << systemMessagePair.second.size() << " "
3148  // << (systemMessagePair.second.size()?systemMessagePair.second[0].message_:"") << __E__;
3149  //if(it != systemMessages_.end())
3150  // __COUTV__(it->second.size());
3151 
3152  for(uint64_t i = 0; it != systemMessages_.end() && i < it->second.size(); ++i)
3153  {
3154  // deliver user specific system message
3155  if(cnt)
3156  retStr += "|";
3157  sprintf(tmp, "%lu", it->second[i].creationTime_);
3158  retStr += std::string(tmp) + "|" + it->second[i].message_;
3159 
3160  it->second[i].delivered_ = true;
3161  ++cnt;
3162  }
3163  it = systemMessages_.find("*");
3164  for(uint64_t i = 0; it != systemMessages_.end() && i < it->second.size(); ++i)
3165  {
3166  // deliver "*" system message
3167  if(cnt)
3168  retStr += "|";
3169  sprintf(tmp, "%lu", it->second[i].creationTime_);
3170  retStr += std::string(tmp) + "|" + it->second[i].message_;
3171 
3172  ++cnt;
3173  }
3174  //__COUTV__(retStr);
3175 
3176  systemMessageCleanup();
3177  //__COUT__ << "Number of users with system messages: " << systemMessages_.size() << __E__;
3178  return retStr;
3179 } //end getSystemMessage()
3180 
3181 //==============================================================================
3182 // systemMessageCleanup
3183 // Cleanup messages if delivered, and targetUser != wildcard *
3184 // For all remaining messages, wait some time before removing (e.g. 30 sec)
3185 void WebUsers::systemMessageCleanup()
3186 {
3187  //__COUT__ << "Number of users with system messages: " << systemMessages_.size() << __E__;
3188  for(auto& userMessagesPair : systemMessages_)
3189  {
3190  //__COUT__ << userMessagesPair.first << " system messages: " <<
3191  // userMessagesPair.second.size() << __E__;
3192 
3193  for(uint64_t i = 0; i < userMessagesPair.second.size(); ++i)
3194  if((userMessagesPair.first != "*" && userMessagesPair.second[i].delivered_) || // delivered and != *
3195  userMessagesPair.second[i].creationTime_ + SYS_CLEANUP_WILDCARD_TIME < time(0)) // expired
3196  {
3197  // remove
3198  userMessagesPair.second.erase(userMessagesPair.second.begin() + i);
3199  --i; // rewind
3200  }
3201 
3202  //__COUT__ << userMessagesPair.first << " remaining system messages: " <<
3203  // userMessagesPair.second.size() << __E__;
3204  }
3205  //__COUT__ << "Number of users with system messages: " << systemMessages_.size() << __E__;
3206 } //end systemMessageCleanup()
3207 
3208 //==============================================================================
3209 // WebUsers::getSecurity
3210 std::string WebUsers::getSecurity() { return securityType_; }
3211 //==============================================================================
3212 // WebUsers::loadSecuritySelection
3213 void WebUsers::loadSecuritySelection()
3214 {
3215  std::string securityFileName = SECURITY_FILE_NAME;
3216  FILE* fp = fopen(securityFileName.c_str(), "r");
3217  char line[100] = "";
3218  if(fp)
3219  fgets(line, 100, fp);
3220  unsigned int i = 0;
3221 
3222  // find first character that is not alphabetic
3223  while(i < strlen(line) && line[i] >= 'A' && line[i] <= 'z')
3224  ++i;
3225  line[i] = '\0'; // end string at first illegal character
3226 
3227  if(strcmp(line, SECURITY_TYPE_NONE.c_str()) == 0 || strcmp(line, SECURITY_TYPE_DIGEST_ACCESS.c_str()) == 0)
3228  securityType_ = line;
3229  else
3230  securityType_ = SECURITY_TYPE_DEFAULT;
3231 
3232  __COUT__ << "The current security type is " << securityType_ << __E__;
3233 
3234  if(fp)
3235  fclose(fp);
3236 
3237  if(securityType_ == SECURITY_TYPE_NONE)
3238  CareAboutCookieCodes_ = false;
3239  else
3240  CareAboutCookieCodes_ = true;
3241 
3242  __COUT__ << "CareAboutCookieCodes_: " << CareAboutCookieCodes_ << __E__;
3243 } //end loadSecuritySelection()()
3244 
3245 //==============================================================================
3246 void WebUsers::NACDisplayThread(const std::string& nac, const std::string& user)
3247 {
3248  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
3250  // thread notifying the user about the admin new account code
3251  // notify for 10 seconds (e.g.)
3252 
3253  // child thread
3254  int i = 0;
3255  for(; i < 5; ++i)
3256  {
3257  std::this_thread::sleep_for(std::chrono::seconds(2));
3258  __COUT__ << "\n******************************************************************** " << __E__;
3259  __COUT__ << "\n******************************************************************** " << __E__;
3260  __COUT__ << "\n\nNew account code = " << nac << " for user: " << user << "\n" << __E__;
3261  __COUT__ << "\n******************************************************************** " << __E__;
3262  __COUT__ << "\n******************************************************************** " << __E__;
3263  }
3264 } //end NACDisplayThread()
3265 
3266 //==============================================================================
3267 void WebUsers::deleteUserData()
3268 {
3269  __COUT__ << "$$$$$$$$$$$$$$ Deleting ALL service user data... $$$$$$$$$$$$" << __E__;
3270 
3271  // delete Login data
3272  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + HASHES_DB_PATH + "/*").c_str());
3273  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_DB_PATH + "/*").c_str());
3274  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_LOGIN_HISTORY_PATH + "/*").c_str());
3275  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + USERS_PREFERENCES_PATH + "/*").c_str());
3276  std::system(("rm -rf " + (std::string)WEB_LOGIN_DB_PATH + TOOLTIP_DB_PATH).c_str());
3277 
3278  std::string serviceDataPath = __ENV__("SERVICE_DATA_PATH");
3279  // delete macro maker folders
3280  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroData/").c_str());
3281  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroHistory/").c_str());
3282  std::system(("rm -rf " + std::string(serviceDataPath) + "/MacroExport/").c_str());
3283 
3284  // delete console folders
3285  std::system(("rm -rf " + std::string(serviceDataPath) + "/ConsolePreferences/").c_str());
3286 
3287  // delete code editor folders
3288  std::system(("rm -rf " + std::string(serviceDataPath) + "/CodeEditorData/").c_str());
3289 
3290  // delete wizard folders
3291  std::system(("rm -rf " + std::string(serviceDataPath) + "/OtsWizardData/").c_str());
3292 
3293  // delete progress bar folders
3294  std::system(("rm -rf " + std::string(serviceDataPath) + "/ProgressBarData/").c_str());
3295 
3296  // delete The Supervisor run folders
3297  std::system(("rm -rf " + std::string(serviceDataPath) + "/RunNumber/").c_str());
3298  std::system(("rm -rf " + std::string(serviceDataPath) + "/RunControlData/").c_str());
3299 
3300  // delete Visualizer folders
3301  std::system(("rm -rf " + std::string(serviceDataPath) + "/VisualizerData/").c_str());
3302 
3303  // DO NOT delete active groups file (this messes with people's configuration world,
3304  // which is not expected when "resetting user info") std::system(("rm -rf " +
3305  // std::string(serviceDataPath) + "/ActiveTableGroups.cfg").c_str());
3306 
3307  // delete Logbook folders
3308  std::system(("rm -rf " + std::string(__ENV__("LOGBOOK_DATA_PATH")) + "/").c_str());
3309 
3310  __COUT__ << "$$$$$$$$$$$$$$ Successfully deleted ALL service user data $$$$$$$$$$$$" << __E__;
3311 } //end deleteUserData()