Rabbit Remote Control 0.0.37
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 nRet = OnOpen(m_Session);
208 if(nRet) break;
209
210 emit sigConnected();
211
212 return bRet;
213 } while(0);
214
215 ssh_disconnect(m_Session);
216 ssh_free(m_Session);
217 m_Session = NULL;
218 return false;
219}
220
221void CChannelSSH::close()
222{
223 qDebug(log) << Q_FUNC_INFO;
224
225 if(!isOpen()) return;
226
227 OnClose();
228
229 if(m_Session) {
230 if(ssh_is_connected(m_Session))
231 ssh_disconnect(m_Session);
232 ssh_free(m_Session);
233 m_Session = NULL;
234 }
235
236 if(m_pcapFile)
237 {
238 ssh_pcap_file_close(m_pcapFile);
239 ssh_pcap_file_free(m_pcapFile);
240 m_pcapFile = nullptr;
241 }
242
243 QIODevice::close();
244}
245
246int CChannelSSH::verifyKnownhost(ssh_session session)
247{
248 int nRet = -1;
249 QString szErr;
250 ssh_key srv_pubkey = NULL;
251
252 auto &net = m_pParameter->m_Net;
253
254 nRet = ssh_get_server_publickey(session, &srv_pubkey);
255 if (nRet < 0) {
256 szErr = tr("SSH failed: Get server public key.") + " " + net.GetHost() + "; [";
257 szErr += ssh_get_error(session);
258 szErr += "]";
259 qCritical(log) << szErr;
260 setErrorString(szErr);
261 return -1;
262 }
263 unsigned char *hash = NULL;
264 size_t nLen = 0;
265 nRet = ssh_get_publickey_hash(srv_pubkey,
266 SSH_PUBLICKEY_HASH_SHA1,
267 &hash,
268 &nLen);
269 ssh_key_free(srv_pubkey);
270 if(nRet) {
271 szErr = tr("SSH failed: Get public key hash value fail.");
272 qCritical(log) << szErr;
273 setErrorString(szErr);
274 return -2;
275 }
276 QByteArray baHash((const char*)hash, nLen);
277 QString szHash = baHash.toHex(':').toStdString().c_str();
278 ssh_clean_pubkey_hash(&hash);
279
280 QMessageBox::StandardButton btRet = QMessageBox::Yes;
281 bool checkBox = false;
282 enum ssh_known_hosts_e state = ssh_session_is_known_server(session);
283 switch(state) {
284 case SSH_KNOWN_HOSTS_OK:
285 nRet = 0;
286 break;
287 case SSH_KNOWN_HOSTS_CHANGED:
288 nRet = -3;
289 szErr = net.GetHost() + " " + tr("the host key for server changed. it is now:") + "\n";
290 szErr += szHash + "\n";
291 szErr += tr("For security reasons, connection will be stopped.") + "\n";
292 szErr += tr("Please look at the OpenSSL documentation on "
293 "how to add a private CA to the store.");
294 qCritical(log) << szErr;
295 setErrorString(szErr);
296 break;
297 case SSH_KNOWN_HOSTS_OTHER:
298 nRet = -4;
299 szErr = net.GetHost() + " " + tr("the host key for this server was not found but an other type of key exists.") + "\n";
300 szErr += tr("An attacker might change the default server key to "
301 "confuse your client into thinking the key does not exist") + "\n";
302 szErr += tr("For security reasons, connection will be stopped.") + "\n";
303 szErr += tr("Please look at the OpenSSL documentation on "
304 "how to add a private CA to the store.");
305 qCritical(log) << szErr;
306 setErrorString(szErr);
307 break;
308 case SSH_KNOWN_HOSTS_NOT_FOUND:
309 nRet = -5;
310 szErr = net.GetHost() + " " + tr("is not find in known host file.") + "\n";
311 szErr += tr("If you accept the host key here, the file will be "
312 "automatically created.") + "\n";
313 szErr += tr("Host key hash:") + "\n" + szHash;
314 qDebug(log) << szErr;
315 if(!m_pBackend) break;
316 emit m_pBackend->sigBlockShowMessageBox(tr("Error"), szErr,
317#ifndef Q_OS_ANDROID
318 QMessageBox::Yes |
319#endif
320 QMessageBox::No | QMessageBox::Ignore,
321 btRet, checkBox);
322 if(QMessageBox::Yes == btRet) {
323 nRet = ssh_session_update_known_hosts(session);
324 if(nRet)
325 {
326 qCritical(log) << "ssh_session_update_known_hosts fail."
327 << ssh_get_error(session);
328 }
329 } if(QMessageBox::Ignore == btRet)
330 nRet = 0;
331 else
332 setErrorString(tr("Reject the host key"));
333 break;
334 case SSH_KNOWN_HOSTS_UNKNOWN:
335 nRet = -6;
336 szErr = net.GetHost() + " " + tr("is unknown. Do you trust the host key?") + "\n";
337 szErr += tr("Host key hash:") + "\n" + szHash;
338 qDebug(log) << szErr;
339 if(!m_pBackend) break;
340 emit m_pBackend->sigBlockShowMessageBox(tr("Error"), szErr,
341#ifndef Q_OS_ANDROID
342 QMessageBox::Yes |
343#endif
344 QMessageBox::No | QMessageBox::Ignore,
345 btRet, checkBox);
346 if(QMessageBox::Yes == btRet) {
347 nRet = ssh_session_update_known_hosts(session);
348 if (SSH_OK != nRet) {
349 qCritical(log) << "ssh_session_update_known_hosts fail."
350 << ssh_get_error(session);
351 }
352 } if(QMessageBox::Ignore == btRet)
353 nRet = 0;
354 else
355 setErrorString(tr("Reject the host key"));
356 break;
357 case SSH_KNOWN_HOSTS_ERROR:
358 nRet = -7;
359 szErr = tr("Error:") + ssh_get_error(session) + "\n";
360 szErr += net.GetHost() + " " + tr("the host key hash:") + "\n" + szHash + "\n";
361 szErr += tr("Will be stopped.");
362 qCritical(log) << szErr;
363 setErrorString(szErr);
364 emit sigError(nRet, szErr);
365 break;
366 }
367
368 return nRet;
369}
370
373 ssh_session session,
374 const QString szUser,
375 const QString szPassword,
376 const QString szPassphrase,
377 const int nMethod)
378{
379 int nRet = 0;
380 int nServerMethod = nMethod;
381
382 qDebug(log) << "Authentication method:" << nMethod;
383 //* Get authentication list from ssh server
384 nRet = ssh_userauth_none(session,
385 szUser.toStdString().c_str());
386 qDebug(log) << "ssh_userauth_none:" << nRet;
387 if(SSH_AUTH_SUCCESS == nRet)
388 return 0;
389
390 char *banner = nullptr;
391 banner = ssh_get_issue_banner(session);
392 if (banner)
393 {
394 qInfo(log) << "banner:" << banner;
395 free(banner);
396 }
397
398 nServerMethod = ssh_userauth_list(session,
399 szUser.toStdString().c_str());
400 qDebug(log) << "ssh_userauth_list:" << nServerMethod;
401 //*/
402
403 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PUBLICKEY) {
404 auto &user = m_pParameter->m_Net.m_User;
405 if(user.GetUseSystemFile()) {
406 qDebug(log) << "User authentication with ssh_userauth_publickey_auto";
407 nRet = ssh_userauth_publickey_auto(session,
408 szUser.toStdString().c_str(),
409 szPassphrase.toStdString().c_str());
410 if(SSH_AUTH_SUCCESS == nRet)
411 return 0;
412 QString szErr = tr("SSH failed: Failed authenticating with publickey:")
413 + ssh_get_error(m_Session);
414 qCritical(log) << szErr;
415 setErrorString(szErr);
416 } else {
417 qDebug(log) << "User authentication with publickey";
418 nRet = authenticationPublicKey(
419 m_Session,
420 user.GetUser(),
421 user.GetPublicKeyFile(),
422 user.GetPrivateKeyFile(),
423 user.GetPassphrase());
424 if(SSH_AUTH_SUCCESS == nRet)
425 return 0;
426 }
427 }
428
429 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PASSWORD) {
430 qDebug(log) << "User authentication with password";
431
432 nRet = ssh_userauth_password(session,
433 szUser.toStdString().c_str(),
434 szPassword.toStdString().c_str());
435 if(nRet) {
436 QString szErr = tr("Failed authenticating with password. User: ")
437 + szUser + "; "
438 + ssh_get_error(session);
439 qCritical(log) << szErr;
440 setErrorString(szErr);
441 return nRet;
442 }
443 }
444
445 return nRet;
446}
447
448int CChannelSSH::authenticationPublicKey(
449 ssh_session session,
450 const QString szUser,
451 const QString szPublicKeyFile,
452 const QString szPrivateKeyFile,
453 const QString szPassphrase)
454{
455 int nRet = 0;
456 QString szErr;
457 ssh_key publicKey = NULL;
458 ssh_key privateKey = NULL;
459
460 do {
461 if(szPublicKeyFile.isEmpty())
462 {
463 szErr = tr("SSH failed: There is not set public key file.");
464 qCritical(log) << szErr;
465 setErrorString(szErr);
466 break;
467 }
468 nRet = ssh_pki_import_pubkey_file(
469 szPublicKeyFile.toStdString().c_str(),
470 &publicKey);
471 if(SSH_OK != nRet) {
472 szErr = tr("SSH failed: Import public key fail.") + szPublicKeyFile;
473 if(SSH_EOF == nRet)
474 szErr += "\n" + tr("The file doesn't exist or permission denied:");
475 qCritical(log) << szErr;
476 setErrorString(szErr);
477 break;
478 }
479
480 nRet = ssh_userauth_try_publickey(
481 session,
482 szUser.toStdString().c_str(),
483 publicKey);
484 if(SSH_AUTH_SUCCESS != nRet)
485 {
486 szErr = tr("SSH failed: Authentication failed. User:") + szUser + "\n";
487 szErr += ssh_get_error(session);
488 qCritical(log) << szErr;
489 setErrorString(szErr);
490 break;
491 }
492
493 if(szPrivateKeyFile.isEmpty())
494 {
495 szErr = tr("SSH failed: There is not set private key file.");
496 qCritical(log) << szErr;
497 setErrorString(szErr);
498 break;
499 }
500 nRet = ssh_pki_import_privkey_file(
501 szPrivateKeyFile.toStdString().c_str(),
502 szPassphrase.toStdString().c_str(),
503 NULL, NULL, &privateKey);
504 if(SSH_OK != nRet) {
505
506 szErr = tr("SSH failed: Import private key fail.") + szPrivateKeyFile;
507 if(SSH_EOF == nRet)
508 szErr += "\n" + tr("The file doesn't exist or permission denied:");
509 qCritical(log) << szErr;
510 setErrorString(szErr);
511 break;
512 }
513
514 nRet = ssh_userauth_publickey(
515 session,
516 szUser.toStdString().c_str(),
517 privateKey);
518 if(SSH_AUTH_SUCCESS != nRet) {
519 szErr = tr("SSH failed: Authentication failed. User:") + szUser + "\n";
520 szErr += ssh_get_error(session);
521 qCritical(log) << szErr;
522 setErrorString(szErr);
523 }
524
525 } while(0);
526
527 if(publicKey)
528 ssh_key_free(publicKey);
529 if(privateKey)
530 ssh_key_free(privateKey);
531
532 return nRet;
533}
534
535int CChannelSSH::OnOpen(ssh_session session)
536{
537 int nRet = 0;
538
539 return nRet;
540}
541
542void CChannelSSH::OnClose()
543{
544}
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:27
void sigConnected()
emit when the channel is connected.
void sigError(int nErr, const QString &szErr)
emit when the channel is error
CParameterUser m_User
[Instance user]