shelluser_jailkit_plugin.inc.php 16.9 KB
Newer Older
tbrehm's avatar
tbrehm committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
<?php

/*
Copyright (c) 2007, Till Brehm, projektfarm Gmbh
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name of ISPConfig nor the names of its contributors
      may be used to endorse or promote products derived from this software without
      specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

class shelluser_jailkit_plugin {
32

tbrehm's avatar
tbrehm committed
33 34 35
	//* $plugin_name and $class_name have to be the same then the name of this class
	var $plugin_name = 'shelluser_jailkit_plugin';
	var $class_name = 'shelluser_jailkit_plugin';
36

tbrehm's avatar
tbrehm committed
37 38 39 40
	//* This function is called during ispconfig installation to determine
	//  if a symlink shall be created for this plugin.
	function onInstall() {
		global $conf;
41

tbrehm's avatar
tbrehm committed
42 43 44 45 46
		if($conf['services']['web'] == true) {
			return true;
		} else {
			return false;
		}
47

tbrehm's avatar
tbrehm committed
48
	}
49 50


tbrehm's avatar
tbrehm committed
51 52 53
	/*
	 	This function is called when the plugin is loaded
	*/
54

tbrehm's avatar
tbrehm committed
55 56
	function onLoad() {
		global $app;
57

tbrehm's avatar
tbrehm committed
58 59 60 61
		/*
		Register for the events
		*/

62 63 64 65 66
		$app->plugins->registerEvent('shell_user_insert', $this->plugin_name, 'insert');
		$app->plugins->registerEvent('shell_user_update', $this->plugin_name, 'update');
		$app->plugins->registerEvent('shell_user_delete', $this->plugin_name, 'delete');


tbrehm's avatar
tbrehm committed
67
	}
68

tbrehm's avatar
tbrehm committed
69
	//* This function is called, when a shell user is inserted in the database
70
	function insert($event_name, $data) {
tbrehm's avatar
tbrehm committed
71
		global $app, $conf;
72

tbrehm's avatar
tbrehm committed
73
		$app->uses('system');
74
		$web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$data['new']['parent_domain_id']);
75

tbrehm's avatar
tbrehm committed
76
		if($app->system->is_user($data['new']['username'])) {
77

tbrehm's avatar
tbrehm committed
78
			/**
79 80 81 82
			 * Setup Jailkit Chroot System If Enabled
			 */


tbrehm's avatar
tbrehm committed
83 84
			if ($data['new']['chroot'] == "jailkit")
			{
85 86


tbrehm's avatar
tbrehm committed
87 88 89 90 91
				// load the server configuration options
				$app->uses("getconf");
				$this->data = $data;
				$this->app = $app;
				$this->jailkit_config = $app->getconf->get_server_config($conf["server_id"], 'jailkit');
92

tbrehm's avatar
tbrehm committed
93
				$this->_update_website_security_level();
94 95 96

				$app->system->web_folder_protection($web['document_root'], false);

tbrehm's avatar
tbrehm committed
97
				$this->_setup_jailkit_chroot();
98

99
				$this->_add_jailkit_user();
100

101
				//* call the ssh-rsa update function
102
				$this->_setup_ssh_rsa();
103

104 105 106
				//$command .= 'usermod -s /usr/sbin/jk_chrootsh -U '.escapeshellcmd($data['new']['username']);
				//exec($command);
				$app->system->usermod($data['new']['username'], 0, 0, '', '/usr/sbin/jk_chrootsh', '', '');
107

108
				//* Unlock user
109
				$command = 'usermod -U '.escapeshellcmd($data['new']['username']).' 2>/dev/null';
110
				exec($command);
111

tbrehm's avatar
tbrehm committed
112
				$this->_update_website_security_level();
113
				$app->system->web_folder_protection($web['document_root'], true);
tbrehm's avatar
tbrehm committed
114
			}
115 116 117

			$app->log("Jailkit Plugin -> insert username:".$data['new']['username'], LOGLEVEL_DEBUG);

tbrehm's avatar
tbrehm committed
118
		} else {
119
			$app->log("Jailkit Plugin -> insert username:".$data['new']['username']." skipped, the user does not exist.", LOGLEVEL_WARN);
tbrehm's avatar
tbrehm committed
120
		}
121

tbrehm's avatar
tbrehm committed
122
	}
123

tbrehm's avatar
tbrehm committed
124
	//* This function is called, when a shell user is updated in the database
