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