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