Skip to content
class.ISPConfigDebianOS.inc.php 32.1 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;
				}
			}
		}
		
		return $version;
	}
	
	public function getPackageAlias($package) {
		switch($package) {
			case 'libssl':
				$package = 'libssl[0-9]*';
				break;
			case 'kernel':
				$package = 'linux-image-[0-9]*';
				break;
		}
		
		return $package;
	}
	
	public function getUpdateCommand($mode = 'update', $dry_run = true) {
		$cmd = false;
		
		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';
			if($dry_run == true) {
				$cmd .= ' -s';
			}
		} elseif($mode == 'install' || $mode == 'partly_update') {
			// for installing / updating specific packages
			$cmd = 'DEBIAN_FRONTEND="noninteractive" apt-get install -qq -y';
			if($dry_run == true) {
				$cmd .= ' -s';
			}
			$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';
		
		return $cmd;
	}
	
	public function getUpdatePackageRegex() {
		$regex = '^\w+\s+(?P<package>\S+)\s+(?:\[(?P<oldversion>\S+)\]\s*)?(?:\((?P<newversion>\S+))?(?:\s|$)';
		
		return $regex;
	}
	
	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!
		}
		
		return $regex;
	}
	
	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, $dry_run = false) {
		ISPConfigLog::info('Writing MySQL config files.', true);
		if(!$dry_run) {
			$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');
		}
	}
	
	protected function getPackagesToInstall($section) {
		if($section === 'mail') {
			$packages = array(
				'spamassassin',
				'dnsutils',
				'rspamd',
				'redis-server',
				'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',
				'postgrey',
				'bind9',
				'dnsutils'
			);
		}
		
		return $packages;
	}
	
	protected function getApacheModulesToEnable() {
		$modules = array('suexec', 'rewrite', 'ssl', 'actions', 'include', 'dav_fs', 'dav', 'auth_digest', 'cgi', 'headers', 'proxy_fcgi', 'alias');
		
		return $modules;
	}
	
	protected function setDefaultPHP($dry_run) {
		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';
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
	}
	
	protected function installPHPMyAdmin($mysql_root_pw, $dry_run) {
		$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";
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		ISPConfigLog::info('Installing package phpmyadmin', true);
		$result = $this->installPackages('phpmyadmin', $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed package phpmyadmin', true);
		} else {
			throw new ISPConfigOSException('Installing package failed.');
		}
	}
	
	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);
	}
	
	protected function setPHPTimezone() {
		if(!is_file('/etc/timezone')) {
			return;
		}
		$tz = trim(file_get_contents('/etc/timezone'));
		if(!in_array($tz, timezone_identifiers_list())) {
			return;
		}
		
		// 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',
			'/etc/php/7.3/apache2/php.ini'
		);
		
		$replace = array(
			'/^;?\s*date\.timezone\s+=.*$/' => 'date.timezone = ' . $tz
		);
		
		foreach($ini_files as $ini) {
			if(is_file($ini)) {
				$this->replaceContents($ini, $replace);
			}
		}
	}
	
	public function runPerfectSetup($dry_run = false) {
		$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
		// enable contrib and non-free
		ISPConfigLog::info('Enabling contrib and non-free repositories.', true);
		$replacements = array(
			'/^(deb.*\s+main)\s*$/' => '$1 contrib non-free'
		);
		if(!$dry_run) {
			$this->replaceContents('/etc/apt/sources.list', $replacements);
		}
		
		$this->updatePackageList($dry_run);
		
		ISPConfigLog::info('Updating packages', true);
		$cmd = $this->getUpdateCommand('update', $dry_run);
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result !== false) {
				ISPConfigLog::info('Updated packages', true);
			} else {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		$packages = array(
			'ssh',
			'openssh-server',
			'nano',
			'vim-nox',
			'ntp',
			'lsb-release',
			'apt-transport-https',
			'ca-certificates',
			'wget',
			'git'
		);
		
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}

		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';
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		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';
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		if(!$dry_run) {
			$this->updatePackageList($dry_run);
		}

		ISPConfigLog::info('Updating packages (after enabling sury and rspamd repos).', true);
		$cmd = $this->getUpdateCommand('update', $dry_run);
		if(!$dry_run) {
			$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;
		}
		if(!$dry_run) {
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;
			if(!$dry_run) {
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);
			if(!$dry_run) {
				$ok = $this->exec('shutdown -r now >/dev/null 2>&1', array(0, 255));
				if($ok === false) {
					throw new ISPConfigOSException('Command for server reboot failed.');
				}

				$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);
		
			if(!$dry_run) {
				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';
		if(!$dry_run) {
			$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',
			'dovecot-imapd',
			'dovecot-pop3d',
			'dovecot-mysql',
			'dovecot-sieve',
			'dovecot-managesieved',
			'dovecot-lmtpd',
			'sudo'
		);
		
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}
		
		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';
			if(!$dry_run) {
				$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, $dry_run);
		
		ISPConfigLog::info('Configuring postfix.', true);
		$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'
				)
			)
		);
		if(!$dry_run) {
			$this->uncommentLines('/etc/postfix/master.cf', $entries);
		}
		
		ISPConfigLog::info('Restarting postfix', true);
		if(!$dry_run) {
			$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'
		);
		if(!$dry_run) {
			$this->replaceContents('/etc/security/limits.conf', $replacements, true);
		
Marius Burkard's avatar
Marius Burkard committed
			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'
		);
		if(!$dry_run) {
			$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');
		
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}
	
		ISPConfigLog::info('Stopping Rspamd.', true);
		if(!$dry_run) {
			$this->stopService('rspamd');
		}
		
