This commit is contained in:
Alexey Berezhok
2024-03-19 22:05:27 +03:00
commit 346a50856b
1572 changed files with 182163 additions and 0 deletions

23
web/inc/2fa/check.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
use RobThree\Auth\TwoFactorAuth;
require_once __DIR__ . "/../vendor/autoload.php";
if (isset($argv[1]) && isset($argv[2])) {
$secret = $argv[1];
$token = $argv[2];
} elseif (isset($_GET["secret"]) && isset($_GET["token"])) {
$secret = htmlspecialchars($_GET["secret"]);
$token = htmlspecialchars($_GET["token"]);
} else {
echo "ERROR: Secret or Token is not set as argument!";
exit();
}
$tfa = new TwoFactorAuth("Hestia Control Panel");
// Verify code
$result = $tfa->verifyCode($secret, $token);
if ($result) {
echo "ok";
}

10
web/inc/2fa/secret.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
use RobThree\Auth\TwoFactorAuth;
require_once __DIR__ . "/../vendor/autoload.php";
$tfa = new TwoFactorAuth("Hestia Control Panel");
$secret = $tfa->createSecret(160); // Though the default is an 80 bits secret (for backwards compatibility reasons) we recommend creating 160+ bits secrets (see RFC 4226 - Algorithm Requirements)
$qrcode = $tfa->getQRCodeImageAsDataUri(gethostname(), $secret);
echo $secret . "-" . $qrcode;

7
web/inc/composer.json Normal file
View File

@@ -0,0 +1,7 @@
{
"require": {
"phpmailer/phpmailer": "6.8.0",
"hestiacp/phpquoteshellarg": "1.0.2",
"robthree/twofactorauth": "2.0.0"
}
}

218
web/inc/composer.lock generated Normal file
View File

