removed ticketsystem; lots of work on cron stuff

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann
2018-12-19 13:09:14 +01:00
parent 903b775f79
commit 3ff20e327f
86 changed files with 3823 additions and 7421 deletions

View File

@@ -1,71 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2016 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Froxlor team <team@froxlor.org> (2016-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Classes
*
*/
class DnsEntry
{
public $record;
public $ttl;
public $class = 'IN';
public $type;
public $priority;
public $content;
public function __construct($record = '', $type = 'A', $content = null, $prio = 0, $ttl = 18000, $class = 'IN')
{
$this->record = $record;
$this->type = $type;
$this->content = $content;
$this->priority = $prio;
$this->ttl = $ttl;
$this->class = $class;
}
public function __toString()
{
$_content = $this->content;
// check content length for txt records for bind9 (multiline)
if (Settings::Get('system.dns_server') != 'pdns' && $this->type == 'TXT' && strlen($_content) >= 255) {
// split string
$_contentlines = str_split($_content, 254);
// first line
$_l = array_shift($_contentlines);
// check for starting quote
if (substr($_l, 0, 1) == '"') {
$_l = substr($_l, 1);
}
$_content = '("' . $_l . '"' . PHP_EOL;
$_l = array_pop($_contentlines);
// check for ending quote
if (substr($_l, - 1) == '"') {
$_l = substr($_l, 0, - 1);
}
foreach ($_contentlines as $_cl) {
// lines in between
$_content .= "\t\t\t\t" . '"' . $_cl . '"' . PHP_EOL;
}
// last line
$_content .= "\t\t\t\t" . '"' . $_l . '")';
}
$result = $this->record . "\t" . $this->ttl . "\t" . $this->class . "\t" . $this->type . "\t" . (($this->priority >= 0 && ($this->type == 'MX' || $this->type == 'SRV')) ? $this->priority . "\t" : "") . $_content . PHP_EOL;
return $result;
}
}

View File

@@ -1,47 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2016 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Froxlor team <team@froxlor.org> (2016-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Classes
*
*/
class DnsZone
{
public $ttl;
public $origin;
public $serial;
public $records;
public function __construct($ttl = 18000, $origin = '', $serial = '', $records = null)
{
$this->ttl = $ttl;
$this->origin = $origin;
$this->serial = $serial;
$this->records = $records;
}
public function __toString()
{
$_zonefile = "\$TTL " . $this->ttl . PHP_EOL;
$_zonefile .= "\$ORIGIN " . $this->origin . "." . PHP_EOL;
if (! empty($this->records)) {
foreach ($this->records as $record) {
$_zonefile .= (string) $record;
}
}
return $_zonefile;
}
}

View File

@@ -1,134 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2016 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Froxlor team <team@froxlor.org> (2016-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
*/
class PowerDNS
{
private static $pdns_db = null;
private static function connectToPdnsDb()
{
// get froxlor pdns config
$cf = Settings::Get('system.bindconf_directory') . '/froxlor/pdns_froxlor.conf';
$config = makeCorrectFile($cf);
if (! file_exists($config)) {
die('PowerDNS configuration file (' . $config . ') not found. Did you go through the configuration templates?' . PHP_EOL);
}
$lines = file($config);
$mysql_data = array();
foreach ($lines as $line) {
$line = trim($line);
if (strtolower(substr($line, 0, 6)) == 'gmysql') {
$namevalue = explode("=", $line);
$mysql_data[$namevalue[0]] = $namevalue[1];
}
}
// build up connection string
$driver = 'mysql';
$dsn = $driver . ":";
$options = array(
'PDO::MYSQL_ATTR_INIT_COMMAND' => 'SET names utf8'
);
$attributes = array(
'ATTR_ERRMODE' => 'ERRMODE_EXCEPTION'
);
$dbconf = array();
$dbconf["dsn"] = array(
'dbname' => $mysql_data["gmysql-dbname"],
'charset' => 'utf8'
);
if (isset($mysql_data['gmysql-socket']) && ! empty($mysql_data['gmysql-socket'])) {
$dbconf["dsn"]['unix_socket'] = makeCorrectFile($mysql_data['gmysql-socket']);
} else {
$dbconf["dsn"]['host'] = $mysql_data['gmysql-host'];
$dbconf["dsn"]['port'] = $mysql_data['gmysql-port'];
}
// add options to dsn-string
foreach ($dbconf["dsn"] as $k => $v) {
$dsn .= $k . "=" . $v . ";";
}
// clean up
unset($dbconf);
// try to connect
try {
self::$pdns_db = new PDO($dsn, $mysql_data['gmysql-user'], $mysql_data['gmysql-password'], $options);
} catch (PDOException $e) {
die($e->getMessage());
}
// set attributes
foreach ($attributes as $k => $v) {
self::$pdns_db->setAttribute(constant("PDO::" . $k), constant("PDO::" . $v));
}
$version_server = self::$pdns_db->getAttribute(PDO::ATTR_SERVER_VERSION);
$sql_mode = 'NO_ENGINE_SUBSTITUTION';
if (version_compare($version_server, '8.0.11', '<')) {
$sql_mode .= ',NO_AUTO_CREATE_USER';
}
self::$pdns_db->exec('SET sql_mode = "'.$sql_mode.'"');
}
/**
* get pdo database connection to powerdns database
*
* @return PDO
*/
public static function getDB()
{
if (! isset(self::$pdns_db) || (self::$pdns_db instanceof PDO) == false) {
self::connectToPdnsDb();
}
return self::$pdns_db;
}
/**
* remove all records and entries of a given domain
*
* @param array $domain
*/
public static function cleanDomainZone($domain = null)
{
if (is_array($domain) && isset($domain['domain'])) {
$pdns_domains_stmt = self::getDB()->prepare("SELECT `id`, `name` FROM `domains` WHERE `name` = :domain");
$del_rec_stmt = self::getDB()->prepare("DELETE FROM `records` WHERE `domain_id` = :did");
$del_meta_stmt = self::getDB()->prepare("DELETE FROM `domainmetadata` WHERE `domain_id` = :did");
$del_dom_stmt = self::getDB()->prepare("DELETE FROM `domains` WHERE `id` = :did");
$pdns_domains_stmt->execute(array(
'domain' => $domain['domain']
));
$pdns_domain = $pdns_domains_stmt->fetch(\PDO::FETCH_ASSOC);
$del_rec_stmt->execute(array(
'did' => $pdns_domain['id']
));
$del_meta_stmt->execute(array(
'did' => $pdns_domain['id']
));
$del_dom_stmt->execute(array(
'did' => $pdns_domain['id']
));
}
}
}

View File

@@ -1,109 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @since 0.9.33
*
*/
/**
* Class frxDirectory handles directory actions and gives information
* about a given directory in connections with its usage in froxlor
*
* @author Michael Kaufmann (d00p) <d00p@froxlor.org>
*
*/
class frxDirectory {
/**
* directory string
*
* @var string
*/
private $_dir = null;
/**
* class constructor, optionally set directory
*
* @param string $dir
*/
public function __construct($dir = null) {
$this->_dir = $dir;
}
/**
* check whether the directory has options set in panel_htaccess
*/
public function hasUserOptions() {
$uo_stmt = Database::prepare("
SELECT COUNT(`id`) as `usropts` FROM `".TABLE_PANEL_HTACCESS."` WHERE `path` = :dir
");
$uo_res = Database::pexecute_first($uo_stmt, array('dir' => makeCorrectDir($this->_dir)));
if ($uo_res != false && isset($uo_res['usropts'])) {
return ($uo_res['usropts'] > 0 ? true : false);
}
return false;
}
/**
* check whether the directory is protected using panel_htpasswd
*/
public function isUserProtected() {
$up_stmt = Database::prepare("
SELECT COUNT(`id`) as `usrprot` FROM `".TABLE_PANEL_HTPASSWDS."` WHERE `path` = :dir
");
$up_res = Database::pexecute_first($up_stmt, array('dir' => makeCorrectDir($this->_dir)));
if ($up_res != false && isset($up_res['usrprot'])) {
return ($up_res['usrprot'] > 0 ? true : false);
}
return false;
}
/**
* Checks if a given directory is valid for multiple configurations
* or should rather be used as a single file
*
* @param bool $ifexists also check whether file/dir exists
*
* @return bool true if usable as dir, false otherwise
*/
public function isConfigDir($ifexists = false) {
if (is_null($this->_dir)) {
trigger_error(__CLASS__.'::'.__FUNCTION__.' has been called with a null value', E_USER_WARNING);
return false;
}
if (file_exists($this->_dir)) {
if (is_dir($this->_dir)) {
$returnval = true;
} else {
$returnval = false;
}
} else {
if (!$ifexists) {
if (substr($this->_dir, -1) == '/') {
$returnval = true;
} else {
$returnval = false;
}
} else {
$returnval = false;
}
}
return $returnval;
}
}

View File

@@ -1,105 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @link http://www.nutime.de/
* @since 0.9.16
*
*/
class phpinterface {
/**
* Domain-Data array
* @var array
*/
private $_domain = array();
/**
* Interface object
* @var object
*/
private $_interface = null;
/**
* Admin-User data array
* @var array
*/
private $_admin_cache = array();
/**
* main constructor
*/
public function __construct($domain) {
$this->_domain = $domain;
$this->_setInterface();
}
/**
* returns the interface-object
* from where we can control it
*/
public function getInterface() {
return $this->_interface;
}
/**
* set interface-object by type of
* php-interface: fcgid or php-fpm
* sets private $_interface variable
*/
private function _setInterface() {
// php-fpm
if ((int)Settings::Get('phpfpm.enabled') == 1) {
$this->_interface = new phpinterface_fpm($this->_domain);
} elseif ((int)Settings::Get('system.mod_fcgid') == 1) {
$this->_interface = new phpinterface_fcgid($this->_domain);
}
}
/**
* return the php-configuration from the database
*
* @param int $php_config_id id of the php-configuration
*
* @return array
*/
public function getPhpConfig($php_config_id) {
$php_config_id = intval($php_config_id);
// If domain has no config, we will use the default one.
if ($php_config_id == 0) {
$php_config_id = 1;
}
if (!isset($this->php_configs_cache[$php_config_id])) {
$stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_PHPCONFIGS . "` WHERE `id` = :id"
);
$this->_php_configs_cache[$php_config_id] = Database::pexecute_first($stmt, array('id' => $php_config_id));
if ((int)Settings::Get('phpfpm.enabled') == 1) {
$stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` WHERE `id` = :id"
);
$this->_php_configs_cache[$php_config_id]['fpm_settings'] = Database::pexecute_first($stmt, array('id' => $this->_php_configs_cache[$php_config_id]['fpmsettingid']));
}
}
return $this->_php_configs_cache[$php_config_id];
}
}

View File

@@ -1,253 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @link http://www.nutime.de/
* @since 0.9.16
*
*/
class phpinterface_fcgid {
/**
* Domain-Data array
* @var array
*/
private $_domain = array();
/**
* Admin-Date cache array
* @var array
*/
private $_admin_cache = array();
/**
* main constructor
*/
public function __construct($domain) {
$this->_domain = $domain;
}
/**
* create fcgid-starter-file
* @param array $phpconfig
*/
public function createConfig($phpconfig) {
// create starter
$starter_file = "#!/bin/sh\n\n";
$starter_file.= "#\n";
$starter_file.= "# starter created/changed on " . date("Y.m.d H:i:s") . " for domain '" . $this->_domain['domain'] . "' with id #" . $this->_domain['id'] . " from php template '" . $phpconfig['description'] . "' with id #" . $phpconfig['id'] . "\n";
$starter_file.= "# Do not change anything in this file, it will be overwritten by the Froxlor Cronjob!\n";
$starter_file.= "#\n\n";
$starter_file.= "umask ".$phpconfig['mod_fcgid_umask']."\n";
$starter_file.= "PHPRC=" . escapeshellarg($this->getConfigDir()) . "\n";
$starter_file.= "export PHPRC\n";
// set number of processes for one domain
if ((int)$this->_domain['mod_fcgid_starter'] != - 1) {
$starter_file.= "PHP_FCGI_CHILDREN=" . (int)$this->_domain['mod_fcgid_starter'] . "\n";
} else {
if ((int)$phpconfig['mod_fcgid_starter'] != - 1) {
$starter_file.= "PHP_FCGI_CHILDREN=" . (int)$phpconfig['mod_fcgid_starter'] . "\n";
} else {
$starter_file.= "PHP_FCGI_CHILDREN=" . (int)Settings::Get('system.mod_fcgid_starter') . "\n";
}
}
$starter_file.= "export PHP_FCGI_CHILDREN\n";
// set number of maximum requests for one domain
if ((int)$this->_domain['mod_fcgid_maxrequests'] != - 1) {
$starter_file.= "PHP_FCGI_MAX_REQUESTS=" . (int)$this->_domain['mod_fcgid_maxrequests'] . "\n";
} else {
if ((int)$phpconfig['mod_fcgid_maxrequests'] != - 1) {
$starter_file.= "PHP_FCGI_MAX_REQUESTS=" . (int)$phpconfig['mod_fcgid_maxrequests'] . "\n";
} else {
$starter_file.= "PHP_FCGI_MAX_REQUESTS=" . (int)Settings::Get('system.mod_fcgid_maxrequests') . "\n";
}
}
$starter_file.= "export PHP_FCGI_MAX_REQUESTS\n";
// Set Binary
$starter_file.= "exec " . $phpconfig['binary'] . " -c " . escapeshellarg($this->getConfigDir()) . "\n";
//remove +i attibute, so starter can be overwritten
if (file_exists($this->getStarterFile())) {
removeImmutable($this->getStarterFile());
}
$starter_file_handler = fopen($this->getStarterFile(), 'w');
fwrite($starter_file_handler, $starter_file);
fclose($starter_file_handler);
safe_exec('chmod 750 ' . escapeshellarg($this->getStarterFile()));
safe_exec('chown ' . $this->_domain['guid'] . ':' . $this->_domain['guid'] . ' ' . escapeshellarg($this->getStarterFile()));
setImmutable($this->getStarterFile());
}
/**
* create customized php.ini
*
* @param array $phpconfig
*/
public function createIniFile($phpconfig) {
$openbasedir = '';
$openbasedirc = ';';
if ($this->_domain['openbasedir'] == '1') {
$openbasedirc = '';
$_phpappendopenbasedir = '';
$_custom_openbasedir = explode(':', Settings::Get('system.mod_fcgid_peardir'));
foreach ($_custom_openbasedir as $cobd) {
$_phpappendopenbasedir .= appendOpenBasedirPath($cobd);
}
$_custom_openbasedir = explode(':', Settings::Get('system.phpappendopenbasedir'));
foreach ($_custom_openbasedir as $cobd) {
$_phpappendopenbasedir .= appendOpenBasedirPath($cobd);
}
if ($this->_domain['openbasedir_path'] == '0'
&& strstr($this->_domain['documentroot'], ":") === false
) {
$openbasedir = appendOpenBasedirPath($this->_domain['documentroot'], true);
} else {
$openbasedir = appendOpenBasedirPath($this->_domain['customerroot'], true);
}
$openbasedir .= appendOpenBasedirPath($this->getTempDir());
$openbasedir .= $_phpappendopenbasedir;
} else {
$openbasedir = 'none';
$openbasedirc = ';';
}
$admin = $this->_getAdminData($this->_domain['adminid']);
$php_ini_variables = array(
'SAFE_MODE' => 'Off', // keep this for compatibility, just in case
'PEAR_DIR' => Settings::Get('system.mod_fcgid_peardir'),
'TMP_DIR' => $this->getTempDir(),
'CUSTOMER_EMAIL' => $this->_domain['email'],
'ADMIN_EMAIL' => $admin['email'],
'DOMAIN' => $this->_domain['domain'],
'CUSTOMER' => $this->_domain['loginname'],
'ADMIN' => $admin['loginname'],
'OPEN_BASEDIR' => $openbasedir,
'OPEN_BASEDIR_C' => $openbasedirc,
'OPEN_BASEDIR_GLOBAL' => Settings::Get('system.phpappendopenbasedir'),
'DOCUMENT_ROOT' => makeCorrectDir($this->_domain['documentroot']),
'CUSTOMER_HOMEDIR' => makeCorrectDir($this->_domain['customerroot'])
);
//insert a small header for the file
$phpini_file = ";\n";
$phpini_file.= "; php.ini created/changed on " . date("Y.m.d H:i:s") . " for domain '" . $this->_domain['domain'] . "' with id #" . $this->_domain['id'] . " from php template '" . $phpconfig['description'] . "' with id #" . $phpconfig['id'] . "\n";
$phpini_file.= "; Do not change anything in this file, it will be overwritten by the Froxlor Cronjob!\n";
$phpini_file.= ";\n\n";
$phpini_file.= replace_variables($phpconfig['phpsettings'], $php_ini_variables);
$phpini_file = str_replace('"none"', 'none', $phpini_file);
//$phpini_file = preg_replace('/\"+/', '"', $phpini_file);
$phpini_file_handler = fopen($this->getIniFile(), 'w');
fwrite($phpini_file_handler, $phpini_file);
fclose($phpini_file_handler);
safe_exec('chown root:0 ' . escapeshellarg($this->getIniFile()));
safe_exec('chmod 0644 ' . escapeshellarg($this->getIniFile()));
}
/**
* fcgid-config directory
*
* @param boolean $createifnotexists create the directory if it does not exist
*
* @return string the directory
*/
public function getConfigDir($createifnotexists = true) {
$configdir = makeCorrectDir(Settings::Get('system.mod_fcgid_configdir') . '/' . $this->_domain['loginname'] . '/' . $this->_domain['domain'] . '/');
if (!is_dir($configdir) && $createifnotexists) {
safe_exec('mkdir -p ' . escapeshellarg($configdir));
safe_exec('chown ' . $this->_domain['guid'] . ':' . $this->_domain['guid'] . ' ' . escapeshellarg($configdir));
}
return $configdir;
}
/**
* fcgid-temp directory
*
* @param boolean $createifnotexists create the directory if it does not exist
*
* @return string the directory
*/
public function getTempDir($createifnotexists = true) {
$tmpdir = makeCorrectDir(Settings::Get('system.mod_fcgid_tmpdir') . '/' . $this->_domain['loginname'] . '/');
if (!is_dir($tmpdir) && $createifnotexists) {
safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
safe_exec('chown -R ' . $this->_domain['guid'] . ':' . $this->_domain['guid'] . ' ' . escapeshellarg($tmpdir));
safe_exec('chmod 0750 ' . escapeshellarg($tmpdir));
}
return $tmpdir;
}
/**
* return path of php-starter file
*
* @return string the directory
*/
public function getStarterFile() {
$starter_filename = makeCorrectFile($this->getConfigDir() . '/php-fcgi-starter');
return $starter_filename;
}
/**
* return path of php.ini file
*
* @return string full with path file-name
*/
public function getIniFile() {
$phpini_filename = makeCorrectFile($this->getConfigDir() . '/php.ini');
return $phpini_filename;
}
/**
* return the admin-data of a specific admin
*
* @param int $adminid id of the admin-user
*
* @return array
*/
private function _getAdminData($adminid) {
$adminid = intval($adminid);
if (!isset($this->_admin_cache[$adminid])) {
$stmt = Database::prepare("
SELECT `email`, `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :id"
);
$this->_admin_cache[$adminid] = Database::pexecute_first($stmt, array('id' => $adminid));
}
return $this->_admin_cache[$adminid];
}
}

View File

@@ -1,405 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @link http://www.nutime.de/
* @since 0.9.16
*
*/
class phpinterface_fpm
{
/**
* Domain-Data array
*
* @var array
*/
private $_domain = array();
/**
* fpm config
*
* @var array
*/
private $_fpm_cfg = array();
/**
* Admin-Date cache array
*
* @var array
*/
private $_admin_cache = array();
/**
* defines what can be used for pool-config from php.ini
* Mostly taken from http://php.net/manual/en/ini.list.php
*
* @var array
*/
private $_ini = array();
/**
* main constructor
*/
public function __construct($domain)
{
if (!isset($domain['fpm_config_id']) || empty($domain['fpm_config_id'])) {
$domain['fpm_config_id'] = 1;
}
$this->_domain = $domain;
$this->_readFpmConfig($domain['fpm_config_id']);
$this->_buildIniMapping();
}
private function _buildIniMapping()
{
$this->_ini = array(
'php_flag' => explode("\n", Settings::Get('phpfpm.ini_flags')),
'php_value' => explode("\n", Settings::Get('phpfpm.ini_values')),
'php_admin_flag' => explode("\n", Settings::Get('phpfpm.ini_admin_flags')),
'php_admin_value' => explode("\n", Settings::Get('phpfpm.ini_admin_values'))
);
}
private function _readFpmConfig($fpm_config_id)
{
$stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_FPMDAEMONS . "` WHERE `id` = :id");
$this->_fpm_cfg = Database::pexecute_first($stmt, array(
'id' => $fpm_config_id
));
}
/**
* create fpm-pool config
*
* @param array $phpconfig
*/
public function createConfig($phpconfig)
{
$fh = @fopen($this->getConfigFile(), 'w');
if ($fh) {
if ($phpconfig['override_fpmconfig'] == 1) {
$this->_fpm_cfg['pm'] = $phpconfig['pm'];
$this->_fpm_cfg['max_children'] = $phpconfig['max_children'];
$this->_fpm_cfg['start_servers'] = $phpconfig['start_servers'];
$this->_fpm_cfg['min_spare_servers'] = $phpconfig['min_spare_servers'];
$this->_fpm_cfg['max_spare_servers'] = $phpconfig['max_spare_servers'];
$this->_fpm_cfg['max_requests'] = $phpconfig['max_requests'];
$this->_fpm_cfg['idle_timeout'] = $phpconfig['idle_timeout'];
$this->_fpm_cfg['limit_extensions'] = $phpconfig['limit_extensions'];
}
$fpm_pm = $this->_fpm_cfg['pm'];
$fpm_children = (int) $this->_fpm_cfg['max_children'];
$fpm_start_servers = (int) $this->_fpm_cfg['start_servers'];
$fpm_min_spare_servers = (int) $this->_fpm_cfg['min_spare_servers'];
$fpm_max_spare_servers = (int) $this->_fpm_cfg['max_spare_servers'];
$fpm_requests = (int) $this->_fpm_cfg['max_requests'];
$fpm_process_idle_timeout = (int) $this->_fpm_cfg['idle_timeout'];
$fpm_limit_extensions = $this->_fpm_cfg['limit_extensions'];
if ($fpm_children == 0) {
$fpm_children = 1;
}
$fpm_config = ';PHP-FPM configuration for "' . $this->_domain['domain'] . '" created on ' . date("Y.m.d H:i:s") . "\n";
$fpm_config .= '[' . $this->_domain['domain'] . ']' . "\n";
$fpm_config .= 'listen = ' . $this->getSocketFile() . "\n";
if ($this->_domain['loginname'] == 'froxlor.panel') {
$fpm_config .= 'listen.owner = ' . $this->_domain['guid'] . "\n";
$fpm_config .= 'listen.group = ' . $this->_domain['guid'] . "\n";
} else {
$fpm_config .= 'listen.owner = ' . $this->_domain['loginname'] . "\n";
$fpm_config .= 'listen.group = ' . $this->_domain['loginname'] . "\n";
}
// see #1418 why this is 0660
$fpm_config .= 'listen.mode = 0660' . "\n";
if ($this->_domain['loginname'] == 'froxlor.panel') {
$fpm_config .= 'user = ' . $this->_domain['guid'] . "\n";
$fpm_config .= 'group = ' . $this->_domain['guid'] . "\n";
} else {
$fpm_config .= 'user = ' . $this->_domain['loginname'] . "\n";
$fpm_config .= 'group = ' . $this->_domain['loginname'] . "\n";
}
$fpm_config .= 'pm = ' . $fpm_pm . "\n";
$fpm_config .= 'pm.max_children = ' . $fpm_children . "\n";
if ($fpm_pm == 'dynamic') {
// honor max_children
if ($fpm_children < $fpm_min_spare_servers) {
$fpm_min_spare_servers = $fpm_children;
}
if ($fpm_children < $fpm_max_spare_servers) {
$fpm_max_spare_servers = $fpm_children;
}
// failsafe, refs #955
if ($fpm_start_servers < $fpm_min_spare_servers) {
$fpm_start_servers = $fpm_min_spare_servers;
}
if ($fpm_start_servers > $fpm_max_spare_servers) {
$fpm_start_servers = $fpm_max_spare_servers;
}
$fpm_config .= 'pm.start_servers = ' . $fpm_start_servers . "\n";
$fpm_config .= 'pm.min_spare_servers = ' . $fpm_min_spare_servers . "\n";
$fpm_config .= 'pm.max_spare_servers = ' . $fpm_max_spare_servers . "\n";
} elseif ($fpm_pm == 'ondemand') {
$fpm_config .= 'pm.process_idle_timeout = ' . $fpm_process_idle_timeout . "\n";
}
$fpm_config .= 'pm.max_requests = ' . $fpm_requests . "\n";
// possible slowlog configs
if ($phpconfig['fpm_slowlog'] == '1') {
$fpm_config .= 'request_terminate_timeout = ' . $phpconfig['fpm_reqterm'] . "\n";
$fpm_config .= 'request_slowlog_timeout = ' . $phpconfig['fpm_reqslow'] . "\n";
$slowlog = makeCorrectFile(Settings::Get('system.logfiles_directory') . '/' . $this->_domain['loginname'] . '-php-slow.log');
$fpm_config .= 'slowlog = ' . $slowlog . "\n";
$fpm_config .= 'catch_workers_output = yes' . "\n";
}
$fpm_config .= ';chroot = ' . makeCorrectDir($this->_domain['documentroot']) . "\n";
$fpm_config .= 'security.limit_extensions = '.$fpm_limit_extensions . "\n";
$tmpdir = makeCorrectDir(Settings::Get('phpfpm.tmpdir') . '/' . $this->_domain['loginname'] . '/');
if (! is_dir($tmpdir)) {
$this->getTempDir();
}
$env_path = Settings::Get('phpfpm.envpath');
if (!empty($env_path)) {
$fpm_config .= 'env[PATH] = ' . $env_path . "\n";
}
$fpm_config .= 'env[TMP] = ' . $tmpdir . "\n";
$fpm_config .= 'env[TMPDIR] = ' . $tmpdir . "\n";
$fpm_config .= 'env[TEMP] = ' . $tmpdir . "\n";
$openbasedir = '';
if ($this->_domain['loginname'] != 'froxlor.panel') {
if ($this->_domain['openbasedir'] == '1') {
$_phpappendopenbasedir = '';
$_custom_openbasedir = explode(':', Settings::Get('phpfpm.peardir'));
foreach ($_custom_openbasedir as $cobd) {
$_phpappendopenbasedir .= appendOpenBasedirPath($cobd);
}
$_custom_openbasedir = explode(':', Settings::Get('system.phpappendopenbasedir'));
foreach ($_custom_openbasedir as $cobd) {
$_phpappendopenbasedir .= appendOpenBasedirPath($cobd);
}
if ($this->_domain['openbasedir_path'] == '0' && strstr($this->_domain['documentroot'], ":") === false) {
$openbasedir = appendOpenBasedirPath($this->_domain['documentroot'], true);
} else {
$openbasedir = appendOpenBasedirPath($this->_domain['customerroot'], true);
}
$openbasedir .= appendOpenBasedirPath($this->getTempDir());
$openbasedir .= $_phpappendopenbasedir;
}
}
$fpm_config .= 'php_admin_value[session.save_path] = ' . makeCorrectDir(Settings::Get('phpfpm.tmpdir') . '/' . $this->_domain['loginname'] . '/') . "\n";
$fpm_config .= 'php_admin_value[upload_tmp_dir] = ' . makeCorrectDir(Settings::Get('phpfpm.tmpdir') . '/' . $this->_domain['loginname'] . '/') . "\n";
$admin = $this->_getAdminData($this->_domain['adminid']);
$php_ini_variables = array(
'SAFE_MODE' => 'Off', // keep this for compatibility, just in case
'PEAR_DIR' => Settings::Get('phpfpm.peardir'),
'TMP_DIR' => $this->getTempDir(),
'CUSTOMER_EMAIL' => $this->_domain['email'],
'ADMIN_EMAIL' => $admin['email'],
'DOMAIN' => $this->_domain['domain'],
'CUSTOMER' => $this->_domain['loginname'],
'ADMIN' => $admin['loginname'],
'OPEN_BASEDIR' => $openbasedir,
'OPEN_BASEDIR_C' => '',
'OPEN_BASEDIR_GLOBAL' => Settings::Get('system.phpappendopenbasedir'),
'DOCUMENT_ROOT' => makeCorrectDir($this->_domain['documentroot']),
'CUSTOMER_HOMEDIR' => makeCorrectDir($this->_domain['customerroot'])
);
$phpini = replace_variables($phpconfig['phpsettings'], $php_ini_variables);
$phpini_array = explode("\n", $phpini);
$fpm_config .= "\n\n";
foreach ($phpini_array as $inisection) {
$is = explode("=", $inisection);
foreach ($this->_ini as $sec => $possibles) {
if (in_array(trim($is[0]), $possibles)) {
// check explicitly for open_basedir
if (trim($is[0]) == 'open_basedir' && $openbasedir == '') {
continue;
}
$fpm_config .= $sec . '[' . trim($is[0]) . '] = ' . trim($is[1]) . "\n";
}
}
}
// now check if 'sendmail_path' has not beed set in the custom-php.ini
// if not we use our fallback-default as usual
if (strpos($fpm_config, 'php_admin_value[sendmail_path]') === false) {
$fpm_config .= 'php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f ' . $this->_domain['email'] . "\n";
}
fwrite($fh, $fpm_config, strlen($fpm_config));
fclose($fh);
}
}
/**
* this is done via createConfig as php-fpm defines
* the ini-values/flags in its pool-config
*
* @param string $phpconfig
*/
public function createIniFile($phpconfig)
{
return;
}
/**
* fpm-config file
*
* @param boolean $createifnotexists
* create the directory if it does not exist
*
* @return string the full path to the file
*/
public function getConfigFile($createifnotexists = true)
{
$configdir = $this->_fpm_cfg['config_dir'];
$config = makeCorrectFile($configdir . '/' . $this->_domain['domain'] . '.conf');
if (! is_dir($configdir) && $createifnotexists) {
safe_exec('mkdir -p ' . escapeshellarg($configdir));
}
return $config;
}
/**
* return path of fpm-socket file
*
* @param boolean $createifnotexists
* create the directory if it does not exist
*
* @return string the full path to the socket
*/
public function getSocketFile($createifnotexists = true)
{
$socketdir = makeCorrectDir(Settings::Get('phpfpm.fastcgi_ipcdir'));
// add fpm-config-id to filename so it's unique for the fpm-daemon and doesn't interfere with running configs when reuilding
$socket = strtolower(makeCorrectFile($socketdir . '/' . $this->_domain['fpm_config_id'] . '-' . $this->_domain['loginname'] . '-' . $this->_domain['domain'] . '-php-fpm.socket'));
if (! is_dir($socketdir) && $createifnotexists) {
safe_exec('mkdir -p ' . escapeshellarg($socketdir));
safe_exec('chown -R ' . Settings::Get('system.httpuser') . ':' . Settings::Get('system.httpgroup') . ' ' . escapeshellarg($socketdir));
}
return $socket;
}
/**
* fpm-temp directory
*
* @param boolean $createifnotexists
* create the directory if it does not exist
*
* @return string the directory
*/
public function getTempDir($createifnotexists = true)
{
$tmpdir = makeCorrectDir(Settings::Get('phpfpm.tmpdir') . '/' . $this->_domain['loginname'] . '/');
if (! is_dir($tmpdir) && $createifnotexists) {
safe_exec('mkdir -p ' . escapeshellarg($tmpdir));
safe_exec('chown -R ' . $this->_domain['guid'] . ':' . $this->_domain['guid'] . ' ' . escapeshellarg($tmpdir));
safe_exec('chmod 0750 ' . escapeshellarg($tmpdir));
}
return $tmpdir;
}
/**
* fastcgi-fakedirectory directory
*
* @param boolean $createifnotexists
* create the directory if it does not exist
*
* @return string the directory
*/
public function getAliasConfigDir($createifnotexists = true)
{
// ensure default...
if (Settings::Get('phpfpm.aliasconfigdir') == null) {
Settings::Set('phpfpm.aliasconfigdir', '/var/www/php-fpm');
}
$configdir = makeCorrectDir(Settings::Get('phpfpm.aliasconfigdir') . '/' . $this->_domain['loginname'] . '/' . $this->_domain['domain'] . '/');
if (! is_dir($configdir) && $createifnotexists) {
safe_exec('mkdir -p ' . escapeshellarg($configdir));
safe_exec('chown ' . $this->_domain['guid'] . ':' . $this->_domain['guid'] . ' ' . escapeshellarg($configdir));
}
return $configdir;
}
/**
* create a dummy fpm pool config with minimal configuration
* (this is used whenever a config directory is empty but needs at least one pool to startup/restart)
*
* @param string $configdir
*/
public static function createDummyPool($configdir)
{
if (! is_dir($configdir)) {
safe_exec('mkdir -p ' . escapeshellarg($configdir));
}
$config = makeCorrectFile($configdir . '/dummy.conf');
$dummy = "[dummy]
user = ".Settings::Get('system.httpuser')."
listen = /run/" . md5($configdir) . "-fpm.sock
pm = static
pm.max_children = 1
";
file_put_contents($config, $dummy);
}
/**
* return the admin-data of a specific admin
*
* @param int $adminid
* id of the admin-user
*
* @return array
*/
private function _getAdminData($adminid)
{
$adminid = intval($adminid);
if (! isset($this->_admin_cache[$adminid])) {
$stmt = Database::prepare("
SELECT `email`, `loginname` FROM `" . TABLE_PANEL_ADMINS . "` WHERE `adminid` = :id");
$this->_admin_cache[$adminid] = Database::pexecute_first($stmt, array(
'id' => $adminid
));
}
return $this->_admin_cache[$adminid];
}
}

View File

@@ -1,595 +0,0 @@
<?php
// Copyright (c) 2015, Stanislav Humplik <sh@analogic.cz>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This file is copied from https://github.com/analogic/lescript
// and modified to work without files and integrate in Froxlor
class lescript
{
// https://letsencrypt.org/repository/
private $logger;
private $client;
private $accountKey;
private $customerid;
private $isFroxlorVhost;
private $isLeProduction;
private $version;
public function __construct($logger, $version = '1')
{
$this->logger = $logger;
$this->version = $version;
if (Settings::Get('system.letsencryptca') == 'production') {
$ca = 'https://acme-v01.api.letsencrypt.org';
} else {
$ca = 'https://acme-staging.api.letsencrypt.org';
}
$this->client = new Client($ca);
$this->log("Using '$ca' to generate certificate");
}
public function initAccount($certrow, $isFroxlorVhost = false)
{
// Let's see if we have the private accountkey
$this->accountKey = $certrow['leprivatekey'];
$this->customerId = (!$isFroxlorVhost ? $certrow['customerid'] : null);
$this->isFroxlorVhost = $isFroxlorVhost;
$this->isLeProduction = (Settings::Get('system.letsencryptca') == 'production');
$leregistered=$certrow['leregistered'];
if (! $this->accountKey || $this->accountKey == 'unset' || !$this->isLeProduction) {
// generate and save new private key for account
// ---------------------------------------------
$this->log('Creating new account key');
$keys = $this->generateKey();
// Only store the accountkey in production, in staging always generate a new key
if ($this->isLeProduction) {
if ($isFroxlorVhost) {
Settings::Set('system.lepublickey', $keys['public']);
Settings::Set('system.leprivatekey', $keys['private']);
Settings::Set('system.leregistered', 0); // key is not registered
} else {
$upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `lepublickey` = :public, `leprivatekey` = :private, `leregistered` = :registered WHERE `customerid` = :customerid;");
Database::pexecute($upd_stmt, array(
'public' => $keys['public'],
'private' => $keys['private'],
'registered' => 0,
'customerid' => $this->customerId
));
}
}
$leregistered=0;
$this->accountKey = $keys['private'];
} else {
$this->log('Using existing account key');
}
if ($leregistered==0) { // Account not registered
$this->log('Starting new account registration');
$response = $this->postNewReg();
if ($this->client->getLastCode() == 409) {
$this->log('The key was already registered. Using existing account.');
} else if ($this->client->getLastCode() == 201) {
$this->log('New account registered.');
} else {
throw new \RuntimeException("Account not initialized, probably due to rate limiting. Whole response: " . json_encode($response));
}
$accountUrl=$this->client->getLastLocation();
$leregistered = 1;
$this->setLeRegisteredState($leregistered); // Account registered
$this->log('Lets encrypt Terms of Service accepted');
}
}
/**
*
* @param array $domains
* @param string $domainkey
* @param string $csr
* optional, same behavior as $reuseCsr from the original class, but we're passing the content of the csr already
*
* @throws \RuntimeException
* @return string[]
*/
public function signDomains(array $domains, $domainkey = null, $csr = null)
{
if (! $this->accountKey) {
throw new \RuntimeException("Account not initialized");
}
$this->log('Starting certificate generation process for domains');
$privateAccountKey = openssl_pkey_get_private($this->accountKey);
$accountKeyDetails = openssl_pkey_get_details($privateAccountKey);
// start domains authentication
// ----------------------------
foreach ($domains as $domain) {
// 1. getting available authentication options
// -------------------------------------------
$this->log("Requesting challenge for $domain");
$response = $this->signedRequest("/acme/new-authz", array(
"resource" => "new-authz",
"identifier" => array(
"type" => "dns",
"value" => $domain
)
));
if ($this->client->getLastCode() == 403) {
$this->log("Got status 403 - setting LE status to unregistered.");
$this->setLeRegisteredState(0);
throw new RuntimeException("Got 'unauthorized' response - we need to re-register at next run. Whole response: " . json_encode($response));
}
// if response is not an array but a string, it's most likely a server-error, e.g.
// <HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY>An error occurred while processing your request.
// <p>Reference&#32;&#35;179&#46;d8be1402&#46;1458059103&#46;3613c4db</BODY></HTML>
if (! is_array($response)) {
throw new RuntimeException("Invalid response from LE for domain $domain. Whole response: " . json_encode($response));
}
if (! array_key_exists('challenges', $response)) {
throw new RuntimeException("No challenges received for $domain. Whole response: " . json_encode($response));
}
// choose http-01 challenge only
$challenge = array_reduce($response['challenges'], function ($v, $w) {
return $v ? $v : ($w['type'] == 'http-01' ? $w : false);
});
if (! $challenge) {
throw new RuntimeException("HTTP Challenge for $domain is not available. Whole response: " . json_encode($response));
}
$this->log("Got challenge token for $domain");
$location = $this->client->getLastLocation();
// 2. saving authentication token for web verification
// ---------------------------------------------------
$directory = Settings::Get('system.letsencryptchallengepath') . '/.well-known/acme-challenge';
$tokenPath = $directory . '/' . $challenge['token'];
if (! file_exists($directory) && ! @mkdir($directory, 0755, true)) {
throw new \RuntimeException("Couldn't create directory to expose challenge: ${tokenPath}");
}
$header = array(
// need to be in precise order!
"e" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["e"]),
"kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["n"])
);
$payload = $challenge['token'] . '.' . Base64UrlSafeEncoder::encode(hash('sha256', json_encode($header), true));
file_put_contents($tokenPath, $payload);
chmod($tokenPath, 0644);
// 3. verification process itself
// -------------------------------
$uri = "http://${domain}/.well-known/acme-challenge/${challenge['token']}";
$this->log("Token for $domain saved at $tokenPath and should be available at $uri");
// simple self check
if (Settings::Get('system.disable_le_selfcheck') == '0')
{
$selfcheckpayload = HttpClient::urlGet($uri, false);
if ($payload !== trim($selfcheckpayload)) {
$errmsg = json_encode(error_get_last());
if ($errmsg != "null") {
$errmsg = "; PHP error: " . $errmsg;
} else {
$errmsg = "";
}
$this->logger->logAction(CRON_ACTION, LOG_WARNING, "[Lets Encrypt self-check] Please check $uri - token seems to be not available. This is just a simple self-check, it might be wrong but consider using this information when Let's Encrypt fails to issue a certificate" . $errmsg);
}
}
$this->log("Sending request to challenge");
// send request to challenge
$result = $this->signedRequest($challenge['uri'], array(
"resource" => "challenge",
"type" => "http-01",
"keyAuthorization" => $payload,
"token" => $challenge['token']
));
// waiting loop
// we wait for a maximum of 30 seconds to avoid endless loops
$count = 0;
do {
if (empty($result['status']) || $result['status'] == "invalid") {
@unlink($tokenPath);
throw new \RuntimeException("Verification ended with error: " . json_encode($result));
}
$ended = ! ($result['status'] === "pending");
if (! $ended) {
$this->log("Verification pending, sleeping 1s");
sleep(1);
$count ++;
}
$result = $this->client->get($location);
} while (! $ended && $count < 30);
$this->log("Verification ended with status: ${result['status']}");
@unlink($tokenPath);
}
// requesting certificate
// ----------------------
// generate private key for domain if not exist
if (empty($domainkey) || Settings::Get('system.letsencryptreuseold') == 0) {
$keys = $this->generateKey();
$domainkey = $keys['private'];
}
// load domain key
$privateDomainKey = openssl_pkey_get_private($domainkey);
$this->client->getLastLinks();
if (empty($csr)) {
$csr = $this->generateCSR($privateDomainKey, $domains);
}
// request certificates creation
$result = $this->signedRequest("/acme/new-cert", array(
'resource' => 'new-cert',
'csr' => $csr
));
if ($this->client->getLastCode() !== 201) {
throw new \RuntimeException("Invalid response code: " . $this->client->getLastCode() . ", " . json_encode($result));
}
$location = $this->client->getLastLocation();
// waiting loop
$certificates = array();
while (1) {
$this->client->getLastLinks();
$result = $this->client->get($location);
if ($this->client->getLastCode() == 202) {
$this->log("Certificate generation pending, sleeping 1s");
sleep(1);
} else
if ($this->client->getLastCode() == 200) {
$this->log("Got certificate! YAY!");
$certificates[] = $this->parsePemFromBody($result);
foreach ($this->client->getLastLinks() as $link) {
$this->log("Requesting chained cert at $link");
$result = $this->client->get($link);
$certificates[] = $this->parsePemFromBody($result);
}
break;
} else {
throw new \RuntimeException("Can't get certificate: HTTP code " . $this->client->getLastCode());
}
}
if (empty($certificates))
throw new \RuntimeException('No certificates generated');
$fullchain = implode("\n", $certificates);
$crt = array_shift($certificates);
$chain = implode("\n", $certificates);
$this->log("Done, returning new certificates and key");
return array(
'fullchain' => $fullchain,
'crt' => $crt,
'chain' => $chain,
'key' => $domainkey,
'csr' => $csr
);
}
private function setLeRegisteredState($state)
{
if ($this->isLeProduction) {
if ($this->isFroxlorVhost) {
Settings::Set('system.leregistered', $state);
} else {
$upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered WHERE `customerid` = :customerid;");
Database::pexecute($upd_stmt, array(
'registered' => $state,
'customerid' => $this->customerId
));
}
}
}
private function parsePemFromBody($body)
{
$pem = chunk_split(base64_encode($body), 64, "\n");
return "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";
}
private function postNewReg()
{
$this->log('Getting last terms of service URL');
$directory = $this->client->get('/directory');
if (!isset($directory['meta']) || !isset($directory['meta']['terms-of-service'])) {
throw new \RuntimeException("No terms of service link available!");
}
$this->log('Sending registration to letsencrypt server');
return $this->signedRequest('/acme/new-reg', array(
'resource' => 'new-reg',
'agreement' => $directory['meta']['terms-of-service']
));
}
private function generateCSR($privateKey, array $domains)
{
$domain = reset($domains);
$san = implode(",", array_map(function ($dns) {
return "DNS:" . $dns;
}, $domains));
$tmpConf = tmpfile();
$tmpConfMeta = stream_get_meta_data($tmpConf);
$tmpConfPath = $tmpConfMeta["uri"];
// workaround to get SAN working
fwrite($tmpConf, 'HOME = .
RANDFILE = $ENV::HOME/.rnd
[ req ]
default_bits = ' . Settings::Get('system.letsencryptkeysize') . '
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
[ v3_req ]
basicConstraints = CA:FALSE
subjectAltName = ' . $san . '
keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
$csr = openssl_csr_new(array(
"CN" => $domain,
"ST" => Settings::Get('system.letsencryptstate'),
"C" => Settings::Get('system.letsencryptcountrycode'),
"O" => "Unknown"
), $privateKey, array(
"config" => $tmpConfPath,
"digest_alg" => "sha256"
));
if (! $csr)
throw new \RuntimeException("CSR couldn't be generated! " . openssl_error_string());
openssl_csr_export($csr, $csr);
fclose($tmpConf);
preg_match('~REQUEST-----(.*)-----END~s', $csr, $matches);
return trim(Base64UrlSafeEncoder::encode(base64_decode($matches[1])));
}
private function generateKey()
{
$res = openssl_pkey_new(array(
"private_key_type" => OPENSSL_KEYTYPE_RSA,
"private_key_bits" => (int) Settings::Get('system.letsencryptkeysize')
));
if (! openssl_pkey_export($res, $privateKey)) {
throw new \RuntimeException("Key export failed!");
}
$details = openssl_pkey_get_details($res);
return array(
'private' => $privateKey,
'public' => $details['key']
);
}
private function signedRequest($uri, array $payload)
{
$privateKey = openssl_pkey_get_private($this->accountKey);
$details = openssl_pkey_get_details($privateKey);
$header = array(
"alg" => "RS256",
"jwk" => array(
"kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($details["rsa"]["n"]),
"e" => Base64UrlSafeEncoder::encode($details["rsa"]["e"])
)
);
$protected = $header;
$protected["nonce"] = $this->client->getLastNonce();
$payload64 = Base64UrlSafeEncoder::encode(str_replace('\\/', '/', json_encode($payload)));
$protected64 = Base64UrlSafeEncoder::encode(json_encode($protected));
openssl_sign($protected64 . '.' . $payload64, $signed, $privateKey, "SHA256");
$signed64 = Base64UrlSafeEncoder::encode($signed);
$data = array(
'header' => $header,
'protected' => $protected64,
'payload' => $payload64,
'signature' => $signed64
);
$this->log("Sending signed request to $uri");
return $this->client->post($uri, json_encode($data));
}
protected function log($message)
{
$this->logger->logAction(CRON_ACTION, LOG_INFO, "letsencrypt " . $message);
}
}
class Client
{
private $lastCode;
private $lastHeader;
private $base;
public function __construct($base)
{
$this->base = $base;
}
private function curl($method, $url, $data = null)
{
$headers = array(
'Accept: application/json',
'Content-Type: application/json'
);
$handle = curl_init();
curl_setopt($handle, CURLOPT_URL, preg_match('~^http~', $url) ? $url : $this->base . $url);
curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_HEADER, true);
// DO NOT DO THAT!
// curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, false);
// curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);
switch ($method) {
case 'GET':
break;
case 'POST':
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
break;
}
$response = curl_exec($handle);
if (curl_errno($handle)) {
throw new \RuntimeException('Curl: ' . curl_error($handle));
}
$header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
$this->lastHeader = $header;
$this->lastCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
$data = json_decode($body, true);
return $data === null ? $body : $data;
}
public function post($url, $data)
{
return $this->curl('POST', $url, $data);
}
public function get($url)
{
return $this->curl('GET', $url);
}
public function getLastNonce()
{
if (preg_match('~Replay\-Nonce: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]);
}
$this->curl('GET', '/directory');
return $this->getLastNonce();
}
public function getLastLocation()
{
if (preg_match('~Location: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]);
}
return null;
}
public function getLastCode()
{
return $this->lastCode;
}
public function getLastLinks()
{
preg_match_all('~Link: <(.+)>;rel="up"~', $this->lastHeader, $matches);
return $matches[1];
}
}
class Base64UrlSafeEncoder
{
public static function encode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
public static function decode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
}

View File

@@ -1,609 +0,0 @@
<?php
// Copyright (c) 2015, Stanislav Humplik <sh@analogic.cz>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This file is copied from https://github.com/analogic/lescript
// and modified to work without files and integrate in Froxlor
class lescript_v2
{
// https://letsencrypt.org/repository/
private $logger;
private $client;
private $accountKey;
private $customerid;
private $isFroxlorVhost;
private $isLeProduction;
private $version;
private $_req_uris = array();
private $_acc_location = null;
public function __construct($logger, $version = '2')
{
$this->logger = $logger;
$this->version = $version;
if (Settings::Get('system.letsencryptca') == 'production') {
$ca = 'https://acme-v02.api.letsencrypt.org';
} else {
$ca = 'https://acme-staging-v02.api.letsencrypt.org';
}
$this->client = new Client($ca);
$this->log("Using '$ca' to generate certificate");
// get request-uris from /directory
$response = $this->client->get('/directory');
$this->_req_uris['newAccount'] = $response['newAccount'];
$this->_req_uris['newOrder'] = $response['newOrder'];
$this->_req_uris['newNonce'] = $response['newNonce'];
$this->_req_uris['revokeCert'] = $response['revokeCert'];
}
public function initAccount($certrow, $isFroxlorVhost = false)
{
// Let's see if we have the private accountkey
$this->accountKey = $certrow['leprivatekey'];
$this->customerId = (! $isFroxlorVhost ? $certrow['customerid'] : null);
$this->isFroxlorVhost = $isFroxlorVhost;
$this->isLeProduction = (Settings::Get('system.letsencryptca') == 'production');
$this->_acc_location = $certrow['leaccount'];
$leregistered = $certrow['leregistered'];
if (! $this->accountKey || $this->accountKey == 'unset' || ! $this->isLeProduction) {
// generate and save new private key for account
// ---------------------------------------------
$this->log('Creating new account key');
$keys = $this->generateKey();
// Only store the accountkey in production, in staging always generate a new key
if ($this->isLeProduction) {
if ($isFroxlorVhost) {
Settings::Set('system.lepublickey', $keys['public']);
Settings::Set('system.leprivatekey', $keys['private']);
Settings::Set('system.leregistered', 0); // key is not registered
} else {
$upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `lepublickey` = :public, `leprivatekey` = :private, `leregistered` = :registered WHERE `customerid` = :customerid;");
Database::pexecute($upd_stmt, array(
'public' => $keys['public'],
'private' => $keys['private'],
'registered' => 0,
'customerid' => $this->customerId
));
}
}
$leregistered = 0;
$this->accountKey = $keys['private'];
} else {
$this->log('Using existing account key');
}
if ($leregistered == 0) { // Account not registered
$this->log('Starting new account registration');
$response = $this->postNewReg();
if ($this->client->getLastCode() == 409) {
$this->log('The key was already registered. Using existing account.');
} else if ($this->client->getLastCode() == 201) {
$this->log('New account registered.');
} else {
throw new \RuntimeException("Account not initialized, probably due to rate limiting. Whole response: " . json_encode($response));
}
$this->_acc_location = $this->client->getLastLocation();
$leregistered = 1;
$this->setLeRegisteredState($leregistered);
}
}
/**
*
* @param array $domains
* @param string $domainkey
* @param string $csr
* optional, same behavior as $reuseCsr from the original class, but we're passing the content of the csr already
*
* @throws \RuntimeException
* @return string[]
*/
public function signDomains(array $domains, $domainkey = null, $csr = null)
{
if (! $this->accountKey) {
throw new \RuntimeException("Account not initialized");
}
$this->log('Starting certificate generation process for domains');
$privateAccountKey = openssl_pkey_get_private($this->accountKey);
$accountKeyDetails = openssl_pkey_get_details($privateAccountKey);
// start domains authentication
// ----------------------------
// Prepare order
$domains_in_order = array();
foreach ($domains as $domain) {
$domains_in_order []= array(
"type" => "dns",
"value" => $domain
);
}
// Send new-order request
$response = $this->signedRequest($this->_req_uris['newOrder'], array(
"identifiers" => $domains_in_order
), false);
if ($this->client->getLastCode() == 403) {
$this->log("Got status 403 - setting LE status to unregistered.");
$this->_acc_location = '';
$this->setLeRegisteredState(0);
throw new RuntimeException("Got 'unauthorized' response - we need to re-register at next run. Whole response: " . json_encode($response));
}
// if response is not an array but a string, it's most likely a server-error, e.g.
// <HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY>An error occurred while processing your request.
// <p>Reference&#32;&#35;179&#46;d8be1402&#46;1458059103&#46;3613c4db</BODY></HTML>
if (! is_array($response)) {
throw new RuntimeException("Invalid response from LE for domain $domain. Whole response: " . json_encode($response));
}
if (! array_key_exists('authorizations', $response)) {
throw new RuntimeException("No authorizations received for $domain. Whole response: " . json_encode($response));
}
$authorizations = $response['authorizations'];
$finalizeLink = $response['finalize'];
$i = 0;
foreach ($authorizations as $authorization) {
// 1. getting available authentication options
// -------------------------------------------
$domain = $response['identifiers'][$i++]['value'];
$this->log("Requesting challenge for $domain");
// get authorization
$auth_response = $this->client->get($authorization);
if (! array_key_exists('challenges', $auth_response)) {
throw new RuntimeException("No challenges received for $domain. Whole response: " . json_encode($auth_response));
}
// choose http-01 challenge only
$challenge = array_reduce($auth_response['challenges'], function ($v, $w) {
return $v ? $v : ($w['type'] == 'http-01' ? $w : false);
});
if (! $challenge) {
throw new RuntimeException("HTTP Challenge for $domain is not available. Whole response: " . json_encode($response));
}
$this->log("Got challenge token for $domain");
$location = $challenge['url'];
// 2. saving authentication token for web verification
// ---------------------------------------------------
$directory = Settings::Get('system.letsencryptchallengepath') . '/.well-known/acme-challenge';
$tokenPath = $directory . '/' . $challenge['token'];
if (! file_exists($directory) && ! @mkdir($directory, 0755, true)) {
throw new \RuntimeException("Couldn't create directory to expose challenge: ${tokenPath}");
}
$header = array(
// need to be in precise order!
"e" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["e"]),
"kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($accountKeyDetails["rsa"]["n"])
);
$payload = $challenge['token'] . '.' . Base64UrlSafeEncoder::encode(hash('sha256', json_encode($header), true));
file_put_contents($tokenPath, $payload);
chmod($tokenPath, 0644);
// 3. verification process itself
// -------------------------------
$uri = "http://${domain}/.well-known/acme-challenge/${challenge['token']}";
$this->log("Token for $domain saved at $tokenPath and should be available at $uri");
// simple self check
if (Settings::Get('system.disable_le_selfcheck') == '0') {
$selfcheckpayload = HttpClient::urlGet($uri, false);
if ($payload !== trim($selfcheckpayload)) {
$errmsg = json_encode(error_get_last());
if ($errmsg != "null") {
$errmsg = "; PHP error: " . $errmsg;
} else {
$errmsg = "";
}
$this->logger->logAction(CRON_ACTION, LOG_WARNING, "[Lets Encrypt self-check] Please check $uri - token seems to be not available. This is just a simple self-check, it might be wrong but consider using this information when Let's Encrypt fails to issue a certificate" . $errmsg);
}
}
$this->log("Sending request to challenge");
// send request to challenge
$result = $this->signedRequest($challenge['url'], array(
"type" => "http-01",
"keyAuthorization" => $payload,
"token" => $challenge['token']
), false);
// waiting loop
// we wait for a maximum of 30 seconds to avoid endless loops
$count = 0;
do {
if (empty($result['status']) || $result['status'] == "invalid") {
@unlink($tokenPath);
throw new \RuntimeException("Verification ended with error: " . json_encode($result));
}
$ended = ! ($result['status'] === "pending" || $result['status'] === "processing");
if (! $ended) {
$this->log("Verification " . $result['status'] . ", sleeping 1s");
sleep(1);
$count ++;
}
$result = $this->client->get($location);
} while (! $ended && $count < 30);
$this->log("Verification ended with status: ${result['status']}");
@unlink($tokenPath);
}
// requesting certificate
// ----------------------
// generate private key for domain if not exist
if (empty($domainkey) || Settings::Get('system.letsencryptreuseold') == 0) {
$keys = $this->generateKey();
$domainkey = $keys['private'];
}
// load domain key
$privateDomainKey = openssl_pkey_get_private($domainkey);
if (empty($csr)) {
$csr = $this->generateCSR($privateDomainKey, $domains);
}
// request certificates creation
$result = $this->signedRequest($finalizeLink, array(
'csr' => $csr
), false);
if ($this->client->getLastCode() !== 200) {
throw new \RuntimeException("Invalid response code: " . $this->client->getLastCode() . ", " . json_encode($result));
}
if (! isset($result['certificate'])) {
throw new \RuntimeException("No certificate URL specified in result");
}
$certificates = array();
$certdata = $this->client->get($result['certificate']);
$this->log("Got certificate! YAY!");
$certificates[] = $certdata;
foreach ($this->client->getLastLinks() as $link) {
$this->log("Requesting chained cert at $link");
$result = $this->client->get($link);
$certificates[] = $result;
}
if (empty($certificates))
throw new \RuntimeException('No certificates generated');
$fullchain = implode("\n", $certificates);
$crt = array_shift($certificates);
$chain = implode("\n", $certificates);
$this->log("Done, returning new certificates and key");
return array(
'fullchain' => $fullchain,
'crt' => $crt,
'chain' => $chain,
'key' => $domainkey,
'csr' => $csr
);
}
private function setLeRegisteredState($state)
{
if ($this->isLeProduction) {
if ($this->isFroxlorVhost) {
Settings::Set('system.leregistered', $state);
Settings::Set('system.leaccount', $this->_acc_location);
} else {
$upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_CUSTOMERS . "` SET `leregistered` = :registered, `leaccount` = :kid WHERE `customerid` = :customerid;");
Database::pexecute($upd_stmt, array(
'registered' => $state,
'kid' => $this->_acc_location,
'customerid' => $this->customerId
));
}
}
}
private function parsePemFromBody($body)
{
$pem = chunk_split(base64_encode($body), 64, "\n");
return "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";
}
private function postNewReg()
{
$this->log('Getting last terms of service URL');
$directory = $this->client->get('/directory');
if (! isset($directory['meta']) || ! isset($directory['meta']['termsOfService'])) {
throw new \RuntimeException("No terms of service link available!");
}
$this->log('Sending registration to letsencrypt server');
return $this->signedRequest($this->_req_uris['newAccount'], array(
'termsOfServiceAgreed' => true
));
}
private function generateCSR($privateKey, array $domains)
{
$domain = reset($domains);
$san = implode(",", array_map(function ($dns) {
return "DNS:" . $dns;
}, $domains));
$tmpConf = tmpfile();
$tmpConfMeta = stream_get_meta_data($tmpConf);
$tmpConfPath = $tmpConfMeta["uri"];
// workaround to get SAN working
fwrite($tmpConf, 'HOME = .
RANDFILE = $ENV::HOME/.rnd
[ req ]
default_bits = ' . Settings::Get('system.letsencryptkeysize') . '
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
[ v3_req ]
basicConstraints = CA:FALSE
subjectAltName = ' . $san . '
keyUsage = nonRepudiation, digitalSignature, keyEncipherment');
$csr = openssl_csr_new(array(
"CN" => $domain,
"ST" => Settings::Get('system.letsencryptstate'),
"C" => Settings::Get('system.letsencryptcountrycode'),
"O" => "Unknown"
), $privateKey, array(
"config" => $tmpConfPath,
"digest_alg" => "sha256"
));
if (! $csr)
throw new \RuntimeException("CSR couldn't be generated! " . openssl_error_string());
openssl_csr_export($csr, $csr);
fclose($tmpConf);
preg_match('~REQUEST-----(.*)-----END~s', $csr, $matches);
return trim(Base64UrlSafeEncoder::encode(base64_decode($matches[1])));
}
private function generateKey()
{
$res = openssl_pkey_new(array(
"private_key_type" => OPENSSL_KEYTYPE_RSA,
"private_key_bits" => (int) Settings::Get('system.letsencryptkeysize')
));
if (! openssl_pkey_export($res, $privateKey)) {
throw new \RuntimeException("Key export failed!");
}
$details = openssl_pkey_get_details($res);
return array(
'private' => $privateKey,
'public' => $details['key']
);
}
private function signedRequest($uri, array $payload, $needs_jwk = true)
{
$privateKey = openssl_pkey_get_private($this->accountKey);
$details = openssl_pkey_get_details($privateKey);
$header = array(
"alg" => "RS256"
);
if ($needs_jwk) {
$header["jwk"] = array(
"kty" => "RSA",
"n" => Base64UrlSafeEncoder::encode($details["rsa"]["n"]),
"e" => Base64UrlSafeEncoder::encode($details["rsa"]["e"])
);
} else {
// need account-url
$header["kid"] = $this->_acc_location;
}
$protected = $header;
$protected["nonce"] = $this->client->getLastNonce();
$protected["url"] = $uri;
$payload64 = Base64UrlSafeEncoder::encode(json_encode($payload, JSON_UNESCAPED_SLASHES));
$protected64 = Base64UrlSafeEncoder::encode(json_encode($protected));
openssl_sign($protected64 . '.' . $payload64, $signed, $privateKey, "SHA256");
$signed64 = Base64UrlSafeEncoder::encode($signed);
$data = array(
'protected' => $protected64,
'payload' => $payload64,
'signature' => $signed64
);
$this->log("Sending signed request to $uri");
return $this->client->post($uri, json_encode($data));
}
protected function log($message)
{
$this->logger->logAction(CRON_ACTION, LOG_INFO, "letsencrypt-v2 " . $message);
}
}
class Client
{
private $lastCode;
public $lastHeader;
private $base;
public function __construct($base)
{
$this->base = $base;
}
private function curl($method, $url, $data = null)
{
$headers = array(
'Accept: application/jose+json',
'Content-Type: application/jose+json'
);
$handle = curl_init();
curl_setopt($handle, CURLOPT_URL, preg_match('~^http~', $url) ? $url : $this->base . $url);
curl_setopt($handle, CURLOPT_HTTPHEADER, $headers);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_HEADER, true);
// DO NOT DO THAT!
// curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, false);
// curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);
switch ($method) {
case 'GET':
break;
case 'POST':
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
break;
}
$response = curl_exec($handle);
if (curl_errno($handle)) {
throw new \RuntimeException('Curl: ' . curl_error($handle));
}
$header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
$this->lastHeader = $header;
$this->lastCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
$data = json_decode($body, true);
return $data === null ? $body : $data;
}
public function post($url, $data)
{
return $this->curl('POST', $url, $data);
}
public function get($url)
{
return $this->curl('GET', $url);
}
public function getLastNonce()
{
if (preg_match('~Replay\-Nonce: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]);
}
$this->curl('GET', '/acme/new-nonce');
return $this->getLastNonce();
}
public function getLastLocation()
{
if (preg_match('~Location: (.+)~i', $this->lastHeader, $matches)) {
return trim($matches[1]);
}
return null;
}
public function getLastCode()
{
return $this->lastCode;
}
public function getLastLinks()
{
preg_match_all('~Link: <(.+)>;rel="up"~', $this->lastHeader, $matches);
return $matches[1];
}
}
class Base64UrlSafeEncoder
{
public static function encode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
public static function decode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
}

View File

@@ -1,794 +0,0 @@
<?php
use \Froxlor\Database;
use \Froxlor\Settings;
use \Froxlor\FroxlorLogger;
/**
* This file is part of the Froxlor project.
* Copyright (c) 2003-2009 the SysCP Team (see authors).
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Logger
*
* @link http://www.nutime.de/
*
* Support Tickets - Tickets-Class
*/
class ticket
{
/**
* Userinfo
*
* @var array
*/
private $userinfo = array();
/**
* Ticket ID
*
* @var int
*/
private $tid = - 1;
/**
* Ticket Data Array
*
* @var array
*/
private $t_data = array();
/**
* Ticket-Object-Array
*
* @var ticket[]
*/
private static $tickets = array();
/**
* Class constructor.
*
* @param
* array userinfo
* @param
* int ticket id
*/
private function __construct($userinfo, $tid = - 1)
{
$this->userinfo = $userinfo;
$this->tid = $tid;
// initialize data array
$this->initData();
// read data from database
$this->readData();
}
/**
* Singleton ftw ;-)
*
* @param
* array userinfo
* @param
* int ticket id
*/
static public function getInstanceOf($_usernfo, $_tid)
{
if (! isset(self::$tickets[$_tid . '-' . $_usernfo['userid']])) {
self::$tickets[$_tid . '-' . $_usernfo['userid']] = new ticket($_usernfo, $_tid);
}
return self::$tickets[$_tid . '-' . $_usernfo['userid']];
}
/**
* Initialize data-array
*/
private function initData()
{
$this->Set('customer', 0, true, true);
$this->Set('admin', 1, true, true);
$this->Set('subject', '', true, true);
$this->Set('category', '0', true, true);
$this->Set('priority', '2', true, true);
$this->Set('message', '', true, true);
$this->Set('dt', 0, true, true);
$this->Set('lastchange', 0, true, true);
$this->Set('ip', '', true, true);
$this->Set('status', '0', true, true);
$this->Set('lastreplier', '0', true, true);
$this->Set('by', '0', true, true);
$this->Set('answerto', '0', true, true);
$this->Set('archived', '0', true, true);
}
/**
* Read ticket data from database.
*/
private function readData()
{
if (isset($this->tid) && $this->tid != - 1) {
if ($this->userinfo['customerid'] > 0) {
$_ticket_stmt = Database::prepare('
SELECT * FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid AND `customerid` = :cid');
$tdata = array(
'tid' => $this->tid,
'cid' => $this->userinfo['customerid']
);
} else {
$_ticket_stmt = Database::prepare('
SELECT * FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid' . ($this->userinfo['customers_see_all'] ? '' : ' AND `adminid` = :adminid'));
$tdata = array(
'tid' => $this->tid
);
if ($this->userinfo['customers_see_all'] != '1') {
$tdata['adminid'] = $this->userinfo['adminid'];
}
}
$_ticket = Database::pexecute_first($_ticket_stmt, $tdata);
if ($_ticket == false) {
throw new Exception("Invalid ticket id");
}
$this->Set('customer', $_ticket['customerid'], true, false);
$this->Set('admin', $_ticket['adminid'], true, false);
$this->Set('subject', $_ticket['subject'], true, false);
$this->Set('category', $_ticket['category'], true, false);
$this->Set('priority', $_ticket['priority'], true, false);
$this->Set('message', $_ticket['message'], true, false);
$this->Set('dt', $_ticket['dt'], true, false);
$this->Set('lastchange', $_ticket['lastchange'], true, false);
$this->Set('ip', $_ticket['ip'], true, false);
$this->Set('status', $_ticket['status'], true, false);
$this->Set('lastreplier', $_ticket['lastreplier'], true, false);
$this->Set('by', $_ticket['by'], true, false);
$this->Set('answerto', $_ticket['answerto'], true, false);
$this->Set('archived', $_ticket['archived'], true, false);
}
}
/**
* Insert data to database
*/
public function Insert()
{
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_TICKETS . "` SET
`customerid` = :customerid,
`adminid` = :adminid,
`category` = :category,
`priority` = :priority,
`subject` = :subject,
`message` = :message,
`dt` = :dt,
`lastchange` = :lastchange,
`ip` = :ip,
`status` = :status,
`lastreplier` = :lastreplier,
`by` = :by,
`answerto` = :answerto");
$ins_data = array(
'customerid' => $this->Get('customer'),
'adminid' => $this->Get('admin'),
'category' => $this->Get('category'),
'priority' => $this->Get('priority'),
'subject' => $this->Get('subject'),
'message' => $this->Get('message'),
'dt' => time(),
'lastchange' => time(),
'ip' => $this->Get('ip'),
'status' => $this->Get('status'),
'lastreplier' => $this->Get('lastreplier'),
'by' => $this->Get('by'),
'answerto' => $this->Get('answerto')
);
Database::pexecute($ins_stmt, $ins_data);
$this->tid = Database::lastInsertId();
return true;
}
/**
* Update data in database
*/
public function Update()
{
// Update "main" ticket
$upd_stmt = Database::prepare('
UPDATE `' . TABLE_PANEL_TICKETS . '` SET
`priority` = :priority,
`lastchange` = :lastchange,
`status` = :status,
`lastreplier` = :lastreplier
WHERE `id` = :tid');
$upd_data = array(
'priority' => $this->Get('priority'),
'lastchange' => $this->Get('lastchange'),
'status' => $this->Get('status'),
'lastreplier' => $this->Get('lastreplier'),
'tid' => $this->tid
);
Database::pexecute($upd_stmt, $upd_data);
return true;
}
/**
* Moves a ticket to the archive
*/
public function Archive()
{
// Update "main" ticket
$upd_stmt = Database::prepare('
UPDATE `' . TABLE_PANEL_TICKETS . '` SET `archived` = "1" WHERE `id` = :tid');
Database::pexecute($upd_stmt, array(
'tid' => $this->tid
));
// Update "answers" to ticket
$upd_stmt = Database::prepare('
UPDATE `' . TABLE_PANEL_TICKETS . '` SET `archived` = "1" WHERE `answerto` = :tid');
Database::pexecute($upd_stmt, array(
'tid' => $this->tid
));
return true;
}
/**
* Remove ticket from database
*/
public function Delete()
{
// Delete "main" ticket
$del_stmt = Database::prepare('
DELETE FROM `' . TABLE_PANEL_TICKETS . '` WHERE `id` = :tid');
Database::pexecute($del_stmt, array(
'tid' => $this->tid
));
// Delete "answers" to ticket"
$del_stmt = Database::prepare('
DELETE FROM `' . TABLE_PANEL_TICKETS . '` WHERE `answerto` = :tid');
Database::pexecute($del_stmt, array(
'tid' => $this->tid
));
return true;
}
/**
* Mail notifications
*/
public function sendMail($customerid = - 1, $template_subject = null, $default_subject = null, $template_body = null, $default_body = null)
{
global $mail, $theme;
// Some checks are to be made here in the future
if ($customerid != - 1) {
// Get e-mail message for customer
$usr_stmt = Database::prepare('
SELECT `name`, `firstname`, `company`, `email`
FROM `' . TABLE_PANEL_CUSTOMERS . '` WHERE `customerid` = :customerid');
$usr = Database::pexecute_first($usr_stmt, array(
'customerid' => $customerid
));
$replace_arr = array(
'FIRSTNAME' => $usr['firstname'],
'NAME' => $usr['name'],
'COMPANY' => $usr['company'],
'SALUTATION' => getCorrectUserSalutation($usr),
'SUBJECT' => $this->Get('subject', true)
);
} else {
$replace_arr = array(
'SUBJECT' => $this->Get('subject', true)
);
}
$tpl_seldata = array(
'adminid' => $this->userinfo['adminid'],
'lang' => $this->userinfo['def_language'],
'tplsubject' => $template_subject
);
$result_stmt = Database::prepare("
SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "`
WHERE `adminid`= :adminid
AND `language`= :lang
AND `templategroup`= 'mails' AND `varname`= :tplsubject");
$result = Database::pexecute_first($result_stmt, $tpl_seldata);
$mail_subject = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $default_subject), $replace_arr));
unset($tpl_seldata['tplsubject']);
$tpl_seldata['tplmailbody'] = $template_body;
$result_stmt = Database::prepare("
SELECT `value` FROM `" . TABLE_PANEL_TEMPLATES . "`
WHERE `adminid`= :adminid
AND `language`= :lang
AND `templategroup`= 'mails' AND `varname`= :tplmailbody");
$result = Database::pexecute_first($result_stmt, $tpl_seldata);
$mail_body = html_entity_decode(replace_variables((($result['value'] != '') ? $result['value'] : $default_body), $replace_arr));
if ($customerid != - 1) {
$_mailerror = false;
try {
$mail->SetFrom(Settings::Get('ticket.noreply_email'), Settings::Get('ticket.noreply_name'));
$mail->Subject = $mail_subject;
$mail->AltBody = $mail_body;
$mail->MsgHTML(str_replace("\n", "<br />", $mail_body));
$mail->AddAddress($usr['email'], $usr['firstname'] . ' ' . $usr['name']);
$mail->Send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
$mailerr_msg = $e->errorMessage();
$_mailerror = true;
} catch (Exception $e) {
$mailerr_msg = $e->getMessage();
$_mailerror = true;
}
if ($_mailerror) {
$rstlog = FroxlorLogger::getInstanceOf(array(
'loginname' => 'ticket_class'
));
$rstlog->logAction(ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg);
standard_error('errorsendingmail', $usr['email']);
}
$mail->ClearAddresses();
} else {
$admin_stmt = Database::prepare("
SELECT `name`, `email` FROM `" . TABLE_PANEL_ADMINS . "`
WHERE `adminid` = :adminid");
$admin = Database::pexecute_first($admin_stmt, array(
'adminid' => $this->userinfo['adminid']
));
$_mailerror = false;
try {
$mail->SetFrom(Settings::Get('ticket.noreply_email'), Settings::Get('ticket.noreply_name'));
$mail->Subject = $mail_subject;
$mail->AltBody = $mail_body;
$mail->MsgHTML(str_replace("\n", "<br />", $mail_body));
$mail->AddAddress($admin['email'], $admin['name']);
$mail->Send();
} catch (\PHPMailer\PHPMailer\Exception $e) {
$mailerr_msg = $e->errorMessage();
$_mailerror = true;
} catch (Exception $e) {
$mailerr_msg = $e->getMessage();
$_mailerror = true;
}
if ($_mailerror) {
$rstlog = FroxlorLogger::getInstanceOf(array(
'loginname' => 'ticket_class'
));
$rstlog->logAction(ADM_ACTION, LOG_ERR, "Error sending mail: " . $mailerr_msg);
standard_error('errorsendingmail', $admin['email']);
}
$mail->ClearAddresses();
}
}
/**
* Add a support-categories
*/
static public function addCategory($_category = null, $_admin = 1, $_order = 1)
{
if ($_category != null && $_category != '') {
if ($_order < 1) {
$_order = 1;
}
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_TICKET_CATS . "` SET
`name` = :name,
`adminid` = :adminid,
`logicalorder` = :lo");
$ins_data = array(
'name' => $_category,
'adminid' => $_admin,
'lo' => $_order
);
Database::pexecute($ins_stmt, $ins_data);
return true;
}
return false;
}
/**
* Edit a support-categories
*/
static public function editCategory($_category = null, $_id = 0, $_order = 1)
{
if ($_category != null && $_category != '' && $_id != 0) {
if ($_order < 1) {
$_order = 1;
}
$upd_stmt = Database::prepare("
UPDATE `" . TABLE_PANEL_TICKET_CATS . "` SET
`name` = :name,
`logicalorder` = :lo
WHERE `id` = :id
");
Database::pexecute($upd_stmt, array(
'name' => $_category,
'lo' => $_order,
'id' => $_id
));
return true;
}
return false;
}
/**
* Delete a support-categories
*/
static public function deleteCategory($_id = 0)
{
if ($_id != 0) {
$result_stmt = Database::prepare("
SELECT COUNT(`id`) as `numtickets` FROM `" . TABLE_PANEL_TICKETS . "`
WHERE `category` = :cat");
$result = Database::pexecute_first($result_stmt, array(
'cat' => $_id
));
if ($result['numtickets'] == "0") {
$del_stmt = Database::prepare("
DELETE FROM `" . TABLE_PANEL_TICKET_CATS . "` WHERE `id` = :id");
Database::pexecute($del_stmt, array(
'id' => $_id
));
return true;
} else {
return false;
}
}
return false;
}
/**
* Return a support-category-name
*/
static public function getCategoryName($_id = 0)
{
if ($_id != 0) {
$stmt = Database::prepare("
SELECT `name` FROM `" . TABLE_PANEL_TICKET_CATS . "` WHERE `id` = :id");
$category = Database::pexecute_first($stmt, array(
'id' => $_id
));
return $category['name'];
}
return null;
}
/**
* get the highest order number
*
* @param object $_uid
* admin-id (optional)
*
* @return int highest order number
*/
static public function getHighestOrderNumber($_uid = 0)
{
$where = '';
$sel_data = array();
if ($_uid > 0) {
$where = " WHERE `adminid` = :adminid";
$sel_data['adminid'] = $_uid;
}
$sql = "SELECT MAX(`logicalorder`) as `highestorder` FROM `" . TABLE_PANEL_TICKET_CATS . "`" . $where . ";";
$result_stmt = Database::prepare($sql);
$result = Database::pexecute_first($result_stmt, $sel_data);
return (isset($result['highestorder']) ? (int) $result['highestorder'] : 0);
}
/**
* returns the last x archived tickets
*/
static public function getLastArchived($_num = 10, $_admin = 1)
{
if ($_num > 0) {
$archived = array();
$counter = 0;
$result_stmt = Database::prepare("
SELECT *, (
SELECT COUNT(`sub`.`id`)
FROM `" . TABLE_PANEL_TICKETS . "` `sub`
WHERE `sub`.`answerto` = `main`.`id`
) as `ticket_answers`
FROM `" . TABLE_PANEL_TICKETS . "` `main`
WHERE `main`.`answerto` = '0' AND `main`.`archived` = '1'
AND `main`.`adminid` = :adminid
ORDER BY `main`.`lastchange` DESC LIMIT 0, " . (int) $_num);
Database::pexecute($result_stmt, array(
'adminid' => $_admin
));
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$archived[$counter]['id'] = $row['id'];
$archived[$counter]['customerid'] = $row['customerid'];
$archived[$counter]['adminid'] = $row['adminid'];
$archived[$counter]['lastreplier'] = $row['lastreplier'];
$archived[$counter]['ticket_answers'] = $row['ticket_answers'];
$archived[$counter]['category'] = $row['category'];
$archived[$counter]['priority'] = $row['priority'];
$archived[$counter]['subject'] = $row['subject'];
$archived[$counter]['message'] = $row['message'];
$archived[$counter]['dt'] = $row['dt'];
$archived[$counter]['lastchange'] = $row['lastchange'];
$archived[$counter]['status'] = $row['status'];
$archived[$counter]['by'] = $row['by'];
$counter ++;
}
if (isset($archived[0]['id'])) {
return $archived;
} else {
return false;
}
}
}
/**
* Returns a sql-statement to search the archive
* including necessary parameter-array for PDO
*
* @return array 0 = query, 1 = params-array
*/
static public function getArchiveSearchStatement($subject = null, $priority = null, $fromdate = null, $todate = null, $message = null, $customer = - 1, $admin = 1, $categories = null)
{
$search_params = array();
$query = "
SELECT `main`.*, (
SELECT COUNT(`sub`.`id`) FROM `" . TABLE_PANEL_TICKETS . "` `sub`
WHERE `sub`.`answerto` = `main`.`id`
) as `ticket_answers`
FROM `" . TABLE_PANEL_TICKETS . "` `main`
WHERE `main`.`archived` = '1' AND `main`.`adminid` = :admin";
$search_params['admin'] = $admin;
if ($subject != NULL && $subject != '') {
$query .= " AND `main`.`subject` LIKE :subject";
$search_params['subject'] = "%" . $subject . "%";
}
if ($priority != null && isset($priority[0]) && $priority[0] != '') {
if (isset($priority[1]) && $priority[1] != '') {
if (isset($priority[2]) && $priority[2] != '') {
$query .= " AND (`main`.`priority` = '1' OR `main`.`priority` = '2' OR `main`.`priority` = '3')";
} else {
$query .= " AND (`main`.`priority` = '1' OR `main`.`priority` = '1')";
}
} elseif (isset($priority[2]) && $priority[2] != '') {
$query .= " AND (`main`.`priority` = '1' OR `main`.`priority` = '3')";
} else {
$query .= " AND `main`.`priority` = '1'";
}
} elseif ($priority != null && isset($priority[1]) && $priority[1] != '') {
if (isset($priority[2]) && $priority[2] != '') {
$query .= " AND (`main`.`priority` = '2' OR `main`.`priority` = '3')";
} else {
$query .= " AND `main`.`priority` = '2'";
}
} elseif ($priority != null) {
if (isset($priority[3]) && $priority[3] != '') {
$query .= " AND `main`.`priority` = '3'";
}
}
if ($fromdate != null && $fromdate > 0) {
$query .= " AND `main`.`lastchange` > :fromdate";
$search_params['fromdate'] = strtotime($fromdate);
}
if ($todate != null && $todate > 0) {
$query .= " AND `main`.`lastchange` < :todate";
$search_params['todate'] = strtotime($todate);
}
if ($message != null && $message != '') {
$query .= " AND `main`.`message` LIKE :message";
$search_params['message'] = "%" . $message . "%";
}
if ($customer != - 1) {
$query .= " AND `main`.`customerid` = :customer";
$search_params['customer'] = $customer;
}
if ($categories != null) {
$cats = array();
foreach ($categories as $index => $catid) {
if ($catid != "") {
$cats[] = $catid;
}
}
if (count($cats) > 0) {
$query .= " AND (";
}
foreach ($cats as $catid) {
if (isset($catid) && $catid > 0) {
$query .= "`main`.`category` = :catid_" . $catid . " OR ";
$search_params['catid_' . $catid] = $catid;
}
}
if (count($cats) > 0) {
$query = substr($query, 0, strlen($query) - 3);
$query .= ") ";
}
}
return array(
'0' => $query,
'1' => $search_params
);
}
/**
* Get statustext by status-no
*/
static public function getStatusText($_lng, $_status = 0)
{
switch ($_status) {
case 0:
return $_lng['ticket']['open'];
break;
case 1:
return $_lng['ticket']['wait_reply'];
break;
case 2:
return $_lng['ticket']['replied'];
break;
default:
return $_lng['ticket']['closed'];
break;
}
}
/**
* Get prioritytext by priority-no
*/
static public function getPriorityText($_lng, $_priority = 0)
{
switch ($_priority) {
case 1:
return $_lng['ticket']['high'];
break;
case 2:
return $_lng['ticket']['normal'];
break;
default:
return $_lng['ticket']['low'];
break;
}
}
private function convertLatin1ToHtml($str)
{
$html_entities = array(
"Ä" => "&Auml;",
"ä" => "&auml;",
"Ö" => "&Ouml;",
"ö" => "&ouml;",
"Ü" => "&Uuml;",
"ü" => "&uuml;",
"ß" => "&szlig;"
/*
* @TODO continue this table for all the special-characters
*/
);
foreach ($html_entities as $key => $value) {
$str = str_replace($key, $value, $str);
}
return $str;
}
/**
* function customerHasTickets
*
* @param
* int customer-id
*
* @return array/bool array of ticket-ids if customer has any, else false
*/
static public function customerHasTickets($_cid = 0)
{
if ($_cid != 0) {
$result_stmt = Database::prepare("
SELECT `id` FROM `" . TABLE_PANEL_TICKETS . "` WHERE `customerid` = :cid");
Database::pexecute($result_stmt, array(
'cid' => $_cid
));
$tickets = array();
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
$tickets[] = $row['id'];
}
return $tickets;
}
return false;
}
/**
* Get a data-var
*/
public function Get($_var = '', $_vartrusted = false)
{
if ($_var != '') {
if (! $_vartrusted) {
$_var = htmlspecialchars($_var);
}
if (isset($this->t_data[$_var])) {
if (strtolower($_var) == 'message') {
// avoid double line-breaks, #1413
$this->t_data[$_var] = str_replace("<br />\n", "\n", $this->t_data[$_var]);
return nl2br($this->t_data[$_var]);
} elseif (strtolower($_var) == 'subject') {
return nl2br($this->t_data[$_var]);
} else {
return $this->t_data[$_var];
}
} else {
return null;
}
}
}
/**
* Set a data-var
*/
public function Set($_var = '', $_value = '', $_vartrusted = false, $_valuetrusted = false)
{
if ($_var != '' && $_value != '') {
if (! $_vartrusted) {
$_var = strip_tags($_var);
}
if (! $_valuetrusted) {
$_value = strip_tags($_value, '<br />');
}
if (strtolower($_var) == 'message' || strtolower($_var) == 'subject') {
$_value = $this->convertLatin1ToHtml($_value);
}
$this->t_data[$_var] = $_value;
}
}
}

View File

@@ -1,315 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @since 0.9.29
*
*/
class ConfigIO
{
/**
* constructor
*/
public function __construct()
{}
/**
* clean up former created configs, including (if enabled)
* awstats, fcgid, php-fpm and of course automatically created
* webserver vhost and diroption files
*
* @return null
*/
public function cleanUp()
{
// old error logs
$this->_cleanErrLogs();
// awstats files
$this->_cleanAwstatsFiles();
// fcgid files
$this->_cleanFcgidFiles();
// php-fpm files
$this->_cleanFpmFiles();
// clean webserver-configs
$this->_cleanWebserverConfigs();
// old htpasswd files
$this->_cleanHtpasswdFiles();
// customer-specified ssl-certificates
$this->_cleanCustomerSslCerts();
}
private function _cleanErrLogs()
{
$err_dir = makeCorrectDir(\Froxlor\Froxlor::getInstallDir() . "/logs/");
if (@is_dir($err_dir)) {
// now get rid of old stuff
// (but append /*.log so we don't delete the directory)
$err_dir .= '/*.log';
safe_exec('rm -f ' . makeCorrectFile($err_dir));
}
}
/**
* remove customer-specified auto-generated ssl-certificates
* (they are being regenerated)
*
* @return null
*/
private function _cleanCustomerSslCerts()
{
/*
* only clean up if we're actually using SSL
*/
if (Settings::Get('system.use_ssl') == '1') {
// get correct directory
$configdir = $this->_getFile('system', 'customer_ssl_path');
if ($configdir !== false) {
$configdir = makeCorrectDir($configdir);
if (@is_dir($configdir)) {
// now get rid of old stuff
// (but append /* so we don't delete the directory)
$configdir .= '/*';
safe_exec('rm -f ' . makeCorrectFile($configdir));
}
}
}
}
/**
* remove webserver related configuration files before regeneration
*
* @return null
*/
private function _cleanWebserverConfigs()
{
// get directories
$configdirs = array();
$dir = $this->_getFile('system', 'apacheconf_vhost');
if ($dir !== false)
$configdirs[] = makeCorrectDir($dir);
$dir = $this->_getFile('system', 'apacheconf_diroptions');
if ($dir !== false)
$configdirs[] = makeCorrectDir($dir);
// file pattern
$pattern = "/^([0-9]){2}_(froxlor|syscp)_(.+)\.conf$/";
// check ALL the folders
foreach ($configdirs as $config_dir) {
// check directory
if (@is_dir($config_dir)) {
// create directory iterator
$its = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($config_dir));
// iterate through all subdirs,
// look for vhost/diroption files
// and delete them
foreach ($its as $fullFileName => $it) {
if ($it->isFile() && preg_match($pattern, $it->getFilename())) {
// remove file
safe_exec('rm -f ' . escapeshellarg(makeCorrectFile($its->getPathname())));
}
}
}
}
}
/**
* remove htpasswd files before regeneration
*
* @return null
*/
private function _cleanHtpasswdFiles()
{
// get correct directory
$configdir = $this->_getFile('system', 'apacheconf_htpasswddir');
if ($configdir !== false) {
$configdir = makeCorrectDir($configdir);
if (@is_dir($configdir)) {
// now get rid of old stuff
// (but append /* so we don't delete the directory)
$configdir .= '/*';
safe_exec('rm -f ' . makeCorrectFile($configdir));
}
}
}
/**
* remove awstats related configuration files before regeneration
*
* @return null
*/
private function _cleanAwstatsFiles()
{
if (Settings::Get('system.awstats_enabled') == '0') {
return;
}
// dhr: cleanout froxlor-generated awstats configs prior to re-creation
$awstatsclean['header'] = "## GENERATED BY FROXLOR\n";
$awstatsclean['headerold'] = "## GENERATED BY SYSCP\n";
$awstatsclean['path'] = $this->_getFile('system', 'awstats_conf');
/**
* don't do anything if the directory does not exist
* (e.g.
* awstats not installed yet or whatever)
* fixes #45
*/
if ($awstatsclean['path'] !== false && is_dir($awstatsclean['path'])) {
$awstatsclean['dir'] = dir($awstatsclean['path']);
while ($awstatsclean['entry'] = $awstatsclean['dir']->read()) {
$awstatsclean['fullentry'] = makeCorrectFile($awstatsclean['path'] . '/' . $awstatsclean['entry']);
/**
* don't do anything if the file does not exist
*/
if (@file_exists($awstatsclean['fullentry'])) {
$awstatsclean['fh'] = fopen($awstatsclean['fullentry'], 'r');
$awstatsclean['headerRead'] = fgets($awstatsclean['fh'], strlen($awstatsclean['header']) + 1);
fclose($awstatsclean['fh']);
if ($awstatsclean['headerRead'] == $awstatsclean['header'] || $awstatsclean['headerRead'] == $awstatsclean['headerold']) {
$awstats_conf_file = makeCorrectFile($awstatsclean['fullentry']);
@unlink($awstats_conf_file);
}
}
}
}
unset($awstatsclean);
// end dhr
}
/**
* remove fcgid related configuration files before regeneration
*
* @return null
*/
private function _cleanFcgidFiles()
{
if (Settings::Get('system.mod_fcgid') == '0') {
return;
}
// get correct directory
$configdir = $this->_getFile('system', 'mod_fcgid_configdir');
if ($configdir !== false) {
$configdir = makeCorrectDir($configdir);
if (@is_dir($configdir)) {
// create directory iterator
$its = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($configdir));
// iterate through all subdirs,
// look for php-fcgi-starter files
// and take immutable-flag away from them
// so we can delete them :)
foreach ($its as $fullFileName => $it) {
if ($it->isFile() && $it->getFilename() == 'php-fcgi-starter') {
// set chattr -i
removeImmutable($its->getPathname());
}
}
// now get rid of old stuff
// (but append /* so we don't delete the directory)
$configdir .= '/*';
safe_exec('rm -rf ' . makeCorrectFile($configdir));
}
}
}
/**
* remove php-fpm related configuration files before regeneration
*
* @return null
*/
private function _cleanFpmFiles()
{
if (Settings::Get('phpfpm.enabled') == '0') {
return;
}
// get all fpm config paths
$fpmconf_sel = Database::prepare("SELECT config_dir FROM `" . TABLE_PANEL_FPMDAEMONS . "`");
Database::pexecute($fpmconf_sel);
$fpmconf_paths = $fpmconf_sel->fetchAll(PDO::FETCH_ASSOC);
// clean all php-fpm config-dirs
foreach ($fpmconf_paths as $configdir) {
$configdir = makeCorrectDir($configdir['config_dir']);
if (@is_dir($configdir)) {
// now get rid of old stuff
// (but append /*.conf so we don't delete the directory)
$configdir .= '/*.conf';
safe_exec('rm -f ' . makeCorrectFile($configdir));
} else {
safe_exec('mkdir -p ' . $configdir);
}
}
// also remove aliasconfigdir #1273
$aliasconfigdir = $this->_getFile('phpfpm', 'aliasconfigdir');
if ($aliasconfigdir !== false) {
$aliasconfigdir = makeCorrectDir($aliasconfigdir);
if (@is_dir($aliasconfigdir)) {
$aliasconfigdir .= '/*';
safe_exec('rm -rf ' . makeCorrectFile($aliasconfigdir));
}
}
}
/**
* returns a file/direcotry from the settings and checks whether it exists
*
* @param string $group
* settings-group
* @param string $varname
* var-name
* @param boolean $check_exists
* check if the file exists
*
* @return string|boolean complete path including filename if any or false on error
*/
private function _getFile($group, $varname, $check_exists = true)
{
// read from settings
$file = Settings::Get($group . '.' . $varname);
// check whether it exists
if ($check_exists && @file_exists($file) == false) {
return false;
}
return $file;
}
}

View File

@@ -1,115 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @since 0.9.29
*
*/
class DomainSSL {
/**
* constructor
*/
public function __construct() {}
/**
* read domain-related (or if empty, parentdomain-related) ssl-certificates from the database
* and (if not empty) set the corresponding array-indices (ssl_cert_file, ssl_key_file,
* ssl_ca_file and ssl_cert_chainfile). Hence the parameter as reference.
*
* @param array $domain domain-array as reference so we can set the corresponding array-indices
*
* @return null
*/
public function setDomainSSLFilesArray(array &$domain = null) {
// check if the domain itself has a certificate defined
$dom_certs_stmt = Database::prepare("
SELECT * FROM `".TABLE_PANEL_DOMAIN_SSL_SETTINGS."` WHERE `domainid` = :domid
");
$dom_certs = Database::pexecute_first($dom_certs_stmt, array('domid' => $domain['id']));
if (!is_array($dom_certs)
|| !isset($dom_certs['ssl_cert_file'])
|| $dom_certs['ssl_cert_file'] == ''
) {
// maybe its parent?
if (isset($domain['parentdomainid']) && $domain['parentdomainid'] != 0) {
$dom_certs = Database::pexecute_first($dom_certs_stmt, array('domid' => $domain['parentdomainid']));
}
}
// check if it's an array and if the most important field is set
if (is_array($dom_certs)
&& isset($dom_certs['ssl_cert_file'])
&& $dom_certs['ssl_cert_file'] != ''
) {
// get destination path
$sslcertpath = makeCorrectDir(Settings::Get('system.customer_ssl_path'));
// create path if it does not exist
if (!file_exists($sslcertpath)) {
safe_exec('mkdir -p '.escapeshellarg($sslcertpath));
}
// make correct files for the certificates
$ssl_files = array(
'ssl_cert_file' => makeCorrectFile($sslcertpath.'/'.$domain['domain'].'.crt'),
'ssl_key_file' => makeCorrectFile($sslcertpath.'/'.$domain['domain'].'.key')
);
if (Settings::Get('system.webserver') == 'lighttpd') {
// put my.crt and my.key together for lighty.
$dom_certs['ssl_cert_file'] = trim($dom_certs['ssl_cert_file'])."\n".trim($dom_certs['ssl_key_file'])."\n";
$ssl_files['ssl_key_file'] = '';
}
// initialize optional files
$ssl_files['ssl_ca_file'] = '';
$ssl_files['ssl_cert_chainfile'] = '';
// set them if they are != empty
if ($dom_certs['ssl_ca_file'] != '') {
$ssl_files['ssl_ca_file'] = makeCorrectFile($sslcertpath.'/'.$domain['domain'].'_CA.pem');
}
if ($dom_certs['ssl_cert_chainfile'] != '') {
if (Settings::Get('system.webserver') == 'nginx') {
// put ca.crt in my.crt, as nginx does not support a separate chain file.
$dom_certs['ssl_cert_file'] = trim($dom_certs['ssl_cert_file'])."\n".trim($dom_certs['ssl_cert_chainfile'])."\n";
} else {
$ssl_files['ssl_cert_chainfile'] = makeCorrectFile($sslcertpath.'/'.$domain['domain'].'_chain.pem');
}
}
// will only be generated to be used externally, froxlor does not need this
if ($dom_certs['ssl_fullchain_file'] != '') {
$ssl_files['ssl_fullchain_file'] = makeCorrectFile($sslcertpath.'/'.$domain['domain'].'_fullchain.pem');
}
// create them on the filesystem
foreach ($ssl_files as $type => $filename) {
if ($filename != '') {
touch($filename);
$_fh = fopen($filename, 'w');
fwrite($_fh, $dom_certs[$type]);
fclose($_fh);
chmod($filename, 0600);
}
}
// override corresponding array values
$domain['ssl_cert_file'] = $ssl_files['ssl_cert_file'];
$domain['ssl_key_file'] = $ssl_files['ssl_key_file'];
$domain['ssl_ca_file'] = $ssl_files['ssl_ca_file'];
$domain['ssl_cert_chainfile'] = $ssl_files['ssl_cert_chainfile'];
}
return;
}
}

View File

@@ -1,110 +0,0 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* For the full copyright and license information, please view the COPYING
* file that was distributed with this source code. You can also view the
* COPYING file online at http://files.froxlor.org/misc/COPYING.txt
*
* @copyright (c) the authors
* @author Michael Kaufmann <mkaufmann@nutime.de>
* @author Froxlor team <team@froxlor.org> (2010-)
* @license GPLv2 http://files.froxlor.org/misc/COPYING.txt
* @package Cron
*
* @since 0.9.31
*
*/
class WebserverBase
{
/**
* returns an array with all entries required for all
* webserver-vhost-configs
*
* @return array
*/
public static function getVhostsToCreate()
{
$query = "SELECT `d`.*, `pd`.`domain` AS `parentdomain`, `c`.`loginname`,
`d`.`phpsettingid`, `c`.`adminid`, `c`.`guid`, `c`.`email`,
`c`.`documentroot` AS `customerroot`, `c`.`deactivated`,
`c`.`phpenabled` AS `phpenabled_customer`,
`d`.`phpenabled` AS `phpenabled_vhost`,
`d`.`mod_fcgid_starter`,`d`.`mod_fcgid_maxrequests`,
`d`.`ocsp_stapling`
FROM `" . TABLE_PANEL_DOMAINS . "` `d`
LEFT JOIN `" . TABLE_PANEL_CUSTOMERS . "` `c` USING(`customerid`)
LEFT JOIN `" . TABLE_PANEL_DOMAINS . "` `pd` ON (`pd`.`id` = `d`.`parentdomainid`)
WHERE `d`.`aliasdomain` IS NULL AND `d`.`email_only` <> '1'
ORDER BY `d`.`parentdomainid` DESC, `d`.`iswildcarddomain`, `d`.`domain` ASC;
";
$result_domains_stmt = Database::query($query);
// prepare IP statement
$ip_stmt = Database::prepare("
SELECT `di`.`id_domain` , `p`.`ssl`, `p`.`ssl_cert_file`, `p`.`ssl_key_file`, `p`.`ssl_ca_file`, `p`.`ssl_cert_chainfile`
FROM `" . TABLE_DOMAINTOIP . "` `di`, `" . TABLE_PANEL_IPSANDPORTS . "` `p`
WHERE `p`.`id` = `di`.`id_ipandports`
AND `di`.`id_domain` = :domainid
AND `p`.`ssl` = '1'
");
// prepare fpm-config select query
$fpm_sel_stmt = Database::prepare("
SELECT f.id FROM `" . TABLE_PANEL_FPMDAEMONS . "` f
LEFT JOIN `" . TABLE_PANEL_PHPCONFIGS . "` p ON p.fpmsettingid = f.id
WHERE p.id = :phpconfigid
");
$domains = array();
while ($domain = $result_domains_stmt->fetch(PDO::FETCH_ASSOC)) {
// set whole domain
$domains[$domain['domain']] = $domain;
// set empty-defaults for non-ssl
$domains[$domain['domain']]['ssl'] = '';
$domains[$domain['domain']]['ssl_cert_file'] = '';
$domains[$domain['domain']]['ssl_key_file'] = '';
$domains[$domain['domain']]['ssl_ca_file'] = '';
$domains[$domain['domain']]['ssl_cert_chainfile'] = '';
// now, if the domain has an ssl ip/port assigned, get
// the corresponding information from the db
if (domainHasSslIpPort($domain['id'])) {
$ssl_ip = Database::pexecute_first($ip_stmt, array(
'domainid' => $domain['id']
));
// set ssl info for domain
$domains[$domain['domain']]['ssl'] = '1';
$domains[$domain['domain']]['ssl_cert_file'] = $ssl_ip['ssl_cert_file'];
$domains[$domain['domain']]['ssl_key_file'] = $ssl_ip['ssl_key_file'];
$domains[$domain['domain']]['ssl_ca_file'] = $ssl_ip['ssl_ca_file'];
$domains[$domain['domain']]['ssl_cert_chainfile'] = $ssl_ip['ssl_cert_chainfile'];
}
// read fpm-config-id if using fpm
if ((int) Settings::Get('phpfpm.enabled') == 1) {
$fpm_config = Database::pexecute_first($fpm_sel_stmt, array(
'phpconfigid' => $domain['phpsettingid']
));
if ($fpm_config) {
$domains[$domain['domain']]['fpm_config_id'] = $fpm_config['id'];
} else {
// fallback
$domains[$domain['domain']]['fpm_config_id'] = 1;
}
}
}
return $domains;
}
}