majore update, adding multiple admins

This commit is contained in:
Matthew Saunders Brown 2023-06-19 11:53:16 -07:00
parent c1a98c5ff6
commit a25a906866
42 changed files with 2340 additions and 204 deletions

View File

@ -14,3 +14,4 @@ rm -r /srv/www/html/panel/f3/.git
find /srv/www/html/panel -type d -exec chmod 755 {} + find /srv/www/html/panel -type d -exec chmod 755 {} +
find /srv/www/html/panel -type f -exec chmod 644 {} + find /srv/www/html/panel -type f -exec chmod 644 {} +
chown -R vpanel:vpanel /srv/www/html/panel chown -R vpanel:vpanel /srv/www/html/panel

View File

@ -95,7 +95,7 @@ class Panel {
} }
public static function validateEmailPassword($password, $password_confirm) { public static function validatePassword($password, $password_confirm) {
global $f3; global $f3;
@ -303,6 +303,136 @@ class Panel {
} }
public static function validateUsername($username) {
global $f3;
$messages = array();
if(strlen($username) < 3) {
$messages[] = "Usernames must be at least 3 characters long.";
}
if(strlen($username) > 16) {
$messages[] = "Usernames can not be longer than 16 characters.";
}
if (!preg_match('/^[[:lower:][:digit:]\.\_\-]{3,16}$/', $username)) {
$messages[] = "Usernames can only contain letters, numbers, and the special characters . _ -";
}
if (preg_match('/^[[:digit:]\.\_\-]/', $username)) {
$messages[] = "Usernames must begin with an alphabetic character.";
}
if (preg_match('/[\.\_\-]$/', $username)) {
$messages[] = "Usernames may not end with a Special Character.";
}
if (preg_match('/[\.\_\-]{2,}/', $username)) {
$messages[] = "Usernames may not have consecutive Special Characters.";
}
if (count($messages) > 0) {
if ($f3->exists('SESSION.messages')) {
$f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages));
} else {
$f3->set('SESSION.messages', $messages);
}
return false;
} else {
return true;
}
}
public static function verifyVhostDomainExists($domain) {
global $f3;
exec("/usr/local/bin/vhost-get.sh -d $domain -c", $output, $result_code);
if ($result_code == 0) {
return TRUE;
} else {
if (count($output) > 0) {
if ($output[0] != "ERROR: $domain not found") {
$f3->set('SESSION.messages', $output[0]);
}
}
return FALSE;
}
}
public static function verifyCertificateExists($domain) {
global $f3;
exec("/usr/local/bin/letsencrypt-get.sh -d $domain -c", $output, $result_code);
if ($result_code == 0) {
return TRUE;
} else {
if (count($output) > 0) {
if ($output[0] != "ERROR: Certificate for $domain not found") {
$f3->set('SESSION.messages', $output[0]);
}
}
return FALSE;
}
}
public static function verifyVmailDomainExists($domain) {
global $f3;
exec("/usr/local/bin/vmail-domains-get.sh -d $domain -c", $output, $result_code);
if ($result_code == 0) {
if (count($output) == 0) {
return FALSE;
} else {
// add check for domain row???
return TRUE;
}
} else {
$f3->set('SESSION.messages', "System error checking if email domain exists.");
return FALSE;
}
}
public static function verifyDkimExists($domain) {
global $f3;
exec("/usr/local/bin/vmail-dkim-get.sh -d $domain -c", $output, $result_code);
if ($result_code == 0) {
return TRUE;
} else {
if (count($output) > 0) {
if ($output[0] != "ERROR: DKIM for $domain does not exist.") {
if (count($output) > 0) {
$f3->set('SESSION.messages', $output[0]);
}
}
}
return FALSE;
}
}
public static function vGet($cmd, $return404 = TRUE) { public static function vGet($cmd, $return404 = TRUE) {
global $f3; global $f3;

View File

@ -0,0 +1,31 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel;
class Cert extends \Panel {
function beforeRoute($f3) {
parent::beforeRoute($f3);
if ($f3->exists('SESSION.domain')) {
/* set base path for vhost links */
if ($f3->get('NAV.mapping') == 'cert') {
$f3->set('NAV.certbase', preg_replace('/\/$/', '', $f3->get('BASE')));
} elseif ($f3->get('NAV.mapping') == 'vpanel') {
$f3->set('NAV.certbase', preg_replace('/\/$/', '', $f3->get('BASE') . '/Certs/' . $f3->get('SESSION.cert')));
}
}
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Cert;
class Certs extends \Panel\Cert {
/* use this to make query */
function beforeRoute($f3) {
parent::beforeRoute($f3);
if ($f3->exists('PARAMS.cert')) {
$cert = $f3->get('PARAMS.cert');
if ($cert_array = $f3->call('\Panel::vGet', array("letsencrypt-get.sh -d $cert -c", FALSE))) {
$f3->set('cert_array', $cert_array[0]);
}
} else {
if ($certs_array = $f3->call('\Panel::vGet', array("letsencrypt-get.sh -c", FALSE))) {
$f3->set('certs_array', $certs_array);
}
}
}
static function get($f3) {
if ($f3->exists('PARAMS.cert')) {
$cert_array = $f3->get('cert_array');
/* remove time from creation date */
$start = $cert_array['start'];
$start_array = explode(' ', $start);
unset($start_array[2]);
$start = implode(' ', $start_array);
$cert_array['start'] = $start;
/* remove time from expiration date */
$end = $cert_array['end'];
$end_array = explode(' ', $end);
unset($end_array[2]);
$end = implode(' ', $end_array);
$cert_array['end'] = $end;
/* remove main cert name from alternatives */
// $common = $cert_array['common'];
$alternative = $cert_array['alternative'];
// $alternative = preg_replace("/^$common/", '', $alternative);
// $alternative = trim($alternative);
$alternative = preg_replace('/ /', '<br>', $alternative);
$cert_array['alternative'] = $alternative;
$f3->set('cert_array', $cert_array);
$cert = $f3->get('PARAMS.cert');
$f3->set('page_header', "Certificate Details for $cert");
echo \Template::instance()->render('cert/certs-cert.html');
} else {
$certs_array = $f3->get('certs_array');
if (is_array($certs_array) && count($certs_array) > 0) {
foreach ($certs_array as $k=>$cert_array) {
/* remove time from expiration date */
$end = $cert_array['end'];
$end_array = explode(' ', $end);
unset($end_array[2]);
$end = implode(' ', $end_array);
$cert_array['end'] = $end;
/* remove main cert name from alternatives */
// $common = $cert_array['common'];
$alternative = $cert_array['alternative'];
// $alternative = preg_replace("/^$common/", '', $alternative);
// $alternative = trim($alternative);
$alternative = preg_replace('/ /', '<br>', $alternative);
$cert_array['alternative'] = $alternative;
$certs_array[$k] = $cert_array;
}
$f3->set('certs_array', $certs_array);
}
$f3->set('page_header', "Certificates");
echo \Template::instance()->render('cert/certs.html');
}
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Cert;
class CertsAdd extends \Panel\Cert {
function beforeRoute($f3) {
parent::beforeRoute($f3);
}
static function get($f3) {
if ($f3->exists('PARAMS.cert')) {
$domain = $f3->get('PARAMS.cert');
if ($f3->call('\Panel::validateDomain', $domain)) {
if ($certdomain_dns = dns_get_record("$domain", DNS_A)) {
if ($certdomain_dns[0]['ip'] == $_SERVER['SERVER_ADDR']) {
if (is_dir('/var/tmp/letsencrypt/')) {
if (is_writable('/var/tmp/letsencrypt/')) {
touch("/var/tmp/letsencrypt/$domain");
$messages = $f3->get('SESSION.messages');
$messages[] = "A background job to create a Security Certificate for $domain has been started.";
$messages[] = "It can take a few seconds, up to one minute, for this job to complete.";
$messages[] = "Wait few seconds and then reload this page to check for the new Security Certificate.";
$f3->set('SESSION.messages', $messages);
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "System error with Security Certificate system.";
$messages[] = "Please try again later.";
$f3->set('SESSION.messages', $messages);
}
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "System error with Security Certificate system.";
$messages[] = "Please try again later.";
$f3->set('SESSION.messages', $messages);
}
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "DNS for $domain does not point to this server IP.";
$messages[] = "Please update DNS and try again.";
$f3->set('SESSION.messages', $messages);
}
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "Error verifying DNS, try again later.";
$f3->set('SESSION.messages', $messages);
}
}
parse_str($f3->get('QUERY'), $output);
if (isset($output['r'])) {
$f3->reroute($output['r']);
}
}
/* default reroute as fallback */
$f3->reroute('/');
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Cert;
class CertsDelete extends \Panel\Cert {
function beforeRoute($f3) {
parent::beforeRoute($f3);
}
static function get($f3) {
$messages = $f3->get('SESSION.messages');
if ($f3->exists('PARAMS.cert')) {
$domain = $f3->get('PARAMS.cert');
if ($f3->call('\Panel::validateDomain', $domain)) {
if ($f3->call('\Panel::verifyCertificateExists', $domain)) {
/* delete the cert */
$f3->call('\Panel::vGet', array("letsencrypt-delete.sh -d $domain -r", FALSE));
/* check for and delete dovecot config */
if (preg_match('/^mail\./i', $domain)) {
$mxdomain = preg_replace('/^mail\./i', '', $f3->get('HOST'));
$f3->call('\Panel::vGet', array("vmail-dovecot-disable.sh -d $mxdomain", FALSE));
}
/* delete apache config (vhost or webmail) */
if (is_link("/etc/apache2/sites-enabled/$domain.conf")) {
$f3->call('\Panel::vGet', array("vhost-disable.sh -d $domain", FALSE));
}
$messages[] = "The Security Certificate and related configs for $domain have been deleted.";
} else {
$messages[] = "Security Certificate for $domain not found.";
}
} else {
$messages[] = "Invalid domain name ($domain).";
}
}
/* reroute user to page the originated from */
parse_str($f3->get('QUERY'), $output);
if (isset($output['r'])) {
$f3->reroute($output['r']);
} else {
/* default reroute as fallback */
$f3->reroute('/');
}
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2023 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel;
class MySQL extends \Panel {
function beforeRoute($f3) {
parent::beforeRoute($f3);
}
}

View File

@ -0,0 +1,27 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\MySQL;
class Databases extends \Panel\MySQL {
/* use this to make query */
function beforeRoute($f3) {
parent::beforeRoute($f3);
}
static function get($f3) {
echo \Template::instance()->render('mysql/databases.html');
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vhost;
class Users extends \Panel\Vhost {
/* use this to make query */
function beforeRoute($f3) {
parent::beforeRoute($f3);
if ($f3->exists('PARAMS.username')) {
$username = $f3->get('PARAMS.username');
if ($users_array = $f3->call('\Panel::vGet', array("vhost-user-get.sh -u $username -c", FALSE))) {
if ($users_array[0]['passwd'] == "") {
$users_array[0]['passwd'] = '(unavailable)';
}
$f3->set('users_array', $users_array[0]);
if ($vhosts_array = $f3->call('\Panel::vGet', array("vhost-get.sh -u $username -c", FALSE))) {
$f3->set('vhosts_array', $vhosts_array);
}
}
} else {
if ($users_array = $f3->call('\Panel::vGet', array("vhost-user-get.sh -c", FALSE))) {
if (is_array($users_array) && count($users_array) > 0) {
foreach ($users_array as $k=>$user_array) {
if ($user_array['passwd'] == "") {
$users_array[$k]['passwd'] = '(unavailable)';
}
}
$f3->set('users_array', $users_array);
}
}
}
}
static function get($f3) {
if ($f3->exists('PARAMS.username')) {
$username = $f3->get('PARAMS.username');
$f3->set('page_header', "User Details");
echo \Template::instance()->render('vhost/users-user.html');
} else {
$f3->set('page_header', "Users");
echo \Template::instance()->render('vhost/users.html');
}
}
}

View File

@ -0,0 +1,90 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vhost;
class UsersAdd extends \Panel\Vhost {
function beforeRoute($f3) {
parent::beforeRoute($f3);
/* get system defaults for new users */
$user = array();
$user['username'] = '';
$user['password'] = '';
$user['password_confirm'] = '';
if ($f3->get('JAILUSER') == '1') {
$user['jailuser'] = 1;
} else {
$user['jailuser'] = 0;
}
$user['fpmmax'] = $f3->get('FPMMAX');
if ($f3->get('WRITEUSERINFO') == '1') {
$user['writeuserinfo'] = 1;
} else {
$user['writeuserinfo'] = 0;
}
if ($f3->get('SHOWWRITEINFO') == '1') {
$user['showwriteinfo'] = 1;
} else {
$user['showwriteinfo'] = 0;
}
$f3->set('user', $user);
}
static function get($f3) {
echo \Template::instance()->render('vhost/users-add.html');
}
function post($f3) {
extract($_POST);
/* force username to be all lower case */
$username = strtolower($username);
$f3->call('\Panel::validateUsername', $username);
/* only validate password if one was submitted, otherwise script will generate random password */
if ($password != '' || $password_confirm != '') {
$f3->call('\Panel::validatePassword', array($password, $password_confirm));
}
/* check for validation errors */
if ($f3->exists('SESSION.messages')) {
$messages = $f3->get('SESSION.messages');
$messages[] = "Please make changes and re-submit the form to try again.";
$f3->set('SESSION.messages', $messages);
$f3->set('user', $_POST);
$f3->call('\Panel\Vhost\UsersAdd::get', $f3);
} else {
$domain = $f3->get('PARAMS.domain');
if ($password != '') {
$password = "-p " . escapeshellarg($password);
}
exec("/usr/local/bin/vhost-user-add.sh -u $username -x $fpmmax $password -w $writeuserinfo", $output, $result_code);
if ($result_code == 0) {
$messages[] = "Success: User $username added.";
if ($jailuser == 1) {
exec("/usr/local/bin/vhost-user-jail.sh -u $username >/dev/null 2>/dev/null &", $output, $result_code);
$messages[] = "Note: User is being jailed. Setting up the jail environment takes about a minute to complete and is run in the background now.";
}
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Users");
} else {
$messages[] = "Error adding user.";
$f3->set('SESSION.messages', $messages);
$f3->call('\Panel\Vhost\UsersAdd::get', $f3);
}
}
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vhost;
class UsersDelete extends \Panel\Vhost {
function beforeRoute($f3) {
parent::beforeRoute($f3);
/* verify user exists */
}
static function get($f3) {
$f3->set('confirm', 'true');
echo \Template::instance()->render('vhost/users-delete.html');
}
function post($f3) {
/* run delete command here */
$username = $f3->get('PARAMS.username');
$messages = array();
$output = system("/usr/local/bin/vhost-user-del.sh -u $username", $result_code);
if ($result_code == 0) {
$messages[] = "User '$username' has been deleted.";
} else {
$messages[] = "Error deleting user '$username'.";
$messages[] = $output;
}
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Users");
// $mapping = $f3->get('NAV.mapping');
// if ($mapping == 'vmail') {
// $f3->reroute("/Users");
// } else {
// $f3->reroute("/Email/$domain/Accounts");
// }
}
}

View File

@ -0,0 +1,150 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vhost;
class UsersEdit extends \Panel\Vhost {
function beforeRoute($f3) {
parent::beforeRoute($f3);
$username = $f3->get('PARAMS.username');
if ($user_array = $f3->call('\Panel::vGet', array("vhost-user-get.sh -u $username -c", FALSE))) {
if ($user_array[0]['passwd'] == "") {
$user_array[0]['passwd'] = '(unavailable)';
}
$f3->set('user_array', $user_array[0]);
}
}
static public function get($f3) {
if ($f3->exists('PARAMS.username')) {
$username = $f3->get('PARAMS.username');
$f3->set('page_header', "Edit User: $username");
echo \Template::instance()->render('vhost/users-edit.html');
} else {
$f3->set('page_header', "Users");
echo \Template::instance()->render('vhost/users.html');
}
}
function post($f3) {
$username = $f3->get('PARAMS.username');
$action = $_POST['action'];
if ($action == 'password') {
$password = $_POST['password'];
$password_confirm = $_POST['password_confirm'];
$f3->call('\Panel::validatePassword', array($password, $password_confirm));
/* check for validation errors */
if ($f3->exists('SESSION.messages')) {
$messages = $f3->get('SESSION.messages');
} else {
$password = escapeshellarg($password);
if ($f3->get('WRITEUSERINFO') == '1') {
exec("/usr/local/bin/vhost-user-mod.sh -u $username -p $password -w", $output, $result_code);
} else {
exec("/usr/local/bin/vhost-user-mod.sh -u $username -p $password", $output, $result_code);
}
if ($result_code == 0) {
$messages[] = "Success: Password for $username updated.";
} else {
$messages[] = "Error updating password.";
}
}
} elseif ($action == 'jail') {
exec("/usr/local/bin/vhost-user-jail.sh -u $username >/dev/null 2>/dev/null &", $output, $result_code);
$messages[] = "User is being jailed. Note: Setting up the jail environment takes about a minute to complete and is run in the background now.";
$f3->reroute("/Users/$username");
} elseif ($action == 'fpmmax') {
$fpmmax = $_POST['fpmmax'];
exec("/usr/local/bin/vhost-user-mod.sh -u $username -x $fpmmax", $output, $result_code);
if ($result_code == 0) {
$messages[] = "Success: PHP Workers updated.";
} else {
$messages[] = "Error updating PHP Workers.";
}
} else {
$messages[] = "Unkown edit action.";
}
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Users/$username");
// /* run mod command here */
// $mbox = $f3->get('PARAMS.mbox');
// $domain = $f3->get('PARAMS.domain');
// $mbox_array = $_POST;
// $f3->set('mbox_array', $mbox_array);
// foreach ($mbox_array as $k=>$v) {
// if (strtolower($v) == 'unlimited') {
// $mbox_array[$k] = 'NULL';
// }
// }
// extract($mbox_array);
//
// if ($password != '') {
// $f3->call('\Panel::validatePassword', array($password, $password_confirm));
// }
// settype($status, "integer");
// $f3->call('\Panel::validateEmailStatus', $status);
// if (strtolower($quota == 'unlimited') || strtolower($quota == 'null')) {
// $quota = "NULL";
// } else {
// settype($quota, "integer");
// }
// $f3->call('\Panel::validateEmailQuota', $quota);
// if (strtolower($ratelimit == 'unlimited') || strtolower($ratelimit == 'null')) {
// $ratelimit = "NULL";
// }
// $f3->call('\Panel::validateEmailRatelimit', $ratelimit);
// $f3->call('\Panel::validateEmailFiltering', $filter);
//
// /* check for validation errors */
// if ($f3->exists('SESSION.messages')) {
// $messages = $f3->get('SESSION.messages');
// $messages[] = "Please make changes and re-submit the form to try again.";
// $f3->set('SESSION.messages', $messages);
// $f3->call('\Panel\Vmail\MboxesEdit::get', $f3);
// } else {
// if ($password != '') {
// $password = escapeshellarg($password);
// $pword_cmd = "-p $password";
// } else {
// $pword_cmd = '';
// }
// exec("/usr/local/bin/vmail-mboxes-mod.sh -e $mbox@$domain $pword_cmd -q $quota -r $ratelimit -s $status -j $filter", $output, $result_code);
// if ($result_code == 0) {
// $messages[] = "Success: Email account $mbox@$domain updated.";
// $f3->set('SESSION.messages', $messages);
// $mapping = $f3->get('NAV.mapping');
// if ($mapping == 'vmail') {
// $f3->reroute("/Accounts/$mbox");
// } else {
// $f3->reroute("/Email/$domain/Accounts/$mbox");
// }
// } else {
// $messages[] = "Error.";
// $f3->set('SESSION.messages', $messages);
// $f3->call('\Panel\Vmail\MboxesEdit::get', $f3);
// }
// }
}
}

View File

@ -20,6 +20,39 @@ class Vhosts extends \Panel\Vhost {
$vhost = $f3->get('PARAMS.vhost'); $vhost = $f3->get('PARAMS.vhost');
if ($vhost_array = $f3->call('\Panel::vGet', array("vhost-get.sh -d $vhost -c", FALSE))) { if ($vhost_array = $f3->call('\Panel::vGet', array("vhost-get.sh -d $vhost -c", FALSE))) {
$f3->set('vhost_array', $vhost_array[0]); $f3->set('vhost_array', $vhost_array[0]);
/* get cert info */
if ($f3->call('\Panel::verifyCertificateExists', $vhost)) {
if ($cert_array = $f3->call('\Panel::vGet', array("letsencrypt-get.sh -d $vhost -c", FALSE))) {
$cert_array = $cert_array[0];
/* remove time from expiration date */
$end = $cert_array['end'];
$end_array = explode(' ', $end);
unset($end_array[2]);
$end = implode(' ', $end_array);
$cert_array['end'] = $end;
/* add line breaks */
$common = $cert_array['common'];
$alternative = $cert_array['alternative'];
$alternative = preg_replace('/ /', '<br>', $alternative);
$cert_array['alternative'] = $alternative;
$f3->set('cert_array', $cert_array);
}
}
/* get user info */
$username = $vhost_array[0]['username'];
if ($users_array = $f3->call('\Panel::vGet', array("vhost-user-get.sh -u $username -c", FALSE))) {
if ($users_array[0]['passwd'] == "") {
$users_array[0]['passwd'] = '(unavailable)';
}
$f3->set('users_array', $users_array[0]);
}
/* get mysql db info */
if ($mysqlinfo_array = $f3->call('\Panel::vGet', array("vhost-mysql-db-get.sh -d $vhost -c", FALSE))) {
$f3->set('mysqlinfo_array', $mysqlinfo_array[0]);
} else {
$mysqlinfo_array = array('(unknown)', '(unknown)', '(unknown)', '(unknown)');
$f3->set('mysqlinfo_array', $mysqlinfo_array);
}
} }
} else { } else {
if ($vhosts_array = $f3->call('\Panel::vGet', array("vhost-get.sh -c", FALSE))) { if ($vhosts_array = $f3->call('\Panel::vGet', array("vhost-get.sh -c", FALSE))) {
@ -41,7 +74,7 @@ class Vhosts extends \Panel\Vhost {
$f3->set('vhost_array', $vhost_array); $f3->set('vhost_array', $vhost_array);
$vhost = $f3->get('PARAMS.vhost'); $vhost = $f3->get('PARAMS.vhost');
$f3->set('page_header', "Website Hosting for $vhost"); $f3->set('page_header', "Details for $vhost");
echo \Template::instance()->render('vhost/vhosts-vhost.html'); echo \Template::instance()->render('vhost/vhosts-vhost.html');
} else { } else {

View File

@ -15,9 +15,26 @@ class VhostsAdd extends \Panel\Vhost {
parent::beforeRoute($f3); parent::beforeRoute($f3);
// /* get vm_domains defaults for "add new" form */ /* get system defaults for new websites */
// $vm_domains_defaults = $f3->call('\Panel::vGet', array("vmail-defaults-get.sh -c", FALSE)); $deploy = array();
// $f3->set('vm_domains_defaults', $vm_domains_defaults[0]); $deploy['username'] = '';
if ($users_array = $f3->call('\Panel::vGet', array("vhost-user-get.sh -c", FALSE))) {
$deploy['users_array'] = $users_array;
} else {
$deploy['users_array'] = array();
}
if ($f3->get('WRITEUSERINFO') == '1') {
$deploy['writeuserinfo'] = 1;
} else {
$deploy['writeuserinfo'] = 0;
}
if ($f3->get('SHOWWRITEINFO') == '1') {
$deploy['showwriteinfo'] = 1;
} else {
$deploy['showwriteinfo'] = 0;
}
$f3->set('deploy', $deploy);
} }
@ -34,93 +51,61 @@ class VhostsAdd extends \Panel\Vhost {
$messages = array(); $messages = array();
/* validate domain */ /* validate domain */
if (preg_match('/^[0-9a-z]([-.]?[0-9a-z])*\.[a-z]{2,24}$/i', strtolower($_POST['domain']))) { $domain = strtolower($_POST['domain']);
// strip www $domain = preg_replace('/^www\./', '', $domain);
$domain = strtolower($_POST['domain']); if ($f3->call('\Panel::validateDomain', $domain)) {
} else { if ($f3->call('\Panel::verifyVhostDomainExists', $domain)) {
$messages[] = "Invalid domain name."; $messages = $f3->get('SESSION.messages');
} $messages[] = "$domain already exists on this server.";
/* validate status */
if ($_POST['status'] != 0 && $_POST['status'] != 1) {
$messages[] = "Invalid 'Status'.";
} else {
$status = $_POST['status'];
}
/* validate mbox_limit */
if (strtolower($_POST['mbox_limit']) == 'unlimited' || strtolower($_POST['mbox_limit'] == 'null')) {
$mbox_limit = "NULL";
} elseif (is_numeric($_POST['mbox_limit'])) {
/* make sure mbox_limit is a possitive integer */
$mbox_limit = abs(intval($_POST['mbox_limit']));
if ($mbox_limit < 1) {
echo "Mailbox Limit must be a positive number or \"Unlimited\".\n";
$messages[] = "Mailbox Limit must be a positive number or \"Unlimited\".";
}
} else {
$messages[] = "Mailbox Limit must be a positive number or \"Unlimited\".";
}
/* validate mbox_quota_default */
if (strtolower($_POST['mbox_quota_default']) == 'unlimited' || strtolower($_POST['mbox_quota_default'] == 'null')) {
$mbox_quota_default = "NULL";
} elseif (is_numeric($_POST['mbox_quota_default'])) {
/* make sure mbox_quota_default is a possitive integer */
$mbox_quota_default = abs(intval($_POST['mbox_quota_default']));
if ($mbox_quota_default < 1) {
echo "Default Quota must be a positive number or \"Unlimited\".\n";
$messages[] = "Default Quota must be a positive number or \"Unlimited\".";
}
} else {
$messages[] = "Default Quota must be a positive number or \"Unlimited\".";
}
/* validate mbox_ratelimit_default */
if (strtolower($_POST['mbox_ratelimit_default']) == 'unlimited' || strtolower($_POST['mbox_ratelimit_default'] == 'null')) {
$mbox_ratelimit_default = "NULL";
} elseif (is_numeric($_POST['mbox_ratelimit_default'])) {
/* make sure mbox_ratelimit_default is a possitive integer */
$mbox_ratelimit_default = abs(intval($_POST['mbox_ratelimit_default']));
if ($mbox_ratelimit_default < 1) {
$messages[] = "Default Rate Limit must be a positive number or \"Unlimited\".";
}
} else {
$messages[] = "Default Rate Limit must be a positive number or \"Unlimited\".";
}
/* check for validation errors */
if (count($messages) > 0) {
$messages[] = "Please re-submit the form to try again.";
$f3->set('SESSION.messages', $messages);
$f3->call('\Panel\Vmail\DomainsAdd::get', $f3);
} else {
/* check if vmail domain already exists */
$domain_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -d $domain -c", FALSE));
if (count($domain_array) == 0) {
/* add email domain */
exec("/usr/local/bin/vmail-domains-add.sh -d $domain -l $mbox_limit -q $mbox_quota_default -r $mbox_ratelimit_default -s $status", $output, $result_code);
if ($result_code == 0) {
$messages[] = "Email Domain $domain has been added.";
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Email/$domain");
} else {
if (count($output) > 0) {
foreach ($output as $k=>$output_message) {
$messages[] = "$output_message";
}
} else {
$messages[] = "Unknown error adding Email Domain $domain.";
}
$f3->set('SESSION.messages', $messages);
$f3->call('\Panel\Vmail\DomainsAdd::get', $f3);
}
} else {
$messages[] = "Email Domain '$domain' already exists.";
$f3->set('SESSION.messages', $messages); $f3->set('SESSION.messages', $messages);
$f3->reroute("/Email/$domain"); $f3->reroute("/Websites/Add");
} else {
$cmd = "/usr/local/bin/vhost-deploy.sh -d $domain";
}
} else {
$f3->reroute("/Websites/Add");
}
/* validate username & set related options */
$username = strtolower($_POST['username']);
if ($f3->get('JAILUSER') == '1') {
$jailuser = 1;
} else {
$jailuser = 0;
}
if ($username == '') {
$cmd = "$cmd -j $jailuser";
} else {
if ($f3->call('\Panel::vGet', array("vhost-user-get.sh -u $username -c", FALSE))) {
$cmd = "$cmd -u $username";
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "Error validating username $username.";
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Websites/Add");
} }
} }
if ($f3->get('WRITEUSERINFO') == '1') {
$writeuserinfo = 1;
} else {
$writeuserinfo = 0;
}
$cmd = "$cmd -w $writeuserinfo";
exec("$cmd", $output, $result_code);
if ($result_code == 0) {
$messages[] = "Success: Website $domain added.";
if ($username == '' && $jailuser == 1) {
$messages[] = "Note: New user is being jailed. Setting up the jail environment takes about a minute to complete and is being run in the background now.";
}
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Websites");
} else {
$messages[] = "Error adding website.";
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Websites/Add");
}
} }
} }

View File

@ -0,0 +1,52 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vhost;
class VhostsDelete extends \Panel\Vhost {
function beforeRoute($f3) {
parent::beforeRoute($f3);
/* verify user exists */
}
static function get($f3) {
$f3->set('confirm', 'true');
echo \Template::instance()->render('vhost/vhosts-delete.html');
}
function post($f3) {
/* run delete command here */
$domain = $f3->get('PARAMS.vhost');
$messages = array();
$output = system("/usr/local/bin/vhost-destroy.sh -d $domain", $result_code);
if ($result_code == 0) {
$messages[] = "Website '$domain' has been deleted.";
} else {
$messages[] = "Error deleting website '$domain'.";
$messages[] = $output;
}
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Websites");
// $mapping = $f3->get('NAV.mapping');
// if ($mapping == 'vmail') {
// $f3->reroute("/Users");
// } else {
// $f3->reroute("/Email/$domain/Accounts");
// }
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vhost;
class VhostsDisable extends \Panel\Vhost {
function beforeRoute($f3) {
parent::beforeRoute($f3);
/* verify user exists */
}
static function get($f3) {
/* run delete command here */
$domain = $f3->get('PARAMS.vhost');
$messages = array();
$output = system("/usr/local/bin/vhost-disable.sh -d $domain", $result_code);
if ($result_code == 0) {
$messages[] = "Website '$domain' has been disabled.";
$messages[] = "NOTICE: Nothing has been deleted and the site can be re-enabled at any time.";
} else {
$messages[] = "Error disabling website '$domain'.";
$messages[] = $output;
}
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Websites/$domain");
// $mapping = $f3->get('NAV.mapping');
// if ($mapping == 'vmail') {
// $f3->reroute("/Users");
// } else {
// $f3->reroute("/Email/$domain/Accounts");
// }
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vhost;
class VhostsEnable extends \Panel\Vhost {
function beforeRoute($f3) {
parent::beforeRoute($f3);
}
static function get($f3) {
/* run enable command here */
$domain = $f3->get('PARAMS.vhost');
$messages = array();
$output = system("/usr/local/bin/vhost-enable.sh -d $domain", $result_code);
if ($result_code == 0) {
$messages[] = "Website '$domain' has been enabled.";
} else {
$messages[] = "Error enabling website '$domain'.";
$messages[] = $output;
}
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Websites/$domain");
}
}

View File

@ -0,0 +1,66 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vmail;
class Dkim extends \Panel\Vmail {
/* use this to make query */
function beforeRoute($f3) {
parent::beforeRoute($f3);
if ($f3->exists('PARAMS.domain')) {
$domain = $f3->get('PARAMS.domain');
$dkim_array = array();
if ($f3->call('\Panel::verifyDkimExists', $domain)) {
if ($dkim_array = $f3->call('\Panel::vGet', array("vmail-dkim-get.sh -d $domain -c", FALSE))) {
$dkim_array = $dkim_array[0];
$dkim_array['dns']['host'] = $dkim_array['selector'] . "._domainkey.$domain";
if ($dns_txt_records = dns_get_record($dkim_array['dns']['host'], DNS_TXT)) {
$dkim_array['dns']['status'] = "Verified";
} else {
$dkim_array['dns']['status'] = "Update";
}
} else {
$dkim_array['dns']['status'] = "Error";
}
} else {
$dkim_array['dns']['status'] = "Create";
}
$f3->set('dkim_array', $dkim_array);
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "Domain not set, must specify domain for DKIM info.";
$f3->set('SESSION.messages', $messages);
$f3->reroute('/');
}
}
static function get($f3) {
if ($f3->exists('PARAMS.domain')) {
$domain = $f3->get('PARAMS.domain');
$f3->set('page_header', "DKIM Details for $domain");
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "Domain not set, must specify domain for DKIM info.";
$f3->set('SESSION.messages', $messages);
}
echo \Template::instance()->render('vmail/dkim.html');
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vmail;
class DkimAdd extends \Panel\Vmail {
function beforeRoute($f3) {
parent::beforeRoute($f3);
}
static function get($f3) {
if ($f3->exists('PARAMS.domain')) {
$domain = $f3->get('PARAMS.domain');
if ($f3->call('\Panel::validateDomain', $domain)) {
if ($f3->call('\Panel::verifyVmailDomainExists', $domain)) {
if ($f3->call('\Panel::verifyDkimExists', $domain)) {
$messages = $f3->get('SESSION.messages');
$messages[] = "DKIM for $domain already exists.";
$f3->set('SESSION.messages', $messages);
} else {
exec("/usr/local/bin/vmail-dkim-add.sh -d $domain", $output, $result_code);
$messages = $f3->get('SESSION.messages');
if ($result_code == 0) {
$messages[] = "DKIM for $domain has been added.";
} else {
$messages[] = "ERROR: adding DKIM for $domain failed.";
}
$f3->set('SESSION.messages', $messages);
}
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "ERROR: $domain is not configured for email on this server.";
$f3->set('SESSION.messages', $messages);
}
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "ERROR: Invalid domain: $domain.";
$f3->set('SESSION.messages', $messages);
}
}
parse_str($f3->get('QUERY'), $output);
if (isset($output['r'])) {
$f3->reroute($output['r']);
} else {
$f3->reroute('/');
}
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* vpanel-stack
* https://git.stack-source.com/msb/vpanel-stack
* Copyright (c) 2022 Matthew Saunders Brown <matthewsaundersbrown@gmail.com>
* GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
*/
namespace Panel\Vmail;
class DkimDelete extends \Panel\Vmail {
function beforeRoute($f3) {
parent::beforeRoute($f3);
}
static function get($f3) {
$f3->set('confirm', 'true');
echo \Template::instance()->render('vmail/dkim-delete.html');
}
static function post($f3) {
if ($f3->exists('PARAMS.domain')) {
$domain = $f3->get('PARAMS.domain');
if ($f3->call('\Panel::validateDomain', $domain)) {
if ($f3->call('\Panel::verifyDkimExists', $domain)) {
/* delete the dkim keys */
exec("/usr/local/bin/vmail-dkim-del.sh -d $domain", $output, $result_code);
$messages = $f3->get('SESSION.messages');
if ($result_code == 0) {
$messages[] = "DKIM keys for $domain have been deleted.";
$messages[] = "Note that you probably want to delete the associated DNS record now too.";
} else {
$messages[] = "Error deleting DKIM keys for $domain.";
}
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "DKIM keys for $domain not found.";
}
} else {
$messages = $f3->get('SESSION.messages');
$messages[] = "Invalid domain name ($domain).";
}
}
$f3->set('SESSION.messages', $messages);
$mapping = $f3->get('NAV.mapping');
if ($mapping == 'vmail') {
$f3->reroute("/");
} else {
$f3->reroute("/Email/$domain");
}
}
}

View File

@ -19,15 +19,132 @@ class Domains extends \Panel\Vmail {
$domain = $f3->get('PARAMS.domain'); $domain = $f3->get('PARAMS.domain');
if ($domain_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -d $domain -c", FALSE))) { if ($domain_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -d $domain -c", FALSE))) {
$f3->set('domain_array', $domain_array[0]); $f3->set('domain_array', $domain_array[0]);
/* get cert info */
$mxdomain = "mail.$domain";
if ($f3->call('\Panel::verifyCertificateExists', $mxdomain)) {
if ($cert_array = $f3->call('\Panel::vGet', array("letsencrypt-get.sh -d $mxdomain -c", FALSE))) {
$cert_array = $cert_array[0];
/* remove time from expiration date */
$end = $cert_array['end'];
$end_array = explode(' ', $end);
unset($end_array[2]);
$end = implode(' ', $end_array);
$cert_array['end'] = $end;
/* add line breaks */
$common = $cert_array['common'];
$alternative = $cert_array['alternative'];
$alternative = preg_replace('/ /', '<br>', $alternative);
$cert_array['alternative'] = $alternative;
$f3->set('cert_array', $cert_array);
}
}
/* get dns info */
$dnsinfo = array();
$dnsinfo['verified_count'] = 0;
$dnsinfo['server_addr'] = $_SERVER['SERVER_ADDR'];
# A record
$dnsinfo['a']['color'] = "red";
if ($certdomain_dns = dns_get_record("$mxdomain", DNS_A)) {
if ($certdomain_dns[0]['ip'] == $dnsinfo['server_addr']) {
$dnsinfo['a']['status'] = "Verified";
$dnsinfo['a']['color'] = "black";
$dnsinfo['verified_count']++;
} else {
$dnsinfo['a']['status'] = "Update";
}
} else {
$dnsinfo['a']['status'] = "Create";
}
# MX record
$dnsinfo['mx']['color'] = "red";
if (getmxrr($domain, $mx)) {
if (in_array($mxdomain, $mx)) {
$dnsinfo['mx']['status'] = "Verified";
$dnsinfo['mx']['color'] = "black";
$dnsinfo['verified_count']++;
} else {
$dnsinfo['mx']['status'] = "Update";
}
} else {
$dnsinfo['mx']['status'] = "Create";
}
# SPF (TXT) record
$dnsinfo['spf']['status'] = "Create";
$dnsinfo['spf']['color'] = "red";
if ($dns_txt_records = dns_get_record("$domain", DNS_TXT)) {
foreach ($dns_txt_records as $k=>$dns_txt_record) {
if (str_starts_with($dns_txt_record['txt'], 'v=spf1')) {
$dnsinfo['spf']['status'] = "Update";
if (str_contains($dns_txt_record['txt'], ' mx ')) {
$dnsinfo['spf']['status'] = "Verified";
$dnsinfo['spf']['color'] = "black";
$dnsinfo['verified_count']++;
}
}
}
}
# DKIM
$dnsinfo['dkim']['color'] = "red";
if ($f3->call('\Panel::verifyDkimExists', $domain)) {
if ($dkim_array = $f3->call('\Panel::vGet', array("vmail-dkim-get.sh -d $domain -c", FALSE))) {
$dkim_hostname = $dkim_array[0]['selector'] . "._domainkey.$domain";
if ($dns_txt_records = dns_get_record($dkim_hostname, DNS_TXT)) {
$dnsinfo['dkim']['status'] = "Verified";
$dnsinfo['dkim']['color'] = "black";
$dnsinfo['verified_count']++;
} else {
$dnsinfo['dkim']['status'] = "Update";
$dnsinfo['dkim']['selector'] = $dkim_array[0]['selector'];
$dnsinfo['dkim']['dkim'] = $dkim_array[0]['dkim'];
}
} else {
$dnsinfo['dkim']['status'] = "Error";
}
} else {
$dnsinfo['dkim']['status'] = "Create";
}
if ($dnsinfo['verified_count'] == 4) {
$dnsinfo['status'] = 'Verified';
} else {
$dnsinfo['status'] = 'Update';
}
$f3->set('dnsinfo', $dnsinfo);
} }
} else { } else {
if ($domains_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -c", FALSE))) { if ($domains_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -c", FALSE))) {
$f3->set('domains_array', $domains_array); $f3->set('domains_array', $domains_array);
} }
} }
} }
// $dnsinfo['mx']['status'] = ready/update/none
// $dnsinfo['spf']['status'] = ready/update/none
// $dnsinfo['spf']['data'] = current_record
// $dnsinfo['dkim']['status'] = ready/update/none
// $dnsinfo['dkim']['selector'] =
// $dnsinfo['dkim']['data'] =
// dns_a gethostbyname()
// dns_mx getmxrr() / dns_get_mx()
// dns_spf checkdnsrr() / dns_check_record()
// dns_dkim
// # domain does not have cert yet, check DNS
// $dns_a_record = $_SERVER['SERVER_ADDR'];
// if ($certdomain_dns = dns_get_record("$certdomain", DNS_A)) {
// if ($certdomain_dns[0]['ip'] == $dns_a_record) {
// $dns_status = "ready";
// } else {
// $dns_status = "update";
// }
// } else {
// $dns_status = "none";
// }
// $f3->set('dns_a_record', $dns_a_record);
// $f3->set('dns_status', $dns_status);
static function get($f3) { static function get($f3) {
if ($f3->exists('PARAMS.domain')) { if ($f3->exists('PARAMS.domain')) {

View File

@ -34,10 +34,13 @@ class DomainsAdd extends \Panel\Vmail {
$messages = array(); $messages = array();
/* validate domain */ /* validate domain */
if (preg_match('/^[0-9a-z]([-.]?[0-9a-z])*\.[a-z]{2,24}$/i', strtolower($_POST['domain']))) { $domain = strtolower($_POST['domain']);
$domain = strtolower($_POST['domain']); $domain = preg_replace('/^mail\./', '', $domain);
} else { $domain = preg_replace('/^www\./', '', $domain);
$messages[] = "Invalid domain name."; if ($f3->call('\Panel::validateDomain', $domain)) {
if ($f3->call('\Panel::verifyVmailDomainExists', $domain)) {
$messages[] = "Email Domain '$domain' already exists.";
}
} }
/* validate status */ /* validate status */
@ -94,32 +97,23 @@ class DomainsAdd extends \Panel\Vmail {
$f3->set('SESSION.messages', $messages); $f3->set('SESSION.messages', $messages);
$f3->call('\Panel\Vmail\DomainsAdd::get', $f3); $f3->call('\Panel\Vmail\DomainsAdd::get', $f3);
} else { } else {
/* check if vmail domain already exists */ /* add email domain */
$domain_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -d $domain -c", FALSE)); exec("/usr/local/bin/vmail-domains-add.sh -d $domain -l $mbox_limit -q $mbox_quota_default -r $mbox_ratelimit_default -s $status", $output, $result_code);
if (count($domain_array) == 0) { if ($result_code == 0) {
/* add email domain */ $messages[] = "Email Domain $domain has been added.";
exec("/usr/local/bin/vmail-domains-add.sh -d $domain -l $mbox_limit -q $mbox_quota_default -r $mbox_ratelimit_default -s $status", $output, $result_code);
if ($result_code == 0) {
$messages[] = "Email Domain $domain has been added.";
$f3->set('SESSION.messages', $messages);
$f3->reroute("/Email/$domain");
} else {
if (count($output) > 0) {
foreach ($output as $k=>$output_message) {
$messages[] = "$output_message";
}
} else {
$messages[] = "Unknown error adding Email Domain $domain.";
}
$f3->set('SESSION.messages', $messages);
$f3->call('\Panel\Vmail\DomainsAdd::get', $f3);
}
} else {
$messages[] = "Email Domain '$domain' already exists.";
$f3->set('SESSION.messages', $messages); $f3->set('SESSION.messages', $messages);
$f3->reroute("/Email/$domain"); $f3->reroute("/Email/$domain");
} else {
if (count($output) > 0) {
foreach ($output as $k=>$output_message) {
$messages[] = "$output_message";
}
} else {
$messages[] = "Unknown error adding Email Domain $domain.";
}
$f3->set('SESSION.messages', $messages);
$f3->call('\Panel\Vmail\DomainsAdd::get', $f3);
} }
} }
} }
} }

View File

@ -58,7 +58,7 @@ class MboxesAdd extends \Panel\Vmail {
extract($_POST); extract($_POST);
$f3->call('\Panel::validateEmailLocalpart', $localpart); $f3->call('\Panel::validateEmailLocalpart', $localpart);
$f3->call('\Panel::validateEmailPassword', array($password, $password_confirm)); $f3->call('\Panel::validatePassword', array($password, $password_confirm));
settype($status, "integer"); settype($status, "integer");
$f3->call('\Panel::validateEmailStatus', $status); $f3->call('\Panel::validateEmailStatus', $status);
if (strtolower($quota == 'unlimited') || strtolower($quota == 'null')) { if (strtolower($quota == 'unlimited') || strtolower($quota == 'null')) {

View File

@ -62,7 +62,7 @@ class MboxesEdit extends \Panel\Vmail {
extract($mbox_array); extract($mbox_array);
if ($password != '') { if ($password != '') {
$f3->call('\Panel::validateEmailPassword', array($password, $password_confirm)); $f3->call('\Panel::validatePassword', array($password, $password_confirm));
} }
settype($status, "integer"); settype($status, "integer");
$f3->call('\Panel::validateEmailStatus', $status); $f3->call('\Panel::validateEmailStatus', $status);

View File

@ -16,6 +16,15 @@ LICENSE=GPL-3.0
LICENSEURL=https://www.gnu.org/licenses/gpl-3.0.txt LICENSEURL=https://www.gnu.org/licenses/gpl-3.0.txt
CASELESS=FALSE CASELESS=FALSE
CACHE=TRUE CACHE=TRUE
; session lifetime in seconds ; Session lifetime in seconds
TIMEOUT=900 TIMEOUT=900
; Remote IP address that is automatically logged in without auth
ADMINIP= ADMINIP=
; Jail new users by default. 1 = Yes, blank or 0 = No
JAILUSER=1
; PHP-FPM pm.max_children. Recommended range 2-12 on Shared Server
FPMMAX=4
; Write user info to /home/username/.passwd. 1 = Yes, blank or 0 = No
WRITEUSERINFO=1
; Show "Write User Info" & "Write DB Info" options. If no then just use defaults above without giving users the option to change. 1 = Yes, blank or 0 = No
SHOWWRITEINFO=0

View File

@ -30,6 +30,9 @@
/Email/@domain/Accounts/@mbox/Forwarding/Add [sync] = Panel\Vmail\ForwardsAdd /Email/@domain/Accounts/@mbox/Forwarding/Add [sync] = Panel\Vmail\ForwardsAdd
/Email/@domain/Accounts/@mbox/Forwarding/Edit [sync] = Panel\Vmail\ForwardsEdit /Email/@domain/Accounts/@mbox/Forwarding/Edit [sync] = Panel\Vmail\ForwardsEdit
/Email/@domain/Accounts/@mbox/Forwarding/Delete [sync] = Panel\Vmail\ForwardsDelete /Email/@domain/Accounts/@mbox/Forwarding/Delete [sync] = Panel\Vmail\ForwardsDelete
/Email/@domain/Dkim [sync] = Panel\Vmail\Dkim
/Email/@domain/Dkim/Add [sync] = Panel\Vmail\DkimAdd
/Email/@domain/Dkim/Delete [sync] = Panel\Vmail\DkimDelete
/Email/@domain/Aliases [sync] = Panel\Vmail\Aliases /Email/@domain/Aliases [sync] = Panel\Vmail\Aliases
/Email/@domain/Aliases/@alias [sync] = Panel\Vmail\Aliases /Email/@domain/Aliases/@alias [sync] = Panel\Vmail\Aliases
/Email/@domain/Autoresponders [sync] = Panel\Vmail\Autoresponders /Email/@domain/Autoresponders [sync] = Panel\Vmail\Autoresponders
@ -38,16 +41,17 @@
/Websites [sync] = Panel\Vhost\Vhosts /Websites [sync] = Panel\Vhost\Vhosts
/Websites/Add [sync] = Panel\Vhost\VhostsAdd /Websites/Add [sync] = Panel\Vhost\VhostsAdd
/Websites/@vhost [sync] = Panel\Vhost\Vhosts /Websites/@vhost [sync] = Panel\Vhost\Vhosts
/Websites/@vhost/Edit [sync] = Panel\Vhost\VhostsEdit /Websites/@vhost/Disable [sync] = Panel\Vhost\VhostsDisable
/Websites/@vhost/Enable [sync] = Panel\Vhost\VhostsEnable
/Websites/@vhost/Delete [sync] = Panel\Vhost\VhostsDelete /Websites/@vhost/Delete [sync] = Panel\Vhost\VhostsDelete
/Websites/@vhost/Databases [sync] = Panel\Vhost\Databases ; /Websites/@vhost/Databases [sync] = Panel\Vhost\Databases
/Websites/@vhost/Databases/Add [sync] = Panel\Vhost\DatabasesAdd ; /Websites/@vhost/Databases/Add [sync] = Panel\Vhost\DatabasesAdd
/Websites/@vhost/Databases/Delete [sync] = Panel\Vhost\DatabasesDelete ; /Websites/@vhost/Databases/Delete [sync] = Panel\Vhost\DatabasesDelete
/Websites/@vhost/Databases/Users [sync] = Panel\Vhost\DatabasesUsers ; /Websites/@vhost/Databases/Users [sync] = Panel\Vhost\DatabasesUsers
/Websites/@vhost/Databases/Users/Add [sync] = Panel\Vhost\DatabasesUsersAdd ; /Websites/@vhost/Databases/Users/Add [sync] = Panel\Vhost\DatabasesUsersAdd
/Websites/@vhost/Databases/Users/Edit [sync] = Panel\Vhost\DatabasesUsersEdit ; /Websites/@vhost/Databases/Users/Edit [sync] = Panel\Vhost\DatabasesUsersEdit
/Websites/@vhost/Databases/Users/Delete [sync] = Panel\Vhost\DatabasesUsersDelete ; /Websites/@vhost/Databases/Users/Delete [sync] = Panel\Vhost\DatabasesUsersDelete
/Websites/Redirects [sync] = Panel\Vhost\Redirects /Websites/Redirects [sync] = Panel\Vhost\Redirects
/Websites/Redirects/Add [sync] = Panel\Vhost\RedirectsAdd /Websites/Redirects/Add [sync] = Panel\Vhost\RedirectsAdd
@ -70,6 +74,14 @@
; System users (ssh, websites) ; System users (ssh, websites)
/Users [sync] = Panel\Vhost\Users /Users [sync] = Panel\Vhost\Users
/Users/Add [sync] = Panel\Vhost\UsersAdd /Users/Add [sync] = Panel\Vhost\UsersAdd
/Users/@user [sync] = Panel\Vhost\Users /Users/@username [sync] = Panel\Vhost\Users
/Users/@user/Edit [sync] = Panel\Vhost\UsersEdit /Users/@username/Edit [sync] = Panel\Vhost\UsersEdit
/Users/@user/Delete [sync] = Panel\Vhost\UsersDelete /Users/@username/Delete [sync] = Panel\Vhost\UsersDelete
/Certs [sync] = Panel\Cert\Certs
/Certs/@cert [sync] = Panel\Cert\Certs
/Certs/@cert/Add [sync] = Panel\Cert\CertsAdd
/Certs/@cert/Delete [sync] = Panel\Cert\CertsDelete
/Databases [sync] = Panel\MySQL\Databases

View File

@ -0,0 +1,40 @@
<include href="header.html" />
<table>
<tr>
<th style="white-space: nowrap; text-align: center;">Certificate</th>
<th style="white-space: nowrap; text-align: center;">Expiration</th>
<th style="white-space: nowrap; text-align: center;">Secured Hostnames</th>
<th>Action</th>
</tr>
<tr>
<td style="white-space: nowrap; text-align: right;">{{ @cert_array.common }}</td>
<td style="white-space: nowrap; text-align: center;">{{ @cert_array.end }}</td>
<td style="white-space: nowrap; text-align: right;">{{ @cert_array.alternative | raw }}</td>
<td style="white-space: nowrap; text-align: right;"><a href="{{@BASE}}/Certs/{{@cert_array.common}}/Delete?r=/Certs">Delete</a></td>
</tr>
</table>
<br><br>
<!--
<table>
<tr>
<td style="white-space: nowrap; text-align: right;"><b>Domain Name:</b></td>
<td style="white-space: nowrap; text-align: center;">{{ @cert_array.common }}</td>
</tr>
<tr>
<td style="white-space: nowrap; text-align: right;"><b>Valid From:</b></td>
<td style="white-space: nowrap; text-align: center;">{{ @cert_array.start }}</td>
</tr>
<tr>
<td style="white-space: nowrap; text-align: right;"><b>Valid Until:</b></td>
<td style="white-space: nowrap; text-align: center;">{{ @cert_array.end }}</td>
</tr>
<tr>
<td style="white-space: nowrap; text-align: right;"><b>Additional Names:</b></td>
<td style="white-space: nowrap; text-align: right;">{{ @cert_array.alternative | raw }}</td>
</tr>
</table>-->
<include href="footer.html" />

38
panel/ui/cert/certs.html Normal file
View File

@ -0,0 +1,38 @@
<include href="header.html" />
<check if="isset(@certs_array)">
<true>
<table>
<tr>
<th style="white-space: nowrap; text-align: center;">Certificate</th>
<th style="white-space: nowrap; text-align: center;">Expiration</th>
<th style="white-space: nowrap; text-align: center;">Secured Hostnames</th>
</tr>
<repeat group="{{ @certs_array }}" value="{{ @cert_array }}">
<tr>
<td style="white-space: nowrap; text-align: right;"><a href="{{@BASE}}/Certs/{{ @cert_array.common }}">{{ @cert_array.common }}</a></td>
<td style="white-space: nowrap; text-align: center;">{{ @cert_array.end }}</td>
<td style="white-space: nowrap; text-align: right;">{{ @cert_array.alternative | raw }}</td>
</tr>
</repeat>
</table>
<p>
<b>Certificate</b> is the name of the configuration and is normally the primary domain secured.<br>
<b>Expiration</b> is the date the certificate expires. Certificates are automatically renewed 30 days before expiration.<br>
<b>Secured Hostnames</b> is the full list of domain names secured by the certificate.<br>
</p>
</true>
<false>
<p>There are no security certificates on this server.</p>
</false>
</check>
<p>
<a href="{{@NAV.fullpath}}/Add">Add new Certificate form</a>
</p>
<include href="footer.html" />

View File

@ -10,6 +10,17 @@
<link rel="icon" href="{{@BASE}}/ui/favicon.ico"> <link rel="icon" href="{{@BASE}}/ui/favicon.ico">
<link href="{{@BASE}}/ui/css/awsm.css" type="text/css" rel="stylesheet" /> <link href="{{@BASE}}/ui/css/awsm.css" type="text/css" rel="stylesheet" />
<title>{{@PACKAGE}}</title> <title>{{@PACKAGE}}</title>
<style>
.hidden {
opacity: 0;
transition: opacity 0.5s ease;
}
.hidden:hover {
opacity: 1;
}
</style>
</head> </head>
<body> <body>
<header> <header>
@ -19,8 +30,9 @@
<check if="{{@NAV.mapping=='vpanel'}}"> <check if="{{@NAV.mapping=='vpanel'}}">
<a href="{{@BASE}}/Websites">Websites</a> | <a href="{{@BASE}}/Websites">Websites</a> |
<a href="{{@BASE}}/Email">Email</a> | <a href="{{@BASE}}/Email">Email</a> |
<a href="{{@BASE}}/mysql/databases">Databases</a> | <a href="{{@BASE}}/Databases">Databases</a> |
<a href="{{@BASE}}/letsencrypt/ertificates">Certificates</a> | <a href="{{@BASE}}/Certs">Certificates</a> |
<a href="{{@BASE}}/Users">Users</a> |
<a href="{{@BASE}}/Logout">Logout</a> <a href="{{@BASE}}/Logout">Logout</a>
</check> </check>

View File

@ -0,0 +1,8 @@
<include href="header.html" />
phpMyAdmin can be reached at <a href="https://{{@HOST}}/phpMyAdmin">https://{{@HOST}}/phpMyAdmin</a>
<br>
<br>
<small>Note that phpMyAdmin requires a two-stage login process.<br>First use the websites "Remote Access" info (SFTP/SSH username & password) for the popup authentication.<br>Then use the MySQL info for the database login page.</small>
<include href="footer.html" />

View File

@ -0,0 +1,185 @@
<include href="header.html" />
<form action="{{@REALM}}" method="POST">
<fieldset>
<legend>Add New System User</legend>
<label for="username">Username <small>(must be between 3 and 16 characters in length)</small></label>
<input id="username" name="username" type="text" placeholder="joe" value="{{ @user.username }}" required>
<label for="password">Password <small>(minimum 8 characters, recommended 12 or more)</small></label>
<input id="password" name="password" type="password" value="{{ @user.password }}">
<label for="password_confirm">Confirm Password <small>(repeat same password)</small></label>
<input id="password_confirm" name="password_confirm" type="password" value="{{ @user.password_confirm }}">
<small>Leave both password fields blank for random password generation.</small>
<label for="jailuser">Jail User</label>
<select id="jailuser" name="jailuser">
<check if="{{ @user.jailuser=='1' }}">
<true>
<option value="1" selected>Yes</option>
<option value="0">No</option>
</true>
<false>
<option value="1">Yes</option>
<option value="0" selected>No</option>
</false>
</check>
</select>
<check if="{{ @user.showwriteinfo=='1' }}">
<true>
<label for="writeuserinfo">Write User Info <small>(Writes to /home/username/.passwd)</small></label>
<select id="writeuserinfo" name="writeuserinfo">
<check if="{{ @user.writeuserinfo=='1' }}">
<true>
<option value="1" selected>Yes</option>
<option value="0">No</option>
</true>
<false>
<option value="1">Yes</option>
<option value="0" selected>No</option>
</false>
</check>
</select>
</true>
<false>
<input type="hidden" name="writeuserinfo" value="{{ @user.writeuserinfo }}">
</false>
</check>
<label for="fpmmax">PHP Workers <small>(Recommended range 2-12 on a Shared Server)</small></label>
<select id="fpmmax" name="fpmmax">
<check if="{{ @user.fpmmax=='2' }}">
<true>
<option value="2" selected>2</option>
</true>
<false>
<option value="2">2</option>
</false>
</check>
<check if="{{ @user.fpmmax=='4' }}">
<true>
<option value="4" selected>4</option>
</true>
<false>
<option value="4">4</option>
</false>
</check>
<check if="{{ @user.fpmmax=='6' }}">
<true>
<option value="6" selected>6</option>
</true>
<false>
<option value="6">6</option>
</false>
</check>
<check if="{{ @user.fpmmax=='8' }}">
<true>
<option value="8" selected>8</option>
</true>
<false>
<option value="8">8</option>
</false>
</check>
<check if="{{ @user.fpmmax=='10' }}">
<true>
<option value="10" selected>10</option>
</true>
<false>
<option value="10">10</option>
</false>
</check>
<check if="{{ @user.fpmmax=='12' }}">
<true>
<option value="12" selected>12</option>
</true>
<false>
<option value="12">12</option>
</false>
</check>
<check if="{{ @user.fpmmax=='16' }}">
<true>
<option value="16" selected>16</option>
</true>
<false>
<option value="16">16</option>
</false>
</check>
<check if="{{ @user.fpmmax=='20' }}">
<true>
<option value="20" selected>20</option>
</true>
<false>
<option value="20">20</option>
</false>
</check>
<check if="{{ @user.fpmmax=='24' }}">
<true>
<option value="24" selected>24</option>
</true>
<false>
<option value="24">24</option>
</false>
</check>
<check if="{{ @user.fpmmax=='30' }}">
<true>
<option value="30" selected>30</option>
</true>
<false>
<option value="30">30</option>
</false>
</check>
<check if="{{ @user.fpmmax=='36' }}">
<true>
<option value="36" selected>36</option>
</true>
<false>
<option value="36">36</option>
</false>
</check>
<check if="{{ @user.fpmmax=='42' }}">
<true>
<option value="42" selected>42</option>
</true>
<false>
<option value="42">42</option>
</false>
</check>
<check if="{{ @user.fpmmax=='48' }}">
<true>
<option value="48" selected>48</option>
</true>
<false>
<option value="48">48</option>
</false>
</check>
</select>
<input type="submit" value="Submit">
<button id="reset" type="reset">Reset</button>
<check if="{{ @NAV.mapping=='vpanel' }}">
<br>
<!-- <small>Enter "Unlimited" for no limit on Quota or Rate Limit.</small>-->
</check>
</fieldset>
</form>
<p>
<b>Username</b> is the system username that a website can be installed under. It will be used for SFTP & SSH access. Mixed case usernames are converted to all lower case.<br>
<b>Password</b> must be at least 8 characters with 12 or more being highly recommended.<br>
Passwords under 15 characters must contain characters from at least three of the following four groups:<br>
Lower Case Letters, Uppler Case Leters, Numbers, Puncuation/Special Characters.<br>
Passwords 15 or more characters long do not have any complexity requirements.<br>
<b>Jail</b> determines if the user will be "jailed". This is recommended on a Shared Server. Once a user is Jailed it can't be undone by these admins.<br>
<check if="{{ @user.showwriteinfo=='1' }}">
<b>Write User Info</b> writes user account information to /home/username/.passwd. Includes encrypted password, which can be unencrypted.<br>
</check>
<b>PHP Workers</b> is maximum number of PHP processes that this user can have running at one time. Any website(s) installed for this user will be limited by this.<br>
</p>
<include href="footer.html" />

View File

@ -0,0 +1,20 @@
<include href="header.html" />
<check if="isset(@confirm)">
<true>
<form action="{{@REALM}}" method="POST">
<fieldset>
<legend>Really Delete User {{ @PARAMS.username }}</legend>
<br>
<input type="submit" value="Delete {{ @PARAMS.username }}">
<br>
<small><b>CAUTION:</b> This will permanently remove the user {{ @PARAMS.username }} and any related settings and files from this server. There is no undo after this!</small>
</fieldset>
</form>
</true>
<false>
Go to <a href="{{@BASE}}/Users">Users</a></br>
</false>
</check>
<include href="footer.html" />

View File

@ -0,0 +1,176 @@
<include href="header.html" />
<!-- password -->
<p>
<form action="{{@REALM}}" method="POST">
<fieldset>
<legend>Change Password for {{ @user_array.username }}</legend>
<input id="action" name="action" type="hidden" value="password">
<label for="password">Password <small></small></label>
<input id="password" name="password" type="password" value="">
<label for="password_confirm">Confirm Password <small>(repeat same password)</small></label>
<input id="password_confirm" name="password_confirm" type="password" value="">
<input type="submit" value="Submit">
<button id="reset" type="reset">Reset</button>
<br>
<b>Password</b> must be at least 8 characters with 12 or more being highly recommended.<br>
Passwords under 15 characters must contain characters from at least three of the following four groups:<br>
Lower Case Letters, Uppler Case Leters, Numbers, Puncuation/Special Characters.<br>
Passwords 15 or more characters long do not have any complexity requirements.<br>
</fieldset>
</form>
</p>
<p>
<!-- jail -->
<form action="{{@REALM}}" method="POST">
<fieldset>
<legend>Jail User {{ @user_array.username }}</legend>
<input id="action" name="action" type="hidden" value="jail">
<check if="{{ @user_array.jailed=='Yes' }}">
<true>
User is Jailed. This can not be undone by this admin.
</true>
<false>
<input type="submit" value="Jail User"><br>
<b>Jail User</b> puts existing user in a secure jail. This is recommended on a Shared Server. Once a user is Jailed it can't be undone by these admins.<br>
</false>
</check>
</fieldset>
</form>
</p>
<!-- php workers -->
<p>
<form action="{{@REALM}}" method="POST">
<fieldset>
<legend>Adjust PHP Workers for {{ @user_array.username }}</legend>
<input id="action" name="action" type="hidden" value="fpmmax">
<check if="{{ @user_array.fpmmax=='0' }}">
<true>
User {{ @user_array.username }} does not have PHP enabled.
</true>
<false>
<label for="fpmmax">Adjust PHP Workers <small>(Recommended range 2-12 on a Shared Server)</small></label>
<select id="fpmmax" name="fpmmax">
<check if="{{ @user_array.fpmmax=='2' }}">
<true>
<option value="2" selected>2</option>
</true>
<false>
<option value="2">2</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='4' }}">
<true>
<option value="4" selected>4</option>
</true>
<false>
<option value="4">4</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='6' }}">
<true>
<option value="6" selected>6</option>
</true>
<false>
<option value="6">6</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='8' }}">
<true>
<option value="8" selected>8</option>
</true>
<false>
<option value="8">8</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='10' }}">
<true>
<option value="10" selected>10</option>
</true>
<false>
<option value="10">10</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='12' }}">
<true>
<option value="12" selected>12</option>
</true>
<false>
<option value="12">12</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='16' }}">
<true>
<option value="16" selected>16</option>
</true>
<false>
<option value="16">16</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='20' }}">
<true>
<option value="20" selected>20</option>
</true>
<false>
<option value="20">20</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='24' }}">
<true>
<option value="24" selected>24</option>
</true>
<false>
<option value="24">24</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='30' }}">
<true>
<option value="30" selected>30</option>
</true>
<false>
<option value="30">30</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='36' }}">
<true>
<option value="36" selected>36</option>
</true>
<false>
<option value="36">36</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='42' }}">
<true>
<option value="42" selected>42</option>
</true>
<false>
<option value="42">42</option>
</false>
</check>
<check if="{{ @user_array.fpmmax=='48' }}">
<true>
<option value="48" selected>48</option>
</true>
<false>
<option value="48">48</option>
</false>
</check>
</select>
<input type="submit" value="Submit">
<button id="reset" type="reset">Reset</button><br>
<b>PHP Workers</b> is maximum number of PHP processes that this user can have running at one time. Any website(s) installed for this user will be limited by this.<br>
</false>
</check>
</fieldset>
</form>
</p>
<include href="footer.html" />

