public function updateUserName($internalID, $externalID = 0, $name = 'LoginSystem:userOperations:updateUserNameFailed') { // update usernames based on data frome external logins // usernames based on internal login can not be updated if ($externalID === 0) { return; } // copy name so we have original name if an error occurs $newName = $name; // currently only bzbb module allowed as external login // so hardwire it until we have a database field for // external login module used // check if bzbb module is loaded if (array_search('bzbb', loginSystem::getModules()) === true) { $result = bzbb::updateUserName($externalID, $newName); } if ($newName !== false && strlen($newName) > 0) { $query = $this->prepare('UPDATE `users` SET `name`=:name WHERE `id`=:id LIMIT 1'); $this->execute($query, array(':name' => array($newName, PDO::PARAM_STR, 50), ':id' => array($internalID, PDO::PARAM_INT))); $this->free($query); } // report error if external login module (e.g. bzbb) failed doing its job if (isset($result) && $result === false) { $this->logError('login/classes/userOperations.php: updateUserName:'******' bzbb::updateUserName(' . strval($externalID) . ', ' . strval($name) . ')' . ' returned false (unknown error).'); } }
public function validateLogin(&$output) { global $config; global $user; global $db; // initialise permissions $user->removeAllPermissions(); // too many login attempts if (!$this->loginAllowed) { // print out the same error message an incorrect password would trigger $output .= 'Your password does not match the stored password.' . ' You may want to <a href="./">try logging in again</a>.' . "\n"; return false; } // set loginname based on POST parameter // no loginname -> login failed if (!isset($_POST['loginname'])) { $output = 'Error: You must specify a name. ' . 'You may <a href="./?module=local&action=form">try logging in again</a>.'; $this->logFailedLoginAttempt('', 'fieldMissing'); return false; } // escape username before storing it in db // that way encoding when displaying (on other pages) // using (X)HTML is not needed $loginname = htmlent($_POST['loginname']); $lenLogin = strlen($loginname); // now take care of empty loginname // also present error message to user if too long or too short loginname if ($lenLogin > 50 || $lenLogin < 1) { $output .= 'User names must be using less than 50 ' . 'but more than 0 <abbr title="characters">chars</abbr>.' . "\n"; $this->logFailedLoginAttempt(substr($_POST['loginname'], 0, 50), $lenLogin > 50 ? 'tooLongUserName' : 'emptyPassword'); return false; } // set password based on POST parameter // no password -> login failed if (!isset($_POST['pw'])) { $this->logFailedLoginAttempt($loginname, 'fieldMissing'); return false; } // check length of password $pw_supplied = $_POST['pw']; $lenPw = strlen($pw_supplied); if ($lenPw < 9 || $lenPw > 32) { $output .= 'Passwords must be using less than 32 but more than 9 ' . '<abbr title="characters">chars</abbr>.' . ' You may want to <a href="./">try logging in again</a>.</p>' . "\n"; $this->logFailedLoginAttempt($loginname, $lenPw > 32 ? 'tooLongPassword' : 'emptyPassword'); return false; } // initialise match variables for password and user $correctUser = false; $correctPw = false; // get user id $query = 'SELECT `id`'; if ($config->getValue('login.modules.forceExternalLoginOnly')) { $query .= ', `external_id` '; } // only one user tries to login so only fetch one entry, speeds up login a lot $query .= ' FROM `users` WHERE `name`=? LIMIT 1'; $query = $db->prepare($query); $db->execute($query, $loginname); // initialise with reserved user id 0 (no user) $userid = (int) 0; $convert_to_external_login = true; while ($row = $db->fetchRow($query)) { $userid = $row['id']; // external_id might contain NULL values // so it is more practical to use isset // instead of checking $convert_to_external_login // and then investigate a possible NULL value if (!isset($row['external_id']) || !(strcmp($row['external_id'], '') === 0)) { $convert_to_external_login = false; } } // local login tried but external login forced in settings if (!$convert_to_external_login && $config->getValue('login.modules.forceExternalLoginOnly')) { $msg = '<span class="unread_messages">You already enabled '; $modules = loginSystem::getModules(); if (array_search('bzbb', $modules) !== false) { $url = urlencode($config->getValue('baseaddress') . 'Login/?module=bzbb&action=login&auth=%TOKEN%,%USERNAME%'); $msg .= '<a href="' . htmlspecialchars('http://my.bzflag.org/weblogin.php?action=weblogin&url=') . $url; $msg .= '">global (my.bzflag.org/bb/) login</a>'; } else { $msg .= 'external logins'; } $msg .= ' for this account.</span>' . "\n"; $output .= $msg; unset($modules); return false; } elseif ($convert_to_external_login) { // convert user account to external login implicitly // find out which login modules are installed $modules = loginSystem::getModules(); // convert to use bzbb external login if (array_search('bzbb', $modules) !== false) { // try to convert the account using the module's inbuilt function $moduleConvertMsg = ''; if (!bzbb::convertAccount($userid, $loginname, $moduleConvertMsg)) { if (strlen($moduleConvertMsg) > 0) { $output .= 'Module bzbb has returned the following error on convertAccount: ' . $moduleConvertMsg; } return false; } if (strlen($moduleConvertMsg) > 0) { // Module bzbb has returned the following success message on convertAccount $output .= $moduleConvertMsg . ' '; } unset($moduleConvertMsg); // stop here if local login not allowed if ($config->getValue('login.modules.forceExternalLoginOnly')) { $output .= 'Local login is disabled by the site admin, though. ' . 'You must use the bzbb login instead.'; return false; } } } if (intval($userid) === 0) { $user->logout(); $output .= 'The specified user is not registered. ' . 'You may want to <a href="./">try logging in again</a>.'; $this->logFailedLoginAttempt($loginname); return false; } // get password from database in order to compare it with the user entered password // only one user tries to login so only fetch one entry, speeds up login a lot $query = 'SELECT `password`, `cipher` FROM `users_passwords` WHERE `userid`=? LIMIT 1'; // execute query $query = $db->prepare($query); if (!$db->execute($query, $userid)) { // query failed $output .= 'Could not retrieve password for you in database.'; $this->logFailedLoginAttempt($loginname, 'missconfiguration'); return false; } // find out what database tells about password algorithm while ($row = $db->fetchRow($query)) { if (!isset($row['cipher'])) { $output .= 'FATAL ERROR: Cipher not set.' . ' You may want to <a href="./">try logging in again</a>.' . "\n"; $db->logError('FATAL ERROR: Local login module: Cipher not set.'); $this->logFailedLoginAttempt($loginname, 'missconfiguration'); return false; } // the algorithm $cipher = $row['cipher']; // the password in db $pw_db = $row['password']; } // little article about password security // http://www.php.net/manual/en/faq.passwords.php // cryptographic salt to use // see http://www.php.net/manual/en/function.crypt.php // TODO: allow overriding user configurable salt part using config $salt = ''; switch ($cipher) { case 'md5': // is md5 cipher supported on this system? if (CRYPT_MD5 != 1) { $salt = false; break; } // 12 char salt, beginning with $1$ $salt = '$1$'; $userSalt = 'thisi$sp$'; break; case 'blowfish': // is blowfish cipher supported on this system? if (CRYPT_BLOWFISH != 1) { $salt = false; break; } // 33 char salt, // beginning with $2a$, followed by cost between 04 and 31, a % and 22 chars in ./0-9A-Za-z $salt = '$2a$'; $userSalt = '09$th1s1sSp4rt4.O.RlySure4b0$'; //$userSalt='07$usesomesillystringforsalt$'; break; default: // an undefined case, very bad // but not caused by end user so do not subtract free login attempts $output .= 'FATAL ERROR: Action for stored cipher in db not set.' . ' This case means you need admin support to resolve the technical issue.' . ' You may want to <a href="./">try logging in again</a>.' . "\n"; $db->logError('FATAL ERROR: Local login module: No action for cipher (' . $cipher . ') set.'); $this->logFailedLoginAttempt($loginname, 'missconfiguration'); return false; } // mention the error to user but don't tell about cipher information // as the latter would likely only help exploiting the password check // again not caused by end user if ($salt === false) { $output .= 'FATAL ERROR: Cipher set and valid but library error on server detected.' . ' This case means you need admin support to resolve the technical issue.' . ' You may want to <a href="./">try logging in again</a>.' . "\n"; $db->logError('FATAL ERROR: Local login module: Cipher (' . $cipher . ') set and valid' . ' but no proper encoding lib available.'); $this->logFailedLoginAttempt($loginname, 'missconfiguration'); return false; } // build the final salt $salt .= $userSalt; // compute the password from user input $pw_gen = crypt($pw_supplied, $salt); // do the actual comparison if (!(strcmp($pw_db, $pw_gen) === 0) && strlen($pw_db) > 0) { // TODO: automatically log these cases and lock account for some hours after several unsuccessful tries $output .= 'Your password does not match the stored password.' . ' You may want to <a href="./">try logging in again</a>.' . "\n"; $this->logFailedLoginAttempt($loginname, 'passwordMismatch'); return false; } // put information into class variable for usage outside of this function $this->info['id'] = $userid; $this->info['username'] = $loginname; // sanity checks passed -> login successful return true; // username and password did match but there might be circumstances // where the caller script decides the login was not successful, though }