Force creation of root password

This commit is contained in:
Tom Mortensen
2021-05-30 14:57:08 -07:00
parent 7c1d093b70
commit bba1d930a4
5 changed files with 860 additions and 409 deletions

415
login.php
View File

@@ -1,47 +1,10 @@
<?php
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "$docroot/webGui/include/Helpers.php";
function fileRead($file) {
$text = "";
if (file_exists($file)) {
$fp = fopen($file,"r");
if (flock($fp, LOCK_EX)) {
$text = fread($fp, filesize($file));
flock($fp, LOCK_UN);
fclose($fp);
}
}
return $text;
}
function fileAppend($file, $text) {
$fp = fopen($file,"a");
if (flock($fp, LOCK_EX)) {
fwrite($fp, $text);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
}
}
function fileWrite($file, $text) {
$fp = fopen($file,"w");
if (flock($fp, LOCK_EX)) {
fwrite($fp, $text);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
}
}
require_once "$docroot/webGui/include/Translations.php";
$var = parse_ini_file('state/var.ini');
$error = '';
$maxfails = 3;
$cooldown = 15*60;
$remote_addr = $_SERVER['REMOTE_ADDR'] ?? "unknown";
@mkdir("/var/log/pwfail/", 0755);
$failfile = "/var/log/pwfail/{$remote_addr}";
if ($_SERVER['REQUEST_URI'] == '/logout') {
// User Logout
if (isset($_COOKIE[session_name()])) {
@@ -54,376 +17,12 @@ if ($_SERVER['REQUEST_URI'] == '/logout') {
$params = session_get_cookie_params();
setcookie(session_name(), '', 0, '/', $params['domain'], $params['secure'], isset($params['httponly']));
}
$error = 'Successfully logged out';
} else if (!empty($_POST['username']) && !empty($_POST['password'])) {
$failtext = fileRead($failfile);
$fails = explode("\n", trim($failtext));
$time = time();
// remove entries older than $cooldown minutes
$updatefails = false;
foreach ((array) $fails as $key => $value) {
if ($value && $time - $value > $cooldown) {
unset ($fails[$key]);
$updatefails = true;
}
}
if ($updatefails) {
$failtext = implode("\n", $fails);
fileWrite($failfile, $failtext);
}
if (count($fails) >= $maxfails) {
$error = 'Too many invalid login attempts';
if (count($fails) == $maxfails)
exec("logger -t webGUI ".escapeshellarg("Ignoring login attempts for {$_POST['username']} from {$remote_addr}"));
} else {
// User Login attempt
foreach (file('/etc/nginx/htpasswd') as $strCredentials) {
list($user,$pwhash) = explode(':', trim($strCredentials));
// Validate credentials
if ($_POST['username'] == $user && password_verify($_POST['password'], $pwhash)) {
// Successful login, start session
@unlink($failfile);
session_start();
$_SESSION['unraid_login'] = time();
$_SESSION['unraid_user'] = $_POST['username'];
session_regenerate_id(true);
session_write_close();
exec("logger -t webGUI ".escapeshellarg("Successful login user {$_POST['username']} from {$remote_addr}"));
header("Location: /".$var['START_PAGE']);
exit;
}
}
// Invalid login
$error = 'Invalid Username or Password';
exec("logger -t webGUI ".escapeshellarg("Unsuccessful login user {$_POST['username']} from {$remote_addr}"));
}
fileAppend($failfile, $time."\n");
$error = _('Successfully logged out');
}
$boot = "/boot/config/plugins/dynamix";
$myfile = "case-model.cfg";
$mycase = file_exists("$boot/$myfile") ? file_get_contents("$boot/$myfile") : false;
extract(parse_plugin_cfg('dynamix',true));
$theme_dark = in_array($display['theme'],['black','gray']);
$result = exec( "/usr/bin/passwd --status root");
if (($result === false) || (substr($result, 0, 6) !== "root P"))
include("$docroot/webGui/include/set-password.php");
else
include("$docroot/webGui/include/login.php");
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
<meta name="referrer" content="same-origin">
<title><?=$var['NAME']?>/Login</title>
<style>
/************************
/
/ Fonts
/
/************************/
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-bold-italic.eot');
src: url('/webGui/styles/clear-sans-bold-italic.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-bold-italic.woff') format('woff'),
url('/webGui/styles/clear-sans-bold-italic.ttf') format('truetype'),
url('/webGui/styles/clear-sans-bold-italic.svg#clear_sansbold_italic') format('svg');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-bold.eot');
src: url('/webGui/styles/clear-sans-bold.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-bold.woff') format('woff'),
url('/webGui/styles/clear-sans-bold.ttf') format('truetype'),
url('/webGui/styles/clear-sans-bold.svg#clear_sansbold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-italic.eot');
src: url('/webGui/styles/clear-sans-italic.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-italic.woff') format('woff'),
url('/webGui/styles/clear-sans-italic.ttf') format('truetype'),
url('/webGui/styles/clear-sans-italic.svg#clear_sansitalic') format('svg');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans.eot');
src: url('/webGui/styles/clear-sans.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans.woff') format('woff'),
url('/webGui/styles/clear-sans.ttf') format('truetype'),
url('/webGui/styles/clear-sans.svg#clear_sansregular') format('svg');
font-weight: normal;
font-style: normal;
}
/************************
/
/ General styling
/
/************************/
body {
background: #<?=$theme_dark?'1C1B1B':'F2F2F2'?>;
color: #<?=$theme_dark?'fff':'1c1b1b'?>;
font-family: ClearSans, sans-serif;
font-size: .875rem;
padding: 0;
margin: 0;
}
a {
text-transform: uppercase;
font-weight: bold;
letter-spacing: 2px;
color: #FF8C2F;
text-decoration: none;
}
a:hover {
color: #f15a2c;
}
h1 {
font-size: 1.8em;
margin: 0;
}
h2 {
font-size: 0.8em;
margin-top: 0;
margin-bottom: 1.8em;
}
.button {
color: #ff8c2f;
font-family: ClearSans, sans-serif;
background: -webkit-gradient(linear,left top,right top,from(#e03237),to(#fd8c3c)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e03237),to(#fd8c3c)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e03237),to(#e03237)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#fd8c3c),to(#fd8c3c)) 100% 100% no-repeat;
background: linear-gradient(90deg,#e03237 0,#fd8c3c) 0 0 no-repeat,linear-gradient(90deg,#e03237 0,#fd8c3c) 0 100% no-repeat,linear-gradient(0deg,#e03237 0,#e03237) 0 100% no-repeat,linear-gradient(0deg,#fd8c3c 0,#fd8c3c) 100% 100% no-repeat;
background-size: 100% 2px,100% 2px,2px 100%,2px 100%;
}
.button:hover {
color: #fff;
background-color: #f15a2c;
background: -webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));
background: linear-gradient(90deg,#e22828 0,#ff8c2f);
-webkit-box-shadow: 0;
box-shadow: 0;
cursor: pointer;
}
.button--small {
font-size: .875rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 2px;
text-align: center;
text-decoration: none;
display: inline-block;
background-color: transparent;
border-radius: .125rem;
border: 0;
-webkit-transition: none;
transition: none;
padding: .75rem 1.5rem;
}
[type=email], [type=number], [type=password], [type=search], [type=tel], [type=text], [type=url], textarea {
font-family: ClearSans, sans-serif;
font-size: .875rem;
background-color: #F2F2F2;
width: 100%;
margin-bottom: 1rem;
border: 2px solid #ccc;
padding: .75rem 1rem;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border-radius: 0;
-webkit-appearance: none;
}
[type=email]:active, [type=email]:focus, [type=number]:active, [type=number]:focus, [type=password]:active, [type=password]:focus, [type=search]:active, [type=search]:focus, [type=tel]:active, [type=tel]:focus, [type=text]:active, [type=text]:focus, [type=url]:active, [type=url]:focus, textarea:active, textarea:focus {
border-color: #ff8c2f;
outline: none;
}
/************************
/
/ Login spesific styling
/
/************************/
#login {
width: 500px;
margin: 6rem auto;
border-radius: 10px;
background: #<?=$theme_dark?'2B2A29':'fff'?>;
}
#login .logo {
position: relative;
overflow: hidden;
height: 120px;
border-radius: 10px 10px 0 0;
}
#login .wordmark {
z-index: 1;
position: relative;
padding: 2rem;
}
#login .wordmark svg {
width: 100px;
}
#login .case {
float: right;
width: 30%;
font-size: 6rem;
text-align: center;
}
#login .case img {
max-width: 96px;
max-height: 96px;
}
#login .error {
color: red;
margin-top: -20px;
}
#login .content {
padding: 2rem;
}
#login .form {
width: 65%;
}
.angle:after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 120px;
background-color: #f15a2c;
background: -webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));
background: linear-gradient(90deg,#e22828 0,#ff8c2f);
-webkit-transform-origin: bottom left;
transform-origin: bottom left;
-webkit-transform: skewY(-6deg);
transform: skewY(-6deg);
-webkit-transition: -webkit-transform .15s linear;
transition: -webkit-transform .15s linear;
transition: transform .15s linear;
transition: transform .15s linear,-webkit-transform .15s linear;
}
.shadow {
-webkit-box-shadow: 0 2px 8px 0 rgba(0,0,0,.12);
box-shadow: 0 2px 8px 0 rgba(0,0,0,.12);
}
/************************
/
/ Cases
/
/************************/
[class^="case-"], [class*=" case-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'cases' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/************************
/
/ Media queries for mobile responsive
/
/************************/
@media (max-width: 500px) {
body {
background: #<?=$theme_dark?'2B2A29':'fff'?>;
}
[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. */
}
#login {
margin: 0;
border-radius: 0;
width: 100%;
}
#login .logo {
border-radius: 0;
}
.shadow {
box-shadow: none;
}
}
</style>
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-cases.css")?>">
<link type="image/png" rel="shortcut icon" href="/webGui/images/<?=$var['mdColor']?>.png">
</head>
<body>
<section id="login" class="shadow">
<div class="logo angle">
<div class="wordmark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222.4 39" class="Nav__logo--white"><path fill="#ffffff" d="M146.70000000000002 29.5H135l-3 9h-6.5L138.9 0h8l13.4 38.5h-7.1L142.6 6.9l-5.8 16.9h8.2l1.7 5.7zM29.7 0v25.4c0 8.9-5.8 13.6-14.9 13.6C5.8 39 0 34.3 0 25.4V0h6.5v25.4c0 5.2 3.2 7.9 8.2 7.9 5.2 0 8.4-2.7 8.4-7.9V0h6.6zM50.9 12v26.5h-6.5V0h6.1l17 26.5V0H74v38.5h-6.1L50.9 12zM171.3 0h6.5v38.5h-6.5V0zM222.4 24.7c0 9-5.9 13.8-15.2 13.8h-14.5V0h14.6c9.2 0 15.1 4.8 15.1 13.8v10.9zm-6.6-10.9c0-5.3-3.3-8.1-8.5-8.1h-8.1v27.1h8c5.3 0 8.6-2.8 8.6-8.1V13.8zM108.3 23.9c4.3-1.6 6.9-5.3 6.9-11.5 0-8.7-5.1-12.4-12.8-12.4H88.8v38.5h6.5V5.7h6.9c3.8 0 6.2 1.8 6.2 6.7s-2.4 6.8-6.2 6.8h-3.4l9.2 19.4h7.5l-7.2-14.7z"></path></svg></div>
</div>
<div class="content">
<h1>
<?=$var['NAME']?>
</h1>
<h2>
<?=$var['COMMENT']?>
</h2>
<div class="case">
<?if ($mycase):?>
<?if (substr($mycase,-4)!='.png'):?>
<span class='case-<?=$mycase?>'></span>
<?else:?>
<img src='<?=autov("/webGui/images/$mycase")?>'>
<?endif;?>
<?endif;?>
</div>
<div class="form">
<form action="/login" method="POST">
<p>
<input name="username" type="text" placeholder="Username" autocapitalize="none" autocomplete="off" autofocus required>
<input name="password" type="password" placeholder="Password" required>
</p>
<? if ($error) echo '<p class="error">'.$error.'</p>'; ?>
<script type="text/javascript">
document.cookie = "cookietest=1";
cookieEnabled = document.cookie.indexOf("cookietest=")!=-1;
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
if (!cookieEnabled) {
document.write('<p class="error">Browser cookie support required for login</p>');
}
</script>
<p>
<button type="submit" class="button button--small">Login</button>
</p>
</form>
</div>
<p><a href="https://wiki.unraid.net/Unraid_6/Troubleshooting#Lost_root_Password" target="_blank">Password recovery</a></p>
</div>
</section>
</body>
</html>