View File

@ -0,0 +1,54 @@
<include href="header.html" />
<table>
<tr>
<th>Username</th>
<th>Password</th>
<th>Jailed</th>
<th>PHP Workers</th>
<th>Action</th>
</tr>
<tr>
<td>{{ @users_array.username }}</td>
<td><span class="hidden">{{ @users_array.passwd }}</span></td>
<td>{{ @users_array.jailed }}</td>
<td>{{ @users_array.fpmmax }}</td>
<td><a href="{{@REALM}}/Edit">Edit</a> <a href="{{@REALM}}/Delete">Delete</a></td>
</tr>
</table>
<br><br>
<h3>Websites</h3>
<br>
<check if="isset(@vhosts_array)">
<true>
<table>
<tr>
<th>Website</th>
<th>Username</th>
<th>Status</th>
<th>Action</th>
</tr>
<repeat group="{{ @vhosts_array }}" value="{{ @vhost_domain }}">
<tr>
<td><a href="{{@BASE}}/Websites/{{ @vhost_domain.virtualhost }}">{{ @vhost_domain.virtualhost }}</a></td>
<td>{{ @vhost_domain.username }}</td>
<td>{{ @vhost_domain.status }}</td>
<td><a href="{{@BASE}}/Websites/{{ @vhost_domain.virtualhost }}/Disable">Disable</a> <a href="{{@BASE}}/Websites/{{ @vhost_domain.virtualhost }}/Delete">Delete</a></td>
</tr>
</repeat>
</table>
</true>
<false>
There are no websites for this user.
</false>
</check>
<include href="footer.html" />

