diff --git a/index.php b/index.php index 205b0517..dd3f2673 100644 --- a/index.php +++ b/index.php @@ -72,6 +72,7 @@ if ($action == '2fa_entercode') { exit(); } $code = Request::post('2fa_code'); + $remember = Request::post('2fa_remember'); // verify entered code $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); // get user-data @@ -105,13 +106,6 @@ if ($action == '2fa_entercode') { $userinfo['adminsession'] = $isadmin; $userinfo['userid'] = $uid; - // if not successful somehow - start again - if (!finishLogin($userinfo)) { - Response::redirectTo('index.php', [ - 'showmessage' => '2' - ]); - } - // when using email-2fa, remove the one-time-code if ($userinfo['type_2fa'] == '1') { $del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid"); @@ -119,6 +113,42 @@ if ($action == '2fa_entercode') { 'uid' => $uid ]); } + + // when remember is activated, set the cookie + if ($remember) { + $selector = base64_encode(Froxlor::genSessionId(9)); + $authenticator = Froxlor::genSessionId(33); + $valid_until = time()+60*60*24*30; + $ins_stmt = Database::prepare(" + INSERT INTO `".TABLE_PANEL_2FA_TOKENS."` SET + `selector` = :selector, + `token` = :authenticator, + `userid` = :userid, + `valid_until` = :valid_until + "); + Database::pexecute($ins_stmt, [ + 'selector' => $selector, + 'authenticator' => hash('sha256', $authenticator), + 'userid' => $uid, + 'valid_until' => $valid_until + ]); + $cookie_params = [ + 'expires' => $valid_until, // 30 days + 'path' => '/', + 'domain' => UI::getCookieHost(), + 'secure' => UI::requestIsHttps(), + 'httponly' => true, + 'samesite' => 'Strict' + ]; + setcookie('frx_2fa_remember', $selector.':'.base64_encode($authenticator), $cookie_params); + } + + // if not successful somehow - start again + if (!finishLogin($userinfo)) { + Response::redirectTo('index.php', [ + 'showmessage' => '2' + ]); + } exit(); } // wrong 2fa code - treat like "wrong password" @@ -349,6 +379,22 @@ if ($action == '2fa_entercode') { // 2FA activated if (Settings::Get('2fa.enabled') == '1' && $userinfo['type_2fa'] > 0) { + + // check for remember cookie + if (!empty($_COOKIE['frx_2fa_remember'])) { + list($selector, $authenticator) = explode(':', $_COOKIE['frx_2fa_remember']); + $sel_stmt = Database::prepare("SELECT `token` FROM `".TABLE_PANEL_2FA_TOKENS."` WHERE `selector` = :selector AND `userid` = :uid AND `valid_until` >= UNIX_TIMESTAMP()"); + $token_check = Database::pexecute_first($sel_stmt, ['selector' => $selector, 'uid' => $userinfo[$uid]]); + if ($token_check && hash_equals($token_check['token'], hash('sha256', base64_decode($authenticator)))) { + if (!finishLogin($userinfo)) { + Response::redirectTo('index.php', [ + 'showmessage' => '2' + ]); + } + exit(); + } + } + // redirect to code-enter-page $_SESSION['secret_2fa'] = ($userinfo['type_2fa'] == 2 ? $userinfo['data_2fa'] : 'email'); $_SESSION['uid_2fa'] = $userinfo[$uid]; @@ -829,8 +875,8 @@ function finishLogin($userinfo) $theme = $userinfo['theme']; } else { $theme = Settings::Get('panel.default_theme'); - CurrentUser::setField('theme', $theme); } + CurrentUser::setField('theme', $theme); $qryparams = []; if (!empty($_SESSION['lastqrystr'])) { diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 299560de..5ffdb4a6 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -730,8 +730,8 @@ opcache.validate_timestamps'), ('panel', 'logo_overridecustom', '0'), ('panel', 'settings_mode', '0'), ('panel', 'menu_collapsed', '1'), - ('panel', 'version', '2.2.0-rc1'), - ('panel', 'db_version', '202401090'); + ('panel', 'version', '2.2.0-rc2'), + ('panel', 'db_version', '202407200'); DROP TABLE IF EXISTS `panel_tasks`; @@ -1049,4 +1049,15 @@ CREATE TABLE `panel_loginlinks` ( `allowed_from` text NOT NULL, UNIQUE KEY `loginname` (`loginname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; + + +DROP TABLE IF EXISTS `panel_2fa_tokens`; +CREATE TABLE `panel_2fa_tokens` ( + `id` int(11) NOT NULL auto_increment, + `selector` varchar(20) NOT NULL, + `token` varchar(200) NOT NULL, + `userid` int(11) NOT NULL default '0', + `valid_until` int(15) NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; FROXLORSQL; diff --git a/install/updates/froxlor/update_2.2.inc.php b/install/updates/froxlor/update_2.2.inc.php index 9e96a98a..46645b5e 100644 --- a/install/updates/froxlor/update_2.2.inc.php +++ b/install/updates/froxlor/update_2.2.inc.php @@ -122,3 +122,26 @@ if (Froxlor::isFroxlorVersion('2.2.0-dev1')) { Update::showUpdateStep("Updating from 2.2.0-dev1 to 2.2.0-rc1", false); Froxlor::updateToVersion('2.2.0-rc1'); } + +if (Froxlor::isDatabaseVersion('202401090')) { + + Update::showUpdateStep("Adding new table for 2fa tokens"); + Database::query("DROP TABLE IF EXISTS `panel_2fa_tokens`;"); + $sql = "CREATE TABLE `panel_2fa_tokens` ( + `id` int(11) NOT NULL auto_increment, + `selector` varchar(20) NOT NULL, + `token` varchar(200) NOT NULL, + `userid` int(11) NOT NULL default '0', + `valid_until` int(15) NOT NULL, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + Database::query($sql); + Update::lastStepStatus(0); + + Froxlor::updateToDbVersion('202407200'); +} + +if (Froxlor::isFroxlorVersion('2.2.0-rc1')) { + Update::showUpdateStep("Updating from 2.2.0-rc1 to 2.2.0-rc2", false); + Froxlor::updateToVersion('2.2.0-rc2'); +} diff --git a/lib/Froxlor/Api/Commands/Emails.php b/lib/Froxlor/Api/Commands/Emails.php index 60e81f59..c52d6dc5 100644 --- a/lib/Froxlor/Api/Commands/Emails.php +++ b/lib/Froxlor/Api/Commands/Emails.php @@ -270,15 +270,6 @@ class Emails extends ApiCommand implements ResourceEntity throw new Exception("You cannot access this resource", 405); } - // if enabling catchall is not allowed by settings, we do not need - // to run update() - if (Settings::Get('catchall.catchall_enabled') != '1') { - Response::standardError([ - 'operationnotpermitted', - 'featureisdisabled' - ], 'catchall', true); - } - $id = $this->getParam('id', true, 0); $ea_optional = $id > 0; $emailaddr = $this->getParam('emailaddr', $ea_optional, ''); @@ -297,30 +288,41 @@ class Emails extends ApiCommand implements ResourceEntity $iscatchall = $this->getBoolParam('iscatchall', true, $result['iscatchall']); $description = $this->getParam('description', true, $result['description']); + // if enabling catchall is not allowed by settings, we do not need + // to run update() + if ($iscatchall && $result['iscatchall'] == 0 && Settings::Get('catchall.catchall_enabled') != '1') { + Response::standardError([ + 'operationnotpermitted', + 'featureisdisabled' + ], 'catchall', true); + } + // get needed customer info to reduce the email-address-counter by one $customer = $this->getCustomerData(); // check for catchall-flag + $email = $result['email_full']; if ($iscatchall) { $iscatchall = '1'; - $email_parts = explode('@', $result['email_full']); - $email = '@' . $email_parts[1]; - // catchall check - $stmt = Database::prepare(" - SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "` - WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1' - "); - $params = [ - "email" => $email, - "cid" => $customer['customerid'] - ]; - $email_check = Database::pexecute_first($stmt, $params, true, true); - if ($email_check) { - Response::standardError('youhavealreadyacatchallforthisdomain', '', true); + $email = $result['email']; + // update only required if it was not a catchall before + if ($result['iscatchall'] == 0) { + $email_parts = explode('@', $result['email_full']); + $email = '@' . $email_parts[1]; + // catchall check + $stmt = Database::prepare(" + SELECT `email_full` FROM `" . TABLE_MAIL_VIRTUAL . "` + WHERE `email` = :email AND `customerid` = :cid AND `iscatchall` = '1' + "); + $params = [ + "email" => $email, + "cid" => $customer['customerid'] + ]; + $email_check = Database::pexecute_first($stmt, $params, true, true); + if ($email_check) { + Response::standardError('youhavealreadyacatchallforthisdomain', '', true); + } } - } else { - $iscatchall = '0'; - $email = $result['email_full']; } $spam_tag_level = Validate::validate($spam_tag_level, 'spam_tag_level', '/^\d{1,}(\.\d{1,2})?$/', '', [7.0], true); diff --git a/lib/Froxlor/Api/Commands/SubDomains.php b/lib/Froxlor/Api/Commands/SubDomains.php index 6c796234..1c4870c2 100644 --- a/lib/Froxlor/Api/Commands/SubDomains.php +++ b/lib/Froxlor/Api/Commands/SubDomains.php @@ -983,9 +983,11 @@ class SubDomains extends ApiCommand implements ResourceEntity '`d`.`letsencrypt`', '`d`.`registration_date`', '`d`.`termination_date`', - '`d`.`deactivated`' + '`d`.`deactivated`', + '`d`.`email_only`', ]; } + $query_fields = []; // prepare select statement @@ -996,7 +998,6 @@ class SubDomains extends ApiCommand implements ResourceEntity LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `da` ON `da`.`aliasdomain`=`d`.`id` LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON `pd`.`id`=`d`.`parentdomainid` WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ") - AND `d`.`email_only` = '0' " . $this->getSearchWhere($query_fields, true) . " GROUP BY `d`.`id` ORDER BY `parentdomainname` ASC, `d`.`parentdomainid` ASC " . $this->getOrderBy(true) . $this->getLimit()); $result = []; @@ -1092,13 +1093,13 @@ class SubDomains extends ApiCommand implements ResourceEntity $this->getUserDetail('customerid') ]; } + if (!empty($customer_ids)) { // prepare select statement $domains_stmt = Database::prepare(" SELECT COUNT(*) as num_subdom FROM `" . TABLE_PANEL_DOMAINS . "` `d` WHERE `d`.`customerid` IN (" . implode(', ', $customer_ids) . ") - AND `d`.`email_only` = '0' "); $result = Database::pexecute_first($domains_stmt, null, true, true); if ($result) { diff --git a/lib/Froxlor/Cli/MasterCron.php b/lib/Froxlor/Cli/MasterCron.php index 61926ecc..7045de8e 100644 --- a/lib/Froxlor/Cli/MasterCron.php +++ b/lib/Froxlor/Cli/MasterCron.php @@ -171,8 +171,9 @@ final class MasterCron extends CliCommand FroxlorLogger::getInstanceOf()->setCronLog(0); } - // clean up possible old login-links + // clean up possible old login-links and 2fa tokens Database::query("DELETE FROM `" . TABLE_PANEL_LOGINLINKS . "` WHERE `valid_until` < UNIX_TIMESTAMP()"); + Database::query("DELETE FROM `" . TABLE_PANEL_2FA_TOKENS . "` WHERE `valid_until` < UNIX_TIMESTAMP()"); return $result; } diff --git a/lib/Froxlor/Dns/Dns.php b/lib/Froxlor/Dns/Dns.php index a8ca8771..b49a7e22 100644 --- a/lib/Froxlor/Dns/Dns.php +++ b/lib/Froxlor/Dns/Dns.php @@ -54,7 +54,7 @@ class Dns $dom_data['uid'] = $userinfo['userid']; } } else { - $where_clause = '`customerid` = :uid AND '; + $where_clause = '`customerid` = :uid AND `email_only` = "0" AND '; $dom_data['uid'] = $userinfo['userid']; } diff --git a/lib/Froxlor/Froxlor.php b/lib/Froxlor/Froxlor.php index 83bc5501..0a5011e3 100644 --- a/lib/Froxlor/Froxlor.php +++ b/lib/Froxlor/Froxlor.php @@ -31,10 +31,10 @@ final class Froxlor { // Main version variable - const VERSION = '2.2.0-rc1'; + const VERSION = '2.2.0-rc2'; // Database version (YYYYMMDDC where C is a daily counter) - const DBVERSION = '202401090'; + const DBVERSION = '202407200'; // Distribution branding-tag (used for Debian etc.) const BRANDING = ''; diff --git a/lib/Froxlor/UI/Callbacks/Domain.php b/lib/Froxlor/UI/Callbacks/Domain.php index b8fb3f68..f635ae25 100644 --- a/lib/Froxlor/UI/Callbacks/Domain.php +++ b/lib/Froxlor/UI/Callbacks/Domain.php @@ -74,6 +74,9 @@ class Domain if ($attributes['fields']['deactivated']) { return lng('admin.deactivated'); } + if ($attributes['fields']['email_only']) { + return lng('domains.email_only'); + } // path or redirect if (preg_match('/^https?\:\/\//', $attributes['fields']['documentroot'])) { return [ @@ -127,7 +130,7 @@ class Domain public static function canViewLogs(array $attributes): bool { - if ((!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) && !$attributes['fields']['deactivated']) { + if ((int)$attributes['fields']['email_only'] == 0 && !$attributes['fields']['deactivated']) { if ((int)UI::getCurrentUser()['adminsession'] == 0 && (bool)UI::getCurrentUser()['logviewenabled']) { return true; } elseif ((int)UI::getCurrentUser()['adminsession'] == 1) { @@ -157,6 +160,7 @@ class Domain && $attributes['fields']['caneditdomain'] == '1' && Settings::Get('system.bind_enable') == '1' && Settings::Get('system.dnsenabled') == '1' + && !$attributes['fields']['email_only'] && !$attributes['fields']['deactivated']; } @@ -169,7 +173,7 @@ class Domain public static function hasLetsEncryptActivated(array $attributes): bool { - return ((bool)$attributes['fields']['letsencrypt'] && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0))); + return ((bool)$attributes['fields']['letsencrypt'] && (int)$attributes['fields']['email_only'] == 0); } /** @@ -181,7 +185,7 @@ class Domain && DDomain::domainHasSslIpPort($attributes['fields']['id']) && (CurrentUser::isAdmin() || (!CurrentUser::isAdmin() && (int)$attributes['fields']['caneditdomain'] == 1)) && (int)$attributes['fields']['letsencrypt'] == 0 - && (!CurrentUser::isAdmin() || (CurrentUser::isAdmin() && (int)$attributes['fields']['email_only'] == 0)) + && !(int)$attributes['fields']['email_only'] && !$attributes['fields']['deactivated'] ) { return true; diff --git a/lib/Froxlor/UI/Callbacks/Text.php b/lib/Froxlor/UI/Callbacks/Text.php index 91df6fdf..c1a8cbef 100644 --- a/lib/Froxlor/UI/Callbacks/Text.php +++ b/lib/Froxlor/UI/Callbacks/Text.php @@ -52,6 +52,14 @@ class Text ]; } + public static function type2fa(array $attributes): array + { + return [ + 'macro' => 'type2fa', + 'data' => (int)$attributes['data'] + ]; + } + public static function customerfullname(array $attributes): string { return User::getCorrectFullUserDetails($attributes['fields'], true); diff --git a/lib/formfields/customer/domains/formfield.domains_edit.php b/lib/formfields/customer/domains/formfield.domains_edit.php index 47dde7bb..6e0ab1cc 100644 --- a/lib/formfields/customer/domains/formfield.domains_edit.php +++ b/lib/formfields/customer/domains/formfield.domains_edit.php @@ -47,13 +47,14 @@ return [ 'values' => $domainips ], 'alias' => [ - 'visible' => $alias_check == '0', + 'visible' => $alias_check == '0' && (int)$result['email_only'] == 0, 'label' => lng('domains.aliasdomain'), 'type' => 'select', 'select_var' => $domains, 'selected' => $result['aliasdomain'] ], 'path' => [ + 'visible' => (int)$result['email_only'] == 0, 'label' => lng('panel.path'), 'desc' => (Settings::Get('panel.pathedit') != 'Dropdown' ? lng('panel.pathDescriptionSubdomain').(Settings::Get('system.documentroot_use_default_value') == 1 ? lng('panel.pathDescriptionEx') : '') : null), 'type' => $pathSelect['type'], @@ -63,13 +64,13 @@ return [ 'note' => $pathSelect['note'] ?? '', ], 'url' => [ - 'visible' => Settings::Get('panel.pathedit') == 'Dropdown', + 'visible' => Settings::Get('panel.pathedit') == 'Dropdown' && (int)$result['email_only'] == 0, 'label' => lng('panel.urloverridespath'), 'type' => 'text', 'value' => $urlvalue ], 'redirectcode' => [ - 'visible' => Settings::Get('customredirect.enabled') == '1', + 'visible' => Settings::Get('customredirect.enabled') == '1' && (int)$result['email_only'] == 0, 'label' => lng('domains.redirectifpathisurl'), 'desc' => lng('domains.redirectifpathisurlinfo'), 'type' => 'select', @@ -77,7 +78,7 @@ return [ 'selected' => $def_code ], 'selectserveralias' => [ - 'visible' => ($result['parentdomainid'] == '0' && $userinfo['subdomains'] != '0') || $result['parentdomainid'] != '0', + 'visible' => (($result['parentdomainid'] == '0' && $userinfo['subdomains'] != '0') || $result['parentdomainid'] != '0') && (int)$result['email_only'] == 0, 'label' => lng('admin.selectserveralias'), 'desc' => lng('admin.selectserveralias_desc'), 'type' => 'select', @@ -85,27 +86,28 @@ return [ 'selected' => $serveraliasoptions_selected ], 'isemaildomain' => [ - 'visible' => ($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $result['parentdomainid'] != '0', + 'visible' => (($result['subcanemaildomain'] == '1' || $result['subcanemaildomain'] == '2') && $result['parentdomainid'] != '0') && (int)$result['email_only'] == 0, 'label' => 'Emaildomain', 'type' => 'checkbox', 'value' => '1', 'checked' => $result['isemaildomain'] ], 'openbasedir_path' => [ - 'visible' => $result['openbasedir'] == '1', + 'visible' => $result['openbasedir'] == '1' && (int)$result['email_only'] == 0, 'label' => lng('domain.openbasedirpath'), 'type' => 'select', 'select_var' => $openbasedir, 'selected' => $result['openbasedir_path'] ], 'phpsettingid' => [ - 'visible' => ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 && $userinfo['phpenabled'] == '1' && $result['phpenabled'] == '1', + 'visible' => ((int)Settings::Get('system.mod_fcgid') == 1 || (int)Settings::Get('phpfpm.enabled') == 1) && count($phpconfigs) > 0 && $userinfo['phpenabled'] == '1' && $result['phpenabled'] == '1' && (int)$result['email_only'] == 0, 'label' => lng('admin.phpsettings.title'), 'type' => 'select', 'select_var' => $phpconfigs, 'selected' => $result['phpsettingid'] ], 'speciallogfile' => [ + 'visible' => (int)$result['email_only'] == 0, 'label' => lng('admin.speciallogfile.title'), 'desc' => lng('admin.speciallogfile.description'), 'type' => 'checkbox', @@ -139,7 +141,7 @@ return [ 'section_bssl' => [ 'title' => lng('admin.webserversettings_ssl'), 'image' => 'icons/domain_edit.png', - 'visible' => Settings::Get('system.use_ssl') == '1' && $ssl_ipsandports && Domain::domainHasSslIpPort($result['id']), + 'visible' => Settings::Get('system.use_ssl') == '1' && $ssl_ipsandports && Domain::domainHasSslIpPort($result['id']) && (int)$result['email_only'] == 0, 'fields' => [ 'sslenabled' => [ 'label' => lng('admin.domain_sslenabled'), @@ -194,6 +196,7 @@ return [ ] ] ] - ] + ], + 'buttons' => ((int)$result['email_only'] == 1) ? [] : null ] ]; diff --git a/lib/tablelisting/admin/tablelisting.admins.php b/lib/tablelisting/admin/tablelisting.admins.php index 637f3d4c..7af1eec3 100644 --- a/lib/tablelisting/admin/tablelisting.admins.php +++ b/lib/tablelisting/admin/tablelisting.admins.php @@ -110,6 +110,12 @@ return [ 'class' => 'text-center', 'callback' => [Text::class, 'boolean'], ], + 'type_2fa' => [ + 'label' => lng('2fa.type_2fa'), + 'field' => 'type_2fa', + 'class' => 'text-center', + 'callback' => [Text::class, 'type2fa'], + ], ], 'visible_columns' => Listing::getVisibleColumnsForListing('admin_list', [ 'loginname', diff --git a/lib/tablelisting/admin/tablelisting.customers.php b/lib/tablelisting/admin/tablelisting.customers.php index de3e8fb5..6c1c7979 100644 --- a/lib/tablelisting/admin/tablelisting.customers.php +++ b/lib/tablelisting/admin/tablelisting.customers.php @@ -149,6 +149,12 @@ return [ 'class' => 'text-center', 'callback' => [Text::class, 'boolean'], ], + 'c.type_2fa' => [ + 'label' => lng('2fa.type_2fa'), + 'field' => 'type_2fa', + 'class' => 'text-center', + 'callback' => [Text::class, 'type2fa'], + ], ], 'visible_columns' => Listing::getVisibleColumnsForListing('customer_list', [ 'c.name', diff --git a/lib/tables.inc.php b/lib/tables.inc.php index 26214333..0805f290 100644 --- a/lib/tables.inc.php +++ b/lib/tables.inc.php @@ -57,3 +57,4 @@ const TABLE_PANEL_PLANS = 'panel_plans'; const TABLE_API_KEYS = 'api_keys'; const TABLE_PANEL_USERCOLUMNS = 'panel_usercolumns'; const TABLE_PANEL_LOGINLINKS = 'panel_loginlinks'; +const TABLE_PANEL_2FA_TOKENS = 'panel_2fa_tokens'; diff --git a/lng/de.lng.php b/lng/de.lng.php index 8a3eb506..167cf8ee 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -48,6 +48,7 @@ return [ '2fa_ga_desc' => 'Das Konto ist eingerichtet, um zeitbasierte Einmalpasswörter via Authenticator-App zu erhalten. Um die gewünschte Authenticator-App einzurichten, scanne bitte den untenstehenden QR-Code. Zum Deaktivieren, klicke auf "2FA deaktivieren"', '2fa_not_activated' => 'Zwei-Faktor Authentifizierung ist nicht aktiviert', '2fa_not_activated_for_user' => 'Zwei-Faktor Authentifizierung ist für den aktuellen Benutzer nicht aktiviert', + 'type_2fa' => '2FA Status', ], 'admin' => [ 'overview' => 'Übersicht', @@ -713,6 +714,7 @@ return [ 'hsts' => 'HSTS aktiviert', 'aliasdomainid' => 'ID der Alias-Domain', 'nodomainsassignedbyadmin' => 'Diesem Account wurde noch keine (aktive) Domain zugewiesen. Bitte kontaktiere deinen Administrator, wenn du der Meinung bist, das ist nicht korrekt.', + 'email_only' => 'Nur E-Mail', ], 'emails' => [ 'description' => 'Hier können Sie Ihre E-Mail-Adressen einrichten.
Ein Konto ist wie Ihr Briefkasten vor der Haustür. Wenn jemand eine E-Mail an Sie schreibt, wird diese in dieses Konto gelegt.

