3#include "ChannelSSHTunnel.h"
4#include <QLoggingCategory>
7#include <QAbstractEventDispatcher>
8#include <QScopedArrayPointer>
10#if defined(Q_OS_LINUX)
11 #include <sys/eventfd.h>
18static Q_LOGGING_CATEGORY(log,
"Channel.SSH.Tunnel")
19static Q_LOGGING_CATEGORY(logSSH, "Channel.SSH.log")
29 m_Parameter(parameter),
30 m_pSocketRead(
nullptr),
31 m_pSocketWrite(
nullptr),
32 m_pSocketException(
nullptr),
35 qDebug(log) <<
"CChannelSSHTunnel::CChannelSSHTunnel()";
36 qDebug(log) <<
"libssh version:" << ssh_version(0);
37 Q_ASSERT(m_Parameter);
42CChannelSSHTunnel::~CChannelSSHTunnel()
44 qDebug(log) <<
"CChannelSSHTunnel::~CChannelSSHTunnel()";
49QString CChannelSSHTunnel::GetDetails()
51 return tr(
"- libssh version: ") + ssh_version(0);
54void CChannelSSHTunnel::cb_log(ssh_session session,
61 qWarning(logSSH) << message;
64 qInfo(logSSH) << message;
67 qDebug(logSSH) << message;
73int CChannelSSHTunnel::WakeUp()
75 if(!m_pEvent)
return 0;
76 return m_pEvent->WakeUp();
84 m_Session = ssh_new();
87 szErr = tr(
"SSH failed: ssh_new.");
88 qCritical(log) << szErr;
89 setErrorString(szErr);
95 qCritical(log) <<
"The parameter is null";
97 Q_ASSERT(m_Parameter);
99 struct ssh_callbacks_struct cb;
100 memset(&cb, 0,
sizeof(
struct ssh_callbacks_struct));
102 cb.log_function = cb_log;;
103 ssh_callbacks_init(&cb);
104 ssh_set_callbacks(m_Session, &cb);
118 int verbosity = SSH_LOG_NOLOG;
119 ssh_options_set(m_Session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
121 if(!m_Parameter->GetServer().isEmpty()){
122 nRet = ssh_options_set(m_Session, SSH_OPTIONS_HOST,
123 m_Parameter->GetServer().toStdString().c_str());
125 szErr = tr(
"SSH failed: Set host fail. host:")
126 + m_Parameter->GetServer() +
";"
127 + ssh_get_error(m_Session);
128 qCritical(log) << szErr;
129 setErrorString(szErr);
134 uint nPort = m_Parameter->GetPort();
135 nRet = ssh_options_set(m_Session, SSH_OPTIONS_PORT, &nPort);
137 szErr = tr(
"SSH failed: Set port fail. port:")
138 + QString::number(m_Parameter->GetPort()) +
";"
139 + ssh_get_error(m_Session);
140 qCritical(log) << szErr;
141 setErrorString(szErr);
145 if(!m_Parameter->GetPcapFile().isEmpty())
147 m_pcapFile = ssh_pcap_file_new();
149 if (ssh_pcap_file_open(m_pcapFile,
150 m_Parameter->GetPcapFile().toStdString().c_str())
152 qCritical(log) <<
"SSH failed: Error opening pcap file: "
153 << m_Parameter->GetPcapFile();
154 ssh_pcap_file_free(m_pcapFile);
155 m_pcapFile =
nullptr;
158 ssh_set_pcap_file(m_Session, m_pcapFile);
160 szErr = tr(
"SSH failed: ssh_pcap_file_new: ")
161 + ssh_get_error(m_Session);
162 qCritical(log) << szErr;
166 nRet = ssh_connect(m_Session);
168 szErr = tr(
"SSH failed: ssh connect ")
169 + m_Parameter->GetServer()
170 +
":" + QString::number(m_Parameter->GetPort())
172 + ssh_get_error(m_Session);
174 qCritical(log) << szErr;
175 setErrorString(szErr);
179 nRet = verifyKnownhost(m_Session);
182 if(((m_Parameter->GetPassword().isEmpty()
183 || m_Parameter->GetUser().isEmpty())
184 && m_Parameter->GetAuthenticationMethod() == SSH_AUTH_METHOD_PASSWORD)
185 || (m_Parameter->GetAuthenticationMethod() == SSH_AUTH_METHOD_PUBLICKEY
186 && m_Parameter->GetPassphrase().isEmpty())
189 if(QDialog::Accepted != nRet)
191 setErrorString(tr(
"User cancel"));
197 m_Parameter->GetUser(),
198 m_Parameter->GetPassword(),
199 m_Parameter->GetPassphrase(),
200 m_Parameter->GetAuthenticationMethod());
203 nRet = forward(m_Session);
206 return QIODevice::open(mode);
209 ssh_disconnect(m_Session);
215void CChannelSSHTunnel::close()
217 qDebug(log) <<
"CChannelSSHTunnel::close()";
241 ssh_pcap_file_close(m_pcapFile);
242 ssh_pcap_file_free(m_pcapFile);
243 m_pcapFile =
nullptr;
247 if(ssh_channel_is_open(m_Channel)) {
248 ssh_channel_close(m_Channel);
250 ssh_channel_free(m_Channel);
254 if(ssh_is_connected(m_Session))
255 ssh_disconnect(m_Session);
263int CChannelSSHTunnel::verifyKnownhost(ssh_session session)
267 ssh_key srv_pubkey = NULL;
268 nRet = ssh_get_server_publickey(session, &srv_pubkey);
270 szErr = tr(
"SSH failed: Get server public key.") +
"(";
271 szErr += ssh_get_error(session);
273 qCritical(log) << szErr;
274 setErrorString(szErr);
277 unsigned char *hash = NULL;
279 nRet = ssh_get_publickey_hash(srv_pubkey,
280 SSH_PUBLICKEY_HASH_SHA1,
283 ssh_key_free(srv_pubkey);
285 szErr = tr(
"SSH failed: Get public key hash value fail.");
286 qCritical(log) << szErr;
287 setErrorString(szErr);
290 QByteArray baHash((
const char*)hash, nLen);
291 QString szHash = baHash.toHex(
':').toStdString().c_str();
292 ssh_clean_pubkey_hash(&hash);
294 QMessageBox::StandardButton btRet;
295 bool checkBox =
false;
296 enum ssh_known_hosts_e state = ssh_session_is_known_server(session);
298 case SSH_KNOWN_HOSTS_OK:
301 case SSH_KNOWN_HOSTS_CHANGED:
303 szErr = tr(
"Host key for server changed. it is now:") +
"\n";
304 szErr += szHash +
"\n";
305 szErr += tr(
"For security reasons, connection will be stopped.") +
"\n";
306 szErr += tr(
"Please look at the OpenSSL documentation on "
307 "how to add a private CA to the store.");
308 qCritical(log) << szErr;
309 setErrorString(szErr);
311 case SSH_KNOWN_HOSTS_OTHER:
313 szErr = tr(
"The host key for this server was not found but an other type of key exists.") +
"\n";
314 szErr += tr(
"An attacker might change the default server key to "
315 "confuse your client into thinking the key does not exist") +
"\n";
316 szErr += tr(
"For security reasons, connection will be stopped.") +
"\n";
317 szErr += tr(
"Please look at the OpenSSL documentation on "
318 "how to add a private CA to the store.");
319 qCritical(log) << szErr;
320 setErrorString(szErr);
322 case SSH_KNOWN_HOSTS_NOT_FOUND:
324 szErr = tr(
"Could not find known host file.") +
"\n";
325 szErr += tr(
"If you accept the host key here, the file will be "
326 "automatically created.") +
"\n";
327 szErr += tr(
"Host key hash:") +
"\n" + szHash;
328 qDebug(log) << szErr;
333 QMessageBox::No | QMessageBox::Ignore,
335 if(QMessageBox::Yes == btRet) {
336 nRet = ssh_session_update_known_hosts(session);
339 qCritical(log) <<
"ssh_session_update_known_hosts fail."
340 << ssh_get_error(session);
342 }
if(QMessageBox::Ignore == btRet)
345 setErrorString(tr(
"Reject the host key"));
347 case SSH_KNOWN_HOSTS_UNKNOWN:
349 szErr = tr(
"The server is unknown. Do you trust the host key?") +
"\n";
350 szErr += tr(
"Host key hash:") +
"\n" + szHash;
351 qDebug(log) << szErr;
356 QMessageBox::No | QMessageBox::Ignore,
358 if(QMessageBox::Yes == btRet) {
359 nRet = ssh_session_update_known_hosts(session);
360 if (SSH_OK != nRet) {
361 qCritical(log) <<
"ssh_session_update_known_hosts fail."
362 << ssh_get_error(session);
364 }
if(QMessageBox::Ignore == btRet)
367 setErrorString(tr(
"Reject the host key"));
369 case SSH_KNOWN_HOSTS_ERROR:
371 szErr = tr(
"Error:") + ssh_get_error(session) +
"\n";
372 szErr += tr(
"Host key hash:") +
"\n" + szHash +
"\n";
373 szErr += tr(
"Will be stopped.");
374 qCritical(log) << szErr;
375 setErrorString(szErr);
386 const QString szUser,
387 const QString szPassword,
388 const QString szPassphrase,
392 int nServerMethod = nMethod;
394 qDebug(log) <<
"Authentication method:" << nMethod;
396 nRet = ssh_userauth_none(session,
397 szUser.toStdString().c_str());
398 qDebug(log) <<
"ssh_userauth_none:" << nRet;
399 if(SSH_AUTH_SUCCESS == nRet)
402 char *banner =
nullptr;
403 banner = ssh_get_issue_banner(session);
406 qDebug(log) <<
"banner:" << banner;
410 nServerMethod = ssh_userauth_list(session,
411 szUser.toStdString().c_str());
412 qDebug(log) <<
"ssh_userauth_list:" << nServerMethod;
415 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PUBLICKEY) {
416 if(m_Parameter->GetUseSystemFile()) {
417 qDebug(log) <<
"User authentication with ssh_userauth_publickey_auto";
418 nRet = ssh_userauth_publickey_auto(session,
419 szUser.toStdString().c_str(),
420 szPassphrase.toStdString().c_str());
421 if(SSH_AUTH_SUCCESS == nRet)
423 QString szErr = tr(
"SSH failed: Failed authenticating with publickey:")
424 + ssh_get_error(m_Session);
425 qCritical(log) << szErr;
426 setErrorString(szErr);
428 qDebug(log) <<
"User authentication with publickey";
429 nRet = authenticationPublicKey(
431 m_Parameter->GetUser(),
432 m_Parameter->GetPublicKeyFile(),
433 m_Parameter->GetPrivateKeyFile(),
434 m_Parameter->GetPassphrase());
435 if(SSH_AUTH_SUCCESS == nRet)
440 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PASSWORD) {
441 qDebug(log) <<
"User authentication with password";
443 nRet = ssh_userauth_password(session,
444 szUser.toStdString().c_str(),
445 szPassword.toStdString().c_str());
447 QString szErr = tr(
"Failed authenticating with password. User: ")
449 + ssh_get_error(session);
450 qCritical(log) << szErr;
451 setErrorString(szErr);
459int CChannelSSHTunnel::authenticationPublicKey(
461 const QString szUser,
462 const QString szPublicKeyFile,
463 const QString szPrivateKeyFile,
464 const QString szPassphrase)
468 ssh_key publicKey = NULL;
469 ssh_key privateKey = NULL;
472 if(szPublicKeyFile.isEmpty())
474 szErr = tr(
"SSH failed: There is not set public key file.");
475 qCritical(log) << szErr;
476 setErrorString(szErr);
479 nRet = ssh_pki_import_pubkey_file(
480 szPublicKeyFile.toStdString().c_str(),
483 szErr = tr(
"SSH failed: Import public key fail.") + szPublicKeyFile;
485 szErr += tr(
"The file doesn't exist or permission denied:");
486 qCritical(log) << szErr;
487 setErrorString(szErr);
491 nRet = ssh_userauth_try_publickey(
493 szUser.toStdString().c_str(),
495 if(SSH_AUTH_SUCCESS != nRet)
497 szErr = tr(
"SSH failed: Authentication failed. User:") + szUser +
"\n";
498 szErr += ssh_get_error(session);
499 qCritical(log) << szErr;
500 setErrorString(szErr);
504 if(szPrivateKeyFile.isEmpty())
506 szErr = tr(
"SSH failed: There is not set private key file.");
507 qCritical(log) << szErr;
508 setErrorString(szErr);
511 nRet = ssh_pki_import_privkey_file(
512 szPrivateKeyFile.toStdString().c_str(),
513 szPassphrase.toStdString().c_str(),
514 NULL, NULL, &privateKey);
517 szErr = tr(
"SSH failed: Import private key fail.") + szPrivateKeyFile;
519 szErr += tr(
"The file doesn't exist or permission denied:");
520 qCritical(log) << szErr;
521 setErrorString(szErr);
525 nRet = ssh_userauth_publickey(
527 szUser.toStdString().c_str(),
529 if(SSH_AUTH_SUCCESS != nRet) {
530 szErr = tr(
"SSH failed: Authentication failed. User:") + szUser +
"\n";
531 szErr += ssh_get_error(session);
532 qCritical(log) << szErr;
533 setErrorString(szErr);
539 ssh_key_free(publicKey);
541 ssh_key_free(privateKey);
546int CChannelSSHTunnel::forward(ssh_session session)
552 m_Channel = ssh_channel_new(session);
553 if(NULL == m_Channel) {
554 qCritical(log) <<
"ssh_channel_new fail." << ssh_get_error(session);
558 nRet = ssh_channel_open_forward(
560 m_Parameter->GetRemoteHost().toStdString().c_str(),
561 m_Parameter->GetRemotePort(),
562 m_Parameter->GetSourceHost().toStdString().c_str(),
563 m_Parameter->GetSourcePort());
565 ssh_channel_free(m_Channel);
569 szErr = tr(
"SSH failed: open forward.") + ssh_get_error(session);
570 szErr +=
"(" + m_Parameter->GetRemoteHost()
571 +
":" + QString::number(m_Parameter->GetRemotePort()) +
")";
572 qCritical(log) << szErr;
573 setErrorString(szErr);
577 qDebug(log) <<
"Connected:"
578 << m_Parameter->GetRemoteHost()
579 +
":" + QString::number(m_Parameter->GetRemotePort())
580 <<
"with ssh turnnel:"
581 << m_Parameter->GetServer()
582 +
":" + QString::number(m_Parameter->GetPort());
602 if(!m_Channel || !ssh_channel_is_open(m_Channel)
603 || ssh_channel_is_eof(m_Channel)) {
604 QString szErr =
"The channel is not open";
605 qCritical(log) << szErr;
606 setErrorString(szErr);
610 struct timeval timeout = {0, 50000};
611 ssh_channel channels[2], channel_out[2];
612 channels[0] = m_Channel;
613 channels[1] =
nullptr;
617 socket_t fd = SSH_INVALID_SOCKET;
619 fd = m_pEvent->GetFd();
620 if(SSH_INVALID_SOCKET != fd)
624 nRet = ssh_select(channels, channel_out, fd + 1, &set, &timeout);
628 szErr =
"ssh_channel_select failed: " + QString::number(nRet);
629 szErr += ssh_get_error(m_Session);
630 qCritical(log) << szErr;
631 setErrorString(szErr);
641 if(SSH_INVALID_SOCKET != fd && FD_ISSET(fd, &set)) {
644 nRet = m_pEvent->Reset();
650 qDebug(log) <<
"There is not channel";
654 if(ssh_channel_is_eof(m_Channel)) {
655 qWarning(log) <<
"Channel is eof";
656 setErrorString(tr(
"The channel is eof"));
660 nRet = ssh_channel_poll(m_Channel, 0);
664 szErr =
"ssh_channel_poll failed. nRet:";
665 szErr += QString::number(nRet);
666 szErr += ssh_get_error(m_Session);
667 setErrorString(szErr);
668 qCritical(log) << szErr;
682qint64 CChannelSSHTunnel::readData(
char *data, qint64 maxlen)
690 Q_ASSERT(data && maxlen >= 0);
691 if(!(data && maxlen >= 0))
694 if(!m_Channel || !ssh_channel_is_open(m_Channel))
697 szErr =
"The channel is not opened";
698 qCritical(log) << szErr;
699 setErrorString(szErr);
727 nRet = ssh_channel_read_nonblocking(m_Channel, data, maxlen, 0);
728 if(SSH_AGAIN == nRet) {
731 }
else if(0 > nRet) {
733 szErr =
"Read data from channel failed. nRet:";
734 szErr += QString::number(nRet);
735 szErr += ssh_get_error(m_Session);
736 qCritical(log) << szErr;
743qint64 CChannelSSHTunnel::writeData(
const char *data, qint64 len)
747 Q_ASSERT(data && len >= 0);
748 if(!(data && len >= 0))
751 if(!m_Channel || !ssh_channel_is_open(m_Channel) || ssh_channel_is_eof(m_Channel))
754 szErr =
"The channel is not opened";
755 qCritical(log) << szErr;
756 setErrorString(szErr);
760 nRet = ssh_channel_write(m_Channel, data, len);
762 if(SSH_AGAIN == nRet)
766 szErr =
"Write data from channel failed:";
767 szErr += ssh_get_error(m_Session);
768 qCritical(log) << szErr;
769 setErrorString(szErr);
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)
virtual bool open(OpenMode mode) override
int authentication(ssh_session session, const QString szUser, const QString szPassword, const QString szPassphrase, const int nMethod=SSH_AUTH_METHOD_PASSWORD)
void sigBlockShowWidget(const QString &className, int &nRet, void *pContext)
Blocks the background thread and displays the window in the foreground thread.
The channel interface class.
void sigConnected()
emit when the channel is connected.
void sigError(int nErr, const QString &szErr)
emit when the channel is error