@@ -0,0 +1,218 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bd5fba3223573f480531e48f5e3ce14e",
"packages": [
{
"name": "hestiacp/phpquoteshellarg",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/hestiacp/phpquoteshellarg.git",
"reference": "7fd1a3a648cdc39a3fe2aab78a1a3a0267f92f49"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hestiacp/phpquoteshellarg/zipball/7fd1a3a648cdc39a3fe2aab78a1a3a0267f92f49",
"reference": "7fd1a3a648cdc39a3fe2aab78a1a3a0267f92f49",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"autoload": {
"files": [
"src/Hestiacp/quoteshellarg/quoteshellarg.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Unlicense"
],
"description": "Improved escape shell arguments for support of special charactars",
"homepage": "https://github.com/hestiacp",
"keywords": [
"escapeshellarg",
"quoteshellarg"
],
"support": {
"source": "https://github.com/hestiacp/phpquoteshellarg/tree/v1.0.2"
},
"time": "2023-07-23T09:16:27+00:00"
},
{
"name": "phpmailer/phpmailer",
"version": "v6.8.0",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "df16b615e371d81fb79e506277faea67a1be18f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/df16b615e371d81fb79e506277faea67a1be18f1",
"reference": "df16b615e371d81fb79e506277faea67a1be18f1",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"ext-hash": "*",
"php": ">=5.5.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.2",
"doctrine/annotations": "^1.2.6 || ^1.13.3",
"php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.7.1",
"yoast/phpunit-polyfills": "^1.0.4"
},
"suggest": {
"ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
"ext-openssl": "Needed for secure SMTP sending and DKIM signing",
"greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
},
"type": "library",
"autoload": {
"psr-4": {
"PHPMailer\\PHPMailer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-only"
],
"authors": [
{
"name": "Marcus Bointon",
"email": "phpmailer@synchromedia.co.uk"
},
{
"name": "Jim Jagielski",
"email": "jimjag@gmail.com"
},
{
"name": "Andy Prevost",
"email": "codeworxtech@users.sourceforge.net"
},
{
"name": "Brent R. Matzelle"
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0"
},
"funding": [
{
"url": "https://github.com/Synchro",
"type": "github"
}
],
"time": "2023-03-06T14:43:22+00:00"
},
{
"name": "robthree/twofactorauth",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/RobThree/TwoFactorAuth.git",
"reference": "27cd1e1392d19f178398e892f59062003c8998a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/27cd1e1392d19f178398e892f59062003c8998a4",
"reference": "27cd1e1392d19f178398e892f59062003c8998a4",
"shasum": ""
},
"require": {
"php": ">=8.1.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.13",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": "^9"
},
"suggest": {
"bacon/bacon-qr-code": "Needed for BaconQrCodeProvider provider",
"endroid/qr-code": "Needed for EndroidQrCodeProvider"
},
"type": "library",
"autoload": {
"psr-4": {
"RobThree\\Auth\\": "lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Janssen",
"homepage": "http://robiii.me",
"role": "Developer"
},
{
"name": "Nicolas CARPi",
"homepage": "https://github.com/NicolasCARPi",
"role": "Developer"
},
{
"name": "Will Power",
"homepage": "https://github.com/willpower232",
"role": "Developer"
}
],
"description": "Two Factor Authentication",
"homepage": "https://github.com/RobThree/TwoFactorAuth",
"keywords": [
"Authentication",
"MFA",
"Multi Factor Authentication",
"Two Factor Authentication",
"authenticator",
"authy",
"php",
"tfa"
],
"support": {
"issues": "https://github.com/RobThree/TwoFactorAuth/issues",
"source": "https://github.com/RobThree/TwoFactorAuth"
},
"funding": [
{
"url": "https://paypal.me/robiii",
"type": "custom"
},
{
"url": "https://github.com/RobThree",
"type": "github"
}
],
"time": "2023-02-25T11:33:28+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.3.0"
}

178
web/inc/helpers.php Normal file
View File

@@ -0,0 +1,178 @@
<?php
use function Hestiacp\quoteshellarg\quoteshellarg;
# Return codes
const E_ARGS = 1;
const E_INVALID = 2;
const E_NOTEXIST = 3;
const E_EXISTS = 4;
const E_SUSPENDED = 5;
const E_UNSUSPENDED = 6;
const E_INUSE = 7;
const E_LIMIT = 8;
const E_PASSWORD = 9;
const E_FORBIDEN = 10;
const E_FORBIDDEN = 10;
const E_DISABLED = 11;
const E_PARSING = 12;
const E_DISK = 13;
const E_LA = 14;
const E_CONNECT = 15;
const E_FTP = 16;
const E_DB = 17;
const E_RRD = 18;
const E_UPDATE = 19;
const E_RESTART = 20;
const E_API_DISABLED = 21;
/**
* Looks for a code equivalent to "exit_code" to use in http_code.
*
* @param int $exit_code
* @param int $default
* @return int
*/
function exit_code_to_http_code(int $exit_code, int $default = 400): int {
switch ($exit_code) {
case 0:
return 200;
case E_ARGS:
// return 500;
return 400;
case E_INVALID:
return 422;
// case E_NOTEXIST:
// return 404;
// case E_EXISTS:
// return 302;
case E_PASSWORD:
return 401;
case E_SUSPENDED:
case E_UNSUSPENDED:
case E_FORBIDEN:
case E_FORBIDDEN:
case E_API_DISABLED:
return 401;
// return 403;
case E_DISABLED:
return 400;
// return 503;
}
return $default;
}
function check_local_ip($addr) {
if (in_array($addr, [$_SERVER["SERVER_ADDR"], "127.0.0.1"])) {
return true;
} else {
return false;
}
}
function get_real_user_ip() {
$ip = $_SERVER["REMOTE_ADDR"];
if (isset($_SERVER["HTTP_CLIENT_IP"]) && !check_local_ip($_SERVER["HTTP_CLIENT_IP"])) {
if (filter_var($_SERVER["HTTP_CLIENT_IP"], FILTER_VALIDATE_IP)) {
$ip = $_SERVER["HTTP_CLIENT_IP"];
}
}
if (
isset($_SERVER["HTTP_X_FORWARDED_FOR"]) &&
!check_local_ip($_SERVER["HTTP_X_FORWARDED_FOR"])
) {
if (filter_var($_SERVER["HTTP_X_FORWARDED_FOR"], FILTER_VALIDATE_IP)) {
$ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
}
}
if (isset($_SERVER["HTTP_FORWARDED_FOR"]) && !check_local_ip($_SERVER["HTTP_FORWARDED_FOR"])) {
if (filter_var($_SERVER["HTTP_FORWARDED_FOR"], FILTER_VALIDATE_IP)) {
$ip = $_SERVER["HTTP_FORWARDED_FOR"];
}
}
if (isset($_SERVER["HTTP_X_FORWARDED"]) && !check_local_ip($_SERVER["HTTP_X_FORWARDED"])) {
if (filter_var($_SERVER["HTTP_X_FORWARDED"], FILTER_VALIDATE_IP)) {
$ip = $_SERVER["HTTP_X_FORWARDED"];
}
}
if (isset($_SERVER["HTTP_FORWARDED"]) && !check_local_ip($_SERVER["HTTP_FORWARDED"])) {
if (filter_var($_SERVER["HTTP_FORWARDED"], FILTER_VALIDATE_IP)) {
$ip = $_SERVER["HTTP_FORWARDED"];
}
}
if (
isset($_SERVER["HTTP_CF_CONNECTING_IP"]) &&
!check_local_ip($_SERVER["HTTP_CF_CONNECTING_IP"])
) {
if (filter_var($_SERVER["HTTP_CF_CONNECTING_IP"], FILTER_VALIDATE_IP)) {
$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];
}
}
return $ip;
}
/**
* Create a history log using 'v-log-action' script.
*
* @param string $message The message for log.
* @param string $category A category for log. Ex: Auth, Firewall, API...
* @param string $level Info|Warning|Error.
* @param string $user A username for save in the user history ou 'system' to save in Hestia history.
* @return int The script result code.
*/
function hst_add_history_log($message, $category = "System", $level = "Info", $user = "system") {
//$message = ucfirst($message);
//$message = str_replace("'", "`", $message);
$category = ucfirst(strtolower($category));
$level = ucfirst(strtolower($level));
$command_args =
quoteshellarg($user) .
" " .
quoteshellarg($level) .
" " .
quoteshellarg($category) .
" " .
quoteshellarg($message);
exec(HESTIA_CMD . "v-log-action " . $command_args, $output, $return_var);
unset($output);
return $return_var;
}
function get_hostname() {
$badValues = [
false,
null,
0,
"",
"localhost",
"127.0.0.1",
"::1",
"0000:0000:0000:0000:0000:0000:0000:0001",
];
$ret = gethostname();
if (in_array($ret, $badValues, true)) {
throw new Exception("gethostname() failed");
}
$ret2 = gethostbyname($ret);
if (in_array($ret2, $badValues, true)) {
return $ret;
}
$ret3 = gethostbyaddr($ret2);
if (in_array($ret3, $badValues, true)) {
return $ret2;
}
return $ret3;
}
function display_title($tab) {
$array1 = ["{{page}}", "{{hostname}}", "{{ip}}", "{{appname}}"];
$array2 = [$tab, get_hostname(), $_SERVER["REMOTE_ADDR"], $_SESSION["APP_NAME"]];
return str_replace($array1, $array2, $_SESSION["TITLE"]);
}

85
web/inc/i18n.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
// Functions for internationalization
// I18N support information here
putenv("LANGUAGE=" . detect_user_language());
setlocale(LC_ALL, "C.UTF-8");
$domain = "hestiacp";
$localedir = "/usr/local/hestia/web/locale";
bindtextdomain($domain, $localedir);
textdomain($domain);
/**
* Detects user language from Accept-Language HTTP header.
* @param string Fallback language (default: 'en')
* @return string Language code (such as 'en' and 'ja')
*/
function detect_user_language() {
if (!empty($_SESSION["language"])) {
return $_SESSION["language"];
} elseif (!empty($_SESSION["LANGUAGE"])) {
return $_SESSION["LANGUAGE"];
} else {
return "en";
}
}
/**
* Translate ISO2 to "Language"
* nl = Dutch, de = German
* @param string iso2 code
* @return string Language
*/
function translate_json($string) {
$json = file_get_contents($_SERVER["DOCUMENT_ROOT"] . "/locale/languages.json");
$json_a = json_decode($json, true);
return $json_a[$string][0] . " (" . $json_a[$string . "_locale"][0] . ")";
}
/**
* Support translation strings that contains html
*/
function htmlify_trans($string, $closingTag) {
$arguments = func_get_args();
return preg_replace_callback(
"/{(.*?)}/", // Ungreedy (*?)
function ($matches) use ($arguments, $closingTag) {
static $i = 1;
$i++;
return $arguments[$i] . $matches[1] . $closingTag;
},
$string,
);
}
function get_email_template($file, $language) {
if (
file_exists(
$_SERVER["HESTIA"] . "/data/templates/email/" . $language . "/" . $file . ".html",
)
) {
return file_get_contents(
$_SERVER["HESTIA"] . "/data/templates/email/" . $language . "/" . $file . ".html",
);
}
if (file_exists($_SERVER["HESTIA"] . "/data/templates/email/" . $file . ".html")) {
return file_get_contents($_SERVER["HESTIA"] . "/data/templates/email/" . $file . ".html");
}
return false;
}
function translate_email($string, $replace) {
$array1 = $array2 = [];
foreach ($replace as $key => $value) {
$array1[] = "{{" . $key . "}}";
$array2[] = $value;
}
return str_replace($array1, $array2, $string);
}
/**
* Detects user language .
* @param string Fallback language (default: 'en')
* @return string Language code (such as 'en' and 'ja')
*/
function detect_login_language() {
}

43
web/inc/mail-wrapper.php Executable file
View File

@@ -0,0 +1,43 @@
#!/usr/local/hestia/php/bin/php
<?php
if (empty($argv[1])) {
echo "ERROR: not enough arguments\n";
echo "USAGE: mail-wrapper.php -s SUBJECT EMAIL [NOTIFY]\n";
exit(3);
}
$options = getopt("s:f:");
if (!empty($argv[4]) && $argv[4] == "no") {
exit();
}
define("NO_AUTH_REQUIRED", true);
include "/usr/local/hestia/web/inc/main.php";
// Set system language
exec(HESTIA_CMD . "v-list-sys-config json", $output, $return_var);
$data = json_decode(implode("", $output), true);
if (!empty($data["config"]["LANGUAGE"])) {
$_SESSION["language"] = $data["config"]["LANGUAGE"];
} else {
$_SESSION["language"] = "en";
}
//define vars
//make hostname detection a bit more feature proof
$hostname = get_hostname();
$from = !empty($_SESSION["FROM_EMAIL"]) ? $_SESSION["FROM_EMAIL"] : "noreply@" . $hostname;
$from_name = !empty($_SESSION["FROM_NAME"]) ? $_SESSION["FROM_NAME"] : $_SESSION["APP_NAME"];
$to = $argv[3] . "\n";
$subject = $argv[2] . "\n";
$mailtext = file_get_contents("php://stdin");
// Send email
if (!empty($to) && !empty($subject)) {
send_email($to, $subject, $mailtext, $from, $from_name);
}
session_destroy();

578
web/inc/main.php Normal file
View File

@@ -0,0 +1,578 @@
<?php
session_start();
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
use function Hestiacp\quoteshellarg\quoteshellarg;
try {
require_once "vendor/autoload.php";
} catch (Throwable $ex) {
$errstr =
"Unable to load required libraries. Please run v-add-sys-dependencies in command line. Error: " .
$ex->getMessage();
trigger_error($errstr);
echo $errstr;
exit(1);
}
define("HESTIA_DIR_BIN", "/usr/local/hestia/bin/");
define("HESTIA_CMD", "/usr/bin/sudo /usr/local/hestia/bin/");
define("DEFAULT_PHP_VERSION", "php-" . exec('php -r "echo substr(phpversion(),0,3);"'));
// Load Hestia Config directly
load_hestia_config();
require_once dirname(__FILE__) . "/prevent_csrf.php";
require_once dirname(__FILE__) . "/helpers.php";
$root_directory = dirname(__FILE__) . "/../../";
function destroy_sessions() {
unset($_SESSION);
session_unset();
session_destroy();
session_start();
}
$i = 0;
// Saving user IPs to the session for preventing session hijacking
$user_combined_ip = "";
if (isset($_SERVER["REMOTE_ADDR"])) {
$user_combined_ip = $_SERVER["REMOTE_ADDR"];
}
if (isset($_SERVER["HTTP_CLIENT_IP"])) {
$user_combined_ip .= "|" . $_SERVER["HTTP_CLIENT_IP"];
}
if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
$user_combined_ip .= "|" . $_SERVER["HTTP_X_FORWARDED_FOR"];
}
if (isset($_SERVER["HTTP_FORWARDED_FOR"])) {
$user_combined_ip .= "|" . $_SERVER["HTTP_FORWARDED_FOR"];
}
if (isset($_SERVER["HTTP_X_FORWARDED"])) {
$user_combined_ip .= "|" . $_SERVER["HTTP_X_FORWARDED"];
}
if (isset($_SERVER["HTTP_FORWARDED"])) {
$user_combined_ip .= "|" . $_SERVER["HTTP_FORWARDED"];
}
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
if (!empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
$user_combined_ip = $_SERVER["HTTP_CF_CONNECTING_IP"];
}
}
if (!isset($_SESSION["user_combined_ip"])) {
$_SESSION["user_combined_ip"] = $user_combined_ip;
}
// Checking user to use session from the same IP he has been logged in
if (
$_SESSION["user_combined_ip"] != $user_combined_ip &&
isset($_SESSION["user"]) &&
$_SESSION["DISABLE_IP_CHECK"] != "yes"
) {
$v_user = quoteshellarg($_SESSION["user"]);
$v_session_id = quoteshellarg($_SESSION["token"]);
exec(HESTIA_CMD . "v-log-user-logout " . $v_user . " " . $v_session_id, $output, $return_var);
destroy_sessions();
header("Location: /login/");
exit();
}
// Check system settings
if (!isset($_SESSION["VERSION"]) && !defined("NO_AUTH_REQUIRED")) {
destroy_sessions();
header("Location: /login/");
exit();
}
// Check user session
if (!isset($_SESSION["user"]) && !defined("NO_AUTH_REQUIRED")) {
destroy_sessions();
header("Location: /login/");
exit();
}
// Generate CSRF Token
if (isset($_SESSION["user"])) {
if (!isset($_SESSION["token"])) {
$token = bin2hex(random_bytes(16));
$_SESSION["token"] = $token;
}
}
if ($_SESSION["RELEASE_BRANCH"] == "release" && $_SESSION["DEBUG_MODE"] == "false") {
define("JS_LATEST_UPDATE", "v=" . $_SESSION["VERSION"]);
} else {
define("JS_LATEST_UPDATE", "r=" . time());
}
if (!defined("NO_AUTH_REQUIRED")) {
if (empty($_SESSION["LAST_ACTIVITY"]) || empty($_SESSION["INACTIVE_SESSION_TIMEOUT"])) {
destroy_sessions();
header("Location: /login/");
} elseif ($_SESSION["INACTIVE_SESSION_TIMEOUT"] * 60 + $_SESSION["LAST_ACTIVITY"] < time()) {
$v_user = quoteshellarg($_SESSION["user"]);
$v_session_id = quoteshellarg($_SESSION["token"]);
exec(
HESTIA_CMD . "v-log-user-logout " . $v_user . " " . $v_session_id,
$output,
$return_var,
);
destroy_sessions();
header("Location: /login/");
exit();
} else {
$_SESSION["LAST_ACTIVITY"] = time();
}
}
function ipUsed() {
[$http_host, $port] = explode(":", $_SERVER["HTTP_HOST"] . ":");
if (filter_var($http_host, FILTER_VALIDATE_IP)) {
return true;
} else {
return false;
}
}
if (isset($_SESSION["user"])) {
$user = quoteshellarg($_SESSION["user"]);
$user_plain = htmlentities($_SESSION["user"]);
}
if (isset($_SESSION["look"]) && $_SESSION["look"] != "" && $_SESSION["userContext"] === "admin") {
$user = quoteshellarg($_SESSION["look"]);
$user_plain = htmlentities($_SESSION["look"]);
}
if (empty($user_plain)) {
$user_plain = "";
}
if (empty($_SESSION["look"])) {
$_SESSION["look"] = "";
}
require_once dirname(__FILE__) . "/i18n.php";
function check_error($return_var) {
if ($return_var > 0) {
header("Location: /error/");
exit();
}
}
function check_return_code($return_var, $output) {
if ($return_var != 0) {
$error = implode("<br>", $output);
if (empty($error)) {
$error = sprintf(_("Error code: %s"), $return_var);
}
$_SESSION["error_msg"] = $error;
}
}
function check_return_code_redirect($return_var, $output, $location) {
if ($return_var != 0) {
$error = implode("<br>", $output);
if (empty($error)) {
$error = sprintf(_("Error code: %s"), $return_var);
}
$_SESSION["error_msg"] = $error;
header("Location:" . $location);
}
}
function render_page($user, $TAB, $page) {
$__template_dir = dirname(__DIR__) . "/templates/";
// Extract global variables
// I think those variables should be passed via arguments
extract($GLOBALS, EXTR_SKIP);
// Header
include $__template_dir . "header.php";
// Panel
$panel = top_panel(empty($_SESSION["look"]) ? $_SESSION["user"] : $_SESSION["look"], $TAB);
// Policies controller
@include_once dirname(__DIR__) . "/inc/policies.php";
// Body
include $__template_dir . "pages/" . $page . ".php";
// Footer
include $__template_dir . "footer.php";
}
// Match $_SESSION['token'] against $_GET['token'] or $_POST['token']
// Usage: verify_csrf($_POST) or verify_csrf($_GET); Use verify_csrf($_POST,true) to return on failure instead of redirect
function verify_csrf($method, $return = false) {
if (
$method["token"] !== $_SESSION["token"] ||
empty($method["token"]) ||
empty($_SESSION["token"])
) {
if ($return === true) {
return false;
} else {
header("Location: /login/");
die();
}
} else {
return true;
}
}
function show_alert_message($data) {
$msgIcon = "";
$msgText = "";
$msgClass = "";
if (!empty($data["error_msg"])) {
$msgIcon = "fa-circle-exclamation";
$msgText = htmlentities($data["error_msg"]);
$msgClass = "inline-alert-danger";
} elseif (!empty($data["ok_msg"])) {
$msgIcon = "fa-circle-check";
$msgText = $data["ok_msg"];
$msgClass = "inline-alert-success";
}
if (!empty($msgText)) {
printf(
'<div class="inline-alert %s u-mb20" role="alert"><i class="fas %s"></i><p>%s</p></div>',
$msgClass,
$msgIcon,
$msgText,
);
}
}
function top_panel($user, $TAB) {
$command = HESTIA_CMD . "v-list-user " . $user . " 'json'";
exec($command, $output, $return_var);
if ($return_var > 0) {
destroy_sessions();
$_SESSION["error_msg"] = _("You are logged out, please log in again.");
header("Location: /login/");
exit();
}
$panel = json_decode(implode("", $output), true);
unset($output);
// Log out active sessions for suspended users
if ($panel[$user]["SUSPENDED"] === "yes" && $_SESSION["POLICY_USER_VIEW_SUSPENDED"] !== "yes") {
if (empty($_SESSION["look"])) {
destroy_sessions();
$_SESSION["error_msg"] = _("You are logged out, please log in again.");
header("Location: /login/");
}
}
// Reset user permissions if changed while logged in
if ($panel[$user]["ROLE"] !== $_SESSION["userContext"] && !isset($_SESSION["look"])) {
unset($_SESSION["userContext"]);
$_SESSION["userContext"] = $panel[$user]["ROLE"];
}
// Load user's selected theme and do not change it when impersonting user
if (isset($panel[$user]["THEME"]) && !isset($_SESSION["look"])) {
$_SESSION["userTheme"] = $panel[$user]["THEME"];
}
// Unset userTheme override variable if POLICY_USER_CHANGE_THEME is set to no
if ($_SESSION["POLICY_USER_CHANGE_THEME"] === "no") {
unset($_SESSION["userTheme"]);
}
// Set preferred sort order
if (!isset($_SESSION["look"])) {
$_SESSION["userSortOrder"] = $panel[$user]["PREF_UI_SORT"];
}
// Set home location URLs
if ($_SESSION["userContext"] === "admin" && empty($_SESSION["look"])) {
// Display users list for administrators unless they are impersonating a user account
$home_url = "/list/user/";
} else {
// Set home location URL based on available package features from account
if ($panel[$user]["WEB_DOMAINS"] != "0") {
$home_url = "/list/web/";
} elseif ($panel[$user]["DNS_DOMAINS"] != "0") {
$home_url = "/list/dns/";
} elseif ($panel[$user]["MAIL_DOMAINS"] != "0") {
$home_url = "/list/mail/";
} elseif ($panel[$user]["DATABASES"] != "0") {
$home_url = "/list/db/";
} elseif ($panel[$user]["CRON_JOBS"] != "0") {
$home_url = "/list/cron/";
} elseif ($panel[$user]["BACKUPS"] != "0") {
$home_url = "/list/backups/";
}
}
include dirname(__FILE__) . "/../templates/includes/panel.php";
return $panel;
}
function translate_date($date) {
$date = new DateTime($date);
return $date->format("d") . " " . _($date->format("M")) . " " . $date->format("Y");
}
function humanize_time($usage) {
if ($usage > 60) {
$usage = $usage / 60;
if ($usage > 24) {
$usage = $usage / 24;
$usage = number_format($usage);
return sprintf(ngettext("%d day", "%d days", $usage), $usage);
} else {
$usage = round($usage);
return sprintf(ngettext("%d hour", "%d hours", $usage), $usage);
}
} else {
$usage = round($usage);
return sprintf(ngettext("%d minute", "%d minutes", $usage), $usage);
}
}
function humanize_usage_size($usage, $round = 2) {
if ($usage == "unlimited") {
return "";
}
$display_usage = $usage;
if ($usage > 1024) {
$usage = $usage / 1024;
if ($usage > 1024) {
$usage = $usage / 1024;
if ($usage > 1024) {
$usage = $usage / 1024;
$display_usage = number_format($usage, $round);
} else {
if ($usage > 999) {
$usage = $usage / 1024;
}
$display_usage = number_format($usage, $round);
}
} else {
if ($usage > 999) {
$usage = $usage / 1024;
}
$display_usage = number_format($usage, $round);
}
} else {
if ($usage > 999) {
$usage = $usage / 1024;
}
$display_usage = number_format($usage, $round);
}
return $display_usage;
}
function humanize_usage_measure($usage) {
if ($usage == "unlimited") {
return;
}
$measure = "kb";
if ($usage > 1024) {
$usage = $usage / 1024;
if ($usage > 1024) {
$usage = $usage / 1024;
$measure = $usage < 1024 ? "tb" : "pb";
if ($usage > 999) {
$usage = $usage / 1024;
$measure = "pb";
}
} else {
$measure = $usage < 1024 ? "gb" : "tb";
if ($usage > 999) {
$usage = $usage / 1024;
$measure = "tb";
}
}
} else {
$measure = $usage < 1024 ? "mb" : "gb";
if ($usage > 999) {
$measure = "gb";
}
}
return $measure;
}
function get_percentage($used, $total) {
if ($total = "unlimited") {
//return 0 if unlimited
return 0;
}
if (!isset($total)) {
$total = 0;
}
if (!isset($used)) {
$used = 0;
}
if ($total == 0) {
$percent = 0;
} else {
$percent = $used / $total;
$percent = $percent * 100;
$percent = number_format($percent, 0, "", "");
if ($percent < 0) {
$percent = 0;
} elseif ($percent > 100) {
$percent = 100;
}
}
return $percent;
}
function send_email($to, $subject, $mailtext, $from, $from_name, $to_name = "") {
$mail = new PHPMailer();
if (isset($_SESSION["USE_SERVER_SMTP"]) && $_SESSION["USE_SERVER_SMTP"] == "true") {
if (!empty($_SESSION["SERVER_SMTP_ADDR"]) && $_SESSION["SERVER_SMTP_ADDR"] != "") {
if (filter_var($_SESSION["SERVER_SMTP_ADDR"], FILTER_VALIDATE_EMAIL)) {
$from = $_SESSION["SERVER_SMTP_ADDR"];
}
}
$mail->IsSMTP();
$mail->Mailer = "smtp";
$mail->SMTPDebug = 0;
$mail->SMTPAuth = true;
$mail->SMTPSecure = $_SESSION["SERVER_SMTP_SECURITY"];
$mail->Port = $_SESSION["SERVER_SMTP_PORT"];
$mail->Host = $_SESSION["SERVER_SMTP_HOST"];
$mail->Username = $_SESSION["SERVER_SMTP_USER"];
$mail->Password = $_SESSION["SERVER_SMTP_PASSWD"];
}
$mail->IsHTML(true);
$mail->ClearReplyTos();
if (empty($to_name)) {
$mail->AddAddress($to);
} else {
$mail->AddAddress($to, $to_name);
}
$mail->SetFrom($from, $from_name);
$mail->CharSet = "utf-8";
$mail->Subject = $subject;
$content = $mailtext;
$content = nl2br($content);
$mail->MsgHTML($content);
$mail->Send();
}
function list_timezones() {
foreach (
["AKST", "AKDT", "PST", "PDT", "MST", "MDT", "CST", "CDT", "EST", "EDT", "AST", "ADT"]
as $timezone
) {
$tz = new DateTimeZone($timezone);
$timezone_offsets[$timezone] = $tz->getOffset(new DateTime());
}
foreach (DateTimeZone::listIdentifiers() as $timezone) {
$tz = new DateTimeZone($timezone);
$timezone_offsets[$timezone] = $tz->getOffset(new DateTime());
}
foreach ($timezone_offsets as $timezone => $offset) {
$offset_prefix = $offset < 0 ? "-" : "+";
$offset_formatted = gmdate("H:i", abs($offset));
$pretty_offset = "UTC{$offset_prefix}{$offset_formatted}";
$c = new DateTime(gmdate("Y-M-d H:i:s"), new DateTimeZone("UTC"));
$c->setTimezone(new DateTimeZone($timezone));
$current_time = $c->format("H:i:s");
$timezone_list[$timezone] = "$timezone [ $current_time ] {$pretty_offset}";
#$timezone_list[$timezone] = "$timezone ${pretty_offset}";
}
return $timezone_list;
}
/**
* A function that tells is it MySQL installed on the system, or it is MariaDB.
*
* Explanation:
* $_SESSION['DB_SYSTEM'] has 'mysql' value even if MariaDB is installed, so you can't figure out is it really MySQL or it's MariaDB.
* So, this function will make it clear.
*
* If MySQL is installed, function will return 'mysql' as a string.
* If MariaDB is installed, function will return 'mariadb' as a string.
*
* Hint: if you want to check if PostgreSQL is installed - check value of $_SESSION['DB_SYSTEM']
*
* @return string
*/
function is_it_mysql_or_mariadb() {
exec(HESTIA_CMD . "v-list-sys-services json", $output, $return_var);
$data = json_decode(implode("", $output), true);
unset($output);
$mysqltype = "mysql";
if (isset($data["mariadb"])) {
$mysqltype = "mariadb";
}
return $mysqltype;
}
function load_hestia_config() {
// Check system configuration
exec(HESTIA_CMD . "v-list-sys-config json", $output, $return_var);
$data = json_decode(implode("", $output), true);
$sys_arr = $data["config"];
foreach ($sys_arr as $key => $value) {
$_SESSION[$key] = $value;
}
}
/**
* Returns the list of all web domains from all users grouped by Backend Template used and owner
*
* @return array
*/
function backendtpl_with_webdomains() {
exec(HESTIA_CMD . "v-list-users json", $output, $return_var);
$users = json_decode(implode("", $output), true);
unset($output);
$backend_list = [];
foreach ($users as $user => $user_details) {
exec(
HESTIA_CMD . "v-list-web-domains " . quoteshellarg($user) . " json",
$output,
$return_var,
);
$domains = json_decode(implode("", $output), true);
unset($output);
foreach ($domains as $domain => $domain_details) {
if (!empty($domain_details["BACKEND"])) {
$backend = $domain_details["BACKEND"];
$backend_list[$backend][$user][] = $domain;
}
}
}
return $backend_list;
}
/**
* Check if password is valid
*
* @return int; 1 / 0
*/
function validate_password($password) {
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(.){8,}$/', $password);
}
function unset_alerts() {
if (!empty($_SESSION["unset_alerts"])) {
if (!empty($_SESSION["error_msg"])) {
unset($_SESSION["error_msg"]);
}
if (!empty($_SESSION["ok_msg"])) {
unset($_SESSION["ok_msg"]);
}
unset($_SESSION["unset_alerts"]);
}
}
register_shutdown_function("unset_alerts");