43
panel/ui/vhost/users.html Normal file
View File

@ -0,0 +1,43 @@
<include href="header.html" />
<check if="isset(@users_array)">
<true>
<table>
<tr>
<th>Username</th>
<th>Password</th>
<th>Jailed</th>
<th>PHP Workers</th>
</tr>
<repeat group="{{ @users_array }}" value="{{ @userinfo }}">
<tr>
<td><a href="{{@BASE}}/Users/{{ @userinfo.username }}">{{ @userinfo.username }}</a></td>
<td><span class="hidden">{{ @userinfo.passwd }}</span></td>
<td>{{ @userinfo.jailed }}</td>
<td>{{ @userinfo.fpmmax }}</td>
</tr>
</repeat>
</table>
<p>
Select a <b>Username</b> name above for more details and options.<br>
<b>Username</b> is the system username that owns the website files.<br>
<!--<b>uid</b> is the system User ID assigned to this user on this server.<br>-->
<b>Password</b> is the unencrypted password for the user, if available. Scroll over field to reveal password.<br>
<b>Jailed</b> is a flag indicating if the user is jailed.<br>
<b>PHP Workers</b> is maximum number of PHP processes that this user can have running at one time.<br>
</p>
</true>
<false>
<p>There are no website system users on this server.</p>
</false>
</check>
<p>
<a href="{{@NAV.fullpath}}/Add">Add new User form</a>
</p>
<include href="footer.html" />

