Passed
Push — develop ( 728f79...da3e03 )
by Портнов
04:48
created

SSHConf::updateShellPassword()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 19
c 2
b 0
f 0
dl 0
loc 25
rs 9.6333
cc 4
nc 6
nop 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2024 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\Common\Models\PbxSettingsConstants;
25
use MikoPBX\Core\System\MikoPBXConfig;
26
use MikoPBX\Core\System\Notifications;
27
use MikoPBX\Core\System\Processes;
28
use MikoPBX\Core\System\Util;
29
use MikoPBX\Core\Workers\WorkerPrepareAdvice;
30
use Phalcon\Di\Injectable;
31
32
/**
33
 * SSH Configuration Management Class.
34
 *
35
 * Manages SSH configurations including password setup, service restarts, and key management.
36
 *
37
 * @package MikoPBX\Core\System\Configs
38
 */
39
class SSHConf extends Injectable
40
{
41
    private MikoPBXConfig $mikoPBXConfig;
42
43
    /**
44
     * Constructor initializing MikoPBX configuration.
45
     */
46
    public function __construct()
47
    {
48
        $this->mikoPBXConfig = new MikoPBXConfig();
49
    }
50
51
    /**
52
     * Configures SSH settings based on current system settings.
53
     *
54
     * @return bool Returns true if configuration is successful, false otherwise.
55
     */
56
    public function configure(): bool
57
    {
58
        $lofFile = '/var/log/lastlog';
59
        if (!file_exists($lofFile)) {
60
            file_put_contents($lofFile, '');
61
        }
62
        $this->generateDropbearKeys();
63
        $sshLogin = $this->getCreateSshUser();
64
        $sshPort  = escapeshellcmd(PbxSettings::getValueByKey(PbxSettingsConstants::SSH_PORT));
65
66
        // Update root password and restart SSH server
67
        $this->updateShellPassword($sshLogin);
68
69
        // Killing existing Dropbear processes before restart
70
        Processes::killByName('dropbear');
71
        usleep(500000); // Delay to ensure process has stopped
72
73
        $sshPasswordDisabled = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_DISABLE_SSH_PASSWORD) === '1';
74
        $options = $sshPasswordDisabled ? '-s' : '';
75
        $dropbear = Util::which('dropbear');
76
        $result = Processes::mwExec("$dropbear -p '$sshPort' $options -c /etc/rc/hello > /var/log/dropbear_start.log");
77
78
        $this->generateAuthorizedKeys($sshLogin);
79
        $this->fixRights($sshLogin);
80
        return ($result === 0);
81
    }
82
83
    /**
84
     * Generates or retrieves SSH keys, handling their storage and retrieval.
85
     *
86
     * @return void
87
     */
88
    private function generateDropbearKeys(): void
89
    {
90
        $keyTypes = [
91
            "rsa" => PbxSettingsConstants::SSH_RSA_KEY,
92
            "dss" => PbxSettingsConstants::SSH_DSS_KEY,
93
            "ecdsa" => PbxSettingsConstants::SSH_ECDSA_KEY,
94
            "ed25519" => PbxSettingsConstants::SSH_ED25519_KEY
95
        ];
96
97
        $dropBearDir = '/etc/dropbear';
98
        Util::mwMkdir($dropBearDir);
99
100
        $dropbearkey = Util::which('dropbearkey');
101
        // Get keys from DB
102
        foreach ($keyTypes as $keyType => $dbKey) {
103
            $resKeyFilePath = "{$dropBearDir}/dropbear_" . $keyType . "_host_key";
104
            $keyValue = trim(PbxSettings::getValueByKey($dbKey));
105
            if (strlen($keyValue) > 100) {
106
                file_put_contents($resKeyFilePath, base64_decode($keyValue));
107
            } elseif (!file_exists($resKeyFilePath)) {
108
                Processes::mwExec("$dropbearkey -t $keyType -f $resKeyFilePath");
109
                $newKey = base64_encode(file_get_contents($resKeyFilePath));
110
                $this->mikoPBXConfig->setGeneralSettings($dbKey, $newKey);
111
            }
112
        }
113
114
    }
115
116
    /**
117
     * Retrieves the designated SSH login username from settings.
118
     *
119
     * @return string SSH login username.
120
     */
121
    private function getCreateSshUser(): string
122
    {
123
        $sshLogin = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_LOGIN);
124
        $homeDir = $this->getUserHomeDir($sshLogin);
125
        // System users, you can't touch them
126
        $mainUsers = ['root', 'www'];
127
        $bbPath = Util::which('busybox');
128
        // We clean all non-system users
129
        exec("$bbPath cut -f 1 -d ':' < /etc/passwd", $systemUsers);
130
        foreach ($systemUsers as $user){
131
            if($sshLogin === $user || in_array($user, $mainUsers, true)){
132
                continue;
133
            }
134
            // Deleting the user
135
            shell_exec("$bbPath deluser $user");
136
            // Deleting the group
137
            shell_exec("$bbPath delgroup $user");
138
        }