125
	function update($event_name, $data) {
tbrehm's avatar
tbrehm committed
126
		global $app, $conf;
127

tbrehm's avatar
tbrehm committed
128
		$app->uses('system');
129
		$web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$data['new']['parent_domain_id']);
130

tbrehm's avatar
tbrehm committed
131
		if($app->system->is_user($data['new']['username'])) {
132 133 134



tbrehm's avatar
tbrehm committed
135
			/**
136 137
			 * Setup Jailkit Chroot System If Enabled
			 */
tbrehm's avatar
tbrehm committed
138 139
			if ($data['new']['chroot'] == "jailkit")
			{
140

tbrehm's avatar
tbrehm committed
141 142 143 144 145
				// load the server configuration options
				$app->uses("getconf");
				$this->data = $data;
				$this->app = $app;
				$this->jailkit_config = $app->getconf->get_server_config($conf["server_id"], 'jailkit');
146

147
				$this->_update_website_security_level();
148 149 150

				$app->system->web_folder_protection($web['document_root'], false);

tbrehm's avatar
tbrehm committed
151 152
				$this->_setup_jailkit_chroot();
				$this->_add_jailkit_user();
153

154
				//* call the ssh-rsa update function
155
				$this->_setup_ssh_rsa();
156

tbrehm's avatar
tbrehm committed
157
				$this->_update_website_security_level();
158 159

				$app->system->web_folder_protection($web['document_root'], true);
tbrehm's avatar
tbrehm committed
160
			}
161 162 163

			$app->log("Jailkit Plugin -> update username:".$data['new']['username'], LOGLEVEL_DEBUG);

tbrehm's avatar
tbrehm committed
164
		} else {
165
			$app->log("Jailkit Plugin -> update username:".$data['new']['username']." skipped, the user does not exist.", LOGLEVEL_WARN);
tbrehm's avatar
tbrehm committed
166
		}
167

tbrehm's avatar
tbrehm committed
168
	}
169

tbrehm's avatar
tbrehm committed
170 171 172
	//* This function is called, when a shell user is deleted in the database
	/**
	 * TODO: Remove chroot user home and from the chroot passwd file
173 174
	 */
	function delete($event_name, $data) {
tbrehm's avatar
tbrehm committed
175
		global $app, $conf;
176

tbrehm's avatar
tbrehm committed
177
		$app->uses('system');
178

179
		$web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$data['old']['parent_domain_id']);
180

tbrehm's avatar
tbrehm committed
181 182 183 184
		if ($data['old']['chroot'] == "jailkit")
		{
			$app->uses("getconf");
			$this->jailkit_config = $app->getconf->get_server_config($conf["server_id"], 'jailkit');
185

tbrehm's avatar
tbrehm committed
186
			$jailkit_chroot_userhome = $this->_get_home_dir($data['old']['username']);
187

tbrehm's avatar
tbrehm committed
188 189
			//commented out proved to be dangerous on config errors
			//exec('rm -rf '.$data['old']['dir'].$jailkit_chroot_userhome);
190 191 192

			$app->system->web_folder_protection($web['document_root'], false);

tbrehm's avatar
tbrehm committed
193
			if(@is_dir($data['old']['dir'].$jailkit_chroot_userhome)) {
194
				$command = 'userdel -f';
195
				$command .= ' '.escapeshellcmd($data['old']['username']).' &> /dev/null';
tbrehm's avatar
tbrehm committed
196
				exec($command);
197
				$app->log("Jailkit Plugin -> delete chroot home:".$data['old']['dir'].$jailkit_chroot_userhome, LOGLEVEL_DEBUG);
tbrehm's avatar
tbrehm committed
198
			}
199 200 201

			$app->system->web_folder_protection($web['document_root'], true);

tbrehm's avatar
tbrehm committed
202
		}
203 204 205 206

		$app->log("Jailkit Plugin -> delete username:".$data['old']['username'], LOGLEVEL_DEBUG);


tbrehm's avatar
tbrehm committed
207
	}
208

