Till Brehm
2014-08-14 65a0b9c10da329a4c9df4f00fb7e26ce04a3c203
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
9edea9 242         $app->uses('system,getconf');
TB 243         
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) {
892d73 254                 // check if we have to delete the dir
MC 255                 $check = $app->db->queryOneRecord('SELECT shell_user_id FROM `shell_user` WHERE `dir` = \'' . $app->db->quote($data['old']['dir']) . '\'');
256                 if(!$check && is_dir($data['old']['dir'])) {
a7e4ec 257                     
TB 258                     $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($data['old']['parent_domain_id']));
259                     
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") {
c65384 295                     $command = 'killall -u '.escapeshellcmd($data['old']['username']).' ; userdel -f';
526b99 296                     $command .= ' '.escapeshellcmd($data['old']['username']).' &> /dev/null';
ac32a4 297                     exec($command);
7fe908 298                     $app->log("Deleted shelluser: ".$data['old']['username'], LOGLEVEL_DEBUG);
ac32a4 299                 }
7fe908 300
396f0e 301             } else {
7fe908 302                 $app->log("UID = $userid for shelluser:".$data['old']['username']." not allowed.", LOGLEVEL_ERROR);
396f0e 303             }
T 304         } else {
7fe908 305             $app->log("User:".$data['new']['username']." does not exist in in /etc/passwd, skipping delete.", LOGLEVEL_WARN);
396f0e 306         }
7fe908 307
396f0e 308     }
7fe908 309
00a055 310     private function _setup_ssh_rsa() {
8ab3cd 311         global $app;
7fe908 312         $this->app->log("ssh-rsa setup shelluser_base", LOGLEVEL_DEBUG);
00a055 313         // Get the client ID, username, and the key
27c623 314         $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 315         $sys_group_data = $this->app->db->queryOneRecord('SELECT * FROM sys_group WHERE sys_group.groupid = '.intval($domain_data['sys_groupid']));
00a055 316         $id = intval($sys_group_data['client_id']);
L 317         $username= $sys_group_data['name'];
27c623 318         $client_data = $this->app->db->queryOneRecord('SELECT * FROM client WHERE client.client_id = '.$id);
00a055 319         $userkey = $client_data['ssh_rsa'];
L 320         unset($domain_data);
321         unset($client_data);
7fe908 322
00a055 323         // ssh-rsa authentication variables
5c93f0 324         //$sshrsa = $this->data['new']['ssh_rsa'];
TB 325         $sshrsa = '';
326         $ssh_users = $app->db->queryAllRecords("SELECT ssh_rsa FROM shell_user WHERE parent_domain_id = ".intval($this->data['new']['parent_domain_id']));
327         if(is_array($ssh_users)) {
328             foreach($ssh_users as $sshu) {
329                 if($sshu['ssh_rsa'] != '') $sshrsa .= "\n".$sshu['ssh_rsa'];
330             }
331         }
332         $sshrsa = trim($sshrsa);
00a055 333         $usrdir = escapeshellcmd($this->data['new']['dir']);
L 334         $sshdir = $usrdir.'/.ssh';
335         $sshkeys= $usrdir.'/.ssh/authorized_keys';
7fe908 336
8ab3cd 337         $app->uses('file');
T 338         $sshrsa = $app->file->unix_nl($sshrsa);
7fe908 339         $sshrsa = $app->file->remove_blank_lines($sshrsa, 0);
MC 340
00a055 341         // If this user has no key yet, generate a pair
8ab3cd 342         if ($userkey == '' && $id > 0){
00a055 343             //Generate ssh-rsa-keys
L 344             exec('ssh-keygen -t rsa -C '.$username.'-rsa-key-'.time().' -f /tmp/id_rsa -N ""');
7fe908 345
8ab3cd 346             // use the public key that has been generated
4bd960 347             $userkey = $app->system->file_get_contents('/tmp/id_rsa.pub');
7fe908 348
00a055 349             // save keypair in client table
4bd960 350             $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 351
4bd960 352             $app->system->unlink('/tmp/id_rsa');
T 353             $app->system->unlink('/tmp/id_rsa.pub');
7fe908 354             $this->app->log("ssh-rsa keypair generated for ".$username, LOGLEVEL_DEBUG);
00a055 355         };
8ab3cd 356
T 357         if (!file_exists($sshkeys)){
00a055 358             // add root's key
8cf78b 359             $app->file->mkdirs($sshdir, '0700');
4bd960 360             if(is_file('/root/.ssh/authorized_keys')) $app->system->file_put_contents($sshkeys, $app->system->file_get_contents('/root/.ssh/authorized_keys'));
7fe908 361
8ab3cd 362             // Remove duplicate keys
8cf78b 363             $existing_keys = @file($sshkeys);
8ab3cd 364             $new_keys = explode("\n", $userkey);
8cf78b 365             $final_keys_arr = @array_merge($existing_keys, $new_keys);
8ab3cd 366             $new_final_keys_arr = array();
T 367             if(is_array($final_keys_arr) && !empty($final_keys_arr)){
368                 foreach($final_keys_arr as $key => $val){
369                     $new_final_keys_arr[$key] = trim($val);
370                 }
371             }
372             $final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
7fe908 373
00a055 374             // add the user's key
4bd960 375             $app->system->file_put_contents($sshkeys, $final_keys);
8ab3cd 376             $app->file->remove_blank_lines($sshkeys);
7fe908 377             $this->app->log("ssh-rsa authorisation keyfile created in ".$sshkeys, LOGLEVEL_DEBUG);
00a055 378         }
7fe908 379
8cf78b 380         //* Get the keys
T 381         $existing_keys = file($sshkeys);
382         $new_keys = explode("\n", $sshrsa);
7fe908 383         $old_keys = explode("\n", $this->data['old']['ssh_rsa']);
MC 384
8cf78b 385         //* Remove all old keys
T 386         if(is_array($old_keys)) {
387             foreach($old_keys as $key => $val) {
7fe908 388                 $k = array_search(trim($val), $existing_keys);
8cf78b 389                 unset($existing_keys[$k]);
T 390             }
00a055 391         }
7fe908 392
8cf78b 393         //* merge the remaining keys and the ones fom the ispconfig database.
T 394         if(is_array($new_keys)) {
395             $final_keys_arr = array_merge($existing_keys, $new_keys);
396         } else {
397             $final_keys_arr = $existing_keys;
398         }
7fe908 399
8cf78b 400         $new_final_keys_arr = array();
T 401         if(is_array($final_keys_arr) && !empty($final_keys_arr)){
402             foreach($final_keys_arr as $key => $val){
403                 $new_final_keys_arr[$key] = trim($val);
404             }
405         }
406         $final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
7fe908 407
MC 408         // add the custom key
4bd960 409         $app->system->file_put_contents($sshkeys, $final_keys);
8cf78b 410         $app->file->remove_blank_lines($sshkeys);
7fe908 411         $this->app->log("ssh-rsa key updated in ".$sshkeys, LOGLEVEL_DEBUG);
MC 412
00a055 413         // set proper file permissions
8cf78b 414         exec("chown -R ".escapeshellcmd($this->data['new']['puser']).":".escapeshellcmd($this->data['new']['pgroup'])." ".$sshdir);
00a055 415         exec("chmod 600 '$sshkeys'");
7fe908 416
08c588 417     }
7fe908 418
396f0e 419
T 420 } // end class
421
8e725d 422 ?>