diff --git a/emhttp/plugins/dynamix/include/.login.php b/emhttp/plugins/dynamix/include/.login.php index 73b6b554b..e6367dcbd 100644 --- a/emhttp/plugins/dynamix/include/.login.php +++ b/emhttp/plugins/dynamix/include/.login.php @@ -2,10 +2,12 @@ // Included in login.php // Only start a session to check if they have a cookie that looks like our session -$server_name = strtok($_SERVER['HTTP_HOST'],":"); +$server_name = strtok($_SERVER['HTTP_HOST'], ":"); if (!empty($_COOKIE['unraid_'.md5($server_name)])) { // Start the session so we can check if $_SESSION has data - if (session_status()==PHP_SESSION_NONE) session_start(); + if (session_status() == PHP_SESSION_NONE) { + session_start(); + } // Check if the user is already logged in if ($_SESSION && !empty($_SESSION['unraid_user'])) { @@ -15,10 +17,11 @@ if (!empty($_COOKIE['unraid_'.md5($server_name)])) { } } -function readFromFile($file): string { +function readFromFile($file): string +{ $text = ""; if (file_exists($file) && filesize($file) > 0) { - $fp = fopen($file,"r"); + $fp = fopen($file, "r"); if (flock($fp, LOCK_EX)) { $text = fread($fp, filesize($file)); flock($fp, LOCK_UN); @@ -28,8 +31,9 @@ function readFromFile($file): string { return $text; } -function appendToFile($file, $text): void { - $fp = fopen($file,"a"); +function appendToFile($file, $text): void +{ + $fp = fopen($file, "a"); if (flock($fp, LOCK_EX)) { fwrite($fp, $text); fflush($fp); @@ -38,8 +42,9 @@ function appendToFile($file, $text): void { } } -function writeToFile($file, $text): void { - $fp = fopen($file,"w"); +function writeToFile($file, $text): void +{ + $fp = fopen($file, "w"); if (flock($fp, LOCK_EX)) { fwrite($fp, $text); fflush($fp); @@ -56,7 +61,8 @@ function isValidTimeStamp($timestamp) && ($timestamp >= ~PHP_INT_MAX); } -function cleanupFails(string $failFile, int $time): int { +function cleanupFails(string $failFile, int $time): int +{ global $cooldown; // Read existing fails @@ -67,8 +73,8 @@ function cleanupFails(string $failFile, int $time): int { // Remove entries older than $cooldown minutes, and entries that are not timestamps $updateFails = false; foreach ((array) $fails as $key => $value) { - if ( !isValidTimeStamp($value) || ($time - $value > $cooldown) || ($value > $time) ) { - unset ($fails[$key]); + if (!isValidTimeStamp($value) || ($time - $value > $cooldown) || ($value > $time)) { + unset($fails[$key]); $updateFails = true; } } @@ -81,94 +87,19 @@ function cleanupFails(string $failFile, int $time): int { return count($fails); } -function verifyUsernamePassword(string $username, string $password): bool { - if ($username != "root") return false; +function verifyUsernamePassword(string $username, string $password): bool +{ + if ($username != "root") { + return false; + } $output = exec("/usr/bin/getent shadow $username"); - if ($output === false) return false; + if ($output === false) { + return false; + } $credentials = explode(":", $output); return password_verify($password, $credentials[1]); } - -function verifyTwoFactorToken(string $username, string $token): bool { - try { - // Create curl client - $curlClient = curl_init(); - curl_setopt($curlClient, CURLOPT_HEADER, true); - curl_setopt($curlClient, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curlClient, CURLOPT_UNIX_SOCKET_PATH, '/var/run/unraid-api.sock'); - curl_setopt($curlClient, CURLOPT_URL, 'http://unixsocket/verify'); - curl_setopt($curlClient, CURLOPT_BUFFERSIZE, 256); - curl_setopt($curlClient, CURLOPT_TIMEOUT, 5); - curl_setopt($curlClient, CURLOPT_HTTPHEADER, array('Content-Type:application/json', 'Origin: /var/run/unraid-notifications.sock')); - curl_setopt($curlClient, CURLOPT_POSTFIELDS, json_encode([ - 'username' => $username, - 'token' => $token - ])); - - // Send the request - curl_exec($curlClient); - - // Get the http status code - $httpCode = curl_getinfo($curlClient, CURLINFO_HTTP_CODE); - - // Close the connection - curl_close($curlClient); - - // Error - // This should accept 200 or 204 status codes - if ($httpCode !== 200 && $httpCode !== 204) { - // Log error to syslog - my_logger("2FA code for {$username} is invalid, blocking access!"); - return false; - } - - // Log success to syslog - my_logger("2FA code for {$username} is valid, allowing login!"); - - // Success - return true; - } catch (Exception $exception) { - // Error - return false; - } -} - -// Check if a haystack ends in a needle -function endsWith($haystack, $needle): bool { - return substr_compare($haystack, $needle, -strlen($needle)) === 0; -} - -// Check if we're accessing this via a wildcard cert -function isWildcardCert(): bool { - global $server_name; - return endsWith($server_name, '.myunraid.net'); -} - -// Check if we're accessing this locally via the expected myunraid.net url -function isLocalAccess(): bool { - global $nginx, $server_name; - return isWildcardCert() && $nginx['NGINX_LANFQDN'] === $server_name; -} - -// Check if we're accessing this remotely via the expected myunraid.net url -function isRemoteAccess(): bool { - global $nginx, $server_name; - return isWildcardCert() && $nginx['NGINX_WANFQDN'] === $server_name; -} - -// Check if 2fa is enabled for local (requires USE_SSL to be "auto" so no alternate urls can access the server) -function isLocalTwoFactorEnabled(): bool { - global $nginx, $my_servers; - return $nginx['NGINX_USESSL'] === "auto" && ($my_servers['local']['2Fa']??'') === 'yes'; -} - -// Check if 2fa is enabled for remote -function isRemoteTwoFactorEnabled(): bool { - global $my_servers; - return ($my_servers['remote']['2Fa']??'') === 'yes'; -} - // Load configs into memory $my_servers = @parse_ini_file('/boot/config/plugins/dynamix.my.servers/myservers.cfg', true); $nginx = @parse_ini_file('/var/local/emhttp/nginx.ini'); @@ -180,38 +111,34 @@ $remote_addr = $_SERVER['REMOTE_ADDR'] ?? "unknown"; $failFile = "/var/log/pwfail/{$remote_addr}"; // Get the credentials -$username = $_POST['username']??''; -$password = $_POST['password']??''; -$token = $_REQUEST['token']??''; - -// Check if we need 2fa -$twoFactorRequired = (isLocalAccess() && isLocalTwoFactorEnabled()) || (isRemoteAccess() && isRemoteTwoFactorEnabled()); +$username = $_POST['username'] ?? ''; +$password = $_POST['password'] ?? ''; // If we have a username + password combo attempt to login if (!empty($username) && !empty($password)) { try { - // Bail if we're missing the 2FA token and we expect one - if (isWildcardCert() && $twoFactorRequired && empty($token)) throw new Exception(_('No 2FA token detected')); - // Read existing fails, cleanup expired ones $time = time(); $failCount = cleanupFails($failFile, $time); // Check if we're limited if ($failCount >= $maxFails) { - if ($failCount == $maxFails) my_logger("Ignoring login attempts for {$username} from {$remote_addr}"); + if ($failCount == $maxFails) { + my_logger("Ignoring login attempts for {$username} from {$remote_addr}"); + } throw new Exception(_('Too many invalid login attempts')); } // Bail if username + password combo doesn't work - if (!verifyUsernamePassword($username, $password)) throw new Exception(_('Invalid username or password')); - - // Bail if we need a token but it's invalid - if (isWildcardCert() && $twoFactorRequired && !verifyTwoFactorToken($username, $token)) throw new Exception(_('Invalid 2FA token')); + if (!verifyUsernamePassword($username, $password)) { + throw new Exception(_('Invalid username or password')); + } // Successful login, start session @unlink($failFile); - if (session_status()==PHP_SESSION_NONE) session_start(); + if (session_status() == PHP_SESSION_NONE) { + session_start(); + } $_SESSION['unraid_login'] = time(); $_SESSION['unraid_user'] = $username; session_regenerate_id(true); @@ -274,8 +201,8 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']); / /************************/ body { - background: ; - color: ; + background: ; + color: ; font-family: clear-sans, sans-serif; font-size: .875rem; padding: 0; @@ -359,7 +286,7 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']); width: 500px; margin: 6rem auto; border-radius: 10px; - background: ; + background: ; } #login::after { content: ""; @@ -392,7 +319,7 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']); } #login .error { color: red; - margin-top: -20px; + margin-top: 1rem; } #login .content { padding: 2rem; @@ -451,7 +378,7 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']); /************************/ @media (max-width: 500px) { body { - background: ; + background: ; } [type=email], [type=number], [type=password], [type=search], [type=tel], [type=text], [type=url], textarea { font-size: 16px; /* This prevents the mobile browser from zooming in on the input-field. */ @@ -483,7 +410,7 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']);

- +

@@ -497,82 +424,38 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']);
-
- -

- - - - - -

- $error

"; ?> -

- -

- - -
-

-
- -
-

-
- -
- -
- - + +

+ + +

+

+ +

+ +

+
- - - -
- -

- + - - - + +