tbrehm's avatar
tbrehm committed
209 210
	function _setup_jailkit_chroot()
	{
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
		global $app;

		//check if the chroot environment is created yet if not create it with a list of program sections from the config
		if (!is_dir($this->data['new']['dir'].'/etc/jailkit'))
		{
			$command = '/usr/local/ispconfig/server/scripts/create_jailkit_chroot.sh';
			$command .= ' '.escapeshellcmd($this->data['new']['dir']);
			$command .= ' \''.$this->jailkit_config['jailkit_chroot_app_sections'].'\'';
			exec($command.' 2>/dev/null');

			$this->app->log("Added jailkit chroot with command: ".$command, LOGLEVEL_DEBUG);

			$this->_add_jailkit_programs();

			//add bash.bashrc script
			//we need to collect the domain name to be used as the HOSTNAME in the bashrc script
			$web = $this->app->db->queryOneRecord("SELECT domain FROM web_domain WHERE domain_id = ".intval($this->data['new']["parent_domain_id"]));

			$this->app->load('tpl');

			$tpl = new tpl();
			$tpl->newTemplate("bash.bashrc.master");

			$tpl->setVar('jailkit_chroot', true);
			$tpl->setVar('domain', $web['domain']);
			$tpl->setVar('home_dir', $this->_get_home_dir(""));

			$bashrc = escapeshellcmd($this->data['new']['dir']).'/etc/bash.bashrc';
			if(@is_file($bashrc) || @is_link($bashrc)) unlink($bashrc);

			file_put_contents($bashrc, $tpl->grab());
			unset($tpl);

			$this->app->log("Added bashrc script : ".$bashrc, LOGLEVEL_DEBUG);

			$tpl = new tpl();
			$tpl->newTemplate("motd.master");

			$tpl->setVar('domain', $web['domain']);

			$motd = escapeshellcmd($this->data['new']['dir']).'/var/run/motd';
			if(@is_file($motd) || @is_link($motd)) unlink($motd);

			$app->system->file_put_contents($motd, $tpl->grab());

		}
tbrehm's avatar
tbrehm committed
257
	}
258

tbrehm's avatar
tbrehm committed
259 260 261 262 263 264
	function _add_jailkit_programs()
	{
		//copy over further programs and its libraries
		$command = '/usr/local/ispconfig/server/scripts/create_jailkit_programs.sh';
		$command .= ' '.escapeshellcmd($this->data['new']['dir']);
		$command .= ' \''.$this->jailkit_config['jailkit_chroot_app_programs'].'\'';
265
		exec($command.' 2>/dev/null');
266 267

		$this->app->log("Added programs to jailkit chroot with command: ".$command, LOGLEVEL_DEBUG);
tbrehm's avatar
tbrehm committed
268
	}
269

tbrehm's avatar
tbrehm committed
270 271
	function _get_home_dir($username)
	{
272
		return str_replace("[username]", escapeshellcmd($username), $this->jailkit_config['jailkit_chroot_home']);
tbrehm's avatar
tbrehm committed
273
	}
274