20
web/inc/policies.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
if (
($_SESSION["userContext"] === "user" &&
$panel[$user]["SUSPENDED"] === "yes" &&
$_SESSION["POLICY_USER_VIEW_SUSPENDED"] === "yes") ||
($_SESSION["userContext"] === "admin" &&
$_SESSION["look"] === "admin" &&
$_SESSION["POLICY_SYSTEM_PROTECTED_ADMIN"] === "yes")
) {
$read_only = "true";
} else {
$read_only = "";
}
if ($read_only === "true") {
$display_mode = "disabled";
} else {
$display_mode = "";
}

151
web/inc/prevent_csrf.php Normal file
View File

@@ -0,0 +1,151 @@
<?php
$check_csrf = true;
if (
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web/inc/mail-wrapper.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia//web/inc/mail-wrapper.php"
) {
$check_csrf = false;
} // execute only from CLI
if (
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web/reset/mail/index.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web//reset/mail/index.php"
) {
$check_csrf = false;
} // Localhost only
if (
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web/api/index.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web//api/index.php"
) {
$check_csrf = false;
} // Own check
if (substr($_SERVER["SCRIPT_FILENAME"], 0, 22) == "/usr/local/hestia/bin/") {
$check_csrf = false;
}
function checkStrictness($level) {
if ($level >= $_SESSION["POLICY_CSRF_STRICTNESS"]) {
return true;
} else {
http_response_code(400);
echo "<h1>Potential CSRF use detected</h1>\n" .
"<p>Please disable any plugins/add-ons inside your browser or contact your system administrator. If you are the system administrator you can run v-change-sys-config-value 'POLICY_CSRF_STRICTNESS' '0' as root to disable this check.<p>" .
"<p>If you followed a bookmark or an static link please <a href='/'>navigate to root</a>";
die();
}
}
function prevent_post_csrf() {
if (!empty($_SERVER["REQUEST_METHOD"])) {
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (!empty($_SERVER["HTTP_HOST"])) {
$hostname = preg_replace(
"/(\[?[^]]*\]?):([0-9]{1,5})$/",
"$1",
$_SERVER["HTTP_HOST"],
);
$port_is_defined = preg_match("/\[?[^]]*\]?:[0-9]{1,5}$/", $_SERVER["HTTP_HOST"]);
if ($port_is_defined) {
$port = preg_replace(
"/(\[?[^]]*\]?):([0-9]{1,5})$/",
"$2",
$_SERVER["HTTP_HOST"],
);
} else {
$port = 443;
}
} else {
$hostname = gethostname();
$port = 443;
}
if (isset($_SERVER["HTTP_ORIGIN"])) {
$origin_host = parse_url($_SERVER["HTTP_ORIGIN"], PHP_URL_HOST);
if (
strcmp($origin_host, gethostname()) === 0 &&
in_array($port, ["443", $_SERVER["SERVER_PORT"]])
) {
return checkStrictness(2);
} else {
if (
strcmp($origin_host, $hostname) === 0 &&
in_array($port, ["443", $_SERVER["SERVER_PORT"]])
) {
return checkStrictness(1);
} else {
return checkStrictness(0);
}
}
}
}
}
}
function prevent_get_csrf() {
if (!empty($_SERVER["REQUEST_METHOD"])) {
if ($_SERVER["REQUEST_METHOD"] === "GET") {
if (!empty($_SERVER["HTTP_HOST"])) {
$hostname = preg_replace(
"/(\[?[^]]*\]?):([0-9]{1,5})$/",
"$1",
$_SERVER["HTTP_HOST"],
);
$port_is_defined = preg_match("/\[?[^]]*\]?:[0-9]{1,5}$/", $_SERVER["HTTP_HOST"]);
if ($port_is_defined) {
$port = preg_replace(
"/(\[?[^]]*\]?):([0-9]{1,5})$/",
"$2",
$_SERVER["HTTP_HOST"],
);
} else {
$port = 443;
}
} else {
$hostname = gethostname();
$port = 443;
}
//list of possible entries route and these should never be blocked
if (
in_array($_SERVER["DOCUMENT_URI"], [
"/list/user/index.php",
"/login/index.php",
"/list/web/index.php",
"/list/dns/index.php",
"/list/mail/index.php",
"/list/db/index.php",
"/list/cron/index.php",
"/list/backup/index.php",
"/reset/index.php",
])
) {
return true;
}
if (isset($_SERVER["HTTP_REFERER"])) {
$referrer_host = parse_url($_SERVER["HTTP_REFERER"], PHP_URL_HOST);
if (
strcmp($referrer_host, gethostname()) === 0 &&
in_array($port, ["443", $_SERVER["SERVER_PORT"]])
) {
return checkStrictness(2);
} else {
if (
strcmp($referrer_host, $hostname) === 0 &&
in_array($port, ["443", $_SERVER["SERVER_PORT"]])
) {
return checkStrictness(1);
} else {
return checkStrictness(0);
}
}
} else {
return checkStrictness(0);
}
}
}
}
if ($check_csrf == true) {
prevent_post_csrf();
prevent_get_csrf();
}

31
web/inc/secure_login.php Normal file
View File

@@ -0,0 +1,31 @@
<?php
$login_url_skip = 0;
if (
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web/reset/mail/index.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web//reset/mail/index.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web/reset/mail/set-ar.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web//reset/mail/set-ar.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web/reset/mail/get-ar.php" ||
$_SERVER["SCRIPT_FILENAME"] == "/usr/local/hestia/web//reset/mail/get-ar.php" ||
substr($_SERVER["SCRIPT_FILENAME"], 0, 21) == "/usr/local/hestia/bin/"
) {
$login_url_skip = 1;
}
if ($login_url_skip == 0) {
if (!isset($login_url_loaded)) {
$login_url_loaded = 1;
if (file_exists("/usr/local/hestia/web/inc/login_url.php")) {
require_once "/usr/local/hestia/web/inc/login_url.php";
if (isset($_GET[$login_url])) {
setcookie($login_url, "1", time() + 31536000, "/", $_SERVER["HTTP_HOST"], true);
header("Location: /login/");
exit();
}
if (!isset($_COOKIE[$login_url])) {
exit();
}
}
}
}