View File

@ -7,22 +7,31 @@
<label for="domain">Domain Name <small>(Do not include 'www', that will be aliased to the main domain)</small></label> <label for="domain">Domain Name <small>(Do not include 'www', that will be aliased to the main domain)</small></label>
<input id="domain" name="domain" type="text" placeholder="example.com" value="" required> <input id="domain" name="domain" type="text" placeholder="example.com" value="" required>
<label for="username">Username <small>(System username, leave empty for auto-creation)</small></label> <label for="username">Username <small>(Used for SSH & SFTP access, leave empty to auto-create new user)</small></label>
<input id="username" name="username" type="text" placeholder="examplec" value="" required> <select id="username" name="username">
<check if="{{ @deploy.username=='' }}">
<true>
<option value="" selected></option>
</true>
<false>
<option value=""></option>
</false>
</check>
<repeat group="{{ @deploy.users_array }}" value="{{ @user_array }}">
<check if="{{ @deploy.username==@user_array.username }}">
<true>
<option value="{{ @user_array.username }}" selected>{{ @user_array.username }}</option>
</true>
<false>
<option value="{{ @user_array.username }}">{{ @user_array.username }}</option>
</false>
</check>
</repeat>
Check to jail user: <input type="checkbox" id="jail" name="jail" value="yes" checked>
<label for="macro">Config <small>(A valid Let's Encrypt certificate is required for VHostHTTPS)</small></label>
<select id="macro" name="macro">
<option value="VHostHTTP" selected>VHostHTTP</option>
<option value="VHostHTTPS">VHostHTTPS</option>
</select> </select>
<small><a href="{{@BASE}}/Users/Add">Add user here</a> first if you want to create a specific username for this site.</small>
<label for="status">Status</label> <br><br>
<select id="status" name="status">
<option value="1" selected>Enabled</option>
<option value="0">Disabled</option>
</select>
<input type="submit" value="Submit"> <input type="submit" value="Submit">
<button id="reset" type="reset" disabled>Reset</button> <button id="reset" type="reset" disabled>Reset</button>
@ -34,10 +43,8 @@
</form> </form>
<p> <p>
<b>Username</b><br> <b>Domain Name</b> Enter the website domain name without the leading www.<br>
<b>Jail</b><br> <b>Username</b> By default this should be left blank which will create a new (unique) username just for this site. Optionally you can pick from a list of existing users, or first go to the User admin and create a specific username &amp; password.<br>
<b>Config</b><br>
<b>Status</b> can be used to temporarily disable the website domain without deleting any settings or files.<br>
</p> </p>

View File

@ -0,0 +1,20 @@
<include href="header.html" />
<check if="isset(@confirm)">
<true>
<form action="{{@REALM}}" method="POST">
<fieldset>
<legend>Really Delete Website {{ @PARAMS.vhost }}</legend>
<br>
<input type="submit" value="Delete {{ @PARAMS.vhost }}">
<br>
<small><b>CAUTION:</b> This will permanently remove the {{ @PARAMS.vhost }} website and any related databases and username from this server. There is no undo after this!</small>
</fieldset>
</form>
</true>
<false>
Go to <a href="{{@BASE}}/Websites">Websites</a></br>
</false>
</check>
<include href="footer.html" />

View File

@ -2,35 +2,97 @@
<table> <table>
<tr> <tr>
<th>Domain</th> <th>Website</th>
<th>Status</th> <th>Status</th>
<th>Mailbox Limit</th> <th>Action</th>
<th>Default Quota</th>
<th>Default Rate Limit</th>
<check if="{{ @NAV.mapping=='vpanel' }}">
<th>Action</th>
</check>
</tr> </tr>
<tr> <tr>
<td>{{ @vhost_array.domain }}</td> <td>{{ @vhost_array.virtualhost }}</td>
<td>{{ @vhost_array.status }}</td> <td>{{ @vhost_array.status }}</td>
<td>{{ @vhost_array.mbox_limit }}</td> <td>
<td>{{ @vhost_array.mbox_quota_default }}</td> <check if="{{ @vhost_array.status=='Enabled' }}">
<td>{{ @vhost_array.mbox_ratelimit_default }}</td> <true><a href="{{@REALM}}/Disable">Disable</a></true>
<check if="{{ @NAV.mapping=='vpanel' }}"> <false><a href="{{@REALM}}/Enable">Enable</a></false>
<td><a href="{{@NAV.vhostbase}}/Edit">Edit</a> <a href="{{@NAV.vhostbase}}/Delete">Delete</a></td> </check>
</check> <a href="{{@REALM}}/Delete">Delete</a>
</td>
</tr> </tr>
</table> </table>
<nav> <br><br>
<ul>
<li><a href="{{@NAV.vhostbase}}/Databases">Databases</a></li> <table>
<li><a href="{{@NAV.vhostbase}}/Certificates">Let's Encrypt</a></li> <tr>
<li><a href="{{@BASE}}/Users/{{@vhost_domain.username}}">System User</a></li> <th>System User</th>
<!-- <li><a href="{{@NAV.vhostbase}}/Forwards">Forwards</a></li> --> <th>PHP Workers</th>
</ul> <th>Action</th>
</nav> </tr>
<tr>
<td>{{ @vhost_array.username }}</td>
<td>{{ @users_array.fpmmax }}</td>
<td><a href="{{@BASE}}/Users/{{ @users_array.username }}/Edit">Edit</a></td>
</tr>
</table>
<br><br>
<check if="isset(@cert_array)">
<true>
<table>
<tr>
<th style="white-space: nowrap;">Certificate</th>
<th style="white-space: nowrap;">Expiration</th>
<th style="white-space: nowrap;">Secured Hostnames</th>
<th>Action</th>
</tr>
<tr>
<td style="white-space: nowrap;">{{ @cert_array.common }}</td>
<td style="white-space: nowrap;">{{ @cert_array.end }}</td>
<td style="white-space: nowrap; text-align: right;">{{ @cert_array.alternative | raw }}</td>
<td style="white-space: nowrap;"><a href="{{@BASE}}/Certs/{{@vhost_array.virtualhost}}/Delete?r={{@PATH}}">Delete</a></td>
</tr>
</table>
</true>
<false>
<a href="{{@BASE}}/Certs/{{@vhost_array.virtualhost}}/Add?r={{@PATH}}">Add Security Certificate</a>
</false>
</check>
<br><br>
<table>
<tr><th colspan="2">Remote Access</th></tr>
<tr><td style="text-align: right;">Protocol:</td><td>SFTP (SSH File Transfer Protocol)</td></tr>
<tr><td style="text-align: right;">Hostname:</td><td>{{ @vhost_array.virtualhost }}</td></tr>
<tr><td style="text-align: right;">Username:</td><td>{{ @vhost_array.username }}</td></tr>
<tr><td style="text-align: right;">Password:</td><td><span class="hidden">{{ @users_array.passwd }}</span> <small>&larr; hover mouse here to reveal</small></td></tr>
<tr><td style="text-align: right;">User Home Directory:</td><td>/home/{{ @vhost_array.username }}/</td></tr>
<tr><td style="text-align: right;">Website Files Directory:</td><td>/srv/www/{{ @vhost_array.username }}/html/</td></tr>
<tr><td style="text-align: right;">User Jailed:</td><td>{{ @users_array.jailed }}</td></tr>
<tr><td style="text-align: right;">Action:</td><td><a href="{{@BASE}}/Users/{{ @users_array.username }}/Edit">Edit User</a></td></tr>
<tr><td style="text-align: right;"><small>Notes:</small></td><td><small>The above information can also be used for SSH access.</small></td></tr>
</table>
<br><br>
<table>
<tr><th colspan="2">MySQL Database Info</th></tr>
<tr><td style="text-align: right;">Hostname:</td><td>{{ @mysqlinfo_array.hostname }}</td></tr>
<tr><td style="text-align: right;">Database:</td><td>{{ @mysqlinfo_array.database }}</td></tr>
<tr><td style="text-align: right;">Username:</td><td>{{ @mysqlinfo_array.username }}</td></tr>
<tr><td style="text-align: right;">Password:</td><td><span class="hidden">{{ @mysqlinfo_array.password }}</span> <small>&larr; hover mouse here to reveal</small></td></tr>
<tr><td style="text-align: right;">phpMyAdmin:</td><td>
<check if="isset(@cert_array)">
<true>
<a href="https://{{ @vhost_array.virtualhost }}/phpMyAdmin">https://{{ @vhost_array.virtualhost }}/phpMyAdmin</a>
</true>
<false>
<a href="https://{{ @HOST }}/phpMyAdmin">https://{{ @HOST }}/phpMyAdmin</a>
</false>
</check>
</td></tr>
<tr><td style="text-align: right;"><small>Notes:</small></td><td><small>phpMyAdmin requires a two-stage login process.<br>First use the "Remote Access" info for the popup authentication.<br>Then use the MySQL info for the database login page.</small></td></tr>
</table>
<include href="footer.html" /> <include href="footer.html" />

View File

@ -6,16 +6,14 @@
<tr> <tr>
<th>Website</th> <th>Website</th>
<th>Username</th> <th>Username</th>
<th>Config</th>
<th>Status</th> <th>Status</th>
</tr> </tr>
<repeat group="{{ @vhosts_array }}" value="{{ @vhost_domain }}"> <repeat group="{{ @vhosts_array }}" value="{{ @vhost_domain }}">
<tr> <tr>
<td><a href="{{@BASE}}/Websites/{{ @vhost_domain.virtualhost }}">{{ @vhost_domain.virtualhost }}</a></td> <td><a href="{{@BASE}}/Websites/{{ @vhost_domain.virtualhost }}">{{ @vhost_domain.virtualhost }}</a></td>
<td>{{ @vhost_domain.username }}</td> <td><a href="{{@BASE}}/Users/{{ @vhost_domain.username }}">{{ @vhost_domain.username }}</a></td>
<td>{{ @vhost_domain.config }}</td>
<td>{{ @vhost_domain.status }}</td> <td>{{ @vhost_domain.status }}</td>
</tr> </tr>

View File

@ -0,0 +1,20 @@
<include href="header.html" />
<check if="isset(@confirm)">
<true>
<form action="{{@REALM}}" method="POST">
<fieldset>
<legend>Really Delete DKIM {{ @PARAMS.domain }}</legend>
<br>
<input type="submit" value="Delete DKIM for {{ @PARAMS.domain }}">
<br>
<small><b>CAUTION:</b> This will permanently remove the DKIM keys for {{ @PARAMS.domain }}.<br>You probably want to delete the associated DNS entry too.</small>
</fieldset>
</form>
</true>
<false>
Go to <a href="{{@NAV.vmailbase}}">{{ @PARAMS.domain }}</a></br>
</false>
</check>
<include href="footer.html" />

44
panel/ui/vmail/dkim.html Normal file
View File

@ -0,0 +1,44 @@
<include href="header.html" />
<check if="isset(@dkim_array)">
<true>
<check if="{{@dkim_array.dns.status == 'Create'}}">
<true>
DKIM for {{@PARAMS.domain}} does not exist.<br>
<a href="{{@NAV.vmailbase}}/Dkim/Add?r={{@PATH}}">Click here to add a DKIM Key now.</a>
</true>
<false>
<check if="{{@dkim_array.dns.status == 'Error'}}">
<true>
Error checking for DKIM for {{@PARAMS.domain}}. Try again later.
</true>
<false>
<table>
<tr><th colspan="2">DKIM (TXT) Record for {{ @PARAMS.domain }}</th></tr>
<check if="{{ @dkim_array.dns.status=='Verified' }}">
<true>
<tr><td colspan="2">This DNS record is all set, nothing to do.</td></tr>
</true>
<false>
<tr><td style="color:red" colspan="2">Please update DNS with the following info.</td></tr>
</false>
</check>
<tr><td style="text-align: right;">Type:</td><td>TXT</td></tr>
<tr><td style="text-align: right;">Host:</td><td>{{ @dkim_array.dns.host }}</td></tr>
<tr><td style="text-align: right;">Value:</td><td style="width: 650px; overflow-wrap: anywhere;">"k=rsa; p={{ @dkim_array.dkim }}"</td></tr>
<tr><td style="text-align: right;">TTL:</td><td>3600 (or default)</td></tr>
<tr><td></td><td><small>This should be the only record for this specific Host value listed above.</small></td></tr>
</table>
<br><br>
<a href="{{@NAV.vmailbase}}/Dkim/Delete">Click here to delete this DKIM Key.</a>
</false>
</check>
</false>
</check>
</true>
</check>
<include href="footer.html" />

View File

@ -12,11 +12,11 @@
</check> </check>
</tr> </tr>
<tr> <tr>
<td>{{ @domain_array.domain }}</td> <td>{{ @domain_array.domain }}</td>
<td>{{ @domain_array.status }}</td> <td>{{ @domain_array.status }}</td>
<td>{{ @domain_array.mbox_limit }}</td> <td>{{ @domain_array.mbox_limit }}</td>
<td>{{ @domain_array.mbox_quota_default }}</td> <td>{{ @domain_array.mbox_quota_default }}</td>
<td>{{ @domain_array.mbox_ratelimit_default }}</td> <td>{{ @domain_array.mbox_ratelimit_default }}</td>
<check if="{{ @NAV.mapping=='vpanel' }}"> <check if="{{ @NAV.mapping=='vpanel' }}">
<td><a href="{{@NAV.vmailbase}}/Edit">Edit</a> <a href="{{@NAV.vmailbase}}/Delete">Delete</a></td> <td><a href="{{@NAV.vmailbase}}/Edit">Edit</a> <a href="{{@NAV.vmailbase}}/Delete">Delete</a></td>
</check> </check>
@ -32,29 +32,171 @@
</ul> </ul>
</nav> </nav>
<hr>
<check if="isset(@cert_array)">
<true>
<table>
<tr>
<th style="white-space: nowrap;">Certificate</th>
<th style="white-space: nowrap;">Expiration</th>
<th style="white-space: nowrap;">Secured Hostnames</th>
<th>Action</th>
</tr>
<tr>
<td style="white-space: nowrap;">{{ @cert_array.common }}</td>
<td style="white-space: nowrap;">{{ @cert_array.end }}</td>
<td style="white-space: nowrap; text-align: right;">{{ @cert_array.alternative | raw }}</td>
<td style="white-space: nowrap;"><a href="{{@BASE}}/Certs/mail.{{@domain_array.domain}}/Delete?r={{@PATH}}">Delete</a></td>
</tr>
</table>
</true>
<false>
<check if="{{ @dnsinfo.a.status=='Verified' }}">
<true>
You need a Security Certificate. <a href="{{@BASE}}/Certs/mail.{{@domain_array.domain}}/Add?r={{@PATH}}">Click Here</a> to add one now.
</true>
<false>
<span style="color:red">You need a Security Certificate for {{ @domain_array.domain }}.</span> Before you can add one you must create the DNS "A" record below. Once that's completed come back here and this message will change to an option to create a Security Certificate.
</false>
</check>
</false>
</check>
<!--<br><br>
<b>DNS:</b> <br>
<b>SPF:</b> <br>
<b>DKIM:</b> <br>
<b>Let's Encrypt:</b><br>
<br><br> <br><br>
Email Application Settings:<br>
mail.example.com etc etc.<br> <check if="{{ @dnsinfo.status=='Verified' }}">
webmail<br> <true>
<br> &check; All email related DNS settings for {{ @domain_array.domain }} have been verified.
POP<br> <br><br>
Port 110 with or without STARTTLS<br> Webmail is available at: <a href="https://mail.{{ @domain_array.domain }}">https://mail.{{ @domain_array.domain }}</a>
Port 995 SSL/TLS<br> </true>
<br> <false>
IMAP<br> <h3 style="color:red">NOTICE: You need to update the DNS settings for {{ @domain_array.domain }}:</h3>
Port 143 with or without STARTTLS<br> <small>Note that after you add DNS records it can take some time for the changes to propagate and show up here.</small>
Port 993 SSL/TLS<br> <br><br>
<br> <table>
SMTP<br> <tr><th style="color:{{ @dnsinfo.a.color }}" colspan="2">{{ @dnsinfo.a.status }} A Record for mail.{{ @domain_array.domain }}</th></tr>
Port 25 plain, no security<br> <check if="{{ @dnsinfo.a.status=='Verified' }}">
Port 587 with or without STARTTLS<br> <true>
Port 465 SSL/TLS<br> <tr><td colspan="2">This DNS record is all set, nothing to do.</td></tr>
<br>--> </true>
<false>
<tr><td style="text-align: right;">Type:</td><td>A</td></tr>
<tr><td style="text-align: right;">Host:</td><td>mail.{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">Value:</td><td>{{ @dnsinfo.server_addr }}</td></tr>
<tr><td style="text-align: right;">TTL:</td><td>3600 (or default)</td></tr>
<tr><td></td><td><small>This should be the only A record for mail.{{ @domain_array.domain }}.</small></td></tr>
</false>
</check>
</table>
<br><br>
<table>
<tr><th style="color:{{ @dnsinfo.mx.color }}" colspan="2">{{ @dnsinfo.mx.status }} MX Record for {{ @domain_array.domain }}</th></tr>
<check if="{{ @dnsinfo.mx.status=='Verified' }}">
<true>
<tr><td colspan="2">This DNS record is all set, nothing to do.</td></tr>
</true>
<false>
<tr><td style="text-align: right;">Type:</td><td>MX</td></tr>
<tr><td style="text-align: right;">Host:</td><td>{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">Value:</td><td>mail.{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">TTL:</td><td>3600 (or default)</td></tr>
<tr><td style="text-align: right;">Priority:</td><td>10 (or default)</td></tr>
<tr><td></td><td><small>This should be the only MX record for {{ @domain_array.domain }}<br>unless you have a backup MX system.</small></td></tr>
</false>
</check>
</table>
<br><br>
<table>
<tr><th style="color:{{ @dnsinfo.spf.color }}" colspan="2">{{ @dnsinfo.spf.status }} SPF (TXT) Record for {{ @domain_array.domain }}</th></tr>
<check if="{{ @dnsinfo.spf.status=='Verified' }}">
<true>
<tr><td colspan="2">This DNS record is all set, nothing to do.</td></tr>
</true>
<false>
<tr><td style="text-align: right;">Type:</td><td>TXT</td></tr>
<tr><td style="text-align: right;">Host:</td><td>{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">Value:</td><td>"v=spf1 a mx -all"</td></tr>
<tr><td style="text-align: right;">TTL:</td><td>3600 (or default)</td></tr>
<tr><td></td><td><small>There may be other TXT records for {{ @domain_array.domain }}.</small></td></tr>
</check>
</table>
<br><br>
<table>
<tr><th style="color:{{ @dnsinfo.dkim.color }}" colspan="2">{{ @dnsinfo.dkim.status }} DKIM (TXT) Record for {{ @domain_array.domain }}</th></tr>
<check if="{{ @dnsinfo.dkim.status=='Verified' }}">
<true>
<tr><td colspan="2">DKIM is all set, nothing to do.</td></tr>
</true>
</check>
<check if="{{ @dnsinfo.dkim.status=='Update' }}">
<true>
<tr><td style="text-align: right;">Type:</td><td>TXT</td></tr>
<tr><td style="text-align: right;">Host:</td><td>{{ @dnsinfo.dkim.selector }}._domainkey.{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">Value:</td><td style="width: 650px; overflow-wrap: anywhere;">"k=rsa; p={{ @dnsinfo.dkim.dkim }}"</td></tr>
<tr><td style="text-align: right;">TTL:</td><td>3600 (or default)</td></tr>
<tr><td></td><td><small>This should be the only TXT record for this specific Host name.</small></td></tr>
</true>
</check>
<check if="{{ @dnsinfo.dkim.status=='Create' }}">
<true>
<tr><td colspan="2"><a href="{{@NAV.vmailbase}}/Dkim/Add?r={{@PATH}}">Add DKIM Key</a><br>Use the link above to create a DKIM configuration. Once that's completed come back here and this message will change to instructions on adding an associated DNS record.</td></tr>
</true>
</check>
</table>
</false>
</check>
<!-- https://www.w3docs.com/snippets/css/how-to-wrap-a-long-string-without-any-whitespace-character.html -->
<hr>
<h3>Email Client Configuration Options</h3>
<small>Use the settings below to configure email clients like<br>Mozilla Thunderbird, Apple Mail, Microsoft Outlook, et cetera.</small>
<br><br>
<table>
<tr><th colspan="2">Incoming Server Settings</th></tr>
<tr><td style="text-align: right;">Protocol:</td><td>IMAP</td></tr>
<tr><td style="text-align: right;">Hostname:</td><td>mail.{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">Port</td><td>143</td></tr>
<tr><td style="text-align: right;">Connection Security:</td><td>STARTTLS</td></tr>
<tr><td style="text-align: right;">Authentication Method:</td><td>Normal password</td></tr>
<tr><td style="text-align: right;">Username:</td><td>(full email address)</td></tr>
<tr><td style="text-align: right;"><small>Notes:</small></td><td><small>Alternative options that work:<br>Connection Security: SSL/TLS<br>Port: 993</small></td></tr>
</table>
<br><br>
<table>
<tr><th colspan="2">Outgoing Server Settings</th></tr>
<tr><td style="text-align: right;">Protocol:</td><td>SMTP</td></tr>
<tr><td style="text-align: right;">Hostname:</td><td>mail.{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">Port</td><td>587</td></tr>
<tr><td style="text-align: right;">Connection Security:</td><td>STARTTLS</td></tr>
<tr><td style="text-align: right;">Authentication Method:</td><td>Normal password</td></tr>
<tr><td style="text-align: right;">Username:</td><td>(full email address)</td></tr>
<tr><td style="text-align: right;"><small>Notes:</small></td><td><small>Alternative options that work:<br>Connection Security: SSL/TLS<br>Port: 465</small></td></tr>
</table>
<br><br>
<table>
<tr><th colspan="2">Alternative Incoming Server Settings</th></tr>
<tr><td style="text-align: right;">Protocol:</td><td>POP3</td></tr>
<tr><td style="text-align: right;">Hostname:</td><td>mail.{{ @domain_array.domain }}</td></tr>
<tr><td style="text-align: right;">Port</td><td>110</td></tr>
<tr><td style="text-align: right;">Connection Security:</td><td>STARTTLS</td></tr>
<tr><td style="text-align: right;">Authentication Method:</td><td>Normal password</td></tr>
<tr><td style="text-align: right;">Username:</td><td>(full email address)</td></tr>
<tr><td style="text-align: right;"><small>Notes:</small></td><td><small>Alternative options that work:<br>Connection Security: SSL/TLS<br>Port: 995</small></td></tr>
<tr><td colspan="2" style="text-align: left;"><small>IMAP (listed above) keeps your folders and emails synced on your<br>server and is the recommended default.<br>POP3 listed here keeps your folders and emails on your local<br>computer and can sometimes be a prefered configuration.</small></td></tr>
</table>
<include href="footer.html" /> <include href="footer.html" />