Till Brehm
2014-08-14 9a916279db0f0d0b985fd7b518242f12cfc8f89f
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'])) {
257                     // delete dir
258                     $homedir = $data['old']['dir'];
259                     if(substr($homedir, -1) !== '/') $homedir .= '/';
260                     $files = array('.bash_logout', '.bash_history', '.bashrc', '.profile');
261                     $dirs = array('.ssh');
262                     foreach($files as $delfile) {
263                         if(is_file($homedir . $delfile) && fileowner($homedir . $delfile) == $userid) unlink($homedir . $delfile);
264                     }
265                     foreach($dirs as $deldir) {
266                         if(is_dir($homedir . $deldir) && fileowner($homedir . $deldir) == $userid) exec('rm -rf ' . escapeshellarg($homedir . $deldir));
267                     }
268                     $empty = true;
269                     $dirres = opendir($homedir);
270                     if($dirres) {
271                         while(($entry = readdir($dirres)) !== false) {
272                             if($entry != '.' && $entry != '..') {
273                                 $empty = false;
274                                 break;
275                             }
276                         }
277                         closedir($dirres);
278                     }
279                     if($empty == true) {
280                         rmdir($homedir);
281                     }
282                     unset($files);
283                     unset($dirs);
284                 }
285                 
ac32a4 286                 // We delete only non jailkit users, jailkit users will be deleted by the jailkit plugin.
T 287                 if ($data['old']['chroot'] != "jailkit") {
c65384 288                     $command = 'killall -u '.escapeshellcmd($data['old']['username']).' ; userdel -f';
526b99 289                     $command .= ' '.escapeshellcmd($data['old']['username']).' &> /dev/null';
ac32a4 290                     exec($command);
7fe908 291                     $app->log("Deleted shelluser: ".$data['old']['username'], LOGLEVEL_DEBUG);
ac32a4 292                 }
7fe908 293
396f0e 294             } else {
7fe908 295                 $app->log("UID = $userid for shelluser:".$data['old']['username']." not allowed.", LOGLEVEL_ERROR);
396f0e 296             }
T 297         } else {
7fe908 298             $app->log("User:".$data['new']['username']." does not exist in in /etc/passwd, skipping delete.", LOGLEVEL_WARN);
396f0e 299         }
7fe908 300
396f0e 301     }
7fe908 302
00a055 303     private function _setup_ssh_rsa() {
8ab3cd 304         global $app;
7fe908 305         $this->app->log("ssh-rsa setup shelluser_base", LOGLEVEL_DEBUG);
00a055 306         // Get the client ID, username, and the key
27c623 307         $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 308         $sys_group_data = $this->app->db->queryOneRecord('SELECT * FROM sys_group WHERE sys_group.groupid = '.intval($domain_data['sys_groupid']));
00a055 309         $id = intval($sys_group_data['client_id']);
L 310         $username= $sys_group_data['name'];
27c623 311         $client_data = $this->app->db->queryOneRecord('SELECT * FROM client WHERE client.client_id = '.$id);
00a055 312         $userkey = $client_data['ssh_rsa'];
L 313         unset($domain_data);
314         unset($client_data);
7fe908 315
00a055 316         // ssh-rsa authentication variables
5c93f0 317         //$sshrsa = $this->data['new']['ssh_rsa'];
TB 318         $sshrsa = '';
319         $ssh_users = $app->db->queryAllRecords("SELECT ssh_rsa FROM shell_user WHERE parent_domain_id = ".intval($this->data['new']['parent_domain_id']));
320         if(is_array($ssh_users)) {
321             foreach($ssh_users as $sshu) {
322                 if($sshu['ssh_rsa'] != '') $sshrsa .= "\n".$sshu['ssh_rsa'];
323             }
324         }
325         $sshrsa = trim($sshrsa);
00a055 326         $usrdir = escapeshellcmd($this->data['new']['dir']);
L 327         $sshdir = $usrdir.'/.ssh';
328         $sshkeys= $usrdir.'/.ssh/authorized_keys';
7fe908 329
8ab3cd 330         $app->uses('file');
T 331         $sshrsa = $app->file->unix_nl($sshrsa);
7fe908 332         $sshrsa = $app->file->remove_blank_lines($sshrsa, 0);
MC 333
00a055 334         // If this user has no key yet, generate a pair
8ab3cd 335         if ($userkey == '' && $id > 0){
00a055 336             //Generate ssh-rsa-keys
L 337             exec('ssh-keygen -t rsa -C '.$username.'-rsa-key-'.time().' -f /tmp/id_rsa -N ""');
7fe908 338
8ab3cd 339             // use the public key that has been generated
4bd960 340             $userkey = $app->system->file_get_contents('/tmp/id_rsa.pub');
7fe908 341
00a055 342             // save keypair in client table
4bd960 343             $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 344
4bd960 345             $app->system->unlink('/tmp/id_rsa');
T 346             $app->system->unlink('/tmp/id_rsa.pub');
7fe908 347             $this->app->log("ssh-rsa keypair generated for ".$username, LOGLEVEL_DEBUG);
00a055 348         };
8ab3cd 349
T 350         if (!file_exists($sshkeys)){
00a055 351             // add root's key
8cf78b 352             $app->file->mkdirs($sshdir, '0700');
4bd960 353             if(is_file('/root/.ssh/authorized_keys')) $app->system->file_put_contents($sshkeys, $app->system->file_get_contents('/root/.ssh/authorized_keys'));
7fe908 354
8ab3cd 355             // Remove duplicate keys
8cf78b 356             $existing_keys = @file($sshkeys);
8ab3cd 357             $new_keys = explode("\n", $userkey);
8cf78b 358             $final_keys_arr = @array_merge($existing_keys, $new_keys);
8ab3cd 359             $new_final_keys_arr = array();
T 360             if(is_array($final_keys_arr) && !empty($final_keys_arr)){
361                 foreach($final_keys_arr as $key => $val){
362                     $new_final_keys_arr[$key] = trim($val);
363                 }
364             }
365             $final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
7fe908 366
00a055 367             // add the user's key
4bd960 368             $app->system->file_put_contents($sshkeys, $final_keys);
8ab3cd 369             $app->file->remove_blank_lines($sshkeys);
7fe908 370             $this->app->log("ssh-rsa authorisation keyfile created in ".$sshkeys, LOGLEVEL_DEBUG);
00a055 371         }
7fe908 372
8cf78b 373         //* Get the keys
T 374         $existing_keys = file($sshkeys);
375         $new_keys = explode("\n", $sshrsa);
7fe908 376         $old_keys = explode("\n", $this->data['old']['ssh_rsa']);
MC 377
8cf78b 378         //* Remove all old keys
T 379         if(is_array($old_keys)) {
380             foreach($old_keys as $key => $val) {
7fe908 381                 $k = array_search(trim($val), $existing_keys);
8cf78b 382                 unset($existing_keys[$k]);
T 383             }
00a055 384         }
7fe908 385
8cf78b 386         //* merge the remaining keys and the ones fom the ispconfig database.
T 387         if(is_array($new_keys)) {
388             $final_keys_arr = array_merge($existing_keys, $new_keys);
389         } else {
390             $final_keys_arr = $existing_keys;
391         }
7fe908 392
8cf78b 393         $new_final_keys_arr = array();
T 394         if(is_array($final_keys_arr) && !empty($final_keys_arr)){
395             foreach($final_keys_arr as $key => $val){
396                 $new_final_keys_arr[$key] = trim($val);
397             }
398         }
399         $final_keys = implode("\n", array_flip(array_flip($new_final_keys_arr)));
7fe908 400
MC 401         // add the custom key
4bd960 402         $app->system->file_put_contents($sshkeys, $final_keys);
8cf78b 403         $app->file->remove_blank_lines($sshkeys);
7fe908 404         $this->app->log("ssh-rsa key updated in ".$sshkeys, LOGLEVEL_DEBUG);
MC 405
00a055 406         // set proper file permissions
8cf78b 407         exec("chown -R ".escapeshellcmd($this->data['new']['puser']).":".escapeshellcmd($this->data['new']['pgroup'])." ".$sshdir);
00a055 408         exec("chmod 600 '$sshkeys'");
7fe908 409
08c588 410     }
7fe908 411
396f0e 412
T 413 } // end class
414
8e725d 415 ?>