tbrehm's avatar
tbrehm committed
275 276
	function _add_jailkit_user()
	{
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
		global $app;

		//add the user to the chroot
		$jailkit_chroot_userhome = $this->_get_home_dir($this->data['new']['username']);
		$jailkit_chroot_puserhome = $this->_get_home_dir($this->data['new']['puser']);

		if(!is_dir($this->data['new']['dir'].'/etc')) mkdir($this->data['new']['dir'].'/etc', 0755);
		if(!is_file($this->data['new']['dir'].'/etc/passwd')) touch($this->data['new']['dir'].'/etc/passwd', 0755);

		// IMPORTANT!
		// ALWAYS create the user. Even if the user was created before
		// if we check if the user exists, then a update (no shell -> jailkit) will not work
		// and the user has FULL ACCESS to the root of the server!
		$command = '/usr/local/ispconfig/server/scripts/create_jailkit_user.sh';
		$command .= ' '.escapeshellcmd($this->data['new']['username']);
		$command .= ' '.escapeshellcmd($this->data['new']['dir']);
		$command .= ' '.$jailkit_chroot_userhome;
		$command .= ' '.escapeshellcmd($this->data['new']['shell']);
		$command .= ' '.$this->data['new']['puser'];
		$command .= ' '.$jailkit_chroot_puserhome;
		exec($command.' 2>/dev/null');

		//* Change the homedir of the shell user and parent user
		//* We have to do this manually as the usermod command fails
		//* when the user is logged in or a command is running under that user
		/*
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
			$passwd_file_array = file('/etc/passwd');
			$passwd_out = '';
			if(is_array($passwd_file_array)) {
				foreach($passwd_file_array as $line) {
					$line = trim($line);
					$parts = explode(':',$line);
					if($parts[0] == $this->data['new']['username']) {
						$parts[5] = escapeshellcmd($this->data['new']['dir'].'/.'.$jailkit_chroot_userhome);
						$parts[6] = escapeshellcmd('/usr/sbin/jk_chrootsh');
						$new_line = implode(':',$parts);
						copy('/etc/passwd','/etc/passwd~');
						chmod('/etc/passwd~',0600);
						$app->uses('system');
						$app->system->replaceLine('/etc/passwd',$line,$new_line,1,0);
					}
				}
319
			}*/
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337

		$app->system->usermod($this->data['new']['username'], 0, 0, $this->data['new']['dir'].'/.'.$jailkit_chroot_userhome, '/usr/sbin/jk_chrootsh');
		$app->system->usermod($this->data['new']['puser'], 0, 0, $this->data['new']['dir'].'/.'.$jailkit_chroot_userhome, '/usr/sbin/jk_chrootsh');

		$this->app->log("Added jailkit user to chroot with command: ".$command, LOGLEVEL_DEBUG);

		if(!is_dir($this->data['new']['dir'].$jailkit_chroot_userhome)) mkdir(escapeshellcmd($this->data['new']['dir'].$jailkit_chroot_userhome), 0755, true);
		$app->system->chown(escapeshellcmd($this->data['new']['dir'].$jailkit_chroot_userhome), $this->data['new']['username']);
		$app->system->chgrp(escapeshellcmd($this->data['new']['dir'].$jailkit_chroot_userhome), $this->data['new']['pgroup']);

		$this->app->log("Added created jailkit user home in : ".$this->data['new']['dir'].$jailkit_chroot_userhome, LOGLEVEL_DEBUG);

		if(!is_dir($this->data['new']['dir'].$jailkit_chroot_puserhome)) mkdir(escapeshellcmd($this->data['new']['dir'].$jailkit_chroot_puserhome), 0755, true);
		$app->system->chown(escapeshellcmd($this->data['new']['dir'].$jailkit_chroot_puserhome), $this->data['new']['puser']);
		$app->system->chgrp(escapeshellcmd($this->data['new']['dir'].$jailkit_chroot_puserhome), $this->data['new']['pgroup']);

		$this->app->log("Added jailkit parent user home in : ".$this->data['new']['dir'].$jailkit_chroot_puserhome, LOGLEVEL_DEBUG);

338

tbrehm's avatar
tbrehm committed
339
	}
340

341 342
	//* Update the website root directory permissions depending on the security level
	function _update_website_security_level() {
343 344
		global $app, $conf;

345 346 347
		// load the server configuration options
		$app->uses("getconf");
		$web_config = $app->getconf->get_server_config($conf["server_id"], 'web');
348

349 350
		// Get the parent website of this shell user
		$web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$this->data['new']['parent_domain_id']);
351

352
		//* If the security level is set to high
353
		if($web_config['security_level'] == 20 && is_array($web)) {
354 355 356 357 358
			$app->system->web_folder_protection($web["document_root"], false);
			$app->system->chmod($web["document_root"], 0755);
			$app->system->chown($web["document_root"], 'root');
			$app->system->chgrp($web["document_root"], 'root');
			$app->system->web_folder_protection($web["document_root"], true);
359
		}
360

361
	}
362

tbrehm's avatar
tbrehm committed
363 364 365
	//* Wrapper for exec function for easier debugging
	private function _exec($command) {
		global $app;
366
		$app->log('exec: '.$command, LOGLEVEL_DEBUG);
tbrehm's avatar
tbrehm committed
367 368
		exec($command);
	}
tbrehm's avatar
tbrehm committed
369

