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){ |
|
|
|
|
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
|
|
|
} |
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.