View File

@@ -1,4 +1,3 @@
Cond="file_exists('/etc/nginx/htpasswd')"
Menu="Buttons:2"
Title="Logout"
Icon="icon-u-logout"

View File

@@ -0,0 +1,413 @@
<?php
// incuded in login.php
function fileRead($file) {
$text = "";
if (file_exists($file)) {
$fp = fopen($file,"r");
if (flock($fp, LOCK_EX)) {
$text = fread($fp, filesize($file));
flock($fp, LOCK_UN);
fclose($fp);
}
}
return $text;
}
function fileAppend($file, $text) {
$fp = fopen($file,"a");
if (flock($fp, LOCK_EX)) {
fwrite($fp, $text);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
}
}
function fileWrite($file, $text) {
$fp = fopen($file,"w");
if (flock($fp, LOCK_EX)) {
fwrite($fp, $text);
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
}
}
$maxfails = 3;
$cooldown = 15*60;
$remote_addr = $_SERVER['REMOTE_ADDR'] ?? "unknown";
$failfile = "/var/log/pwfail/{$remote_addr}";
if (!empty($_POST['username']) && !empty($_POST['password'])) {
@mkdir("/var/log/pwfail/", 0755);
$failtext = fileRead($failfile);
$fails = explode("\n", trim($failtext));
$time = time();
// remove entries older than $cooldown minutes
$updatefails = false;
foreach ((array) $fails as $key => $value) {
if ($value && $time - $value > $cooldown) {
unset ($fails[$key]);
$updatefails = true;
}
}
if ($updatefails) {
$failtext = implode("\n", $fails);
fileWrite($failfile, $failtext);
}
if (count($fails) >= $maxfails) {
$error = _('Too many invalid login attempts');
if (count($fails) == $maxfails)
exec("logger -t webGUI ".escapeshellarg("Ignoring login attempts for {$_POST['username']} from {$remote_addr}"));
} else {
// User Login attempt, validate credentials
if (($_POST['username'] == "root")) {
// more: integrate with PAM to avoid direct access to /etc/shadow and validate other user names (future)
$output = exec("/usr/bin/grep root /etc/shadow");
if ($output !== false) {
$strCredentials = explode(":", $output);
if (password_verify($_POST['password'], $strCredentials[1])) {
// Successful login, start session
@unlink($failfile);
session_start();
$_SESSION['unraid_login'] = time();
$_SESSION['unraid_user'] = $_POST['username'];
session_regenerate_id(true);
session_write_close();
exec("logger -t webGUI ".escapeshellarg("Successful login user {$_POST['username']} from {$remote_addr}"));
header("Location: /".$var['START_PAGE']);
exit;
}
}
}
// Invalid login
$error = _('Invalid Username or Password');
exec("logger -t webGUI ".escapeshellarg("Unsuccessful login user {$_POST['username']} from {$remote_addr}"));
}
fileAppend($failfile, $time."\n");
}
$boot = "/boot/config/plugins/dynamix";
$myfile = "case-model.cfg";
$mycase = file_exists("$boot/$myfile") ? file_get_contents("$boot/$myfile") : false;
extract(parse_plugin_cfg('dynamix',true));
$theme_dark = in_array($display['theme'],['black','gray']);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
<meta name="referrer" content="same-origin">
<title><?=$var['NAME']?>/Login</title>
<style>
/************************
/
/ Fonts
/
/************************/
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-bold-italic.eot');
src: url('/webGui/styles/clear-sans-bold-italic.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-bold-italic.woff') format('woff'),
url('/webGui/styles/clear-sans-bold-italic.ttf') format('truetype'),
url('/webGui/styles/clear-sans-bold-italic.svg#clear_sansbold_italic') format('svg');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-bold.eot');
src: url('/webGui/styles/clear-sans-bold.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-bold.woff') format('woff'),
url('/webGui/styles/clear-sans-bold.ttf') format('truetype'),
url('/webGui/styles/clear-sans-bold.svg#clear_sansbold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-italic.eot');
src: url('/webGui/styles/clear-sans-italic.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-italic.woff') format('woff'),
url('/webGui/styles/clear-sans-italic.ttf') format('truetype'),
url('/webGui/styles/clear-sans-italic.svg#clear_sansitalic') format('svg');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans.eot');
src: url('/webGui/styles/clear-sans.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans.woff') format('woff'),
url('/webGui/styles/clear-sans.ttf') format('truetype'),
url('/webGui/styles/clear-sans.svg#clear_sansregular') format('svg');
font-weight: normal;
font-style: normal;
}
/************************
/
/ General styling
/
/************************/
body {
background: #<?=$theme_dark?'1C1B1B':'F2F2F2'?>;
color: #<?=$theme_dark?'fff':'1c1b1b'?>;
font-family: ClearSans, sans-serif;
font-size: .875rem;
padding: 0;
margin: 0;
}
a {
text-transform: uppercase;
font-weight: bold;
letter-spacing: 2px;
color: #FF8C2F;
text-decoration: none;
}
a:hover {
color: #f15a2c;
}
h1 {
font-size: 1.8em;
margin: 0;
}
h2 {
font-size: 0.8em;
margin-top: 0;
margin-bottom: 1.8em;
}
.button {
color: #ff8c2f;
font-family: ClearSans, sans-serif;
background: -webkit-gradient(linear,left top,right top,from(#e03237),to(#fd8c3c)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e03237),to(#fd8c3c)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e03237),to(#e03237)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#fd8c3c),to(#fd8c3c)) 100% 100% no-repeat;
background: linear-gradient(90deg,#e03237 0,#fd8c3c) 0 0 no-repeat,linear-gradient(90deg,#e03237 0,#fd8c3c) 0 100% no-repeat,linear-gradient(0deg,#e03237 0,#e03237) 0 100% no-repeat,linear-gradient(0deg,#fd8c3c 0,#fd8c3c) 100% 100% no-repeat;
background-size: 100% 2px,100% 2px,2px 100%,2px 100%;
}
.button:hover {
color: #fff;
background-color: #f15a2c;
background: -webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));
background: linear-gradient(90deg,#e22828 0,#ff8c2f);
-webkit-box-shadow: 0;
box-shadow: 0;
cursor: pointer;
}
.button--small {
font-size: .875rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 2px;
text-align: center;
text-decoration: none;
display: inline-block;
background-color: transparent;
border-radius: .125rem;
border: 0;
-webkit-transition: none;
transition: none;
padding: .75rem 1.5rem;
}
[type=email], [type=number], [type=password], [type=search], [type=tel], [type=text], [type=url], textarea {
font-family: ClearSans, sans-serif;
font-size: .875rem;
background-color: #F2F2F2;
width: 100%;
margin-bottom: 1rem;
border: 2px solid #ccc;
padding: .75rem 1rem;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border-radius: 0;
-webkit-appearance: none;
}
[type=email]:active, [type=email]:focus, [type=number]:active, [type=number]:focus, [type=password]:active, [type=password]:focus, [type=search]:active, [type=search]:focus, [type=tel]:active, [type=tel]:focus, [type=text]:active, [type=text]:focus, [type=url]:active, [type=url]:focus, textarea:active, textarea:focus {
border-color: #ff8c2f;
outline: none;
}
/************************
/
/ Login spesific styling
/
/************************/
#login {
width: 500px;
margin: 6rem auto;
border-radius: 10px;
background: #<?=$theme_dark?'2B2A29':'fff'?>;
}
#login .logo {
position: relative;
overflow: hidden;
height: 120px;
border-radius: 10px 10px 0 0;
}
#login .wordmark {
z-index: 1;
position: relative;
padding: 2rem;
}
#login .wordmark svg {
width: 100px;
}
#login .case {
float: right;
width: 30%;
font-size: 6rem;
text-align: center;
}
#login .case img {
max-width: 96px;
max-height: 96px;
}
#login .error {
color: red;
margin-top: -20px;
}
#login .content {
padding: 2rem;
}
#login .form {
width: 65%;
}
.angle:after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 120px;
background-color: #f15a2c;
background: -webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));
background: linear-gradient(90deg,#e22828 0,#ff8c2f);
-webkit-transform-origin: bottom left;
transform-origin: bottom left;
-webkit-transform: skewY(-6deg);
transform: skewY(-6deg);
-webkit-transition: -webkit-transform .15s linear;
transition: -webkit-transform .15s linear;
transition: transform .15s linear;
transition: transform .15s linear,-webkit-transform .15s linear;
}
.shadow {
-webkit-box-shadow: 0 2px 8px 0 rgba(0,0,0,.12);
box-shadow: 0 2px 8px 0 rgba(0,0,0,.12);
}
/************************
/
/ Cases
/
/************************/
[class^="case-"], [class*=" case-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'cases' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/************************
/
/ Media queries for mobile responsive
/
/************************/
@media (max-width: 500px) {
body {
background: #<?=$theme_dark?'2B2A29':'fff'?>;
}
[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. */
}
#login {
margin: 0;
border-radius: 0;
width: 100%;
}
#login .logo {
border-radius: 0;
}
.shadow {
box-shadow: none;
}
}
</style>
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/default-cases.css")?>">
<link type="image/png" rel="shortcut icon" href="/webGui/images/<?=$var['mdColor']?>.png">
</head>
<body>
<section id="login" class="shadow">
<div class="logo angle">
<div class="wordmark"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222.4 39" class="Nav__logo--white"><path fill="#ffffff" d="M146.70000000000002 29.5H135l-3 9h-6.5L138.9 0h8l13.4 38.5h-7.1L142.6 6.9l-5.8 16.9h8.2l1.7 5.7zM29.7 0v25.4c0 8.9-5.8 13.6-14.9 13.6C5.8 39 0 34.3 0 25.4V0h6.5v25.4c0 5.2 3.2 7.9 8.2 7.9 5.2 0 8.4-2.7 8.4-7.9V0h6.6zM50.9 12v26.5h-6.5V0h6.1l17 26.5V0H74v38.5h-6.1L50.9 12zM171.3 0h6.5v38.5h-6.5V0zM222.4 24.7c0 9-5.9 13.8-15.2 13.8h-14.5V0h14.6c9.2 0 15.1 4.8 15.1 13.8v10.9zm-6.6-10.9c0-5.3-3.3-8.1-8.5-8.1h-8.1v27.1h8c5.3 0 8.6-2.8 8.6-8.1V13.8zM108.3 23.9c4.3-1.6 6.9-5.3 6.9-11.5 0-8.7-5.1-12.4-12.8-12.4H88.8v38.5h6.5V5.7h6.9c3.8 0 6.2 1.8 6.2 6.7s-2.4 6.8-6.2 6.8h-3.4l9.2 19.4h7.5l-7.2-14.7z"></path></svg></div>
</div>
<div class="content">
<h1>
<?=$var['NAME']?>
</h1>
<h2>
<?=$var['COMMENT']?>
</h2>
<div class="case">
<?if ($mycase):?>
<?if (substr($mycase,-4)!='.png'):?>
<span class='case-<?=$mycase?>'></span>
<?else:?>
<img src='<?=autov("/webGui/images/$mycase")?>'>
<?endif;?>
<?endif;?>
</div>
<div class="form">
<form action="/login" method="POST">
<p>
<input name="username" type="text" placeholder="<?= _('Username') ?>" autocapitalize="none" autocomplete="off" autofocus required>
<input name="password" type="password" placeholder="<?= _('Password') ?>" required>
</p>
<? if ($error) echo '<p class="error">'.$error.'</p>'; ?>
<script type="text/javascript">
document.cookie = "cookietest=1";
cookieEnabled = document.cookie.indexOf("cookietest=")!=-1;
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
if (!cookieEnabled) {
document.write('<p class="error">Browser cookie support required for login</p>');
}
</script>
<p>
<button type="submit" class="button button--small"><?= _('Login') ?></button>
</p>
</form>
</div>
<p><a href="https://wiki.unraid.net/Manual/Troubleshooting#Lost_root_Password" target="_blank"><?= _('Password recovery') ?></a></p>
</div>
</section>
</body>
</html>