Die Zugangsdaten lauten wie folgt: (Die Angaben in kursiver Schrift sind durch die jeweiligen Einträge zu ersetzen)

Hostname: Domainname
Benutzername: Kontoname / E-Mail-Adresse
Passwort: das gewählte Passwort', @@ -1031,6 +1033,7 @@ return [ 'combination_not_found' => 'Kombination aus Benutzername und E-Mail Adresse stimmen nicht überein.', '2fa' => 'Zwei-Faktor Authentifizierung (2FA)', '2facode' => 'Bitte 2FA Code angeben', + '2faremember' => 'Browser vertrauen', ], 'mails' => [ 'pop_success' => [ diff --git a/lng/en.lng.php b/lng/en.lng.php index 112686c7..d51a4506 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -49,6 +49,7 @@ return [ '2fa_ga_desc' => 'Your account is set up to use time-based one-time passwords via authenticator-app. Please scan the QR code below with your desired authenticator app to generate the codes. To deactivate, click on "Deactivate 2FA"', '2fa_not_activated' => 'Two-factor authentication is not enabled', '2fa_not_activated_for_user' => 'Two-factor authentication is not enabled for the current user', + 'type_2fa' => '2FA status', ], 'admin' => [ 'overview' => 'Overview', @@ -784,6 +785,7 @@ return [ 'hsts' => 'HSTS enabled', 'aliasdomainid' => 'ID of alias domain', 'nodomainsassignedbyadmin' => 'Your account has currently no (active) domains assigned to it. Please contact your administrator if you think this is wrong.', + 'email_only' => 'Email only', ], 'emails' => [ 'description' => 'Here you can create and change your email addresses.
An account is like your letterbox in front of your house. If someone sends you an email, it will be dropped into the account.

