Falko Timme
2014-12-04 4664a295e13ac95ffe280e955af0721e82422e24
commit | author | age
396f0e 1 <?php
T 2
3 /*
4 Copyright (c) 2007, Till Brehm, projektfarm Gmbh
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9
10     * Redistributions of source code must retain the above copyright notice,
11       this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright notice,
13       this list of conditions and the following disclaimer in the documentation
14       and/or other materials provided with the distribution.
15     * Neither the name of ISPConfig nor the names of its contributors
16       may be used to endorse or promote products derived from this software without
17       specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 class shelluser_base_plugin {
7fe908 32
396f0e 33     var $plugin_name = 'shelluser_base_plugin';
T 34     var $class_name = 'shelluser_base_plugin';
35     var $min_uid = 499;
7fe908 36
396f0e 37     //* This function is called during ispconfig installation to determine
T 38     //  if a symlink shall be created for this plugin.
39     function onInstall() {
40         global $conf;
7fe908 41
396f0e 42         if($conf['services']['web'] == true) {
T 43             return true;
44         } else {
45             return false;
46         }
7fe908 47
396f0e 48     }
7fe908 49
MC 50
396f0e 51     /*
T 52          This function is called when the plugin is loaded
53     */
7fe908 54
396f0e 55     function onLoad() {
T 56         global $app;
7fe908 57
396f0e 58         /*
T 59         Register for the events
60         */
9edea9 61         
7fe908 62         $app->plugins->registerEvent('shell_user_insert', $this->plugin_name, 'insert');
MC 63         $app->plugins->registerEvent('shell_user_update', $this->plugin_name, 'update');
64         $app->plugins->registerEvent('shell_user_delete', $this->plugin_name, 'delete');
9edea9 65         
7fe908 66
396f0e 67     }
7fe908 68
MC 69
70     function insert($event_name, $data) {
396f0e 71         global $app, $conf;
9edea9 72         
TB 73         $app->uses('system,getconf');
74         
75         $security_config = $app->getconf->get_security_config('permissions');
76         if($security_config['allow_shell_user'] != 'yes') {
77             $app->log('Shell user plugin disabled by security settings.',LOGLEVEL_WARN);
78             return false;
79         }
7fe908 80
b67344 81         //* Check if the resulting path is inside the docroot
T 82         $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($data['new']['parent_domain_id']));
6d21f1 83         if(substr($data['new']['dir'],0,strlen($web['document_root'])) != $web['document_root']) {
FT 84             $app->log('Directory of the shell user is outside of website docroot.',LOGLEVEL_WARN);
85             return false;
86         }
87         if(strpos($data['new']['dir'], '/../') !== false || substr($data['new']['dir'],-3) == '/..') {
88             $app->log('Directory of the shell user is not valid.',LOGLEVEL_WARN);
b67344 89             return false;
T 90         }
64ea56 91         
MC 92         if(!$app->system->is_allowed_user($data['new']['username'], false, false)
93             || !$app->system->is_allowed_user($data['new']['puser'], true, true)
94             || !$app->system->is_allowed_group($data['new']['pgroup'], true, true)) {
95             $app->log('Shell user must not be root or in group root.',LOGLEVEL_WARN);
96             return false;
97         }
7fe908 98
396f0e 99         if($app->system->is_user($data['new']['puser'])) {
7fe908 100
396f0e 101             // Get the UID of the parent user
T 102             $uid = intval($app->system->getuid($data['new']['puser']));
103             if($uid > $this->min_uid) {
892d73 104                 //* Remove webfolder protection
MC 105                 $app->system->web_folder_protection($web['document_root'], false);
106
107                 if(!is_dir($data['new']['dir'])){
108                     $app->file->mkdirs(escapeshellcmd($data['new']['dir']), '0700');
109                     $app->system->chown(escapeshellcmd($data['new']['dir']),escapeshellcmd($data['new']['username']));
110                     $app->system->chgrp(escapeshellcmd($data['new']['dir']),escapeshellcmd($data['new']['pgroup']));
111                 }
396f0e 112                 $command = 'useradd';
e47d46 113                 $command .= ' -d '.escapeshellcmd($data['new']['dir']);
T 114                 $command .= ' -g '.escapeshellcmd($data['new']['pgroup']);
115                 $command .= ' -o '; // non unique
116                 if($data['new']['password'] != '') $command .= ' -p '.escapeshellcmd($data['new']['password']);
117                 $command .= ' -s '.escapeshellcmd($data['new']['shell']);
118                 $command .= ' -u '.escapeshellcmd($uid);
396f0e 119                 $command .= ' '.escapeshellcmd($data['new']['username']);
7fe908 120
396f0e 121                 exec($command);
7fe908 122                 $app->log("Executed command: ".$command, LOGLEVEL_DEBUG);
MC 123                 $app->log("Added shelluser: ".$data['new']['username'], LOGLEVEL_DEBUG);
124
08c588 125                 // call the ssh-rsa update function
L 126                 $app->uses("getconf");
127                 $this->data = $data;
128                 $this->app = $app;
129                 $this->_setup_ssh_rsa();
7fe908 130
12e119 131                 //* Create .bash_history file
4bd960 132                 $app->system->touch(escapeshellcmd($data['new']['dir']).'/.bash_history');
T 133                 $app->system->chmod(escapeshellcmd($data['new']['dir']).'/.bash_history', 0755);
134                 $app->system->chown(escapeshellcmd($data['new']['dir']).'/.bash_history', $data['new']['username']);
135                 $app->system->chgrp(escapeshellcmd($data['new']['dir']).'/.bash_history', $data['new']['pgroup']);
7fe908 136
396f0e 137                 //* Disable shell user temporarily if we use jailkit
T 138                 if($data['new']['chroot'] == 'jailkit') {
526b99 139                     $command = 'usermod -s /bin/false -L '.escapeshellcmd($data['new']['username']).' 2>/dev/null';
396f0e 140                     exec($command);
7fe908 141                     $app->log("Disabling shelluser temporarily: ".$command, LOGLEVEL_DEBUG);
396f0e 142                 }
7fe908 143
4b9329 144                 //* Add webfolder protection again
7fe908 145                 $app->system->web_folder_protection($web['document_root'], true);
396f0e 146             } else {
7fe908 147                 $app->log("UID = $uid for shelluser:".$data['new']['username']." not allowed.", LOGLEVEL_ERROR);
396f0e 148             }
T 149         } else {
7fe908 150             $app->log("Skipping insertion of user:".$data['new']['username'].", parent user ".$data['new']['puser']." does not exist.", LOGLEVEL_WARN);
396f0e 151         }
T 152     }
7fe908 153
MC 154     function update($event_name, $data) {
396f0e 155         global $app, $conf;
7fe908 156
9edea9 157         $app->uses('system,getconf');
TB 158         
159         $security_config = $app->getconf->get_security_config('permissions');
160         if($security_config['allow_shell_user'] != 'yes') {
161             $app->log('Shell user plugin disabled by security settings.',LOGLEVEL_WARN);
162             return false;
163         }
7fe908 164
b67344 165         //* Check if the resulting path is inside the docroot
T 166         $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($data['new']['parent_domain_id']));
6d21f1 167         if(substr($data['new']['dir'],0,strlen($web['document_root'])) != $web['document_root']) {
FT 168             $app->log('Directory of the shell user is outside of website docroot.',LOGLEVEL_WARN);
169             return false;
170         }
171         
172         if(strpos($data['new']['dir'], '/../') !== false || substr($data['new']['dir'],-3) == '/..') {
173             $app->log('Directory of the shell user is not valid.',LOGLEVEL_WARN);
b67344 174             return false;
T 175         }
7fe908 176
64ea56 177         if(!$app->system->is_allowed_user($data['new']['username'], false, false)
MC 178             || !$app->system->is_allowed_user($data['new']['puser'], true, true)
179             || !$app->system->is_allowed_group($data['new']['pgroup'], true, true)) {
180             $app->log('Shell user must not be root or in group root.',LOGLEVEL_WARN);
181             return false;
182         }
183         
396f0e 184         if($app->system->is_user($data['new']['puser'])) {
T 185             // Get the UID of the parent user
186             $uid = intval($app->system->getuid($data['new']['puser']));
187             if($uid > $this->min_uid) {
188                 // Check if the user that we want to update exists, if not, we insert it
189                 if($app->system->is_user($data['old']['username'])) {
ff6a68 190                     /*
396f0e 191                     $command = 'usermod';
T 192                     $command .= ' --home '.escapeshellcmd($data['new']['dir']);
193                     $command .= ' --gid '.escapeshellcmd($data['new']['pgroup']);
194                     // $command .= ' --non-unique ';
195                     $command .= ' --password '.escapeshellcmd($data['new']['password']);
196                     if($data['new']['chroot'] != 'jailkit') $command .= ' --shell '.escapeshellcmd($data['new']['shell']);
197                     // $command .= ' --uid '.escapeshellcmd($uid);
198                     $command .= ' --login '.escapeshellcmd($data['new']['username']);
199                     $command .= ' '.escapeshellcmd($data['old']['username']);
7fe908 200
396f0e 201                     exec($command);
e47d46 202                     $app->log("Executed command: $command ",LOGLEVEL_DEBUG);
ff6a68 203                     */
3f478f 204                     //$groupinfo = $app->system->posix_getgrnam($data['new']['pgroup']);
6d21f1 205                     if($data['new']['dir'] != $data['old']['dir'] && !is_dir($data['new']['dir'])){
FT 206                         $app->file->mkdirs(escapeshellcmd($data['new']['dir']), '0700');
207                         $app->system->chown(escapeshellcmd($data['new']['dir']),escapeshellcmd($data['new']['username']));
208                         $app->system->chgrp(escapeshellcmd($data['new']['dir']),escapeshellcmd($data['new']['pgroup']));
209                     }
7fe908 210                     $app->system->usermod($data['old']['username'], 0, $app->system->getgid($data['new']['pgroup']), $data['new']['dir'], $data['new']['shell'], $data['new']['password'], $data['new']['username']);
MC 211                     $app->log("Updated shelluser: ".$data['old']['username'], LOGLEVEL_DEBUG);
212
08c588 213                     // call the ssh-rsa update function
L 214                     $app->uses("getconf");
215                     $this->data = $data;
216                     $this->app = $app;
217                     $this->_setup_ssh_rsa();
7fe908 218
12e119 219                     //* Create .bash_history file
T 220                     if(!is_file($data['new']['dir']).'/.bash_history') {
4bd960 221                         $app->system->touch(escapeshellcmd($data['new']['dir']).'/.bash_history');
T 222                         $app->system->chmod(escapeshellcmd($data['new']['dir']).'/.bash_history', 0755);
7fe908 223                         $app->system->chown(escapeshellcmd($data['new']['dir']).'/.bash_history', escapeshellcmd($data['new']['username']));
MC 224                         $app->system->chgrp(escapeshellcmd($data['new']['dir']).'/.bash_history', escapeshellcmd($data['new']['pgroup']));
12e119 225                     }
7fe908 226
396f0e 227                 } else {
T 228                     // The user does not exist, so we insert it now
7fe908 229                     $this->insert($event_name, $data);
396f0e 230                 }
T 231             } else {
7fe908 232                 $app->log("UID = $uid for shelluser:".$data['new']['username']." not allowed.", LOGLEVEL_ERROR);
396f0e 233             }
T 234         } else {
7fe908 235             $app->log("Skipping update for user:".$data['new']['username'].", parent user ".$data['new']['puser']." does not exist.", LOGLEVEL_WARN);
396f0e 236         }
T 237     }
7fe908 238
MC 239     function delete($event_name, $data) {
396f0e 240         global $app, $conf;
7fe908 241
4664a2 242         $app->uses('system,getconf,services');
9edea9 243         
TB 244         $security_config = $app->getconf->get_security_config('permissions');
245         if($security_config['allow_shell_user'] != 'yes') {
246             $app->log('Shell user plugin disabled by security settings.',LOGLEVEL_WARN);
247             return false;
248         }
7fe908 249
396f0e 250         if($app->system->is_user($data['old']['username'])) {
T 251             // Get the UID of the user
252             $userid = intval($app->system->getuid($data['old']['username']));
253             if($userid > $this->min_uid) {
4664a2 254                 $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($data['old']['parent_domain_id']));
FT 255                     
892d73 256                 // check if we have to delete the dir
MC 257                 $check = $app->db->queryOneRecord('SELECT shell_user_id FROM `shell_user` WHERE `dir` = \'' . $app->db->quote($data['old']['dir']) . '\'');
258                 if(!$check && is_dir($data['old']['dir'])) {
a7e4ec 259                     
TB 260                     $app->system->web_folder_protection($web['document_root'], false);
261                     
892d73 262                     // delete dir
MC 263                     $homedir = $data['old']['dir'];
264                     if(substr($homedir, -1) !== '/') $homedir .= '/';
265                     $files = array('.bash_logout', '.bash_history', '.bashrc', '.profile');
65a0b9 266                     $dirs = array('.ssh', '.cache');
892d73 267                     foreach($files as $delfile) {
MC 268                         if(is_file($homedir . $delfile) && fileowner($homedir . $delfile) == $userid) unlink($homedir . $delfile);
269                     }
270                     foreach($dirs as $deldir) {
271                         if(is_dir($homedir . $deldir) && fileowner($homedir . $deldir) == $userid) exec('rm -rf ' . escapeshellarg($homedir . $deldir));
272                     }
273                     $empty = true;
274                     $dirres = opendir($homedir);
275                     if($dirres) {
276                         while(($entry = readdir($dirres)) !== false) {
277                             if($entry != '.' && $entry != '..') {
278                                 $empty = false;
279                                 break;
280                             }
281                         }
282                         closedir($dirres);
283                     }
284                     if($empty == true) {
285                         rmdir($homedir);
286                     }
287                     unset($files);
288                     unset($dirs);
a7e4ec 289                     
TB 290                     $app->system->web_folder_protection($web['document_root'], true);
892d73 291                 }
MC 292                 
ac32a4 293                 // We delete only non jailkit users, jailkit users will be deleted by the jailkit plugin.
T 294                 if ($data['old']['chroot'] != "jailkit") {
4664a2 295                     // if this web uses PHP-FPM, that PPH-FPM service must be stopped before we can delete this user
FT 296                     if($web['php'] == 'php-fpm'){
297                         if(trim($web['fastcgi_php_version']) != ''){
298                             $default_php_fpm = false;
299                             list($custom_php_fpm_name, $custom_php_fpm_init_script, $custom_php_fpm_ini_dir, $custom_php_fpm_pool_dir) = explode(':', trim($web['fastcgi_php_version']));
300                         } else {
301                             $default_php_fpm = true;
302                         }
303                         $web_config = $app->getconf->get_server_config($conf["server_id"], 'web');
304                         if(!$default_php_fpm){
305                             $app->services->restartService('php-fpm', 'stop:'.$custom_php_fpm_init_script);
306                         } else {
307                             $app->services->restartService('php-fpm', 'stop:'.$conf['init_scripts'].'/'.$web_config['php_fpm_init_script']);
308                         }
309                     }
c65384 310                     $command = 'killall -u '.escapeshellcmd($data['old']['username']).' ; userdel -f';
526b99 311                     $command .= ' '.escapeshellcmd($data['old']['username']).' &> /dev/null';
ac32a4 312                     exec($command);
7fe908 313                     $app->log("Deleted shelluser: ".$data['old']['username'], LOGLEVEL_DEBUG);
4664a2 314                     // start PHP-FPM again
FT 315                     if($web['php'] == 'php-fpm'){
316                         if(!$default_php_fpm){
317                             $app->services->restartService('php-fpm', 'start:'.$custom_php_fpm_init_script);
318                         } else {
319                             $app->services->restartService('php-fpm', 'start:'.$conf['init_scripts'].'/'.$web_config['php_fpm_init_script']);
320                         }
321                     }
ac32a4 322                 }
7fe908 323
396f0e 324             } else {
7fe908 325                 $app->log("UID = $userid for shelluser:".$data['old']['username']." not allowed.", LOGLEVEL_ERROR);
396f0e 326             }
T 327         } else {
7fe908 328             $app->log("User:".$data['new']['username']." does not exist in in /etc/passwd, skipping delete.", LOGLEVEL_WARN);
396f0e 329         }
7fe908 330
396f0e 331     }
7fe908 332
00a055 333     private function _setup_ssh_rsa() {
8ab3cd 334         global $app;
7fe908 335         $this->app->log("ssh-rsa setup shelluser_base", LOGLEVEL_DEBUG);
00a055 336         // Get the client ID, username, and the key
27c623 337         $domain_data = $this->app->db->queryOneRecord('SELECT sys_groupid FROM web_domain WHERE web_domain.domain_id = '.intval($this->data['new']['parent_domain_id']));
L 338         $sys_group_data = $this->app->db->queryOneRecord('SELECT * FROM sys_group WHERE sys_group.groupid = '.intval($domain_data['sys_groupid']));
00a055 339         $id = intval($sys_group_data['client_id']);
L 340         $username= $sys_group_data['name'];
27c623 341         $client_data = $this->app->db->queryOneRecord('SELECT * FROM client WHERE client.client_id = '.$id);
00a055 342         $userkey = $client_data['ssh_rsa'];
L 343         unset($domain_data);
344         unset($client_data);
7fe908 345
00a055 346         // ssh-rsa authentication variables
5c93f0 347         //$sshrsa = $this->data['new']['ssh_rsa'];
TB 348         $sshrsa = '';
349         $ssh_users = $app->db->queryAllRecords("SELECT ssh_rsa FROM shell_user WHERE parent_domain_id = ".intval($this->data['new']['parent_domain_id']));
350         if(is_array($ssh_users)) {
351             foreach($ssh_users as $sshu) {
352                 if($sshu['ssh_rsa'] != '') $sshrsa .= "\n".$sshu['ssh_rsa'];
353             }
354         }
355         $sshrsa = trim($sshrsa);
00a055 356         $usrdir = escapeshellcmd($this->data['new']['dir']);
L 357         $sshdir = $usrdir.'/.ssh';
358         $sshkeys= $usrdir.'/.ssh/authorized_keys';
7fe908 359
8ab3cd 360         $app->uses('file');
T 361         $sshrsa = $app->file->unix_nl($sshrsa);
7fe908 362         $sshrsa = $app->file->remove_blank_lines($sshrsa, 0);
MC 363
00a055 364         // If this user has no key yet, generate a pair
8ab3cd 365         if ($userkey == '' && $id > 0){
00a055 366             //Generate ssh-rsa-keys
L 367             exec('ssh-keygen -t rsa -C '.$username.'-rsa-key-'.time().' -f /tmp/id_rsa -N ""');
7fe908 368
8ab3cd 369             // use the public key that has been generated
4bd960 370             $userkey = $app->system->file_get_contents('/tmp/id_rsa.pub');
7fe908 371
00a055 372             // save keypair in client table
4bd960 373             $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);
7fe908 374
4bd960 375             $app->system->unlink('/tmp/id_rsa');
T 376             $app->system->unlink('/tmp/id_rsa.pub');
7fe908 377             $this->app->log("ssh-rsa keypair generated for ".$username, LOGLEVEL_DEBUG);
00a055 378         };
8ab3cd 379
T 380         if (!file_exists($sshkeys)){
00a055 381             // add root's key
8cf78b 382             $app->file->mkdirs($sshdir, '0700');
4bd960 383             if(is_file('/root/.ssh/authorized_keys')) $app->system->file_put_contents($sshkeys, $app->system->file_get_contents('/root/.ssh/authorized_keys'));
7fe908 384
8ab3cd 385             // Remove duplicate keys
8cf78b 386             $existing_keys = @file($sshkeys);
8ab3cd 387             $new_keys = explode("\n", $userkey);
8cf78b 388             $final_keys_arr = @array_merge($existing_keys, $new_keys);
8ab3cd 389             $new_final_keys_arr = array();
T 390             if(is_array($final_keys_arr) && !empty($final_keys_arr)){
391                 foreach($final_keys_arr as $key => $val){
392                     $new_final_keys_arr[$key] = trim($val);
393                 }
394             }
395             $final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
7fe908 396
00a055 397             // add the user's key
4bd960 398             $app->system->file_put_contents($sshkeys, $final_keys);
8ab3cd 399             $app->file->remove_blank_lines($sshkeys);
7fe908 400             $this->app->log("ssh-rsa authorisation keyfile created in ".$sshkeys, LOGLEVEL_DEBUG);
00a055 401         }
7fe908 402
8cf78b 403         //* Get the keys
T 404         $existing_keys = file($sshkeys);
405         $new_keys = explode("\n", $sshrsa);
7fe908 406         $old_keys = explode("\n", $this->data['old']['ssh_rsa']);
MC 407
8cf78b 408         //* Remove all old keys
T 409         if(is_array($old_keys)) {
410             foreach($old_keys as $key => $val) {
7fe908 411                 $k = array_search(trim($val), $existing_keys);
8cf78b 412                 unset($existing_keys[$k]);
T 413             }
00a055 414         }
7fe908 415
8cf78b 416         //* merge the remaining keys and the ones fom the ispconfig database.
T 417         if(is_array($new_keys)) {
418             $final_keys_arr = array_merge($existing_keys, $new_keys);
419         } else {
420             $final_keys_arr = $existing_keys;
421         }
7fe908 422
8cf78b 423         $new_final_keys_arr = array();
T 424         if(is_array($final_keys_arr) && !empty($final_keys_arr)){
425             foreach($final_keys_arr as $key => $val){
426                 $new_final_keys_arr[$key] = trim($val);
427             }
428         }
429         $final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
7fe908 430
MC 431         // add the custom key
4bd960 432         $app->system->file_put_contents($sshkeys, $final_keys);
8cf78b 433         $app->file->remove_blank_lines($sshkeys);
7fe908 434         $this->app->log("ssh-rsa key updated in ".$sshkeys, LOGLEVEL_DEBUG);
MC 435
00a055 436         // set proper file permissions
8cf78b 437         exec("chown -R ".escapeshellcmd($this->data['new']['puser']).":".escapeshellcmd($this->data['new']['pgroup'])." ".$sshdir);
00a055 438         exec("chmod 600 '$sshkeys'");
7fe908 439
08c588 440     }
7fe908 441
396f0e 442
T 443 } // end class
444
8e725d 445 ?>