diff --git a/README.md b/README.md index 0220e25..e18d3b9 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,15 @@ Projects created for use with the TrinityCore World of Warcraft emulator: https://www.trinitycore.org/ # Requirements - gmp module is required to be enabled in PHP. On linux this means either `sudo apt install php-gmp` or editing the default php.ini and uncomment the `extension=gmp` line + +#### PHP Version >= 5.0.0 + +[gmp](https://www.php.net/manual/en/book.gmp.php) module is required to be enabled in PHP. + * On Linux do `sudo apt install php-gmp` + * On Windows you should have `php_gmp.dll` in your extensions folder of your PHP installation + * Edit the default php.ini and uncomment the `extension=gmp` line. + +[pdo_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php) module is required to be enabled in PHP. + * On Linux do `sudo apt install php-mysql` + * On Windows you should have `php_pdo_mysql.dll` in your extensions folder of your PHP installation + * Edit the default php.ini and uncomment the `extension=pdo_mysql` line. diff --git a/Trinity Account Creator/index.html b/Trinity Account Creator/index.html index 3d3779c..0236a0d 100644 --- a/Trinity Account Creator/index.html +++ b/Trinity Account Creator/index.html @@ -72,6 +72,13 @@ /> + diff --git a/Trinity Account Creator/js/script.js b/Trinity Account Creator/js/script.js index 2a6b9de..472f906 100644 --- a/Trinity Account Creator/js/script.js +++ b/Trinity Account Creator/js/script.js @@ -23,26 +23,49 @@ function submitForm() { // Create a callback function. xhr.onreadystatechange = function() { - if (xhr.readyState == 4 && xhr.status == 200) { - // Transaction was successful. - if (xhr.responseText == '0') { - // Reset the form first. - document.getElementById('accountForm').reset(); - - // Update the status message. - document.getElementById('statusMessage').value = - 'Account created successfully!'; - document.getElementById('statusMessage').style.color = 'green'; - } else if (xhr.responseText == '1') { - document.getElementById('statusMessage').value = 'Email is invalid.'; - document.getElementById('statusMessage').style.color = 'yellow'; - } else if (xhr.responseText == '2') { - document.getElementById('statusMessage').value = - 'Account already exists.'; - document.getElementById('statusMessage').style.color = 'red'; - } else if (xhr.responseText == '3') { - document.getElementById('statusMessage').value = - 'Unknown error occurred.
Please try again.'; + if (xhr.readyState == 4) { + if (xhr.status == 200) { + // Transaction was successful. + if (xhr.responseText == '0') { + // Reset the form first. + document.getElementById('accountForm').reset(); + + // Update the status message. + document.getElementById('statusMessage').value = 'Account created successfully!'; + document.getElementById('statusMessage').style.color = 'green'; + } else if (xhr.responseText == '1') { + document.getElementById('statusMessage').value = 'Account already exists.'; + document.getElementById('statusMessage').style.color = 'red'; + } else if (xhr.responseText == '2') { + document.getElementById('statusMessage').value = 'Connection failed.'; + document.getElementById('statusMessage').style.color = 'red'; + } else if (xhr.responseText == '3') { + document.getElementById('statusMessage').value = 'Username is empty.'; + document.getElementById('statusMessage').style.color = 'yellow'; + } else if (xhr.responseText == '4') { + document.getElementById('statusMessage').value = 'Username is invalid.'; + document.getElementById('statusMessage').style.color = 'yellow'; + } else if (xhr.responseText == '5') { + document.getElementById('statusMessage').value = 'Username is too long.'; + document.getElementById('statusMessage').style.color = 'yellow'; + } else if (xhr.responseText == '6') { + document.getElementById('statusMessage').value = 'Password is empty.'; + document.getElementById('statusMessage').style.color = 'yellow'; + } else if (xhr.responseText == '7') { + document.getElementById('statusMessage').value = 'Password is too long.'; + document.getElementById('statusMessage').style.color = 'yellow'; + } else if (xhr.responseText == '8') { + document.getElementById('statusMessage').value = 'Email is empty.'; + document.getElementById('statusMessage').style.color = 'yellow'; + } else if (xhr.responseText == '9') { + document.getElementById('statusMessage').value = 'Email is invalid.'; + document.getElementById('statusMessage').style.color = 'yellow'; + } else { + document.getElementById('statusMessage').value = 'Unknown error occurred.'; + document.getElementById('statusMessage').style.color = 'red'; + } + } else { + document.getElementById('statusMessage').value = 'Unknown error occurred.'; document.getElementById('statusMessage').style.color = 'red'; } } @@ -59,3 +82,6 @@ $('#accountForm').submit(function(event) { // Cancel the form submission, so we can use AJAX instead. event.preventDefault(); }); + +// Reset status message on page reload +document.getElementById('statusMessage').value = ''; diff --git a/Trinity Account Creator/php/compat_random.php b/Trinity Account Creator/php/compat_random.php new file mode 100644 index 0000000..5e8a08b --- /dev/null +++ b/Trinity Account Creator/php/compat_random.php @@ -0,0 +1,99 @@ +GetRandom($b,0)); + $rand = ($rand !== false) ? str_pad($rand, $b, chr(0)) : false; + return ($rand !== false && bytelen($rand) == $b) ? $rand : null; + } + } + } catch (Exception $e) { } + } + + if (!is_callable('random_bytes')) { + if (is_callable('gmp_random_bits')) { + function random_bytes($b) { + $rand = ''; + for ($i = 1; $i <= $b; $i++) { + $rand .= chr(gmp_intval(gmp_random_bits(8))); + } + return ($rand !== false && bytelen($rand) == $b) ? $rand : null; + } + } else if (is_callable('mt_rand')) { + function random_bytes($b) { + $rand = ''; + for ($i = 1; $i <= $b; $i++) { + $rand .= chr(mt_rand(0,255)); + } + return ($rand !== false && bytelen($rand) == $b) ? $rand : null; + } + } else { + function random_bytes($b) { + $rand = ''; + for ($i = 1; $i <= $b; $i++) { + $rand .= chr(rand(0,255)); + } + return ($rand !== false && bytelen($rand) == $b) ? $rand : null; + } + } + } +} + +if (!is_callable('random_int')) { + if (is_callable('gmp_random_range')) { + function random_int($min, $max) { + return gmp_intval(gmp_random_range($min, $max)); + } + } elseif (is_callable('mt_rand')) { + function random_int($min, $max) { + mt_srand(random_bytes(3)); + return mt_rand($min, $max); + } + } elseif (is_callable('rand')) { + function random_int($min, $max) { + srand(random_bytes(3)); + return rand($min, $max); + } + } +} + +?> diff --git a/Trinity Account Creator/php/createAccount.php b/Trinity Account Creator/php/createAccount.php index 30db93d..04b9546 100644 --- a/Trinity Account Creator/php/createAccount.php +++ b/Trinity Account Creator/php/createAccount.php @@ -1,83 +1,123 @@ isOpen()) { + echo "2"; // Connection failed + return; + } + // Get POST data and validate. if ($_SERVER["REQUEST_METHOD"] == "POST") { - $username = validateInput($_POST['username']); - $email = validateInput($_POST['email']); - $password = validateInput($_POST['password']); - } - - if (!isset($username) || !is_string($username)) - throw new InvalidArgumentException("Username is invalid or empty."); - - if (!isset($password) || !is_string($password)) - throw new InvalidArgumentException("Password is invalid or empty."); - - if (!isset($email)) - throw new InvalidArgumentException("Email is empty."); - + $username = trim($_POST['username']); + $email = trim($_POST['email']); + $password = $_POST['password']; + } + + if (!isset($username) || !is_string($username) || empty($username)) { + echo "3"; // Username is empty. + return; + } + $username = validateInput($username); + if (!isset($username)) { + echo "4"; // Username is invalid. + return; + } + + // username has 16 byte limit on TC server + if (strlen($username) > 16) { + echo "5"; // Username is too long. + return; + } + + if (!isset($password) || !is_string($password) || empty($password)) { + echo "6"; // Password is empty. + return; + } + + // password has a 16 character limit on 3.3.5.12340 client even when SRP6 does not have such limitation + if (strlen($password) > 64 || iconv_strlen($password, 'utf-8') > 16) { + echo "7"; // Password is too long. + return; + } + + if (!isset($email)) { + echo "8"; // Email is empty. + return; + } + if (strlen($email) > 255) { + echo "9"; // Email is invalid. + return; + } if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { - echo "1"; // Returns that email is invalid, to update status message. + echo "9"; // Email is invalid. return; } + $username = $db->strtoupper_az($username); + $email = $db->strtoupper_az($email); + try { - // First, we need to check if the account name already exists. $accountCheckQuery = "SELECT * FROM account WHERE username = ?"; $accountCheckParams = array($username); - + $results = $db->queryMultiRow($accountCheckQuery, $accountCheckParams); - + if ($db->getRowCount($results) > 0) { - // Account already exists, inform user and stop transaction. - echo "2"; + echo "1"; // Close connection to the database. $db->close(); return; } - + // If no account exists, create a new one. - - // Get the SHA1 encrypted password. + + // Get the SRP6 salt and verifier tokens list($salt, $verifier) = $db->getRegistrationData($username, $password); - - $accountCreateQuery = "INSERT INTO account(username, salt, verifier, email) VALUES(?, ?, ?, ?)"; - $accountCreateParams = array($username, $salt, $verifier, $email); - + + $accountCreateQuery = "INSERT INTO account(username, salt, verifier, reg_mail, email) VALUES(?, ?, ?, ?, ?)"; + $accountCreateParams = array($username, $salt, $verifier, $email, $email); + // Execute the query. $db->insertQuery($accountCreateQuery, $accountCreateParams); - + // Close connection to the database. $db->close(); - + + //error_log("Account created: '" . $username . "' '". $email . "'"); + // Return successful to AJAX call. - echo "0"; - + echo "0"; // Account created successfully! } catch(PDOException $e) { - echo "3"; // Update status message with unknown error occurred. - error_log("PDO Database error occurred: " . $e->getMessage()); + echo "-1"; // Unknown error occured. + error_log("Database error: " . $e->getMessage()); } catch (Exception $e) { - echo "3"; // Update status message with unknown error occurred. - error_log("Unknown error occurred: " . $e->getMessage()); + echo "-1"; // Unknown error occured. + error_log("Unknown error: " . $e->getMessage()); } - + // Validates POST input data. - function validateInput($param) { - $param = trim($param); - $param = stripslashes($param); - $param = htmlspecialchars($param); - - return $param; - } + function validateInput($param) { + $valid = stripslashes($param); + $valid = htmlspecialchars($valid, ENT_QUOTES); + $valid = preg_replace('/\s+/', '', $valid); + return ($param == $valid) ? $param : null; + } ?> diff --git a/Trinity Account Creator/php/db.php b/Trinity Account Creator/php/db.php index 9e2dd11..44ee5d3 100644 --- a/Trinity Account Creator/php/db.php +++ b/Trinity Account Creator/php/db.php @@ -1,36 +1,45 @@ host;dbname=$this->dbname", $this->username, $this->password); - - // Set the PDO error mode to Exception. - $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - + $options = array( + PDO::ATTR_TIMEOUT => 5, // Timeout is 5 seconds + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION // Set PDO error mode to Exception. + ); + if (version_compare(PHP_VERSION, "5.3.6", "<")) { + $connection = new PDO("mysql:host=$this->host;port=$this->port;dbname=$this->dbname", $this->username, $this->password, $options); + $connection->exec('SET NAMES utf8'); + } + else { + $connection = new PDO("mysql:host=$this->host;port=$this->port;dbname=$this->dbname;charset=utf8", $this->username, $this->password, $options); + } + $this->conn = $connection; } catch (PDOException $e) { - echo "Connection failed: " . $e->getMessage(); + error_log("Connection failed: " . $e->getMessage()); } } - + // Executes an INSERT query on the database. public function insertQuery($query, $params) { if ($query) { @@ -39,54 +48,63 @@ public function insertQuery($query, $params) { $stmt->execute($params); } catch (PDOException $e) { - error_log("Error when inserting a new account: " . $e->getMessage()); + throw $e; } } } - + // Fetches and returns the next row from the result set. public function querySingleRow($query, $params) { if ($query) { - $stmt = $this->conn->prepare($query); - $stmt->execute($params); - - $row = $stmt->fetch(PDO::FETCH_ASSOC); - - return $row; + try { + $stmt = $this->conn->prepare($query); + $stmt->execute($params); + + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + return $row; + } + catch (PDOException $e) { + throw $e; + } } else return null; } - + // Returns an array containing all of the result set rows. public function queryMultiRow($query, $params) { if ($query) { - $stmt = $this->conn->prepare($query); - $stmt->execute($params); - - $results = $stmt->fetchAll(PDO::FETCH_ASSOC); - - return $results; + try { + $stmt = $this->conn->prepare($query); + $stmt->execute($params); + + $results = $stmt->fetchAll(PDO::FETCH_ASSOC); + + return $results; + } + catch (PDOException $e) { + throw $e; + } } else return null; } - + // Returns the row count from a PDO result set. public function getRowCount($results) { - // We can use the count() function here, because the result set is either // an associative or numerical array. if ($results) { return count($results); } } - + // Returns the last inserted row or sequence. public function getLastInsertId() { return $this->conn->lastInsertId(); } - + private function calculateSRP6Verifier($username, $password, $salt) { // algorithm constants @@ -94,7 +112,7 @@ private function calculateSRP6Verifier($username, $password, $salt) $N = gmp_init('894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7', 16); // calculate first hash - $h1 = sha1(strtoupper($username . ':' . $password), TRUE); + $h1 = sha1($this->strtoupper_az($username . ':' . $password), TRUE); // calculate second hash $h2 = sha1($salt.$h1, TRUE); @@ -114,25 +132,33 @@ private function calculateSRP6Verifier($username, $password, $salt) // done! return $verifier; } - + // Returns SRP6 parameters to register this username/password combination with public function getRegistrationData($username, $password) { // generate a random salt $salt = random_bytes(32); - + // calculate verifier using this salt $verifier = $this->calculateSRP6Verifier($username, $password, $salt); // done - this is what you put in the account table! return array($salt, $verifier); } - + + public function strtoupper_az($str) { + //return preg_replace_callback('/[a-z]/',function($matches){return strtoupper($matches[0]);},$str); + return preg_replace_callback('/[a-z]/',function($matches){return chr(ord($matches[0])-32);},$str); + } + + public function isOpen() { + return ($this->conn != null); + } + // Close the database connection. public function close() { $this->conn = null; } - } ?> diff --git a/Trinity Account Creator/php/vars.php b/Trinity Account Creator/php/vars.php new file mode 100644 index 0000000..9a1d6ae --- /dev/null +++ b/Trinity Account Creator/php/vars.php @@ -0,0 +1,55 @@ +