139
        if ($sshLogin !== 'root') {
140
            // Adding a group '$sshLogin'
141
            shell_exec("$bbPath addgroup $sshLogin");
142
            // Adding user '$sshLogin'
143
            shell_exec("$bbPath adduser -h $homeDir -g 'MikoPBX SSH Admin' -s /bin/bash -G root -D '$sshLogin'");
144
            // Adding a user to the group '$sshLogin'
145
            shell_exec("$bbPath addgroup -S '$sshLogin' $sshLogin");
146
            // Adding a user to the group 'root'
147
            shell_exec("$bbPath addgroup -S '$sshLogin' root");
148
        }
149
        return $sshLogin;
150
    }
151
152
    /**
153
     * Retrieves or assigns the home directory for a specified username.
154
     *
155
     * @param string $sshLogin SSH login username.
156
     * @return string Home directory path.
157
     */
158
    private function getUserHomeDir(string $sshLogin = 'root'): string
159
    {
160
        $homeDir = ($sshLogin === 'root') ? '/root' : "/home/$sshLogin";
161
        Util::mwMkdir($homeDir);
162
        return $homeDir;
163
    }
164
165
    /**
166
     * Updates the shell password for specified SSH login.
167
     *
168
     * @param string $sshLogin SSH login username.
169
     * @return void
170
     */
171
    private function updateShellPassword(string $sshLogin = 'root'): void
172
    {
173
        $password           = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_PASSWORD);
174
        $hashString         = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_PASSWORD_HASH_STRING);
175
        $disablePassLogin   = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_DISABLE_SSH_PASSWORD);
176
177
        $echo     = Util::which('echo');
178
        $chpasswd = Util::which('chpasswd');
179
        $passwd   = Util::which('passwd');
180
        Processes::mwExec("$passwd -l www");
181
        if ($disablePassLogin === '1') {
182
            Processes::mwExec("$passwd -l $sshLogin");
183
            Processes::mwExec("$passwd -l root");
184
        } elseif($sshLogin === 'root') {
185
            Processes::mwExec("$echo '$sshLogin:$password' | $chpasswd");
186
        } else {
187
            Processes::mwExec("$passwd -l root");
188
            Processes::mwExec("$echo '$sshLogin:$password' | $chpasswd");
189
        }
190
191
        // Security hash check and notification
192
        $currentHash = md5_file('/etc/shadow');
193
        PbxSettings::setValue(PbxSettingsConstants::SSH_PASSWORD_HASH_FILE, $currentHash);
194
        if ($hashString !== md5($password)) {
195
            PbxSettings::setValue(PbxSettingsConstants::SSH_PASSWORD_HASH_STRING, md5($password));
196
        }
197
    }
198
199
    /**
200
     * Generates and stores the authorized_keys based on database settings for a specified SSH login.
201
     *
202
     * @param string $sshLogin SSH login username.
203
     * @return void
204
     */
205
    private function generateAuthorizedKeys(string $sshLogin = 'root'): void
206
    {
207
        $homeDir = $this->getUserHomeDir($sshLogin);
208
        $sshDir = "$homeDir/.ssh";
209
        Util::mwMkdir($sshDir);
210
211
        $authorizedKeys = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_AUTHORIZED_KEYS);
212
        file_put_contents("$sshDir/authorized_keys", $authorizedKeys);
213
    }
214
215
    /**
216
     * Corrects file permissions and ownership for SSH user directories.
217
     *
218
     * Sets appropriate permissions and ownership on the home directory and SSH-related files to secure the environment.
219
     *
220
     * @param string $sshLogin SSH login username.
221
     * @return void
222
     */
223
    private function fixRights(string $sshLogin = 'root'): void
224
    {
225
        $homeDir = $this->getUserHomeDir($sshLogin);
226
        $sshDir = "$homeDir/.ssh";
227
        $authorizedKeysFile = "$sshDir/authorized_keys";
228
229
        // Ensure the SSH directory exists before attempting to modify it
230
        if (!file_exists($sshDir) || !file_exists($authorizedKeysFile)) {
231
            return; // Early exit if essential directories or files are missing
232
        }
233
234
        // Commands used for modifying file permissions and ownership
235
        $chmod = Util::which('chmod');
236
        $chown = Util::which('chown');
237
238
        // Set directory permissions securely
239
        Processes::mwExec("$chmod 700 $homeDir"); // Only the user can access the home directory
240
        Processes::mwExec("$chmod 700 $sshDir");  // Only the user can access the .ssh directory
241
242
        // Set file permissions securely
243
        Processes::mwExec("$chmod 600 $authorizedKeysFile"); // Only the user can read/write authorized_keys
244
245
        // Change ownership to the user for both home and SSH directories
246
        Processes::mwExec("$chown -R $sshLogin:$sshLogin $homeDir");
247
    }
248
}