Skip to content
system.inc.php 84.8 KiB
Newer Older
	function is_mounted($mountpoint){
		//$cmd = 'df 2>/dev/null | grep " '.$mountpoint.'$"';
		$cmd = 'mount 2>/dev/null | grep ?';
		$this->exec_safe($cmd, ' on '. $mountpoint . ' type ');
		$return_var = $this->last_exec_retcode();
		return $return_var == 0 ? true : false;
	function mount_backup_dir($backup_dir, $mount_cmd = '/usr/local/ispconfig/server/scripts/backup_dir_mount.sh'){
		if($this->is_mounted($backup_dir)) return true;
		$mounted = true;
		if ( 	is_file($mount_cmd) &&
				is_executable($mount_cmd) &&
				fileowner($mount_cmd) === 0
		) {
			if (!$this->is_mounted($backup_dir)){
				sleep(1);
				if (!$this->is_mounted($backup_dir)) $mounted = false;
			}
		} else $mounted = false;
		if (!$mounted) {
			//* send email to admin that backup directory could not be mounted
			$global_config = $app->getconf->get_global_config('mail');
			if($global_config['admin_mail'] != ''){
				$subject = 'Backup directory '.$backup_dir.' could not be mounted';
				$message = "Backup directory ".$backup_dir." could not be mounted.\n\nThe command\n\n".$mount_cmd."\n\nfailed.";
				mail($global_config['admin_mail'], $subject, $message);
			}
		}

	function umount_backup_dir($backup_dir, $mount_cmd = '/usr/local/ispconfig/server/scripts/backup_dir_umount.sh'){
		if ( 	is_file($mount_cmd) &&
				is_executable($mount_cmd) &&
				fileowner($mount_cmd) === 0
		) {
			if ($this->is_mounted($backup_dir)){
				exec($mount_cmd);
				sleep(1);

Florian Schaal's avatar
Florian Schaal committed
		        $unmounted = $this->is_mounted($backup_dir) == 0 ? true : false;
				if(!$unmounted) {
					//* send email to admin that backup directory could not be unmounted
					$global_config = $app->getconf->get_global_config('mail');
					if($global_config['admin_mail'] != ''){
						$subject = 'Backup directory '.$backup_dir.' could not be unmounted';
						$message = "Backup directory ".$backup_dir." could not be unmounted.\n\nThe command\n\n".$mount_cmd."\n\nfailed.";
						mail($global_config['admin_mail'], $subject, $message);
					}
				}
	function _getinitcommand($servicename, $action, $init_script_directory = '', $check_service) {
		/* removed upstart support - deprecated
		if(is_executable('/sbin/initctl')){
			exec('/sbin/initctl version 2>/dev/null | /bin/grep -q upstart', $retval['output'], $retval['retval']);
			if(intval($retval['retval']) == 0) return 'service '.$servicename.' '.$action;
		}
		*/
		
		if(!in_array($action,array('restart','reload','force-reload'))) {
			$app->log('Invalid init command action '.$action,LOGLEVEL_WARN);
			return false;
		}
		//* systemd (now default in all supported OS)
		if(is_executable('/bin/systemd') || is_executable('/usr/bin/systemctl')){
			$app->log('Trying to use Systemd to restart service',LOGLEVEL_DEBUG);
			
			//* Test service name via regex
			if(preg_match('/[a-zA-Z0-9\.\-\_]/',$servicename)) {
			
				//* Test if systemd service is enabled
				if ($check_service) {
					$this->exec_safe("systemctl is-enabled ? 2>&1", $servicename);
					$ret_val = $this->last_exec_retcode();
				} else {
					$app->log('Systemd service '.$servicename.' not found or not enabled.',LOGLEVEL_DEBUG);
				}
			
				//* Return service command
				if ($ret_val == 0 || !$check_service) {
					return 'systemctl '.$action.' '.$servicename.'.service';
				} else {
					$app->log('Failed to use Systemd to restart service '.$servicename.', we try init script instead.',LOGLEVEL_DEBUG);
				}
			} else {
				$app->log('Systemd service name contains invalid chars: '.$servicename,LOGLEVEL_DEBUG);
		} else {
			$app->log('Not using Systemd to restart services',LOGLEVEL_DEBUG);
		//* sysvinit fallback
		$app->log('Using init script to restart service',LOGLEVEL_DEBUG);
		
		//* Get init script directory
		if($init_script_directory == '') $init_script_directory = $conf['init_scripts'];
		if(substr($init_script_directory, -1) === '/') $init_script_directory = substr($init_script_directory, 0, -1);
		$init_script_directory = realpath($init_script_directory);
		
		//* Check init script dir
		if(!is_dir($init_script_directory)) {
			$app->log('Init script directory '.$init_script_directory.' not found',LOGLEVEL_WARN);
			return false;
		}
		
		//* Forbidden init script paths
		if(substr($init_script_directory,0,4) == '/var' || substr($init_script_directory,0,4) == '/tmp') {
			$app->log('Do not put init scripts in /var or /tmp folder.',LOGLEVEL_WARN);
			return false;
		}
		
		//* Check init script dir owner
		if(fileowner($init_script_directory) !== 0) {
			$app->log('Init script directory '.$init_script_directory.' not owned by root user',LOGLEVEL_WARN);
			return false;
		}
		
		$full_init_script_path = realpath($init_script_directory.'/'.$servicename);
		
		if($full_init_script_path == '') {
			$app->log('No init script, we quit here.',LOGLEVEL_WARN);
			return false;
		}
		
		//* Check init script
		if(!is_file($full_init_script_path)) {
			$app->log('Init script '.$full_init_script_path.' not found',LOGLEVEL_WARN);
			return false;
		}
		
		//* Check init script owner
		if(fileowner($full_init_script_path) !== 0) {
			$app->log('Init script '.$full_init_script_path.' not owned by root user',LOGLEVEL_WARN);
			return false;
		}
		
		if($check_service && is_executable($full_init_script_path)) {
			return $full_init_script_path.' '.$action;
			return $full_init_script_path.' '.$action;
	function getinitcommand($servicename, $action, $init_script_directory = '', $check_service=true) {
		if (is_array($servicename)) {
			foreach($servicename as $service) {
				$out = $this->_getinitcommand($service, $action, $init_script_directory, true);
				if ($out != '') return $out;
			}
		} else {
			return $this->_getinitcommand($servicename, $action, $init_script_directory, $check_service);
		}
	}

        function getopensslversion($get_minor = false) {
                global $app;
                if($this->is_installed('openssl')) $cmd = 'openssl version';
                else {
			$app->log("Could not check OpenSSL version, openssl not found.", LOGLEVEL_DEBUG);
                        return '1.0.1';
                }

		exec($cmd, $output, $return_var);
                if($return_var != 0 || !$output[0]) {
			$app->log("Could not check OpenSSL version, openssl did not return any data.", LOGLEVEL_WARN);
                        return '1.0.1';
                }
                if(preg_match('/OpenSSL\s*(\d+)(\.(\d+)(\.(\d+))*)?(\D|$)/i', $output[0], $matches)) {
			return $matches[1] . (isset($matches[3]) ? '.' . $matches[3] : '') . (isset($matches[5]) && $get_minor == true ? '.' . $matches[5] : '');
                } else {
			$app->log("Could not check OpenSSL version, did not find version string in openssl output.", LOGLEVEL_WARN);
			return '1.0.1';
                }

	}

	function getnginxversion($get_minor = false) {
		global $app;

		if($this->is_installed('nginx')) $cmd = 'nginx -v 2>&1';
		else {
                        $app->log("Could not check Nginx version, nginx not found.", LOGLEVEL_DEBUG);
                        return false;
                }

		exec($cmd, $output, $return_var);

		if($return_var != 0 || !$output[0]) {
                        $app->log("Could not check Nginx version, nginx did not return any data.", LOGLEVEL_WARN);
                        return false;
		}

		if(preg_match('/nginx version: nginx\/\s*(\d+)(\.(\d+)(\.(\d+))*)?(\D|$)/i', $output[0], $matches)) {
			return $matches[1] . (isset($matches[3]) ? '.' . $matches[3] : '') . (isset($matches[5]) && $get_minor == true ? '.' . $matches[5] : '');
                } else {
                        $app->log("Could not check Nginx version, did not find version string in nginx output.", LOGLEVEL_WARN);
                        return false;
                }
	}
	function getapacheversion($get_minor = false) {
		global $app;
		$cmd = '';
		if($this->is_installed('apache2ctl')) $cmd = 'apache2ctl -v';
		elseif($this->is_installed('apachectl')) $cmd = 'apachectl -v';
		else {
			$app->log("Could not check apache version, apachectl not found.", LOGLEVEL_DEBUG);
		exec($cmd, $output, $return_var);
		if($return_var != 0 || !$output[0]) {
			$app->log("Could not check apache version, apachectl did not return any data.", LOGLEVEL_WARN);
			return '2.2';
		}
		if(preg_match('/version:\s*Apache\/(\d+)(\.(\d+)(\.(\d+))*)?(\D|$)/i', $output[0], $matches)) {
			return $matches[1] . (isset($matches[3]) ? '.' . $matches[3] : '') . (isset($matches[5]) && $get_minor == true ? '.' . $matches[5] : '');
		} else {
			$app->log("Could not check apache version, did not find version string in apachectl output.", LOGLEVEL_WARN);
			return '2.2';
		}
	}
	function getapachemodules() {
		global $app;
		if($this->is_installed('apache2ctl')) $cmd = 'apache2ctl -t -D DUMP_MODULES';
		elseif($this->is_installed('apachectl')) $cmd = 'apachectl -t -D DUMP_MODULES';
		else {
			$app->log("Could not check apache modules, apachectl not found.", LOGLEVEL_WARN);
			return array();
		}
		exec($cmd . ' 2>/dev/null', $output, $return_var);
		if($return_var != 0 || !$output[0]) {
			$app->log("Could not check apache modules, apachectl did not return any data.", LOGLEVEL_WARN);
			return array();
		}
		$modules = array();
		for($i = 0; $i < count($output); $i++) {
			if(preg_match('/^\s*(\w+)\s+\((shared|static)\)\s*$/', $output[$i], $matches)) {
				$modules[] = $matches[1];
			}
		}
	//* ISPConfig mail function
	public function mail($to, $subject, $text, $from, $filepath = '', $filetype = 'application/pdf', $filename = '', $cc = '', $bcc = '', $from_name = '') {
		global $app, $conf;

		if($conf['demo_mode'] == true) $app->error("Mail sending disabled in demo mode.");

		$app->uses('getconf,ispcmail');
		$mail_config = $app->getconf->get_global_config('mail');
		if($mail_config['smtp_enabled'] == 'y') {
			$mail_config['use_smtp'] = true;
			$app->ispcmail->setOptions($mail_config);
		}
		$app->ispcmail->setSender($from, $from_name);
		$app->ispcmail->setSubject($subject);
		$app->ispcmail->setMailText($text);

		if($filepath != '') {
			if(!file_exists($filepath)) $app->error("Mail attachement does not exist ".$filepath);
			$app->ispcmail->readAttachFile($filepath);
		}

		if($cc != '') $app->ispcmail->setHeader('Cc', $cc);
		if($bcc != '') $app->ispcmail->setHeader('Bcc', $bcc);

		$app->ispcmail->send($to);
		$app->ispcmail->finish();
Marius Cramer's avatar
Marius Cramer committed
	public function is_allowed_user($username, $check_id = true, $restrict_names = false) {
		global $app;
Till Brehm's avatar
Till Brehm committed
		$name_blacklist = array('root','ispconfig','vmail','getmail');
		if(in_array($username,$name_blacklist)) return false;
		if(preg_match('/^[a-zA-Z0-9\.\-_]{1,32}$/', $username) == false) return false;
Marius Cramer's avatar
Marius Cramer committed
		if($check_id && intval($this->getuid($username)) < $this->min_uid) return false;
Marius Cramer's avatar
Marius Cramer committed
		if($restrict_names == true && preg_match('/^web\d+$/', $username) == false) return false;
Marius Cramer's avatar
Marius Cramer committed
		return true;
	}
	public function is_allowed_group($groupname, $check_id = true, $restrict_names = false) {
Marius Cramer's avatar
Marius Cramer committed
		global $app;
Till Brehm's avatar
Till Brehm committed
		$name_blacklist = array('root','ispconfig','vmail','getmail');
		if(in_array($groupname,$name_blacklist)) return false;
		if(preg_match('/^[a-zA-Z0-9\.\-_]{1,32}$/', $groupname) == false) return false;
		if($check_id && intval($this->getgid($groupname)) < $this->min_gid) return false;
Marius Cramer's avatar
Marius Cramer committed
		if($restrict_names == true && preg_match('/^client\d+$/', $groupname) == false) return false;
Marius Cramer's avatar
Marius Cramer committed
		return true;
	}
	public function is_allowed_path($path) {
		global $app;

		$path = $app->functions->normalize_path($path);
		if(file_exists($path)) {
			$path = realpath($path);
		}

		$blacklisted_paths_regex = array(
			'@^/$@',
			'@^/proc(/.*)?$@',
			'@^/sys(/.*)?$@',
			'@^/etc(/.*)?$@',
			'@^/dev(/.*)?$@',
			'@^/tmp(/.*)?$@',
			'@^/run(/.*)?$@',
			'@^/boot(/.*)?$@',
			'@^/root(/.*)?$@',
			'@^/var(/?|/backups?(/.*)?)?$@',
		);

		foreach($blacklisted_paths_regex as $regex) {
			if(preg_match($regex, $path)) {
				return false;
			}
		}

		return true;
	}

	public function last_exec_out() {
		return $this->_last_exec_out;
	}
	public function last_exec_retcode() {
		return $this->_last_exec_retcode;
	}
	public function exec_safe($cmd) {
		$arg_count = func_num_args();
		if($arg_count != substr_count($cmd, '?') + 1) {
			trigger_error('Placeholder count not matching argument list.', E_USER_WARNING);
			return false;
		}
		if($arg_count > 1) {

			$pos = 0;
			$a = 0;
			foreach($args as $value) {
				$a++;
				$pos = strpos($cmd, '?', $pos);
				if($pos === false) {
					break;
				}
				$value = escapeshellarg($value);
				$cmd = substr_replace($cmd, $value, $pos, 1);
				$pos += strlen($value);
			}
		}
		$this->_last_exec_out = null;
		$this->_last_exec_retcode = null;
		$ret = exec($cmd, $this->_last_exec_out, $this->_last_exec_retcode);
Marius Burkard's avatar
Marius Burkard committed
		$app->log("safe_exec cmd: " . $cmd . " - return code: " . $this->_last_exec_retcode, LOGLEVEL_DEBUG);
	public function system_safe($cmd) {
		call_user_func_array(array($this, 'exec_safe'), func_get_args());
		return implode("\n", $this->_last_exec_out);
	}
	public function create_jailkit_user($username, $home_dir, $user_home_dir, $shell = '/bin/bash', $p_user = null, $p_user_home_dir = null) {
		// Disallow operating on root directory
		if(realpath($home_dir) == '/') {
			$app->log("create_jailkit_user: invalid home_dir: $home_dir", LOGLEVEL_WARN);
			return false;
		}

		// Check if USERHOMEDIR already exists
		if(!is_dir($home_dir . '/.' . $user_home_dir)) {
			$this->mkdirpath($home_dir . '/.' . $user_home_dir, 0755, $username);
		}

		// Reconfigure the chroot home directory for the user
		$cmd = 'usermod --home=? ? 2>/dev/null';
		$this->exec_safe($cmd, $home_dir . '/.' . $user_home_dir, $username);

		// Add the chroot user
		$cmd = 'jk_jailuser -n -s ? -j ? ?';
		$this->exec_safe($cmd, $shell, $home_dir, $username);

		//  We have to reconfigure the chroot home directory for the parent user
		if($p_user !== null) {
			$cmd = 'usermod --home=? ? 2>/dev/null';
			$this->exec_safe($cmd, $home_dir . '/.' . $p_user_home_dir, $p_user);
		}
	public function create_jailkit_chroot($home_dir, $app_sections = array(), $options = array()) {
		global $app;
$app->log("create_jailkit_chroot: called for home_dir $home_dir with options: " . print_r($options, true), LOGLEVEL_DEBUG);
		// Disallow operating on root directory
		if(realpath($home_dir) == '/') {
			$app->log("create_jailkit_chroot: invalid home_dir: $home_dir", LOGLEVEL_WARN);
			return false;
		}

		if(!is_dir($home_dir)) {
			$app->log("create_jailkit_chroot: jail directory does not exist: $home_dir", LOGLEVEL_WARN);
			return false;
		}
		if(empty($app_sections)) {
			return true;
		} elseif(is_string($app_sections)) {
			$app_sections = preg_split('/[\s,]+/', $app_sections);
		}
		if(! is_array($options)) {
			$options = (is_string($options) ? preg_split('/[\s,]+/', $options) : array());
		}

		// Change ownership of the chroot directory to root
		$this->chown($home_dir, 'root');
		$this->chgrp($home_dir, 'root');

		$program_args = '';
		foreach ($options as $opt) {
			switch ($opt) {
Jesse Norell's avatar
Jesse Norell committed
			case '-k':
			case 'hardlink':
				$program_args .= ' -k';
				break;
Jesse Norell's avatar
Jesse Norell committed
			case '-f':
			case 'force':
				$program_args .= ' -f';
				break;
			}
		}
		# /etc/jailkit/jk_init.ini is the default path, probably not needed?
		$program_args .= ' -c /etc/jailkit/jk_init.ini -j ?';
		foreach($app_sections as $app_section) {
			# should check that section exists with jk_init --list ?
			$program_args .= ' ' . escapeshellarg($app_section);
		}

		// Initialize the chroot into the specified directory with the specified applications
		$cmd = 'jk_init' . $program_args;
		$this->exec_safe($cmd, $home_dir);

		// Create the tmp and /var/run directories
		if(!is_dir($home_dir . '/tmp')) {
			$this->mkdirpath($home_dir . '/tmp', 0770);
		} else {
			$this->chmod($home_dir . '/tmp', 0770, true);
		}
		if(!is_dir($home_dir . '/var/run')) {
			$this->mkdirpath($home_dir . '/var/run', 0755);
		} else {
			$this->chmod($home_dir . '/var/run', 0755, true);
		}
		if(!is_dir($home_dir . '/var/tmp')) {
			$this->mkdirpath($home_dir . '/var/tmp', 0770);
		} else {
			$this->chmod($home_dir . '/var/tmp', 0770, true);
		}
		// Fix permissions of the root directory
		$this->chmod($home_dir . '/bin', 0755, true);  // was chmod g-w $CHROOT_HOMEDIR/bin

		return true;
	}

	public function create_jailkit_programs($home_dir, $programs = array(), $options = array()) {
		global $app;
$app->log("create_jailkit_programs: called for home_dir $home_dir with options: " . print_r($options, true), LOGLEVEL_DEBUG);
		// Disallow operating on root directory
		if(realpath($home_dir) == '/') {
			$app->log("create_jailkit_programs: invalid home_dir: $home_dir", LOGLEVEL_WARN);
			return false;
		}

		if(!is_dir($home_dir)) {
			$app->log("create_jailkit_programs: jail directory does not exist: $home_dir", LOGLEVEL_WARN);
			return false;
		}
		if(empty($programs)) {
			return true;
		} elseif(is_string($programs)) {
			$programs = preg_split('/[\s,]+/', $programs);
		if(! is_array($options)) {
			$options = (is_string($options) ? preg_split('/[\s,]+/', $options) : array());
		}

		# prohibit ill-advised copying paths known to be sensitive/problematic
		# (easy to bypass if needed, eg. use /./etc)
		$blacklisted_paths_regex = array(
Jesse Norell's avatar
Jesse Norell committed
			'@^/$@',
			'@^/proc(/.*)?$@',
			'@^/sys(/.*)?$@',
			'@^/etc/?$@',
			'@^/dev/?$@',
			'@^/tmp/?$@',
			'@^/run/?$@',
			'@^/boot/?$@',
			'@^/var(/?|/backups?/?)?$@',
		foreach ($options as $opt) {
			switch ($opt) {
Jesse Norell's avatar
Jesse Norell committed
			case '-k':
			case 'hardlink':
				$program_args .= ' -k';
				break;
Jesse Norell's avatar
Jesse Norell committed
			case '-f':
			case 'force':
				$program_args .= ' -f';
				break;
			}
		}
		$program_args .= ' -j ?';

		$bad_paths = array();
		foreach($programs as $prog) {
			foreach ($blacklisted_paths_regex as $re) {
				if (preg_match($re, $prog, $matches)) {
					$bad_paths[] = $matches[0];
				}
			}
			if (count($bad_paths) > 0) {
				$app->log("Prohibited path not added to jail $home_dir: " . implode(", ", $bad_paths), LOGLEVEL_WARN);
			} else {
				$program_args .= ' ' . escapeshellarg($prog);
			}
		if (count($programs) > count($bad_paths)) {
			$cmd = 'jk_cp' . $program_args;
			$this->exec_safe($cmd, $home_dir);
		}
	public function update_jailkit_chroot($home_dir, $sections = array(), $programs = array(), $options = array()) {
Jesse Norell's avatar
Jesse Norell committed
$app->log("update_jailkit_chroot called for $home_dir with options ".print_r($options, true), LOGLEVEL_DEBUG);
		$app->uses('ini_parser');

		// Disallow operating on root directory
		if(realpath($home_dir) == '/') {
			$app->log("update_jailkit_chroot: invalid home_dir: $home_dir", LOGLEVEL_WARN);
			return false;
		}

		if(!is_dir($home_dir)) {
			$app->log("update_jailkit_chroot: jail directory does not exist: $home_dir", LOGLEVEL_WARN);
		$jailkit_directories = array(
			'bin',
			'dev',
			'etc',
			'lib',
			'lib32',
			'lib64',
			'opt',
			'sys',
			'usr',
			'var',
		);

		$opts = array();
		$jk_update_args = '';
		$jk_cp_args = '';
		foreach ($options as $opt) {
			switch ($opt) {
Jesse Norell's avatar
Jesse Norell committed
			case '-k':
			case 'hardlink':
				$opts[] = 'hardlink';
				$jk_update_args .= ' -k';
				$jk_cp_args .= ' -k';
				break;
Jesse Norell's avatar
Jesse Norell committed
			case '-f':
			case 'force':
				$opts[] = 'force';
				$jk_cp_args .= ' -f';
				break;
			default:
				if (preg_match('@^skip[ =]/?(.+)$@', $opt, $matches) ) {
					if (in_array($matches[1], $jailkit_directories)) {
						$app->log("update_jailkit_chroot: skipping update of jailkit directory $home_dir/".$matches[1]
							. "; if this is in use as a web folder, it is insecure and should be fixed.", LOGLEVEL_WARN);
					}
					$jailkit_directories = $app->functions->array_unset_by_value($jailkit_directories, $matches[1]);
					$skips .= ' --skip=/'.escapeshellarg($matches[1]);
				}
				break;
		// Change ownership of the chroot directory to root
		$this->chown($home_dir, 'root');
		$this->chgrp($home_dir, 'root');
		$multiple_links = array();
		foreach ($jailkit_directories as $dir) {
			$root_dir = '/'.$dir;
			$jail_dir = rtrim($home_dir, '/') . '/'.$dir;

			if (!is_dir($jail_dir)) {
				$skips .= " --skip=/$dir";
				continue;
			}

			// if directory exists in jail but not in root, remove it
			if (is_dir($jail_dir) && !is_dir($root_dir)) {
				$this->rmdir($jail_dir, true);
				$skips .= " --skip=/$dir";
Jesse Norell's avatar
Jesse Norell committed
			$this->remove_broken_symlinks($jail_dir, true);
			$this->remove_recursive_symlinks($jail_dir, $home_dir, true);
			// save list of hardlinked files
Jesse Norell's avatar
Jesse Norell committed
			if (!(in_array('hardlink', $opts) || in_array('allow_hardlink', $options))) {
$app->log("update_jailkit_chroot: searching for hardlinks in $jail_dir", LOGLEVEL_DEBUG);
                                $find_multiple_links = function ( $path ) use ( &$find_multiple_links ) {
					$found = array();
Jesse Norell's avatar
Jesse Norell committed
					if (is_dir($path) && !is_link($path)) {
						$objects = array_diff(scandir($path), array('.', '..'));
						foreach ($objects as $object) {
							$ret = $find_multiple_links( "$path/$object" );
							if (count($ret) > 0) {
								$found = array_merge($found, $ret);
							}
						}
Jesse Norell's avatar
Jesse Norell committed
					} elseif (is_file($path)) {
						$stat = lstat($path);
						if ($stat['nlink'] > 1) {
							$found[$path] = $path;
						}
					}
					return $found;
				};

Jesse Norell's avatar
Jesse Norell committed
				$ret = $find_multiple_links($jail_dir);
				if (count($ret) > 0) {
					$multiple_links = array_merge($multiple_links, $ret);
				}
Jesse Norell's avatar
Jesse Norell committed
				// remove broken symlinks a second time after hardlink cleanup
				$this->remove_broken_symlinks($jail_dir, true);
			}
else { $app->log("update_jailkit_chroot: NOT searching for hardlinks in $jail_dir, options: ".print_r($options, true), LOGLEVEL_DEBUG); }
Jesse Norell's avatar
Jesse Norell committed
		foreach ($multiple_links as $file) {
			$app->log("update_jailkit_chroot: removing hardlinked file: $file", LOGLEVEL_DEBUG);
			unlink($file);
		}
		$cmd = 'jk_update --jail=?' . $jk_update_args . $skips;
Jesse Norell's avatar
Jesse Norell committed
		$this->exec_safe($cmd, $home_dir);
$app->log('jk_update returned: '.print_r($this->_last_exec_out, true), LOGLEVEL_DEBUG);
		# handle jk_update output
Jesse Norell's avatar
Jesse Norell committed
		foreach ($this->_last_exec_out as $line) {
			# jk_update sample output:
			# skip /var/www/clients/client1/web1/opt/
			# removing outdated file /var/www/clients/client15/web19/usr/bin/host
			# removing deprecated directory /var/www/clients/client15/web19/usr/lib/x86_64-linux-gnu/libtasn1.so.6.5.3
			# Creating symlink /var/www/clients/client15/web19/lib/x86_64-linux-gnu/libicudata.so.65 to libicudata.so.65.1
			# Copying /usr/bin/mysql to /var/www/clients/client15/web19/usr/bin/mysql
			if (preg_match('@^(skip|removing (outdated|deprecated)|Creating|Copying)@', $line)) {
				continue;
			}

			# jk_update sample output:
			# ERROR: failed to remove deprecated directory /var/www/clients/client1/web10/usr/lib/x86_64-linux-gnu/libGeoIP.so.1.6.9
			if (preg_match('@^(?:[^ ]+ ){6}(?:.+)('.preg_quote($home_dir, '@').'.+)@', $line, $matches)) {
				# remove deprecated files that jk_update failed to remove
				if (is_file($matches[1]) || is_link($matches[1])) {
Jesse Norell's avatar
Jesse Norell committed
$app->log("update_jailkit_chroot: removing deprecated file which jk_update failed to remove:  ".$matches[1], LOGLEVEL_DEBUG);
					unlink($matches[1]);
				} elseif (is_dir($matches[1]) && !is_link($matches[1])) {
Jesse Norell's avatar
Jesse Norell committed
$app->log("update_jailkit_chroot: removing deprecated directory which jk_update failed to remove:  ".$matches[1], LOGLEVEL_DEBUG);
					$this->rmdir($matches[1], true);
				} else {
					# unhandled error
					//$app->log("jk_update error for jail $home_dir:  ".$matches[1], LOGLEVEL_DEBUG);
					// at least for 3.2 beta, lets gather some of this info:
					$app->log("jk_update error for jail $home_dir, feel free to pass to ispconfig developers:  ".print_r( $matches, true), LOGLEVEL_DEBUG);

			# any other ERROR or WARNING
			# sample so far:
			# ERROR: /usr/bin/nano does not exist
			# WARNING: section [whatever] does not exist in /etc/jailkit/jk_init.ini
			} elseif (preg_match('/^(WARNING|ERROR)/', $line, $matches)) {
				$app->log("jk_update: $line", LOGLEVEL_DEBUG);
		// reinstall jailkit sections and programs
		if(!(empty($sections) && empty($programs))) {
			$this->create_jailkit_chroot($home_dir, $sections, $opts);
			$this->create_jailkit_programs($home_dir, $programs, $opts);
		}
		// Create the tmp and /var/run directories
		if(!is_dir($home_dir . '/tmp')) {
			$this->mkdirpath($home_dir . '/tmp', 0770);
			$this->chmod($home_dir . '/tmp', 0770, true);
		}
		if(!is_dir($home_dir . '/var/run')) {
			$this->mkdirpath($home_dir . '/var/run', 0755);
		} else {
			$this->chmod($home_dir . '/var/run', 0755, true);
		}
		if(!is_dir($home_dir . '/var/tmp')) {
			$this->mkdirpath($home_dir . '/var/tmp', 0770);
		} else {
			$this->chmod($home_dir . '/var/tmp', 0770, true);
		}

		// TODO: Set /usr/bin/php symlink to php version of the website.
		//
		// Currently server_php does not have a field for the cli path;
		// we can guess/determing according to OS-specific conventions or add that field.
		// Then symlink /usr/bin/php (or correct OS-specific path) to that location.
		// search for any hardlinked files which are now missing
Jesse Norell's avatar
Jesse Norell committed
		if (!(in_array('hardlink', $opts) || in_array('allow_hardlink', $options))) {
			foreach ($multiple_links as $file) {
				if (!is_file($file)) {
					// strip $home_dir from $file
Jesse Norell's avatar
Jesse Norell committed
					if (substr($file, 0, strlen(rtrim($home_dir, '/'))) == rtrim($home_dir, '/')) {
						$file = substr($file, strlen(rtrim($home_dir, '/')));
					}
					if (is_file($file)) { // file exists in root
Jesse Norell's avatar
Jesse Norell committed
						$app->log("update_jailkit_chroot: previously hardlinked file still missing, running jk_cp to restore: $file", LOGLEVEL_DEBUG);
						$cmd = 'jk_cp -j ? ' . $jk_cp_args . ' ' . escapeshellarg($file);
						$this->exec_safe($cmd, $home_dir);
					} else {
						// not necessarily an error
Jesse Norell's avatar
Jesse Norell committed
						$app->log("update_jailkit_chroot: previously hardlinked file was not restored and is no longer present in system: $file", LOGLEVEL_DEBUG);
		}

		// Fix permissions of the root firectory
		$this->chmod($home_dir . '/bin', 0755, true);  // was chmod g-w $CHROOT_HOMEDIR/bin
		// remove non-existent jails from /etc/jailkit/jk_socketd.ini
		if (is_file('/etc/jailkit/jk_socketd.ini')) {
			$rewrite = false;
			$jk_socketd_ini = $app->ini_parser->parse_ini_file('/etc/jailkit/jk_socketd.ini');
			foreach ($jk_socketd_ini as $log => $settings) {
Jesse Norell's avatar
Jesse Norell committed
				$jail = preg_replace('@/dev/log$@', '', $log);
				if ($jail != $log && !is_dir($jail)) {
					unset($jk_socketd_ini[$log]);
					$rewrite=true;
				}
			}
			if ($rewrite) {
				$app->log('update_jailkit_chroot: writing /etc/jailkit/jk_socketd.ini', LOGLEVEL_DEBUG);
				$app->ini_parse->write_ini_file($jk_socketd_ini, '/etc/jailkit/jk_socketd.ini');
			}
		}

	public function delete_jailkit_chroot($home_dir, $options = array()) {
$app->log("delete_jailkit_chroot called for $home_dir with options ".print_r($options, true), LOGLEVEL_DEBUG);
		$app->uses('ini_parser');

		// Disallow operating on root directory
		if(realpath($home_dir) == '/') {
			$app->log("delete_jailkit_chroot: invalid home_dir: $home_dir", LOGLEVEL_WARN);
			return false;
		}

		if(!is_dir($home_dir)) {
			$app->log("delete_jailkit_chroot: jail directory does not exist: $home_dir", LOGLEVEL_DEBUG);
			return false;
		}

		$jailkit_directories = array(
			'bin',
			'dev',
			'etc',
			'lib',
			'lib32',
			'lib64',
			'opt',
			'sys',
			'usr',
			'var',
Jesse Norell's avatar
Jesse Norell committed
			'run',		# not used by jailkit, but added for cleanup
		foreach ($options as $opt) {
			switch ($opt) {
			default:
				if (preg_match('@^skip[ =]/?(.+)$@', $opt, $matches) ) {
					$matches[1] = ltrim($matches[1], '/');
					if (in_array($matches[1], $jailkit_directories)) {
						$app->log("delete_jailkit_chroot: skipping removal of jailkit directory .$home_dir/".$matches[1]
							. "; if this is in use as a web folder, it is insecure and should be fixed.", LOGLEVEL_WARN);
					}
					$jailkit_directories = $app->functions->array_unset_by_value($jailkit_directories, $matches[1]);
				}
				break;
			}
		}

		$removed = '';
		foreach ($jailkit_directories as $dir) {
			$jail_dir = rtrim($home_dir, '/') . '/'.$dir;

Jesse Norell's avatar
Jesse Norell committed
			if (is_link($jail_dir)) {
				unlink($jail_dir);
				$removed .= ' /'.$dir;
			} elseif (is_dir($jail_dir)) {
				$this->rmdir($jail_dir, true);
				$removed .= ' /'.$dir;
			}

		}

		$app->log("delete_jailkit_chroot: removed from jail $home_dir: $removed", LOGLEVEL_DEBUG);
Jesse Norell's avatar
Jesse Norell committed
		// remove /home if empty
		$home = rtrim($home_dir, '/') . '/home';
		@rmdir($home);  # ok to fail if non-empty

Jesse Norell's avatar
Jesse Norell committed
		// otherwise archive under /private
		$private = rtrim($home_dir, '/') . '/private';
		if (is_dir($home) && is_dir($private)) {
			$archive = $private.'/home-'.date('c');
			rename($home, $archive);
		}

		// remove $home_dir from /etc/jailkit/jk_socketd.ini
		if (is_file('/etc/jailkit/jk_socketd.ini')) {
			$jk_socketd_ini = $app->ini_parser->parse_ini_file('/etc/jailkit/jk_socketd.ini');
			$log = $home . '/dev/log';
			if (isset($jk_socketd_ini[$log])) {
				unset($jk_socketd_ini[$log]);
				$app->log('delete_jailkit_chroot: writing /etc/jailkit/jk_socketd.ini', LOGLEVEL_DEBUG);
				$app->ini_parse->write_ini_file($jk_socketd_ini, '/etc/jailkit/jk_socketd.ini');
			}
		}

	public function pipe_exec($cmd, $stdin, &$retval = null, &$stderr = null) {
		$descriptors = array(
			0 => array('pipe', 'r'),
			1 => array('pipe', 'w'),
			2 => array('pipe', 'w')
		);
		$result = '';
		$pipes = null;
		$proc = proc_open($cmd, $descriptors, $pipes);
		if(is_resource($proc)) {
			fwrite($pipes[0], $stdin);
			fclose($pipes[0]);
			$result = stream_get_contents($pipes[1]);
			$stderr = stream_get_contents($pipes[2]);
			fclose($pipes[1]);
			fclose($pipes[2]);
	private function get_sudo_command($cmd, $run_as_user) {
		return 'sudo -u ' . escapeshellarg($run_as_user) . ' sh -c ' . escapeshellarg($cmd);
	}
	private function check_run_as_user($username) {
		if(preg_match('/^[a-zA-Z0-9_\-]+$/', $username)) {
			return true;
		} else{
			return false;
		}
	}
tbrehm's avatar
tbrehm committed
}