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