370
	private function _setup_ssh_rsa() {
371
		global $app;
372
		$this->app->log("ssh-rsa setup shelluser_jailkit", LOGLEVEL_DEBUG);
373
		// Get the client ID, username, and the key
374 375
		$domain_data = $this->app->db->queryOneRecord('SELECT sys_groupid FROM web_domain WHERE web_domain.domain_id = '.intval($this->data['new']['parent_domain_id']));
		$sys_group_data = $this->app->db->queryOneRecord('SELECT * FROM sys_group WHERE sys_group.groupid = '.intval($domain_data['sys_groupid']));
376 377
		$id = intval($sys_group_data['client_id']);
		$username= $sys_group_data['name'];
378
		$client_data = $this->app->db->queryOneRecord('SELECT * FROM client WHERE client.client_id = '.$id);
379 380 381
		$userkey = $client_data['ssh_rsa'];
		unset($domain_data);
		unset($client_data);
382

383
		// ssh-rsa authentication variables
384
		$sshrsa = $this->data['new']['ssh_rsa'];
385
		$usrdir = escapeshellcmd($this->data['new']['dir']).'/'.$this->_get_home_dir($this->data['new']['username']);
386 387
		$sshdir = $usrdir.'/.ssh';
		$sshkeys= $usrdir.'/.ssh/authorized_keys';
388

389 390
		$app->uses('file');
		$sshrsa = $app->file->unix_nl($sshrsa);
391 392
		$sshrsa = $app->file->remove_blank_lines($sshrsa, 0);

393
		// If this user has no key yet, generate a pair
394
		if ($userkey == '' && $id > 0){
395 396
			//Generate ssh-rsa-keys
			exec('ssh-keygen -t rsa -C '.$username.'-rsa-key-'.time().' -f /tmp/id_rsa -N ""');
397

398
			// use the public key that has been generated
399
			$userkey = $app->system->file_get_contents('/tmp/id_rsa.pub');
400

401
			// save keypair in client table
402
			$this->app->db->query("UPDATE client SET created_at = ".time().", id_rsa = '".$app->db->quote($app->system->file_get_contents('/tmp/id_rsa'))."', ssh_rsa = '".$app->db->quote($userkey)."' WHERE client_id = ".$id);
403

404 405
			$app->system->unlink('/tmp/id_rsa');
			$app->system->unlink('/tmp/id_rsa.pub');
406
			$this->app->log("ssh-rsa keypair generated for ".$username, LOGLEVEL_DEBUG);
407
		};
408

409
		if (!file_exists($sshkeys)){
410
			// add root's key
411
			$app->file->mkdirs($sshdir, '0755');
412
			if(is_file('/root/.ssh/authorized_keys')) $app->system->file_put_contents($sshkeys, $app->system->file_get_contents('/root/.ssh/authorized_keys'));
413

414
			// Remove duplicate keys
415
			$existing_keys = @file($sshkeys);
416
			$new_keys = explode("\n", $userkey);
417
			$final_keys_arr = @array_merge($existing_keys, $new_keys);
418 419 420 421 422 423 424
			$new_final_keys_arr = array();
			if(is_array($final_keys_arr) && !empty($final_keys_arr)){
				foreach($final_keys_arr as $key => $val){
					$new_final_keys_arr[$key] = trim($val);
				}
			}
			$final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
425

426
			// add the user's key
427 428
			file_put_contents($sshkeys, $final_keys);
			$app->file->remove_blank_lines($sshkeys);
429
			$this->app->log("ssh-rsa authorisation keyfile created in ".$sshkeys, LOGLEVEL_DEBUG);
430
		}
431 432 433
		//* Get the keys
		$existing_keys = file($sshkeys);
		$new_keys = explode("\n", $sshrsa);
434 435
		$old_keys = explode("\n", $this->data['old']['ssh_rsa']);

436 437 438
		//* Remove all old keys
		if(is_array($old_keys)) {
			foreach($old_keys as $key => $val) {
439
				$k = array_search(trim($val), $existing_keys);
440
				unset($existing_keys[$k]);
441
			}
442
		}
443

444 445 446 447 448
		//* merge the remaining keys and the ones fom the ispconfig database.
		if(is_array($new_keys)) {
			$final_keys_arr = array_merge($existing_keys, $new_keys);
		} else {
			$final_keys_arr = $existing_keys;
449
		}
450

451 452 453 454 455 456 457
		$new_final_keys_arr = array();
		if(is_array($final_keys_arr) && !empty($final_keys_arr)){
			foreach($final_keys_arr as $key => $val){
				$new_final_keys_arr[$key] = trim($val);
			}
		}
		$final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
458 459

		// add the custom key
460
		$app->system->file_put_contents($sshkeys, $final_keys);
461
		$app->file->remove_blank_lines($sshkeys);
462 463
		$this->app->log("ssh-rsa key updated in ".$sshkeys, LOGLEVEL_DEBUG);

464
		// set proper file permissions
465 466
		exec("chown -R ".escapeshellcmd($this->data['new']['puser']).":".escapeshellcmd($this->data['new']['pgroup'])." ".$sshdir);
		exec("chmod 700 ".$sshdir);
467
		exec("chmod 600 '$sshkeys'");
468

469
	}
470

tbrehm's avatar
tbrehm committed
471 472
} // end class

473
?>