Passed
Push — develop ( 4dd23c...f02a5e )
by Портнов
05:07
created

SSHConf::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\Core\System\Configs;
21
22
23
use MikoPBX\Common\Models\PbxSettings;
24
use MikoPBX\Core\System\MikoPBXConfig;
25
use MikoPBX\Core\System\Notifications;
26
use MikoPBX\Core\System\Processes;
27
use MikoPBX\Core\System\Util;
28
use Phalcon\Di\Injectable;
29
30
class SSHConf extends Injectable
31
{
32
    private MikoPBXConfig $mikoPBXConfig;
33
    public const CHECK_PASSWORD_FILE = '/var/etc/last-check-password';
34
35
    /**
36
     * SSHConf constructor.
37
     */
38
    public function __construct()
39
    {
40
        $this->mikoPBXConfig = new MikoPBXConfig();
41
    }
42
43
    /**
44
     * Configure SSH settings
45
     **/
46
    public function configure(): bool
47
    {
48
        if(Util::isSystemctl() && ! Util::isDocker()){
49
            // Не настраиваем.
50
            return true;
51
        }
52
        $lofFile = '/var/log/lastlog';
53
        if(!file_exists($lofFile)){
54
            file_put_contents($lofFile, '');
55
        }
56
        $dropBearDir = '/etc/dropbear';
57
        Util::mwMkdir($dropBearDir);
58
59
        $keytypes = [
60
            "rsa"   => "SSHRsaKey",
61
            "dss"   => "SSHDssKey",
62
            "ecdsa" => "SSHecdsaKey"
63
        ];
64
65
        $options = ($this->mikoPBXConfig->getGeneralSettings('SSHDisablePasswordLogins') === '1')?'-s':'';
66
        // Get keys from DB
67
        $dropbearkeyPath = Util::which('dropbearkey');
68
        $dropbearPath    = Util::which('dropbear');
69
        foreach ($keytypes as $keytype => $db_key) {
70
            $res_keyfilepath = "{$dropBearDir}/dropbear_" . $keytype . "_host_key";
71
            $key             = $this->mikoPBXConfig->getGeneralSettings($db_key);
72
            $key             = (isset($key) && is_string($key)) ? trim($key) : "";
73
            if (strlen($key) > 100) {
74
                // Store key to file
75
                file_put_contents($res_keyfilepath, base64_decode($key));
76
            }
77
            // If key not exists, we will generate and store new one into file and database
78
            if ( ! file_exists($res_keyfilepath)) {
79
                // Generation
80
                Processes::mwExec("{$dropbearkeyPath} -t $keytype -f $res_keyfilepath");
81
                // Storing
82
                $new_key = base64_encode(file_get_contents($res_keyfilepath));
83
                $this->mikoPBXConfig->setGeneralSettings($db_key, $new_key);
84
            }
85
        }
86
        $ssh_port = escapeshellcmd($this->mikoPBXConfig->getGeneralSettings('SSHPort'));
87
        // Restart dropbear
88
        $this->updateShellPassword();
89
90
        Processes::killByName('dropbear');
91
        usleep(500000);
92
        $result = Processes::mwExec("{$dropbearPath} -p '{$ssh_port}' $options -c /etc/rc/hello > /var/log/dropbear_start.log");
93
        $this->generateAuthorizedKeys();
94
95
        return ($result === 0);
96
    }
97
98
    /**
99
     * Stores authorized_keys from DB to files
100
     */
101
    public function generateAuthorizedKeys(): void
102
    {
103
        $ssh_dir = '/root/.ssh';
104
        Util::mwMkdir($ssh_dir);
105
        $conf_data = $this->mikoPBXConfig->getGeneralSettings('SSHAuthorizedKeys');
106
        file_put_contents("{$ssh_dir}/authorized_keys", $conf_data);
107
    }
108
109
    /**
110
     * Setups root user password
111
     **/
112
    public function updateShellPassword(): void
113
    {
114
        $password       = $this->mikoPBXConfig->getGeneralSettings('SSHPassword');
115
        $hashString     = $this->mikoPBXConfig->getGeneralSettings('SSHPasswordHashString');
116
        $echoPath       = Util::which('echo');
117
        $chpasswdPath   = Util::which('chpasswd');
118
        Processes::mwExec("{$echoPath} \"root:$password\" | {$chpasswdPath}");
119
        $this->mikoPBXConfig->setGeneralSettings('SSHPasswordHash',       md5_file('/etc/passwd'));
120
        if($hashString !== md5($password)){
121
            $this->mikoPBXConfig->setGeneralSettings('SSHPasswordHashString', md5($password));
122
            $this->sendNotify('Attention! SSH password changed!', ['The password for SSH access to the PBX has been changed']);
123
            unlink(self::CHECK_PASSWORD_FILE);
124
        }
125
    }
126
127
    /**
128
     * @param $subject
129
     * @param $messages
130
     * @return void
131
     */
132
    private function sendNotify($subject, $messages):void
133
    {
134
        if(!Notifications::checkConnection(Notifications::TYPE_PHP_MAILER)){
135
            return;
136
        }
137
        $subject = Util::translate($subject, false);
138
        $text = '';
139
        foreach ($messages as $message){
140
            $text .= PHP_EOL.Util::translate($message, false);
141
        }
142
        $adminMail = $this->mikoPBXConfig->getGeneralSettings('SystemNotificationsEmail');
143
        $notify = new Notifications();
144
        $notify->sendMail($adminMail, $subject, trim($text));
145
    }
146
147
    /**
148
     * Проверка пароля на случай, если он был изменен не обычным способом (взлом системы).
149
     * @return void
150
     */
151
    public static function checkPassword():void
152
    {
153
        $enableNotify = true;
154
        $data = stat(self::CHECK_PASSWORD_FILE);
155
        if($data){
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
156
            $enableNotify = (time() - stat('/etc/asterisk/asterisk.conf')['mtime']??0) > 60*60*4;
157
        }
158
        $messages   = [];
159
        $password   = PbxSettings::getValueByKey('SSHPassword');
160
        $hashString = PbxSettings::getValueByKey('SSHPasswordHashString');
161
        $hashFile   = PbxSettings::getValueByKey('SSHPasswordHash');
162
        if($hashString !== md5($password)){
163
            // Изменился пароль. Не обычным способом.
164
            $messages[] = 'The SSH password was not changed from the web interface.';
165
        }
166
        if($hashFile   !== md5_file('/etc/passwd')){
167
            // Системный пароль не соответствует тому, что установлен в конфигурационном файле.
168
            $messages[] = 'The system password does not match what is set in the configuration file.';
169
        }
170
        if(!empty($messages) && $enableNotify){
171
            file_put_contents(self::CHECK_PASSWORD_FILE, time());
172
            $SSHConf = new SSHConf();
173
            $SSHConf->sendNotify('Attention! SSH password compromised', $messages);
174
        }
175
    }
176
}