Marius Burkard's avatar
Marius Burkard committed
		ISPConfigLog::info('(Re)starting Bind.', true);
		if(!$dry_run) {
			$this->restartService('bind9');
		}
		
		ISPConfigLog::info('Disabling spamassassin daemon.', true);
		if(!$dry_run) {
			$this->stopService('spamassassin');
			$this->exec('systemctl disable spamassassin 2>&1');
		}
		
		//$cmd = 'sudo -u unbound unbound-anchor -a /var/lib/unbound/root.key';
		if(!$dry_run) {
			/*$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
			$this->restartService('unbound');
			*/
Marius Burkard's avatar
Marius Burkard committed
			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);
		}
		$packages = array(
			'apache2',
			'apache2-doc',
			'apache2-utils',
			'libapache2-mod-fcgid',
			'apache2-suexec-pristine',
			'libapache2-mod-php',
			'php-pear',
			'php-memcache',
			'php-imagick',
			'php-gettext',
			'mcrypt',
			'imagemagick',
			'libruby',
			'libapache2-mod-python',
			'memcached',
			'libapache2-mod-passenger',
			'php-apcu'
		);
		
		$php_versions = array(
			'5.6',
			'7.0',
			'7.1',
			'7.2',
			'7.3'
		);
		
		$php_modules = array(
			'common',
			'gd',
			'mysql',
			'imap',
			'cli',
			'cgi',
			'mcrypt',
			'curl',
			'intl',
			'pspell',
			'recode',
			'sqlite3',
			'tidy',
			'xmlrpc',
			'xsl',
			'zip',
			'mbstring',
			'soap',
			'fpm',
			'opcache'
		);
		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;
				}
				$packages[] = 'php' . $curver . '-' . $curmod;
			}
		}
		
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}
		
		ISPConfigLog::info('Enabling apache modules.', true);
