Rabbit Remote Control 0.1.0-bate10
Loading...
Searching...
No Matches
ChannelSSH.cpp
1#include "ChannelSSH.h"
2
3#include <QLoggingCategory>
4
5static Q_LOGGING_CATEGORY(log, "SSH")
6static Q_LOGGING_CATEGORY(logSSH, "SSH.log")
7
9 bool bWakeUp, QObject *parent)
10 : CChannel{parent}
11 , m_Session(nullptr)
12 , m_Channel(nullptr)
13 , m_pBackend(pBackend)
14 , m_pParameter(pPara)
15 , m_pEvent(nullptr)
16 , m_pcapFile(nullptr)
17{
18 if(bWakeUp)
19 m_pEvent = new Channel::CEvent(this);
20}
21
22CChannelSSH::~CChannelSSH()
23{
24 qDebug(log) << Q_FUNC_INFO;
25}
26
28{
29 QString szDetail = " - " + tr("libssh version:") + " " + ssh_version(0) + "\n";
30 szDetail += " - " + tr("libssh protocol version:") + " " + QString::number(ssh_get_version(m_Session)) + "\n";
31 szDetail += " - " + tr("OpenSSH server:") + " " + QString::number(ssh_get_openssh_version(m_Session)) + "\n";
32 return szDetail;
33}
34
35void CChannelSSH::cb_log(ssh_session session,
36 int priority,
37 const char *message,
38 void *userdata)
39{
40 switch (priority) {
41 case SSH_LOG_WARN:
42 qWarning(logSSH) << message;
43 break;
44 case SSH_LOG_INFO:
45 qInfo(logSSH) << message;
46 case SSH_LOG_DEBUG:
47 case SSH_LOG_TRACE:
48 qDebug(logSSH) << message;
49 default:
50 break;
51 }
52}
53
54int CChannelSSH::WakeUp()
55{
56 if(!m_pEvent) return 0;
57 return m_pEvent->WakeUp();
58}
59
60bool CChannelSSH::open(OpenMode mode)
61{
62 int nRet = 0;
63 QString szErr;
64
65 if(!m_pParameter) {
66 qCritical(log) << "The parameter is null";
67 }
68 Q_ASSERT(m_pParameter);
69
70 bool bRet = QIODevice::open(mode);
71 if(!bRet)
72 return bRet;
73
74 m_Session = ssh_new();
75 if(NULL == m_Session)
76 {
77 szErr = tr("SSH failed: ssh_new.");
78 qCritical(log) << szErr;
79 setErrorString(szErr);
80 return false;
81 }
82
83 do{
84 struct ssh_callbacks_struct cb;
85 memset(&cb, 0, sizeof(struct ssh_callbacks_struct));
86 cb.userdata = this;
87 cb.log_function = cb_log;
88 ssh_callbacks_init(&cb);
89 ssh_set_callbacks(m_Session, &cb);
90 ssh_set_log_level(SSH_LOG_TRACE);
91
92 /*
93 int value = 1;
94 ssh_options_set(m_Session, SSH_OPTIONS_NODELAY, (const void*)&value);//*/
95
96 /*
97 See: [ssh_options_set()](https://api.libssh.org/stable/group__libssh__session.html#ga7a801b85800baa3f4e16f5b47db0a73d)
98 SSH_LOG_NOLOG: No logging
99 SSH_LOG_WARNING: Only warnings
100 SSH_LOG_PROTOCOL: High level protocol information
101 SSH_LOG_PACKET: Lower level protocol information, packet level
102 SSH_LOG_FUNCTIONS: Every function path The default is SSH_LOG_NOLOG.
103 */
104 int verbosity = SSH_LOG_NOLOG;
105 ssh_options_set(m_Session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
106
107 auto &net = m_pParameter->m_Net;
108 if(net.GetHost().isEmpty()) {
109 szErr = tr("SSH failed: the server is empty");
110 qCritical(log) << szErr;
111 setErrorString(szErr);
112 break;
113 } else {
114 nRet = ssh_options_set(m_Session, SSH_OPTIONS_HOST,
115 net.GetHost().toStdString().c_str());
116 if(nRet) {
117 szErr = tr("SSH failed: Set host fail. host:")
118 + net.GetHost() + ";"
119 + ssh_get_error(m_Session);
120 qCritical(log) << szErr;
121 setErrorString(szErr);
122 break;
123 }
124 }
125
126 uint nPort = net.GetPort();
127 nRet = ssh_options_set(m_Session, SSH_OPTIONS_PORT, &nPort);
128 if(nRet) {
129 szErr = tr("SSH failed: Set port fail. port:")
130 + QString::number(net.GetPort()) + ";"
131 + ssh_get_error(m_Session);
132 qCritical(log) << szErr;
133 setErrorString(szErr);
134 break;
135 }
136
137 if(!m_pParameter->GetPcapFile().isEmpty())
138 {
139 m_pcapFile = ssh_pcap_file_new();
140 if(m_pcapFile) {
141 if (ssh_pcap_file_open(
142 m_pcapFile, m_pParameter->GetPcapFile().toStdString().c_str())
143 == SSH_ERROR) {
144 qCritical(log) << "SSH failed: Error opening pcap file: "
145 << m_pParameter->GetPcapFile();
146 ssh_pcap_file_free(m_pcapFile);
147 m_pcapFile = nullptr;
148 }
149 if(m_pcapFile)
150 ssh_set_pcap_file(m_Session, m_pcapFile);
151 } else {
152 szErr = tr("SSH failed: ssh_pcap_file_new: ")
153 + ssh_get_error(m_Session);
154 qCritical(log) << szErr;
155 }
156 }
157
158 nRet = ssh_connect(m_Session);
159 if(nRet) {
160 szErr = tr("SSH failed: ssh connect ")
161 + net.GetHost()
162 + ":" + QString::number(net.GetPort())
163 + " ("
164 + ssh_get_error(m_Session);
165 szErr += ")";
166 qCritical(log) << szErr;
167 setErrorString(szErr);
168 break;
169 }
170
171 nRet = verifyKnownhost(m_Session);
172 if(nRet){
173 break;
174 }
175
176 auto &user = m_pParameter->m_Net.m_User;
177 if(((user.GetUsedType() == CParameterUser::TYPE::UserPassword)
178 && (user.GetPassword().isEmpty() || user.GetUser().isEmpty()))
179 || ((user.GetUsedType() == CParameterUser::TYPE::PublicKey)
180 && user.GetPassphrase().isEmpty())
181 && m_pBackend) {
182 emit m_pBackend->sigBlockShowWidget("CDlgUserPassword", nRet, &m_pParameter->m_Net);
183 if(QDialog::Accepted != nRet)
184 {
185 setErrorString(tr("User cancel"));
186 return false;
187 }
188 }
189
190 int nMeth = SSH_AUTH_METHOD_PUBLICKEY;
191 switch(user.GetUsedType()) {
192 case CParameterUser::TYPE::UserPassword:
193 nMeth = SSH_AUTH_METHOD_PASSWORD;
194 break;
195 case CParameterUser::TYPE::PublicKey:
196 nMeth = SSH_AUTH_METHOD_PUBLICKEY;
197 break;
198 }
199
200 nRet = authentication(m_Session,
201 user.GetUser(),
202 user.GetPassword(),
203 user.GetPassphrase(),
204 nMeth);
205 if(nRet) break;
206
207 emit sigSecurityLevel(CSecurityLevel::Level::SecureMask);
208
209 nRet = OnOpen(m_Session);
210 if(nRet) break;
211
212 emit sigConnected();
213
214 return bRet;
215 } while(0);
216
217 ssh_disconnect(m_Session);
218 ssh_free(m_Session);
219 m_Session = NULL;
220 return false;
221}
222
223void CChannelSSH::close()
224{
225 qDebug(log) << Q_FUNC_INFO;
226
227 if(!isOpen()) return;
228
229 OnClose();
230
231 if(m_Session) {
232 if(ssh_is_connected(m_Session))
233 ssh_disconnect(m_Session);
234 ssh_free(m_Session);
235 m_Session = NULL;
236 }
237
238 if(m_pcapFile)
239 {
240 ssh_pcap_file_close(m_pcapFile);
241 ssh_pcap_file_free(m_pcapFile);
242 m_pcapFile = nullptr;
243 }
244
245 QIODevice::close();
246}
247
248int CChannelSSH::verifyKnownhost(ssh_session session)
249{
250 int nRet = -1;
251 QString szErr;
252 ssh_key srv_pubkey = NULL;
253
254 auto &net = m_pParameter->m_Net;
255
256 nRet = ssh_get_server_publickey(session, &srv_pubkey);
257 if (nRet < 0) {
258 szErr = tr("SSH failed: Get server public key.") + " " + net.GetHost() + "; [";
259 szErr += ssh_get_error(session);
260 szErr += "]";
261 qCritical(log) << szErr;
262 setErrorString(szErr);
263 return -1;
264 }
265 unsigned char *hash = NULL;
266 size_t nLen = 0;
267 nRet = ssh_get_publickey_hash(srv_pubkey,
268 SSH_PUBLICKEY_HASH_SHA1,
269 &hash,
270 &nLen);
271 ssh_key_free(srv_pubkey);
272 if(nRet) {
273 szErr = tr("SSH failed: Get public key hash value fail.");
274 qCritical(log) << szErr;
275 setErrorString(szErr);
276 return -2;
277 }
278 QByteArray baHash((const char*)hash, nLen);
279 QString szHash = baHash.toHex(':').toStdString().c_str();
280 ssh_clean_pubkey_hash(&hash);
281
282 QMessageBox::StandardButton btRet = QMessageBox::Yes;
283 bool checkBox = false;
284 enum ssh_known_hosts_e state = ssh_session_is_known_server(session);
285 switch(state) {
286 case SSH_KNOWN_HOSTS_OK:
287 nRet = 0;
288 break;
289 case SSH_KNOWN_HOSTS_CHANGED:
290 nRet = -3;
291 szErr = net.GetHost() + " " + tr("the host key for server changed. it is now:") + "\n";
292 szErr += szHash + "\n";
293 szErr += tr("For security reasons, connection will be stopped.") + "\n";
294 szErr += tr("Please look at the OpenSSL documentation on "
295 "how to add a private CA to the store.");
296 qCritical(log) << szErr;
297 setErrorString(szErr);
298 break;
299 case SSH_KNOWN_HOSTS_OTHER:
300 nRet = -4;
301 szErr = net.GetHost() + " " + tr("the host key for this server was not found but an other type of key exists.") + "\n";
302 szErr += tr("An attacker might change the default server key to "
303 "confuse your client into thinking the key does not exist") + "\n";
304 szErr += tr("For security reasons, connection will be stopped.") + "\n";
305 szErr += tr("Please look at the OpenSSL documentation on "
306 "how to add a private CA to the store.");
307 qCritical(log) << szErr;
308 setErrorString(szErr);
309 break;
310 case SSH_KNOWN_HOSTS_NOT_FOUND:
311 nRet = -5;
312 szErr = net.GetHost() + " " + tr("is not find in known host file.") + "\n";
313 szErr += tr("If you accept the host key here, the file will be "
314 "automatically created.") + "\n";
315 szErr += tr("Host key hash:") + "\n" + szHash;
316 qDebug(log) << szErr;
317 if(!m_pBackend) break;
318 emit m_pBackend->sigBlockShowMessageBox(
319 tr("Error"), szErr,
320#ifndef Q_OS_ANDROID
321 QMessageBox::Yes |
322#endif
323 QMessageBox::No | QMessageBox::Ignore,
324 btRet, checkBox);
325 if(QMessageBox::Yes == btRet) {
326 nRet = ssh_session_update_known_hosts(session);
327 if(nRet)
328 {
329 qCritical(log) << "ssh_session_update_known_hosts fail."
330 << ssh_get_error(session);
331 }
332 } if(QMessageBox::Ignore == btRet)
333 nRet = 0;
334 else
335 setErrorString(tr("Reject the host key"));
336 break;
337 case SSH_KNOWN_HOSTS_UNKNOWN:
338 nRet = -6;
339 szErr = net.GetHost() + " " + tr("is unknown. Do you trust the host key?") + "\n";
340 szErr += tr("Host key hash:") + "\n" + szHash;
341 qDebug(log) << szErr;
342 if(!m_pBackend) break;
343 emit m_pBackend->sigBlockShowMessageBox(
344 tr("Error"), szErr,
345#ifndef Q_OS_ANDROID
346 QMessageBox::Yes |
347#endif
348 QMessageBox::No | QMessageBox::Ignore,
349 btRet, checkBox);
350 if(QMessageBox::Yes == btRet) {
351 nRet = ssh_session_update_known_hosts(session);
352 if (SSH_OK != nRet) {
353 qCritical(log) << "ssh_session_update_known_hosts fail."
354 << ssh_get_error(session);
355 }
356 } if(QMessageBox::Ignore == btRet)
357 nRet = 0;
358 else
359 setErrorString(tr("Reject the host key"));
360 break;
361 case SSH_KNOWN_HOSTS_ERROR:
362 nRet = -7;
363 szErr = tr("Error:") + ssh_get_error(session) + "\n";
364 szErr += net.GetHost() + " " + tr("the host key hash:") + "\n" + szHash + "\n";
365 szErr += tr("Will be stopped.");
366 qCritical(log) << szErr;
367 setErrorString(szErr);
368 emit sigError(nRet, szErr);
369 break;
370 }
371
372 return nRet;
373}
374
377 ssh_session session,
378 const QString szUser,
379 const QString szPassword,
380 const QString szPassphrase,
381 const int nMethod)
382{
383 int nRet = 0;
384 int nServerMethod = nMethod;
385
386 qDebug(log) << "Authentication method:" << nMethod;
387 //* Get authentication list from ssh server
388 nRet = ssh_userauth_none(session,
389 szUser.toStdString().c_str());
390 qDebug(log) << "ssh_userauth_none:" << nRet;
391 if(SSH_AUTH_SUCCESS == nRet)
392 return 0;
393
394 char *banner = nullptr;
395 banner = ssh_get_issue_banner(session);
396 if (banner)
397 {
398 qInfo(log) << "banner:" << banner;
399 free(banner);
400 }
401
402 nServerMethod = ssh_userauth_list(session,
403 szUser.toStdString().c_str());
404 qDebug(log) << "ssh_userauth_list:" << nServerMethod;
405 //*/
406
407 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PUBLICKEY) {
408 auto &user = m_pParameter->m_Net.m_User;
409 if(user.GetUseSystemFile()) {
410 qDebug(log) << "User authentication with ssh_userauth_publickey_auto";
411 nRet = ssh_userauth_publickey_auto(session,
412 szUser.toStdString().c_str(),
413 szPassphrase.toStdString().c_str());
414 if(SSH_AUTH_SUCCESS == nRet)
415 return 0;
416 QString szErr = tr("SSH failed: Failed authenticating with publickey:")
417 + ssh_get_error(m_Session);
418 qCritical(log) << szErr;
419 setErrorString(szErr);
420 } else {
421 qDebug(log) << "User authentication with publickey";
422 nRet = authenticationPublicKey(
423 m_Session,
424 user.GetUser(),
425 user.GetPublicKeyFile(),
426 user.GetPrivateKeyFile(),
427 user.GetPassphrase());
428 if(SSH_AUTH_SUCCESS == nRet)
429 return 0;
430 }
431 }
432
433 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PASSWORD) {
434 qDebug(log) << "User authentication with password";
435
436 nRet = ssh_userauth_password(session,
437 szUser.toStdString().c_str(),
438 szPassword.toStdString().c_str());
439 if(nRet) {
440 QString szErr = tr("Failed authenticating with password. User: ")
441 + szUser + "; "
442 + ssh_get_error(session);
443 qCritical(log) << szErr;
444 setErrorString(szErr);
445 return nRet;
446 }
447 }
448
449 return nRet;
450}
451
452int CChannelSSH::authenticationPublicKey(
453 ssh_session session,
454 const QString szUser,
455 const QString szPublicKeyFile,
456 const QString szPrivateKeyFile,
457 const QString szPassphrase)
458{
459 int nRet = 0;
460 QString szErr;
461 ssh_key publicKey = NULL;
462 ssh_key privateKey = NULL;
463
464 do {
465 if(szPublicKeyFile.isEmpty())
466 {
467 szErr = tr("SSH failed: There is not set public key file.");
468 qCritical(log) << szErr;
469 setErrorString(szErr);
470 break;
471 }
472 nRet = ssh_pki_import_pubkey_file(
473 szPublicKeyFile.toStdString().c_str(),
474 &publicKey);
475 if(SSH_OK != nRet) {
476 szErr = tr("SSH failed: Import public key fail.") + szPublicKeyFile;
477 if(SSH_EOF == nRet)
478 szErr += "\n" + tr("The file doesn't exist or permission denied:");
479 qCritical(log) << szErr;
480 setErrorString(szErr);
481 break;
482 }
483
484 nRet = ssh_userauth_try_publickey(
485 session,
486 szUser.toStdString().c_str(),
487 publicKey);
488 if(SSH_AUTH_SUCCESS != nRet)
489 {
490 szErr = tr("SSH failed: Authentication failed. User:") + szUser + "\n";
491 szErr += ssh_get_error(session);
492 qCritical(log) << szErr;
493 setErrorString(szErr);
494 break;
495 }
496
497 if(szPrivateKeyFile.isEmpty())
498 {
499 szErr = tr("SSH failed: There is not set private key file.");
500 qCritical(log) << szErr;
501 setErrorString(szErr);
502 break;
503 }
504 nRet = ssh_pki_import_privkey_file(
505 szPrivateKeyFile.toStdString().c_str(),
506 szPassphrase.toStdString().c_str(),
507 NULL, NULL, &privateKey);
508 if(SSH_OK != nRet) {
509 szErr = tr("SSH failed: Import private key fail.") + szPrivateKeyFile;
510 if(SSH_EOF == nRet)
511 szErr += "\n" + tr("The file doesn't exist or permission denied:");
512 qCritical(log) << szErr;
513 setErrorString(szErr);
514 break;
515 }
516
517 nRet = ssh_userauth_publickey(
518 session,
519 szUser.toStdString().c_str(),
520 privateKey);
521 if(SSH_AUTH_SUCCESS != nRet) {
522 szErr = tr("SSH failed: Authentication failed. User:") + szUser + "\n";
523 szErr += ssh_get_error(session);
524 qCritical(log) << szErr;
525 setErrorString(szErr);
526 }
527 } while(0);
528
529 if(publicKey)
530 ssh_key_free(publicKey);
531 if(privateKey)
532 ssh_key_free(privateKey);
533
534 return nRet;
535}
536
537int CChannelSSH::OnOpen(ssh_session session)
538{
539 int nRet = 0;
540
541 return nRet;
542}
543
544void CChannelSSH::OnClose()
545{
546}
Backend interface.
Definition Backend.h:42
void sigBlockShowMessageBox(const QString &szTitle, const QString &szMessage, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton &nRet, bool &checkBox, QString checkBoxContext=QString())
Block background threads and display message dialogs in foreground threads (QMessageBox)
void sigBlockShowWidget(const QString &className, int &nRet, void *pContext)
Blocks the background thread and displays the window in the foreground thread.
virtual QString GetDetails() override
Depend on information.
int authentication(ssh_session session, const QString szUser, const QString szPassword, const QString szPassphrase, const int nMethod=SSH_AUTH_METHOD_PASSWORD)
virtual bool open(OpenMode mode) override
The channel interface class.
Definition Channel.h:28
void sigConnected()
emit when the channel is connected.
void sigError(int nErr, const QString &szErr)
emit when the channel is error
void sigSecurityLevel(CSecurityLevel::Levels level)
Triggered when the security level changes.
CParameterUser m_User
[Instance user]