Passed
Push — develop ( 074444...1c8c15 )
by Nikolay
05:52 queued 24s
created

SSHConf::fixRights()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 24
rs 9.9
cc 3
nc 2
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->getSSHLogin();
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 getSSHLogin(): string
122
    {
123
        $sshLogin = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_LOGIN);
124
        $homeDir = $this->getUserHomeDir($sshLogin);
125
        $passwdPath = '/etc/passwd';
126
        $newEntry = "$sshLogin:x:0:0:MikoPBX Admin:$homeDir:/bin/bash\n";
127
128
        if ($sshLogin !== 'root') {
129
            // Read the current contents of the passwd file
130
            $passwdContent = file_get_contents($passwdPath);
131
            $lines = explode("\n", $passwdContent);
132
            $updated = false;
133
134
            // Check each line and update the entry if it exists
135
            foreach ($lines as &$line) {
136
                if (strpos($line, "$sshLogin:") === 0) {
137
                    $line = $newEntry;
138
                    $updated = true;
139
                    break;
140
                }
141
            }
142
            unset($line); // break the reference with the last element
143
144
            // If the entry was updated, rewrite the file
145
            if ($updated) {
146
                file_put_contents($passwdPath, implode("\n", $lines));
147
            } else {
148
                // Append the new entry if it wasn't found
149
                file_put_contents($passwdPath, $newEntry, FILE_APPEND);
150
            }
151
        }
152
153
        return $sshLogin;
154
    }
155
156
    /**
157
     * Retrieves or assigns the home directory for a specified username.
158
     *
159
     * @param string $sshLogin SSH login username.
160
     * @return string Home directory path.
161
     */
162
    private function getUserHomeDir(string $sshLogin = 'root'): string
163
    {
164
        $homeDir = ($sshLogin === 'root') ? '/root' : "/home/$sshLogin";
165
        Util::mwMkdir($homeDir);
166
        return $homeDir;
167
    }
168
169
    /**
170
     * Updates the shell password for specified SSH login.
171
     *
172
     * @param string $sshLogin SSH login username.
173
     * @return void
174
     */
175
    private function updateShellPassword(string $sshLogin = 'root'): void
176
    {
177
        $password = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_PASSWORD);
178
        $hashString = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_PASSWORD_HASH_STRING);
179
        $disablePassLogin = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_DISABLE_SSH_PASSWORD);
180
181
        $echo = Util::which('echo');
182
        $chpasswd = Util::which('chpasswd');
183
        $passwd = Util::which('passwd');
184
        Processes::mwExec("{$passwd} -l www");
185
        if ($disablePassLogin === '1') {
186
            Processes::mwExec("$passwd -l $sshLogin");
187
        } else {
188
            Processes::mwExec("$echo '$sshLogin:$password' | $chpasswd");
189
        }
190
191
        // Security hash check and notification
192
        $currentHash = md5_file('/etc/shadow');
193
        $this->mikoPBXConfig->setGeneralSettings(PbxSettingsConstants::SSH_PASSWORD_HASH_FILE, $currentHash);
194
        if ($hashString !== md5($password)) {
195
            $this->mikoPBXConfig->setGeneralSettings(PbxSettingsConstants::SSH_PASSWORD_HASH_STRING, md5($password));
196
            Notifications::sendAdminNotification('adv_SSHPasswordWasChangedSubject', ['adv_SSHPasswordWasChangedBody'], true);
197
            WorkerPrepareAdvice::afterChangeSSHConf();
198
        }
199
    }
200
201
    /**
202
     * Generates and stores the authorized_keys based on database settings for a specified SSH login.
203
     *
204
     * @param string $sshLogin SSH login username.
205
     * @return void
206
     */
207
    private function generateAuthorizedKeys(string $sshLogin = 'root'): void
208
    {
209
        $homeDir = $this->getUserHomeDir($sshLogin);
210
        $sshDir = "$homeDir/.ssh";
211
        Util::mwMkdir($sshDir);
212
213
        $authorizedKeys = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_AUTHORIZED_KEYS);
214
        file_put_contents("{$sshDir}/authorized_keys", $authorizedKeys);
215
    }
216
217
    /**
218
     * Corrects file permissions and ownership for SSH user directories.
219
     *
220
     * Sets appropriate permissions and ownership on the home directory and SSH-related files to secure the environment.
221
     *
222
     * @param string $sshLogin SSH login username.
223
     * @return void
224
     */
225
    private function fixRights(string $sshLogin = 'root'): void
226
    {
227
        $homeDir = $this->getUserHomeDir($sshLogin);
228
        $sshDir = "$homeDir/.ssh";
229
        $authorizedKeysFile = "$sshDir/authorized_keys";
230
231
        // Ensure the SSH directory exists before attempting to modify it
232
        if (!file_exists($sshDir) || !file_exists($authorizedKeysFile)) {
233
            return; // Early exit if essential directories or files are missing
234
        }
235
236
        // Commands used for modifying file permissions and ownership
237
        $chmod = Util::which('chmod');
238
        $chown = Util::which('chown');
239
240
        // Set directory permissions securely
241
        Processes::mwExec("$chmod 700 $homeDir"); // Only the user can access the home directory
242
        Processes::mwExec("$chmod 700 $sshDir");  // Only the user can access the .ssh directory
243
244
        // Set file permissions securely
245
        Processes::mwExec("$chmod 600 $authorizedKeysFile"); // Only the user can read/write authorized_keys
246
247
        // Change ownership to the user for both home and SSH directories
248
        Processes::mwExec("$chown -R $sshLogin:$sshLogin $homeDir");
249
    }
250
}