Marius Burkard's avatar
Marius Burkard committed
		$modules = $this->getApacheModulesToEnable();
		$cmd = 'a2enmod ' . implode(' ', $modules) . ' 2>&1';
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
Marius Burkard's avatar
Marius Burkard committed
		try {
			if(!$dry_run) {
				$this->setPHPTimezone();
Marius Burkard's avatar
Marius Burkard committed
			$this->setDefaultPHP($dry_run);
		} catch (Exception $ex) {
			throw $ex;
		}
		
		if(!$dry_run) {
			foreach($php_versions as $curver) {
				$this->restartService('php' . $curver . '-fpm');
			}
		}
		
Marius Burkard's avatar
Marius Burkard committed
		try{
			$this->installPHPMyAdmin($mysql_root_pw, $dry_run);
		} catch(Exception $ex) {
			throw $ex;
		}
		
		ISPConfigLog::info('HTTPoxy config.', true);
		$httpoxy = '<IfModule mod_headers.c>' . "\n" . '    RequestHeader unset Proxy early' . "\n" . '</IfModule>';
Marius Burkard's avatar
Marius Burkard committed
		file_put_contents('/etc/apache2/conf-available/httpoxy.conf', $httpoxy);
		$cmd = 'a2enconf httpoxy 2>&1';
		if(!$dry_run) {
			$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 certbot-auto "https://dl.eff.org/certbot-auto" 2>&1 && chmod a+x ./certbot-auto && ./certbot-auto --install-only --quiet --noninteractive 2>&1';
		$cmd = 'cd /tmp ; wget -O -  https://get.acme.sh 2>/dev/null | sh 2>/dev/null';
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				ISPConfigLog::warn('Installing letsencrypt failed.', true);
			} else {
				ISPConfigLog::info('Letsencrypt installed.', true);
			}
		}
		
		/* TODO: mailman */
		
		$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';
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		$packages = array(
			'pure-ftpd-common',
			'pure-ftpd-mysql',
			'quota',
			'quotatool',
			'haveged',
			'webalizer',
			'awstats',
Marius Burkard's avatar
Marius Burkard committed
			'geoip-database',
			'libclass-dbi-mysql-perl',
			'libtimedate-perl'
		);
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}
		
		/* TODO FTP TLS! */
		
		$this->restartService('pure-ftpd-mysql');
		
		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='
			)
		);
		if(!$dry_run) {
			$this->replaceContents('/etc/fstab', $replacements);
		}
		
		$cmd = 'mount -o remount / 2>&1 && quotaoff -avug 2>&1 && quotacheck -avugm 2>&1 && quotaon -avug 2>&1';
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		ISPConfigLog::info('Disabling awstats cron.', true);
		$entries = array(
			array(
				'first_line' => '/.*/',
				'last_line' => '/####nomatch###/',
				'search' => '/.*/'
			)
		);
		if(!$dry_run) {
			$this->commentLines('/etc/cron.d/awstats', $entries);
		}
		
		$packages = array(
			'build-essential',
			'autoconf',
			'automake',
			'libtool',
			'flex',
			'bison',
			'debhelper',
			'binutils'
		);
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}
		
Marius Burkard's avatar
Marius Burkard committed
		$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* )';
		if(!$dry_run) {
			$result = $this->exec($cmd, array(), 3);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		$packages = array(
			'fail2ban',
			'ufw'
		);
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}
		
		$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';
Marius Burkard's avatar
Marius Burkard committed
		file_put_contents('/etc/fail2ban/jail.local', $jk_jail);
		unset($jk_jail);
		
		$this->restartService('fail2ban');
		
Marius Burkard's avatar
Marius Burkard committed
		if(!$dry_run) {
			$this->fixDbconfigCommon();
		}
		
		ISPConfigLog::info('Installing roundcube.', true);
		
		$cmd = 'APP_PASS="' . ISPConfigFunctions::generatePassword(15) . '"' . "\n";
		$cmd .= 'ROOT_PASS="' . $mysql_root_pw . '"' . "\n";
		$cmd .= 'APP_DB_PASS="' . ISPConfigFunctions::generatePassword(15) . '"' . "\n";
		$cmd .= 'echo "roundcube-core roundcube/dbconfig-install boolean true" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "roundcube-core roundcube/database-type select mysql" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "roundcube-core roundcube/mysql/admin-user string root" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "roundcube-core roundcube/mysql/admin-pass password $ROOT_PASS" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "roundcube-core roundcube/mysql/app-pass password $APP_DB_PASS" | debconf-set-selections 2>&1' . "\n";
		$cmd .= 'echo "roundcube-core roundcube/reconfigure-webserver multiselect apache2" | debconf-set-selections 2>&1' . "\n";
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		$packages = array(
			'roundcube',
			'roundcube-core',
			'roundcube-mysql',
			'roundcube-plugins'
		);
		
		ISPConfigLog::info('Installing packages ' . implode(', ', $packages), true);
		$result = $this->installPackages($packages, $dry_run);
		if($result !== false) {
			ISPConfigLog::info('Installed packages ' . implode(', ', $packages), true);
		} else {
			throw new ISPConfigOSException('Installing packages failed.');
		}

		$replacements = array(
			'/^\s*\$config\s*\[["\']default_host["\']\]\s*=.*$/m' => '$config[\'default_host\'] = \'localhost\';',
			'/^\s*\$config\s*\[["\']smtp_server["\']\]\s*=.*$/m' => '$config[\'smtp_server\'] = \'%h\';',
			'/^\s*\$config\s*\[["\']smtp_user["\']\]\s*=.*$/m' => '$config[\'smtp_user\'] = \'%u\';',
			'/^\s*\$config\s*\[["\']smtp_pass["\']\]\s*=.*$/m' => '$config[\'smtp_pass\'] = \'%p\';'
		);
		if(!$dry_run) {
			$result = $this->replaceContents('/etc/roundcube/config.inc.php', $replacements);
		}
		
		$replacements = array(
			'/^\s*#*\s*Alias\s+\/roundcube\s+\/var\/lib\/roundcube\s*$/m' => 'Alias /webmail /var/lib/roundcube'
		);
		if(!$dry_run) {
			$result = $this->replaceContents('/etc/apache2/conf-enabled/roundcube.conf', $replacements);
			$this->restartService('apache2');
		}
		
		ISPConfigLog::info('Installing ISPConfig3.', true);
		
		$ispconfig_admin_pw = ISPConfigFunctions::generatePassword(15);
		
		$autoinstall = '[install]
