Skip to content
class.ISPConfigDebianOS.inc.php 39.6 KiB
Newer Older
<?php
/**
 * Description of class
 *
 * @author croydon
 */
class ISPConfigDebianOS extends ISPConfigBaseOS {
	public function getPackageVersion($package) {
		$cmd = 'dpkg --list ' . $package . ' 2>&1';
		$result = $this->exec($cmd);
		$version = false;
		$matches = array();
		if(preg_match_all('/^ii\s+\S+\s+(\S+)(?:\s|$)/m', $result, $matches, PREG_SET_ORDER)) {
			for($i = 0; $i < count($matches); $i++) {
				$tmp_version = $matches[$i][1];
				if(!$version || ISPProtectFunctions::version_compare($version, $tmp_version, '<')) {
					$version = $tmp_version;
				}
			}
		}
	public function getPackageAlias($package) {
		switch($package) {
			case 'libssl':
				$package = 'libssl[0-9]*';
				break;
			case 'kernel':
				$package = 'linux-image-[0-9]*';
				break;
		}
Marius Burkard's avatar
Marius Burkard committed
	public function getUpdateCommand($mode = 'update') {
		if($mode == 'prepare') {
			$cmd = 'DEBIAN_FRONTEND="noninteractive" apt-get update -qq -y';
		} elseif($mode == 'update') {
			// for updating all updateable packages
			$cmd = 'DEBIAN_FRONTEND="noninteractive" apt-get dist-upgrade -qq -y';
		} elseif($mode == 'install' || $mode == 'partly_update') {
			// for installing / updating specific packages
			$cmd = 'DEBIAN_FRONTEND="noninteractive" apt-get install -qq -y';
			$cmd .= ' <PACKAGES>';
		}
		$cmd = 'while fuser /var/lib/dpkg/lock >/dev/null 2>&1 || fuser /var/lib/apt/lists/lock >/dev/null 2>&1 ; do sleep 2; done; ' . $cmd . ' 2>&1';
	public function getUpdatePackageRegex() {
		$regex = '^\w+\s+(?P<package>\S+)\s+(?:\[(?P<oldversion>\S+)\]\s*)?(?:\((?P<newversion>\S+))?(?:\s|$)';
	public function getInstallPackageRegex($mode = '') {
		if($mode == 'oldversion') {
			$regex = '(?P<package>\S+)\s+(?:(?P<oldversion>\d\S+)\s+)?\(.*\.deb';
		} elseif($mode == 'newversion') {
			$regex = '(?:^|\s+)(?P<package>\S+)\s+\((?P<newversion>\d\S*)\)\s+';
		} else {
			$regex = ''; // not on debian!
		}
	public function getRestartServiceCommand($service, $command = 'restart') {
Marius Burkard's avatar
Marius Burkard committed
		if($command != 'start' && $command != 'stop' && $command != 'status') {
			$command = 'restart';
		}

		switch($service) {
			case 'apache':
				$service = 'apache2';
				break;
			case 'pureftpd':
				$service = 'pure-ftpd-mysql';
				break;
		}
		return 'service ' . escapeshellarg($service) . ' ' . $command . ' 2>&1';
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function updateMySQLConfig($mysql_root_pw) {
Marius Burkard's avatar
Marius Burkard committed
		ISPConfigLog::info('Writing MySQL config files.', true);
Marius Burkard's avatar
Marius Burkard committed
		$this->replaceContents('/etc/mysql/debian.cnf', array('/^password\s*=.*$/m' => 'password = ' . $mysql_root_pw));
		$this->replaceContents('/etc/mysql/mariadb.conf.d/50-server.cnf', array('/^bind-address/m' => '#bind-address', '/^sql-mode\s*=.*?$/m' => 'sql-mode = "NO_ENGINE_SUBSTITUTION"'), true, 'mysqld');
Marius Burkard's avatar
Marius Burkard committed
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function getPackagesToInstall($section) {
		if($section === 'mail') {
			$packages = array(
				'dnsutils',
				'resolvconf',
				'clamav',
				'clamav-daemon',
				'unzip',
				'bzip2',
				'arj',
				'nomarch',
				'lzop',
				'cabextract',
				'apt-listchanges',
				'libnet-ldap-perl',
				'libauthen-sasl-perl',
				'clamav-docs',
				'daemon',
				'libio-string-perl',
				'libio-socket-ssl-perl',
				'libnet-ident-perl',
				'zip',
				'libnet-dns-perl',
				'libdbd-mysql-perl',
				'bind9',
				'dnsutils'
			);
			if(ISPConfig::shallInstall('mail')) {
				$packages[] = 'spamassassin';
				if(ISPConfig::wantsAmavis()) {
					$packages[] = 'amavisd-new';
				} else {
					$packages[] = 'rspamd';
					$packages[] = 'redis-server';
				}
				$packages[] = 'postgrey';
			}
Marius Burkard's avatar
Marius Burkard committed
		}
Marius Burkard's avatar
Marius Burkard committed
		return $packages;
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function getApacheModulesToEnable() {
		$modules = array('suexec', 'rewrite', 'ssl', 'actions', 'include', 'dav_fs', 'dav', 'auth_digest', 'cgi', 'headers', 'proxy_fcgi', 'alias');
Marius Burkard's avatar
Marius Burkard committed
		return $modules;
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function setDefaultPHP() {
Marius Burkard's avatar
Marius Burkard committed
		ISPConfigLog::info('Settings default system php version.', true);
		$cmd = 'update-alternatives --set php /usr/bin/php7.0 ; update-alternatives --set php-cgi /usr/bin/php-cgi7.0';
Marius Burkard's avatar
Marius Burkard committed
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
Marius Burkard's avatar
Marius Burkard committed
	protected function installPHPMyAdmin($mysql_root_pw) {
		if(!ISPConfig::shallInstall('web') || !ISPConfig::shallInstall('pma')) {
			return;
		}
Marius Burkard's avatar
Marius Burkard committed
		$cmd = 'APP_PASS="' . ISPConfigFunctions::generatePassword(15) . '"' . "\n";
		$cmd .= 'ROOT_PASS="' . $mysql_root_pw . '"' . "\n";
		$cmd .= 'APP_DB_PASS="' . ISPConfigFunctions::generatePassword(15) . '"' . "\n";
		$cmd .= 'echo "phpmyadmin phpmyadmin/dbconfig-install boolean true" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "phpmyadmin phpmyadmin/app-password-confirm password $APP_PASS" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "phpmyadmin phpmyadmin/mysql/admin-user string root" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "phpmyadmin phpmyadmin/mysql/admin-pass password $ROOT_PASS" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "phpmyadmin phpmyadmin/mysql/app-pass password $APP_DB_PASS" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "phpmyadmin phpmyadmin/reconfigure-webserver multiselect apache2" | debconf-set-selections 2>&1' . "\n";
Marius Burkard's avatar
Marius Burkard committed
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
Marius Burkard's avatar
Marius Burkard committed
		}
		$this->installPackages('phpmyadmin');
Marius Burkard's avatar
Marius Burkard committed
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function fixDbconfigCommon() {
		ISPConfigLog::info('Fixing dbconfig-common if neccessary');
		$replacements = array(
			'/_dbc_nodb="yes" dbc_mysql_exec/' => '_dbc_nodb="yes"; dbc_mysql_exec'
		);
		$this->replaceContents('/usr/share/dbconfig-common/internal/mysql', $replacements, false);
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function setPHPTimezone() {
		if(!is_file('/etc/timezone')) {
			return;
		}
		$tz = trim(file_get_contents('/etc/timezone'));
		if(!in_array($tz, timezone_identifiers_list())) {
			return;
		}
Marius Burkard's avatar
Marius Burkard committed
		// set in all php inis
		$ini_files = array(
			'/etc/php/5.6/cgi/php.ini',
			'/etc/php/5.6/cli/php.ini',
			'/etc/php/5.6/fpm/php.ini',
			'/etc/php/5.6/apache2/php.ini',
			'/etc/php/7.0/cgi/php.ini',
			'/etc/php/7.0/cli/php.ini',
			'/etc/php/7.0/fpm/php.ini',
			'/etc/php/7.0/apache2/php.ini',
			'/etc/php/7.1/cgi/php.ini',
			'/etc/php/7.1/cli/php.ini',
			'/etc/php/7.1/fpm/php.ini',
			'/etc/php/7.1/apache2/php.ini',
			'/etc/php/7.2/cgi/php.ini',
			'/etc/php/7.2/cli/php.ini',
			'/etc/php/7.2/fpm/php.ini',
			'/etc/php/7.2/apache2/php.ini',
			'/etc/php/7.3/cgi/php.ini',
			'/etc/php/7.3/cli/php.ini',
			'/etc/php/7.3/fpm/php.ini',
Marius Burkard's avatar
Marius Burkard committed
			'/etc/php/7.3/apache2/php.ini',
			'/etc/php/7.4/cgi/php.ini',
			'/etc/php/7.4/cli/php.ini',
			'/etc/php/7.4/fpm/php.ini',
			'/etc/php/7.4/apache2/php.ini'
Marius Burkard's avatar
Marius Burkard committed
		);
Marius Burkard's avatar
Marius Burkard committed
		$replace = array(
			'/^;?\s*date\.timezone\s+=.*$/' => 'date.timezone = ' . $tz
		);
Marius Burkard's avatar
Marius Burkard committed
		foreach($ini_files as $ini) {
			if(is_file($ini)) {
				$this->replaceContents($ini, $replace);
			}
		}
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function configureApt() {
		// enable contrib and non-free
		ISPConfigLog::info('Enabling contrib and non-free repositories.', true);
		$replacements = array(
			'/^(deb.*\s+main)\s*$/' => '$1 contrib non-free'
		);

		$this->replaceContents('/etc/apt/sources.list', $replacements);
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function addSuryRepo() {
		ISPConfigLog::info('Activating sury php repository.', true);
		$cmd = 'wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg >/dev/null 2>&1 ; echo "deb https://packages.sury.org/php/ $(lsb_release -c -s) main" > /etc/apt/sources.list.d/php.list';
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
Marius Burkard's avatar
Marius Burkard committed
	}
Marius Burkard's avatar
Marius Burkard committed
	protected function getFail2BanJail() {
		$jk_jail = '[pure-ftpd]
enabled = true
port = ftp
filter = pure-ftpd
logpath = /var/log/syslog
maxretry = 3

[dovecot]
enabled = true
filter = dovecot
logpath = /var/log/mail.log
maxretry = 5

[postfix-sasl]
enabled = true
port = smtp
filter = postfix-sasl
logpath = /var/log/mail.log
maxretry = 3';
		return $jk_jail;
	}
	protected function installMailman($host_name) {
		if(!ISPConfig::shallInstall('mail') || !ISPConfig::shallInstall('mailman')) {
			return;
		}
		ISPConfigLog::info('Installing Mailman', true);
		$cmd = 'echo "mailman mailman/site_languages multiselect de (German), en (English)" | debconf-set-selections 2>&1' . "\n";
Marius Burkard's avatar
Marius Burkard committed
		if(isset($_GET['lang']) && $_GET['lang'] === 'de') {
			$cmd .= 'echo "mailman mailman/default_server_language select de (German)" | debconf-set-selections 2>&1';
		} else {
			$cmd .= 'echo "mailman mailman/default_server_language select en (English)" | debconf-set-selections 2>&1';
		}
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		}
		$package = 'mailman';
		$this->installPackages($package);
		if(!is_dir('/var/lib/mailman/lists/mailman')) {
			$listpw = ISPConfigFunctions::generatePassword(12);
			$cmd = 'newlist -q -e ' . escapeshellarg($host_name) . ' mailman ' . escapeshellarg('root@' . $host_name) . ' ' . escapeshellarg($listpw);
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		$add_content = '## mailman mailing list
mailman:              "|/var/lib/mailman/mail/mailman post mailman"
mailman-admin:        "|/var/lib/mailman/mail/mailman admin mailman"
mailman-bounces:      "|/var/lib/mailman/mail/mailman bounces mailman"
mailman-confirm:      "|/var/lib/mailman/mail/mailman confirm mailman"
mailman-join:         "|/var/lib/mailman/mail/mailman join mailman"
mailman-leave:        "|/var/lib/mailman/mail/mailman leave mailman"
mailman-owner:        "|/var/lib/mailman/mail/mailman owner mailman"
mailman-request:      "|/var/lib/mailman/mail/mailman request mailman"
mailman-subscribe:    "|/var/lib/mailman/mail/mailman subscribe mailman"
mailman-unsubscribe:  "|/var/lib/mailman/mail/mailman unsubscribe mailman"';
		$fp = fopen('/etc/aliases', 'r+');
		if(!$fp) {
			throw new ISPConfigOSException('Opening /etc/aliases failed.');
		}
		$found = false;
		while(!feof($fp)) {
			$line = trim(fgets($fp));
			if($line === '## mailman mailing list') {
				$found = true;
				break;
			}
		}
		if($found === false) {
			fseek($fp, SEEK_END);
			fwrite($fp, "\n\n" . $add_content);
		}
		$cmd = 'newaliases';
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		}
Marius Burkard's avatar
Marius Burkard committed
		if(ISPConfig::$WEBSERVER === ISPC_WEBSERVER_APACHE) {
			if(!is_link('/etc/apache2/conf-enabled/mailman.conf') && !is_file('/etc/apache2/conf-enabled/mailman.conf')) {
				symlink('/etc/mailman/apache.conf', '/etc/apache2/conf-enabled/mailman.conf');
			}
		$this->restartService('postfix');
		$this->restartService('mailman');
Marius Burkard's avatar
Marius Burkard committed
		if(ISPConfig::$WEBSERVER === ISPC_WEBSERVER_APACHE) {
			$this->restartService('apache2');
		}
	protected function installPackages($packages) {
		if(is_string($packages)) {
			$packages = array($packages);
		}
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = parent::installPackages($packages);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}
Marius Burkard's avatar
Marius Burkard committed
	public function runPerfectSetup() {
		$log_filename = 'setup-' . strftime('%Y%m%d%H%M%S', time()) . '.log';
		ISPConfigLog::setLogFile($log_filename);
		if(is_file('/usr/local/ispconfig/server/lib/config.inc.php')) {
			ISPConfigLog::error('The server already has ISPConfig installed. Aborting.', true);
			return false;
		}
Marius Burkard's avatar
Marius Burkard committed
		$this->configureApt();
		$this->updatePackageList();
		ISPConfigLog::info('Updating packages', true);
Marius Burkard's avatar
Marius Burkard committed
		$cmd = $this->getUpdateCommand('update');
		$result = $this->exec($cmd);
		if($result !== false) {
			ISPConfigLog::info('Updated packages', true);
		} else {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		}
Marius Burkard's avatar
Marius Burkard committed
		try {
			$this->beforePackageInstall();
		} catch (Exception $ex) {
			throw $ex;
		$packages = array(
			'ssh',
			'openssh-server',
			'nano',
			'vim-nox',
			'ntp',
			'lsb-release',
			'apt-transport-https',
			'ca-certificates',
			'wget',
			'git'
		);
		$this->installPackages($packages);
		if(ISPConfig::shallInstall('mail') && !ISPConfig::wantsAmavis()) {
			ISPConfigLog::info('Activating rspamd repository.', true);
			$cmd = 'wget -O - "https://rspamd.com/apt-stable/gpg.key" 2>/dev/null | apt-key add - 2>/dev/null ; echo "deb http://rspamd.com/apt-stable/ $(lsb_release -c -s) main" > /etc/apt/sources.list.d/rspamd.list';
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		if(ISPConfig::wantsPHP() !== 'system') {
			$this->addSuryRepo();
		}
Marius Burkard's avatar
Marius Burkard committed
		$this->updatePackageList();
		ISPConfigLog::info('Updating packages (after enabling 3rd party repos).', true);
Marius Burkard's avatar
Marius Burkard committed
		$cmd = $this->getUpdateCommand('update');
		$result = $this->exec($cmd);
		if($result !== false) {
			ISPConfigLog::info('Updated packages', true);
		} else {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		/*$hostname_changed = false;
		ISPConfigLog::info('Setting hostname to ' . $host_name, true);
		$dotpos = strpos($host_name, '.');
		if($dotpos !== false) {
			$short_hostname = substr($host_name, 0, $dotpos);
		} else {
			$short_hostname = '';
		}
		$hosts_entry = $this->ip_address . "\t" . $host_name . ($short_hostname ? ' ' . $short_hostname : '');
Marius Burkard's avatar
Marius Burkard committed
		if(is_file('/etc/cloud/templates/hosts.tmpl')) {
			$use_hosts_file = '/etc/cloud/templates/hosts.tmpl';
		} else {
			$use_hosts_file = '/etc/hosts';
		}
Marius Burkard's avatar
Marius Burkard committed
		$content = file_get_contents($use_hosts_file);
		if(preg_match('/^\s*' . preg_quote($this->ip_address, '/') . ' (.*?)$/m', $content, $matches)) {
			ISPConfigLog::info('Hostname is currently set to ' . $matches[1]);
			$content = str_replace($matches[0], $hosts_entry, $content);
			if($matches[0] != $hosts_entry) {
				$hostname_changed = true;
			}
		} else {
			ISPConfigLog::info('Hostname not found in hosts file.');
			$content .= "\n" . $hosts_entry;
			$hostname_changed = true;
		}
Marius Burkard's avatar
Marius Burkard committed
		file_put_contents($use_hosts_file, $content);
Marius Burkard's avatar
Marius Burkard committed
		$content = trim(file_get_contents('/etc/hostname'));
		if($content != $short_hostname) {
			ISPConfigLog::info('/etc/hostname is currently set to ' . $content, true);
			$hostname_changed = true;
Marius Burkard's avatar
Marius Burkard committed
			file_put_contents('/etc/hostname', $short_hostname);
		ISPConfigLog::info('Hostname saved.', true);
		if($hostname_changed) {
			ISPConfigLog::info('Rebooting server.', true);
Marius Burkard's avatar
Marius Burkard committed
			$ok = $this->exec('shutdown -r now >/dev/null 2>&1', array(0, 255));
			if($ok === false) {
				throw new ISPConfigOSException('Command for server reboot failed.');
			}
Marius Burkard's avatar
Marius Burkard committed
			$ok = $this->waitForReboot(30, 1200);
			if(!$ok) {
				throw new ISPConfigOSException('Timeout waiting for server to come up.');
			ISPConfigLog::info('Server online again.', true);
		}*/
		ISPConfigLog::info('Checking hostname.', true);
		$host_name = false;
		$cmd = 'hostname -f 2>&1';
		$check = $this->exec($cmd);
		if($check === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		} else {
			$host_name = $check;
		}/* elseif(trim($check) !== $host_name) {
			ISPConfigLog::warn('Hostname mismatch: ' . $check . ' != ' . $host_name);
		}*/
		$cmd = 'hostname 2>&1';
		$check = $this->exec($cmd);
		if($check === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		}/* elseif(trim($check) !== $short_hostname) {
			ISPConfigLog::warn('Short hostname mismatch: ' . $check . ' != ' . $short_hostname);
		}*/
		if($host_name == '') {
			ISPConfigLog::error('Could not read the host name of your server. Please check it is correctly set.', true);
			throw new ISPConfigOSException('Invalid host name or host name not found.');
		} elseif(substr_count($host_name, '.') < 2) {
			ISPConfigLog::error('The host name ' . $host_name . ' of your server is no fully qualified domain name (xyz.domain.com). Please check it is correctly set.', true);
			throw new ISPConfigOSException('Host name is no FQDN.');
		}
		$cmd = 'readlink /bin/sh 2>&1';
		$check = trim($this->exec($cmd));
		if($check === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		} elseif($check !== 'bash') {
			//debconf-show dash
			ISPConfigLog::info('Default shell is currently ' . $check . '.', true);
Marius Burkard's avatar
Marius Burkard committed
			ISPConfigLog::info('Setting bash as default shell.', true);
			$cmd = 'echo "dash dash/sh boolean false" | debconf-set-selections && DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash 2>&1';
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}

			$cmd = 'readlink /bin/sh 2>&1';
			$check = trim($this->exec($cmd));
			ISPConfigLog::info('Default shell is now ' . $check . '.', true);
		}
		$cmd = 'echo "postfix postfix/mailname string ' . $host_name . '" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "postfix postfix/main_mailer_type select Internet Site" | debconf-set-selections 2>&1';
Marius Burkard's avatar
Marius Burkard committed
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		$packages = array(
Marius Burkard's avatar
Marius Burkard committed
			'dbconfig-common',
			'postfix',
			'postfix-mysql',
			'postfix-doc',
			'mariadb-client',
			'mariadb-server',
			'openssl',
			'getmail4',
			'rkhunter',
			'binutils',
			'sudo'
		);
		$this->installPackages($packages);
		if(ISPConfig::shallInstall('mail')) {
			$packages = array(
				'dovecot-imapd',
				'dovecot-pop3d',
				'dovecot-mysql',
				'dovecot-sieve',
				'dovecot-managesieved',
				'dovecot-lmtpd'
			);
			$this->installPackages($packages);
		ISPConfigLog::info('Generating mySQL password.', true);
		// generate random password
		$mysql_root_pw = ISPConfigFunctions::generatePassword(20);
		$escaped_pw = preg_replace('/[\'\\\\]/', '\\$1', $mysql_root_pw);
		$queries = array(
			'DELETE FROM mysql.user WHERE User=\'\';',
			'DELETE FROM mysql.user WHERE User=\'root\' AND Host NOT IN (\'localhost\', \'127.0.0.1\', \'::1\');',
			'DROP DATABASE IF EXISTS test;',
			'DELETE FROM mysql.db WHERE Db=\'test\' OR Db=\'test\\_%\';',
			'UPDATE mysql.user SET Password=PASSWORD(\'' . $escaped_pw . '\') WHERE User=\'root\';',
			'UPDATE mysql.user SET plugin = \'mysql_native_password\' WHERE User=\'root\';',
			'FLUSH PRIVILEGES;'
		);
		foreach($queries as $query) {
			$cmd = 'mysql --defaults-file=/etc/mysql/debian.cnf -e ' . escapeshellarg($query) . ' 2>&1';
Marius Burkard's avatar
Marius Burkard committed
			$result = $this->exec($cmd);
			if($result === false) {
				ISPConfigLog::warn('Query ' . $query . ' failed.', true);
Marius Burkard's avatar
Marius Burkard committed
		$this->updateMySQLConfig($mysql_root_pw);
		if(ISPConfig::shallInstall('mail')) {
			ISPConfigLog::info('Configuring postfix.', true);
			$entries = array(
				array(
					'first_line' => '/^submission\s+inet/',
					'last_line' => '/^[a-z]/',
					'skip_last_line' => true,
					'search' => '/^\s+-o/'
				),
				array(
					'first_line' => '/^smtps\s+inet/',
					'last_line' => '/^[a-z]/',
					'skip_last_line' => true,
					'search' => '/^\s+-o/'
				)
			);
			$this->commentLines('/etc/postfix/master.cf', $entries);
			$entries = array(
				array(
					'first_line' => '/^#?submission\s+inet/',
					'last_line' => null,
					'search' => null,
					'add_lines' => array(
						' -o syslog_name=postfix/submission',
						' -o smtpd_tls_security_level=encrypt',
						' -o smtpd_sasl_auth_enable=yes',
						' -o smtpd_client_restrictions=permit_sasl_authenticated,reject'
					)
				),
				array(
					'first_line' => '/^#?smtps\s+inet/',
					'last_line' => null,
					'search' => null,
					'add_lines' => array(
						' -o syslog_name=postfix/smtps',
						' -o smtpd_tls_wrappermode=yes',
						' -o smtpd_sasl_auth_enable=yes',
						' -o smtpd_client_restrictions=permit_sasl_authenticated,reject'
					)
			);
			$this->uncommentLines('/etc/postfix/master.cf', $entries);
		}
		ISPConfigLog::info('Restarting postfix', true);
Marius Burkard's avatar
Marius Burkard committed
		$this->restartService('postfix');
		$replacements = array(
			'/^mysql\s+soft\s+nofile\s+.*/' => 'mysql soft nofile 65535',
			'/^mysql\s+hard\s+nofile\s+.*/' => 'mysql hard nofile 65535'
		);
Marius Burkard's avatar
Marius Burkard committed
		$this->replaceContents('/etc/security/limits.conf', $replacements, true);

		if(!is_dir('/etc/systemd/system/mysql.service.d/')) {
			mkdir('/etc/systemd/system/mysql.service.d/', 0777, true);
		$replacements = array(
			'/^\s*LimitNOFILE\s*=.*?$/m' => 'LimitNOFILE=infinity'
		);
Marius Burkard's avatar
Marius Burkard committed
		$this->replaceContents('/etc/systemd/system/mysql.service.d/limits.conf', $replacements, true, 'Service');

		$this->exec('systemctl daemon-reload 2>&1');
		$this->restartService('mysql');
Marius Burkard's avatar
Marius Burkard committed
		$packages = $this->getPackagesToInstall('mail');
		$this->installPackages($packages);
		if(ISPConfig::shallInstall('mail') && !ISPConfig::wantsAmavis()) {
			ISPConfigLog::info('Stopping Rspamd.', true);
			$this->stopService('rspamd');
Marius Burkard's avatar
Marius Burkard committed
		ISPConfigLog::info('(Re)starting Bind.', true);
Marius Burkard's avatar
Marius Burkard committed
		$this->restartService('bind9');
		ISPConfigLog::info('Disabling spamassassin daemon.', true);
Marius Burkard's avatar
Marius Burkard committed
		$this->stopService('spamassassin');
		$this->exec('systemctl disable spamassassin 2>&1');
Marius Burkard's avatar
Marius Burkard committed
		$this->afterPackageInstall('mail');
		//$cmd = 'sudo -u unbound unbound-anchor -a /var/lib/unbound/root.key';
Marius Burkard's avatar
Marius Burkard committed
		/*$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		}
		$this->restartService('unbound');
		*/
		if(!is_dir('/etc/resolvconf/resolv.conf.d')) {
			mkdir('/etc/resolvconf/resolv.conf.d', 0755);
		}
		$this->addLines('/etc/resolvconf/resolv.conf.d/head', 'nameserver 127.0.0.1', false);
		$cmd = 'resolvconf -u 2>&1';
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		ISPConfigLog::info('Checking local dns resolver.', true);
		$cmd = 'nslookup denic.de | grep Server';
		$result = $this->exec($cmd);
		if($result === false) {
			throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
		} elseif(strpos($result, '127.0.0.1') === false) {
			ISPConfigLog::warn('Unexpected resolver response: ' . $result, true);
		}
		if(ISPConfig::shallInstall('web')) {
Marius Burkard's avatar
Marius Burkard committed
			$this->stopService('apache2');
			$this->stopService('nginx');

			if(ISPConfig::$WEBSERVER === ISPC_WEBSERVER_APACHE) {
				$packages = array(
					'apache2',
					'apache2-doc',
					'apache2-utils',
					'libapache2-mod-fcgid',
					'apache2-suexec-pristine',
					'libapache2-mod-php',
					'libapache2-mod-python',
					'libapache2-mod-passenger'
				);
			} elseif(ISPConfig::$WEBSERVER === ISPC_WEBSERVER_NGINX) {
				$packages = array(
					'nginx-full',
					'fcgiwrap'
				);
			}
			$this->installPackages($packages);
Marius Burkard's avatar
Marius Burkard committed
			if(ISPConfig::$WEBSERVER === ISPC_WEBSERVER_NGINX) {
				$this->stopService('apache2');
				$cmd = 'systemctl disable apache2';
				$this->exec($cmd); // ignore if this fails
				$this->startService('nginx');
			}
		$packages = array(
			'php-pear',
			'php-memcache',
			'php-imagick',
			'php-gettext',
			'mcrypt',
			'imagemagick',
			'libruby',
			'memcached',
			'php-apcu'
		);
		if(ISPConfig::wantsPHP() === 'system') {
			$php_versions = array($this->getSystemPHPVersion());
		} else {
			$php_versions = array(
				'5.6',
				'7.0',
				'7.1',
				'7.2',
Marius Burkard's avatar
Marius Burkard committed
				'7.3',
				'7.4'
		$php_modules = array(
			'common',
			'gd',
			'mysql',
			'imap',
			'cli',
			'mcrypt',
			'curl',
			'intl',
			'pspell',
			'recode',
			'sqlite3',
			'tidy',
			'xmlrpc',
			'xsl',
			'zip',
			'mbstring',
			'soap',
			'opcache'
		);
		if(ISPConfig::shallInstall('web')) {
			$php_modules[] = 'cgi';
			$php_modules[] = 'fpm';
		}

		foreach($php_versions as $curver) {
			$packages[] = 'php' . $curver;
			reset($php_modules);
			foreach($php_modules as $curmod) {
				if(version_compare($curver, '7.2', '>=') && in_array($curmod, array('mcrypt'), true)) {
					continue;
				}
				if(version_compare($curver, '7.4', '>=') && in_array($curmod, array('recode'), true)) {
					continue;
				}
				$packages[] = 'php' . $curver . '-' . $curmod;
			}
		}
		$this->installPackages($packages);
Marius Burkard's avatar
Marius Burkard committed
		if(ISPConfig::shallInstall('web') && ISPConfig::$WEBSERVER === ISPC_WEBSERVER_APACHE) {
			ISPConfigLog::info('Enabling apache modules.', true);
			$modules = $this->getApacheModulesToEnable();
			$cmd = 'a2enmod ' . implode(' ', $modules) . ' 2>&1';
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
Marius Burkard's avatar
Marius Burkard committed
		try {
Marius Burkard's avatar
Marius Burkard committed
			$this->setPHPTimezone();
			$this->setDefaultPHP();
Marius Burkard's avatar
Marius Burkard committed
		} catch (Exception $ex) {
			throw $ex;
Marius Burkard's avatar
Marius Burkard committed
		foreach($php_versions as $curver) {
			$this->restartService('php' . $curver . '-fpm');
Marius Burkard's avatar
Marius Burkard committed
		try{
Marius Burkard's avatar
Marius Burkard committed
			$this->installPHPMyAdmin($mysql_root_pw);
Marius Burkard's avatar
Marius Burkard committed
		} catch(Exception $ex) {
			throw $ex;
		}
		if(ISPConfig::shallInstall('web') && ISPConfig::$WEBSERVER === ISPC_WEBSERVER_APACHE) {
			ISPConfigLog::info('HTTPoxy config.', true);
			$httpoxy = '<IfModule mod_headers.c>' . "\n" . '    RequestHeader unset Proxy early' . "\n" . '</IfModule>';
			file_put_contents('/etc/apache2/conf-available/httpoxy.conf', $httpoxy);
			$cmd = 'a2enconf httpoxy 2>&1';
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}

			$this->restartService('apache2');
		ISPConfigLog::info('Installing letsencrypt (acme.sh).', true);
		$cmd = 'cd /tmp ; wget -O -  https://get.acme.sh 2>/dev/null | sh 2>/dev/null';
Marius Burkard's avatar
Marius Burkard committed
		$result = $this->exec($cmd);
		if($result === false) {
			ISPConfigLog::warn('Installing letsencrypt failed.', true);
		} else {
			ISPConfigLog::info('Letsencrypt installed.', true);
		$mailman_password = '';
		if(ISPConfig::shallInstall('mailman')) {
			$mailman_password = $this->installMailman($host_name);
		$packages = array(
			'quota',
			'quotatool',
			'haveged',
Marius Burkard's avatar
Marius Burkard committed
			'geoip-database',
			'libclass-dbi-mysql-perl',
			'libtimedate-perl',
			'build-essential',
			'autoconf',
			'automake',
			'libtool',
			'flex',
			'bison',
			'debhelper',
			'binutils'
		$this->installPackages($packages);
		if(ISPConfig::shallInstall('quota')) {
			// check kernel if it is virtual
			$check = $this->getPackageVersion('linux-image-virtual');
			if($check) {
				ISPConfigLog::info('Installing extra quota package for virtual kernel.', true);
				$this->installPackages('linux-image-extra-virtual');

				// check kernel version from dpkg vs version running
				$check = $this->getPackageVersion('linux-image-extra-virtual');
				$running_version = php_uname('r');
				if(!is_dir('/lib/modules/' . $running_version . '/kernel/fs/quota/') || !is_file('/lib/modules/' . $running_version . '/kernel/fs/quota/quota_v2.ko')) {
					$running_version = preg_replace('/^([0-9\.]+(?:-\d+)?)(?:-.*?)?$/', '$1', $running_version);
					try {
						$this->installPackages('linux-image-extra-virtual=' . $running_version . '*');
					} catch (Exception $ex) {
						// ignore it
					}

					// check if quota module is available
					if(!$this->exec('modinfo quota_v1 quota_v2 2>&1')) {
						ISPConfigLog::error('The running kernel version (' . $running_version . ') does not match your installed kernel modules (' . $check . '). Currently there is no quota available! Please reboot your server to load the new kernel and run the autoinstaller again or start it with --no-quota to disable quota completely.', true);
						throw new ISPConfigOSException('Installation aborted due to missing dependencies.');
					}
				}

				ISPConfigLog::info('Enabling quota modules for virtual kernel.', true);
				$cmd = 'modprobe quota_v2 quota_v1 2>&1';
				$result = $this->exec($cmd);
				if($result === false) {
					throw new ISPConfigOSException('Enabling quota modules failed.');
				}
			}

			ISPConfigLog::info('Adding quota to fstab.', true);
			$replacements = array(
				'/^(\S+\s+\/\s+ext\d)\s+(\S+)\s+(\d\s+\d)\s*$/m' => array(
					'replace' => '$1 $2,usrjquota=quota.user,grpjquota=quota.group,jqfmt=vfsv0 $3',
					'ifnot' => 'usrjquota='
				)
			);
			$this->replaceContents('/etc/fstab', $replacements);

			$cmd = 'mount -o remount / 2>&1 && quotaoff -avug 2>&1 && quotacheck -avugm 2>&1 && quotaon -avug 2>&1';
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		if(ISPConfig::shallInstall('web')) {
			$cmd = 'echo "pure-ftpd-common pure-ftpd/standalone-or-inetd select standalone" | debconf-set-selections 2>&1' . "\n";
			$cmd .= 'echo "pure-ftpd-common pure-ftpd/virtualchroot boolean true" | debconf-set-selections 2>&1';
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}

			$packages = array(
				'pure-ftpd-common',
				'pure-ftpd-mysql',
				'webalizer',
				'awstats'
			);
			$this->installPackages($packages);

			ISPConfigLog::info('Enabling TLS for pureftpd', true);
			if(!is_dir('/etc/pure-ftpd/conf')) {
				mkdir('/etc/pure-ftpd/conf', 0755);
			}
			file_put_contents('/etc/pure-ftpd/conf/TLS', '1');
			if(!is_dir('/etc/ssl/private')) {
				mkdir('/etc/ssl/private', 0755, true);
			}

			$ssl_subject = '/C=DE/ST=None/L=None/O=IT/CN=' . $host_name;
			$cmd = 'openssl req -x509 -nodes -days 7300 -newkey rsa:2048 -subj ' . escapeshellarg($ssl_subject) . ' -keyout /etc/ssl/private/pure-ftpd.pem -out /etc/ssl/private/pure-ftpd.pem > /dev/null 2>&1';
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
			chmod('/etc/ssl/private/pure-ftpd.pem', 0600);

			// set passive port range if needed
			$ftp_ports = ISPConfig::getFTPPassivePorts();
			if($ftp_ports) {
				file_put_contents('/etc/pure-ftpd/conf/PassivePortRange', $ftp_ports['from'] . ' ' . $ftp_ports['to']);
			}
			$this->restartService('pure-ftpd-mysql');
			ISPConfigLog::info('Disabling awstats cron.', true);
			$entries = array(
				array(
					'first_line' => '/.*/',
					'last_line' => '/####nomatch###/',
					'search' => '/.*/'
				)
			);
			$this->commentLines('/etc/cron.d/awstats', $entries);
			$cmd = 'cd /tmp ; ( wget -O jailkit-2.20.tar.gz "http://olivier.sessink.nl/jailkit/jailkit-2.20.tar.gz" > /dev/null 2>&1 && tar xzf jailkit-2.20.tar.gz 2>&1 ) && ( cd jailkit-2.20 ; echo 5 > debian/compat ; ./debian/rules binary 2>&1 ) && ( cd /tmp ; dpkg -i jailkit_2.20-1_*.deb 2>&1 ; rm -rf jailkit-2.20* )';
			$result = $this->exec($cmd, array(), 3);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		$packages = array(
		if(ISPConfig::shallInstall('firewall')) {
			$packages[] = 'ufw';

		$this->installPackages($packages);
Marius Burkard's avatar
Marius Burkard committed
		$jk_jail = $this->getFail2BanJail();
Marius Burkard's avatar
Marius Burkard committed
		file_put_contents('/etc/fail2ban/jail.local', $jk_jail);
		$this->restartService('fail2ban');
Marius Burkard's avatar
Marius Burkard committed
		$this->fixDbconfigCommon();