View File

@@ -0,0 +1,440 @@
<?php
// included in login.php
$REMOTE_ADDR = $_SERVER['REMOTE_ADDR'] ?? "unknown";
$MAX_PASS_LENGTH = 128;
$VALIDATION_MESSAGES = [
'empty' => _('root requires a password'),
'mismatch' => _('Password confirmation does not match'),
'maxLength' => _('Max password length is 128 characters'),
'saveError' => _('Unable to set password'),
];
$POST_ERROR = '';
/**
* POST handler
*/
if (!empty($_POST['password']) && !empty($_POST['confirmPassword'])) {
if ($_POST['password'] !== $_POST['confirmPassword']) return $POST_ERROR = $VALIDATION_MESSAGES['mismatch'];
if (strlen($_POST['password']) > $MAX_PASS_LENGTH) return $POST_ERROR = $VALIDATION_MESSAGES['maxLength'];
$userName = 'root';
$userPassword = base64_encode($_POST['password']);
$result = exec("/usr/local/sbin/emcmd 'cmdUserEdit=Change&userName=$userName&userPassword=$userPassword'");
if ($result == 0) {
// PAM service will log to syslog: "password changed for root"
session_start();
$_SESSION['unraid_login'] = time();
$_SESSION['unraid_user'] = 'root';
session_regenerate_id(true);
session_write_close();
header("Location: /".$var['START_PAGE']);
exit;
}
// Error when attempting to set password
exec("logger -t webGUI ".escapeshellarg($VALIDATION_MESSAGES['saveError'] . " [REMOTE_ADDR]: {$REMOTE_ADDR}"));
return $POST_ERROR = $VALIDATION_MESSAGES['saveError'];
}
extract(parse_plugin_cfg('dynamix',true));
$THEME_DARK = in_array($display['theme'],['black','gray']);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
<meta name="referrer" content="same-origin">
<title><?=$var['NAME']?>/<? _('SetPassword') ?></title>
<link rel="icon" href="webGui/images/animated-logo.svg" sizes="any" type="image/svg+xml">
<style>
/************************
/
/ Fonts
/
/************************/
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-bold-italic.eot');
src: url('/webGui/styles/clear-sans-bold-italic.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-bold-italic.woff') format('woff'),
url('/webGui/styles/clear-sans-bold-italic.ttf') format('truetype'),
url('/webGui/styles/clear-sans-bold-italic.svg#clear_sansbold_italic') format('svg');
font-weight: bold;
font-style: italic;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-bold.eot');
src: url('/webGui/styles/clear-sans-bold.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-bold.woff') format('woff'),
url('/webGui/styles/clear-sans-bold.ttf') format('truetype'),
url('/webGui/styles/clear-sans-bold.svg#clear_sansbold') format('svg');
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans-italic.eot');
src: url('/webGui/styles/clear-sans-italic.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans-italic.woff') format('woff'),
url('/webGui/styles/clear-sans-italic.ttf') format('truetype'),
url('/webGui/styles/clear-sans-italic.svg#clear_sansitalic') format('svg');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'ClearSans';
src: url('/webGui/styles/clear-sans.eot');
src: url('/webGui/styles/clear-sans.eot?#iefix') format('embedded-opentype'),
url('/webGui/styles/clear-sans.woff') format('woff'),
url('/webGui/styles/clear-sans.ttf') format('truetype'),
url('/webGui/styles/clear-sans.svg#clear_sansregular') format('svg');
font-weight: normal;
font-style: normal;
}
/************************
/
/ General styling
/
/************************/
:root {
--body-bg: <?= $THEME_DARK ? '#1c1b1b' : '#f2f2f2' ?>;
--body-text-color: <?= $THEME_DARK ? '#fff' : '#1c1b1b' ?>;
--section-bg: <?= $THEME_DARK ? '#1c1b1b' : '#f2f2f2' ?>;
--shadow: <?= $THEME_DARK ? 'rgba(115,115,115,.12)' : 'rgba(0,0,0,.12)' ?>;
--form-text-color: <?= $THEME_DARK ? '#f2f2f2' : '#1c1b1b' ?>;
--form-bg-color: <?= $THEME_DARK ? 'rgba(26,26,26,0.4)' : '#f2f2f2' ?>;
--form-border-color: <?= $THEME_DARK ? '#2B2A29' : '#ccc' ?>;
}
body {
background: var(--body-bg);
color: var(--body-text-color);
font-family: ClearSans, sans-serif;
font-size: .875rem;
padding: 0;
margin: 0;
}
a {
text-transform: uppercase;
font-weight: bold;
letter-spacing: 2px;
color: #FF8C2F;
text-decoration: none;
}
a:hover {
color: #f15a2c;
}
h1 {
font-size: 1.8rem;
margin: 0;
}
h2 {
font-size: .8rem;
margin-top: 0;
margin-bottom: 1em;
}
.button {
color: #ff8c2f;
font-family: ClearSans, sans-serif;
background: -webkit-gradient(linear,left top,right top,from(#e03237),to(#fd8c3c)) 0 0 no-repeat,-webkit-gradient(linear,left top,right top,from(#e03237),to(#fd8c3c)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#e03237),to(#e03237)) 0 100% no-repeat,-webkit-gradient(linear,left bottom,left top,from(#fd8c3c),to(#fd8c3c)) 100% 100% no-repeat;
background: linear-gradient(90deg,#e03237 0,#fd8c3c) 0 0 no-repeat,linear-gradient(90deg,#e03237 0,#fd8c3c) 0 100% no-repeat,linear-gradient(0deg,#e03237 0,#e03237) 0 100% no-repeat,linear-gradient(0deg,#fd8c3c 0,#fd8c3c) 100% 100% no-repeat;
background-size: 100% 2px,100% 2px,2px 100%,2px 100%;
}
.button:disabled {
opacity: .5;
cursor: not-allowed;
}
.button:hover,
.button:focus {
color: #fff;
background-color: #f15a2c;
background: -webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));
background: linear-gradient(90deg,#e22828 0,#ff8c2f);
-webkit-box-shadow: 0;
box-shadow: 0;
cursor: pointer;
outline: none;
}
.button--small {
font-size: .875rem;
font-weight: 600;
line-height: 1;
text-transform: uppercase;
letter-spacing: 2px;
text-align: center;
text-decoration: none;
display: inline-block;
background-color: transparent;
border-radius: .125rem;
border: 0;
-webkit-transition: none;
transition: none;
padding: .75rem 1.5rem;
}
[type=password],
[type=text] {
color: var(--form-text-color);
font-family: ClearSans, sans-serif;
font-size: .875rem;
background-color: var(--form-bg-color);
width: 100%;
margin-top: .25rem;
margin-bottom: 1rem;
border: 2px solid var(--form-border-color);
padding: .75rem 1rem;
-webkit-box-sizing: border-box;
box-sizing: border-box;
border-radius: 0;
-webkit-appearance: none;
}
[type=password]:focus,
[type=text]:focus {
border-color: #ff8c2f;
outline: none;
}
[type=password]:disabled,
[type=text]:disabled {
cursor: not-allowed;
opacity: .5;
}
/************************
/
/ Utility Classes
/
/************************/
.w-100px { width: 100px }
.w-full { width: 100% }
.relative { position: relative }
.flex { display: flex }
.flex-auto { flex: auto }
.flex-col { flex-direction: column }
.flex-row { flex-direction: row }
.justify-between { justify-content: space-between }
.justify-end { justify-content: flex-end }
.invisible { visibility: hidden }
/************************
/
/ Login spesific styling
/
/************************/
section {
width: 500px;
margin: 6rem auto;
border-radius: 10px;
background: var(--section-bg);
-webkit-box-shadow: 0 2px 8px 0 var(--shadow);
box-shadow: 0 2px 8px 0 var(--shadow);
}
.logo {
z-index: 1;
position: relative;
padding: 2rem;
width: 100px;
}
.error {
color: #E22828;
font-weight: bold;
margin-top: 0;
}
.content { padding: 2rem }
.angle {
position: relative;
overflow: hidden;
height: 120px;
border-radius: 10px 10px 0 0;
}
.angle:after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 120px;
background-color: #f15a2c;
background: -webkit-gradient(linear,left top,right top,from(#e22828),to(#ff8c2f));
background: linear-gradient(90deg,#e22828 0,#ff8c2f);
-webkit-transform-origin: bottom left;
transform-origin: bottom left;
-webkit-transform: skewY(-6deg);
transform: skewY(-6deg);
-webkit-transition: -webkit-transform .15s linear;
transition: -webkit-transform .15s linear;
transition: transform .15s linear;
transition: transform .15s linear,-webkit-transform .15s linear;
}
.pass-toggle {
color: #ff8c2f;
border: 0;
appearance: none;
background: transparent;
}
.pass-toggle:hover,
.pass-toggle:focus {
color: #f15a2c;
outline: none;
}
.pass-toggle svg {
fill: currentColor;
height: 1rem;
width: 1rem;
}
/************************
/
/ Media queries for mobile responsive
/
/************************/
@media (max-width: 500px) {
body {
background: var(--section-bg);
}
[type=password],
[type=text] {
font-size: 16px; /* This prevents the mobile browser from zooming in on the input-field. */
}
section {
margin: 0;
border-radius: 0;
width: 100%;
box-shadow: none;
}
.angle { border-radius: 0 }
}
</style>
<noscript>
<style type="text/css">
.js-validate { display: none }
</style>
</noscript>
</head>
<body>
<section>
<div class="angle">
<div class="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222.4 39"><path fill="#ffffff" d="M146.70000000000002 29.5H135l-3 9h-6.5L138.9 0h8l13.4 38.5h-7.1L142.6 6.9l-5.8 16.9h8.2l1.7 5.7zM29.7 0v25.4c0 8.9-5.8 13.6-14.9 13.6C5.8 39 0 34.3 0 25.4V0h6.5v25.4c0 5.2 3.2 7.9 8.2 7.9 5.2 0 8.4-2.7 8.4-7.9V0h6.6zM50.9 12v26.5h-6.5V0h6.1l17 26.5V0H74v38.5h-6.1L50.9 12zM171.3 0h6.5v38.5h-6.5V0zM222.4 24.7c0 9-5.9 13.8-15.2 13.8h-14.5V0h14.6c9.2 0 15.1 4.8 15.1 13.8v10.9zm-6.6-10.9c0-5.3-3.3-8.1-8.5-8.1h-8.1v27.1h8c5.3 0 8.6-2.8 8.6-8.1V13.8zM108.3 23.9c4.3-1.6 6.9-5.3 6.9-11.5 0-8.7-5.1-12.4-12.8-12.4H88.8v38.5h6.5V5.7h6.9c3.8 0 6.2 1.8 6.2 6.7s-2.4 6.8-6.2 6.8h-3.4l9.2 19.4h7.5l-7.2-14.7z"></path></svg>
</div>
</div>
<div class="content">
<header>
<h1><?=$var['NAME']?></h1>
<h2><?=$var['COMMENT']?></h2>
<p>Please set a password for the root user account. Maximum length is 128 characters.</p>
</header>
<noscript>
<p class="error"><?= _('The Unraid OS webgui requires JavaScript. Please enable it.') ?></p>
<p class="error"><?= _('Please also ensure you have cookies enabled.') ?></p>
</noscript>
<form action="/login" method="POST" class="js-validate w-full flex flex-col">
<label for="password"><?= _('Username') ?></label>
<input name="username" type="text" value="root" disabled title="<?= _('Username not changeable') ?>">
<div class="flex flex-row items-center justify-between">
<label for="password" class="flex-auto"><?= _('Password') ?></label>
<button type="button" tabIndex="-1" class="js-pass-toggle pass-toggle" title="<?= _('Show Password') ?>">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path d="M24,9A23.654,23.654,0,0,0,2,24a23.633,23.633,0,0,0,44,0A23.643,23.643,0,0,0,24,9Zm0,25A10,10,0,1,1,34,24,10,10,0,0,1,24,34Zm0-16a6,6,0,1,0,6,6A6,6,0,0,0,24,18Z"/>
<g class="js-pass-toggle-hide">
<rect x="20.133" y="2.117" height="44" transform="translate(23.536 -8.587) rotate(45)" />
<rect x="22" y="3.984" width="4" height="44" transform="translate(25.403 -9.36) rotate(45)" fill="#f2f2f2" />
</g>
</svg>
</button>
</div>
<input id="password" name="password" type="password" max="128" autocomplete="new-password" autofocus required>
<label for="confirmPassword"><?= _('Confirm Password') ?></label>
<input id="confirmPassword" name="confirmPassword" type="password" max="128" autocomplete="new-password" required>
<p class="js-error error"><?= @$POST_ERROR ?></p>
<div class="flex justify-end">
<button disabled type="submit" class="js-submit button button--small"><?= _('Set Password') ?></button>
</div>
</form>
</div>
</section>
<script type="text/javascript">
// cookie check
document.cookie = "cookietest=1";
cookieEnabled = document.cookie.indexOf("cookietest=")!=-1;
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
if (!cookieEnabled) document.write('<p class="error">Browser cookie support required for Unraid OS webgui</p>');
// Password toggling
const $passToggle = document.querySelector('.js-pass-toggle');
const $passToggleHideSvg = $passToggle.querySelector('.js-pass-toggle-hide');
const $passInputs = document.querySelectorAll('[type=password]');
let hidePass = true;
$passToggle.addEventListener('click', () => {
hidePass = !hidePass;
if (!hidePass) $passToggleHideSvg.classList.add('invisible'); // toggle svg elements
else $passToggleHideSvg.classList.remove('invisible');
$passInputs.forEach($el => $el.type = hidePass ? 'password' : 'text'); // change input types
$passToggle.setAttribute('title', hidePass ? "<?= _('Show Password') ?>" : "<?= _('Hide Password') ?>"); // change toggle title
});
// front-end validation
const $submitBtn = document.querySelector('.js-submit');
const $passInput = document.querySelector('[name=password]');
const $confirmPassInput = document.querySelector('[name=confirmPassword]');
const $errorTarget = document.querySelector('.js-error');
const maxPassLength = <?= $MAX_PASS_LENGTH ?>;
let displayValidation = false; // user put values in both inputs. now always check on change or debounced blur.
// helper functions
function debounce(func, timeout = 300){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function validate() {
// User has entered values into both password fields. Let's start to nag them until they can submit
if ($passInput.value && $confirmPassInput.value) displayValidation = true;
const inputsEmpty = !$passInput.value || !$confirmPassInput.value;
const inputsMismatch = $passInput.value !== $confirmPassInput.value;
const passTooLong = $passInput.value.length > maxPassLength || $confirmPassInput.value.length > maxPassLength;
if (inputsEmpty || inputsMismatch || passTooLong) {
$submitBtn.setAttribute('disabled', true); // always ensure we keep disabled when no match
// only display error when we know the user has put values into both fields. Don't want to annoy the crap out of them too much.
if (displayValidation) {
if (inputsMismatch) return $errorTarget.innerText = '<?=$VALIDATION_MESSAGES['mismatch']?>';
if (inputsEmpty) return $errorTarget.innerText = '<?=$VALIDATION_MESSAGES['empty']?>';
if (passTooLong) return $errorTarget.innerText = '<?=$VALIDATION_MESSAGES['maxLength']?>';
}
return false;
}
// passwords match remove errors and allow submission
$errorTarget.innerText = '';
$submitBtn.removeAttribute('disabled');
return true;
};
// event 🦻
$passInputs.forEach($el => {
$el.addEventListener('change', () => debounce(validate()));
$el.addEventListener('keyup', () => {
if (displayValidation) debounce(validate()); // Wait until displayValidation is swapped in a change event
});
});
</script>
</body>
</html>