language=en
install_mode=expert
hostname=' . $host_name . '
mysql_hostname=localhost
mysql_port=3306
mysql_root_user=root
mysql_root_password=' . $mysql_root_pw . '
mysql_database=dbispconfig
mysql_charset=utf8
http_server=apache
ispconfig_port=8080
ispconfig_use_ssl=y
ispconfig_admin_password=' . $ispconfig_admin_pw . '

[ssl_cert]
ssl_cert_country=DE
ssl_cert_state=Dummy
ssl_cert_locality=Dummy
ssl_cert_organisation=Dummy
ssl_cert_organisation_unit=IT
ssl_cert_common_name=' . $host_name . '
ssl_cert_email=

[expert]
mysql_ispconfig_user=ispconfig
mysql_ispconfig_password=' . ISPConfigFunctions::generatePassword(15) . '
join_multiserver_setup=n
mysql_master_hostname=
mysql_master_root_user=
mysql_master_root_password=
mysql_master_database=
configure_mail=y
configure_jailkit=y
configure_ftp=y
configure_dns=y
configure_apache=y
configure_nginx=y
configure_firewall=y
configure_webserver=y
install_ispconfig_web_interface=y

[update]
do_backup=yes
mysql_root_password=' . $mysql_root_pw . '
mysql_master_hostname=
mysql_master_root_user=
mysql_master_root_password=
mysql_master_database=
reconfigure_permissions_in_master_database=no
reconfigure_services=yes
ispconfig_port=8080
create_new_ispconfig_ssl_cert=no
reconfigure_crontab=yes

; These are for service-detection (defaulting to old behaviour where alle changes were automatically accepted)
svc_detect_change_mail_server=yes
svc_detect_change_web_server=yes
svc_detect_change_dns_server=yes
svc_detect_change_xmpp_server=yes
svc_detect_change_firewall_server=yes
svc_detect_change_vserver_server=yes
svc_detect_change_db_server=yes';
		if(!$dry_run) {
Marius Burkard's avatar
Marius Burkard committed
			file_put_contents('/tmp/ispconfig.autoinstall.ini', $autoinstall);
		}
		
		$cmd = 'cd /tmp ; rm -rf ispconfig3_install 2>&1 ; wget -O ispconfig.tar.gz "https://www.ispconfig.org/downloads/ISPConfig-3-stable.tar.gz" >/dev/null 2>&1 ; tar xzf ispconfig.tar.gz ; cd ispconfig3_install ; cd install ; php -q install.php --autoinstall=/tmp/ispconfig.autoinstall.ini 2>&1 ; cd /tmp ; rm -rf ispconfig3_install 2>&1'; // ispconfig.autoinstall.ini removal!
		if(!$dry_run) {
			$result = $this->exec($cmd);
			if($result === false) {
				throw new ISPConfigOSException('Command ' . $cmd . ' failed.');
			}
		}
		
		ISPConfigLog::info('Adding php versions to ISPConfig.', true);
		
		$server_id = 0;
		$ispc_config = ISPConfigConnector::getLocalConfig();