To download your emails use the following settings in your mailprogram: (The data in italics has to be changed to the equivalents you typed in!)
Hostname: domainname
Username: account name / e-mail address
password: the password you\'ve chosen', @@ -1103,6 +1105,7 @@ return [ 'combination_not_found' => 'Combination of user and email address not found.', '2fa' => 'Two-factor authentication (2FA)', '2facode' => 'Please enter 2FA code', + '2faremember' => 'Trust browser', ], 'mails' => [ 'pop_success' => [ diff --git a/logfiles_viewer.php b/logfiles_viewer.php index 20d00d7c..e3a65872 100644 --- a/logfiles_viewer.php +++ b/logfiles_viewer.php @@ -61,6 +61,10 @@ if (function_exists('exec')) { } $domain = json_decode($json_result, true)['data']; + if ($domain['email_only']) { + Response::dynamicError("There are no webserver logfiles for email only domains."); + } + $speciallogfile = ''; if ($domain['speciallogfile'] == '1') { if ($domain['parentdomainid'] == '0') { diff --git a/ssl_editor.php b/ssl_editor.php index b0d1c770..8fea36d9 100644 --- a/ssl_editor.php +++ b/ssl_editor.php @@ -50,6 +50,10 @@ if ($action == '' || $action == 'view') { } $result_domain = json_decode($json_result, true)['data']; + if ($result_domain['email_only']) { + Response::dynamicError("There are no ssl-certificates for email only domains."); + } + if (Request::post('send') == 'send') { $do_insert = Request::post('do_insert', 0) == 1; try { diff --git a/templates/Froxlor/login/enter2fa.html.twig b/templates/Froxlor/login/enter2fa.html.twig index b435bb0b..37cc3d66 100644 --- a/templates/Froxlor/login/enter2fa.html.twig +++ b/templates/Froxlor/login/enter2fa.html.twig @@ -22,6 +22,14 @@ +
+
+ + + +
+
+
diff --git a/templates/Froxlor/table/macros.html.twig b/templates/Froxlor/table/macros.html.twig index 3a8e4910..c5cdab92 100644 --- a/templates/Froxlor/table/macros.html.twig +++ b/templates/Froxlor/table/macros.html.twig @@ -29,6 +29,16 @@ {% endif %} {% endmacro %} +{% macro type2fa(data) %} + {% if (data == 1) %} + + {% elseif (data == 2) %} + + {% else %} + + {% endif %} +{% endmacro %} + {% macro link(data) %} {% apply spaceless %} diff --git a/templates/Froxlor/table/table.html.twig b/templates/Froxlor/table/table.html.twig index 3c4384cb..578dad3f 100644 --- a/templates/Froxlor/table/table.html.twig +++ b/templates/Froxlor/table/table.html.twig @@ -47,6 +47,8 @@ {{ macros.domainWithSan(td.data.data) }} {% elseif td.data.macro == 'actions' %} {{ macros.actions(td.data.data) }} + {% elseif td.data.macro == 'type2fa' %} + {{ macros.type2fa(td.data.data) }} {% else %} Table macro '{{ td.data.macro|json_encode }}' is not implemented! Unable to handle this data: {{ td.data|json_encode }}