3#include "libssh/libssh.h"
4#include "libssh/callbacks.h"
6#include "ChannelSSHTunnel.h"
7#include <QLoggingCategory>
10#include <QAbstractEventDispatcher>
11#include <QScopedArrayPointer>
13#if defined(Q_OS_LINUX)
14 #include <sys/eventfd.h>
21static Q_LOGGING_CATEGORY(log,
"Channel.SSH.Tunnel")
22static Q_LOGGING_CATEGORY(logSSH, "Channel.SSH.log")
35 m_pParameter(parameter),
44 qDebug(log) <<
"CChannelSSHTunnel::CChannelSSHTunnel()";
45 qDebug(log) <<
"libssh version:" << ssh_version(0);
51CChannelSSHTunnel::~CChannelSSHTunnel()
53 qDebug(log) <<
"CChannelSSHTunnel::~CChannelSSHTunnel()";
60 return tr(
"- libssh version: ") + ssh_version(0);
63void CChannelSSHTunnel::cb_log(ssh_session session,
70 qWarning(logSSH) << message;
73 qInfo(logSSH) << message;
76 qDebug(logSSH) << message;
82int CChannelSSHTunnel::GetSocket()
85 return ssh_get_fd(m_Session);
86 return SSH_INVALID_SOCKET;
89int CChannelSSHTunnel::WakeUp()
91 if(!m_pEvent)
return 0;
92 return m_pEvent->WakeUp();
101 qCritical(log) <<
"The parameter is null";
104 qCritical(log) <<
"The remote net parameter is null";
105 Q_ASSERT(m_pParameter);
106 Q_ASSERT(m_pRemoteNet);
108 bool bRet = QIODevice::open(mode);
112 m_Session = ssh_new();
113 if(NULL == m_Session)
115 szErr = tr(
"SSH failed: ssh_new.");
116 qCritical(log) << szErr;
117 setErrorString(szErr);
122 struct ssh_callbacks_struct cb;
123 memset(&cb, 0,
sizeof(
struct ssh_callbacks_struct));
125 cb.log_function = cb_log;;
126 ssh_callbacks_init(&cb);
127 ssh_set_callbacks(m_Session, &cb);
141 int verbosity = SSH_LOG_NOLOG;
142 ssh_options_set(m_Session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
144 auto &net = m_pParameter->m_Net;
145 if(net.GetHost().isEmpty()) {
146 szErr = tr(
"SSH failed: the server is empty");
147 qCritical(log) << szErr;
148 setErrorString(szErr);
151 nRet = ssh_options_set(m_Session, SSH_OPTIONS_HOST,
152 net.GetHost().toStdString().c_str());
154 szErr = tr(
"SSH failed: Set host fail. host:")
155 + net.GetHost() +
";"
156 + ssh_get_error(m_Session);
157 qCritical(log) << szErr;
158 setErrorString(szErr);
163 uint nPort = net.GetPort();
164 nRet = ssh_options_set(m_Session, SSH_OPTIONS_PORT, &nPort);
166 szErr = tr(
"SSH failed: Set port fail. port:")
167 + QString::number(net.GetPort()) +
";"
168 + ssh_get_error(m_Session);
169 qCritical(log) << szErr;
170 setErrorString(szErr);
174 if(!m_pParameter->GetPcapFile().isEmpty())
176 m_pcapFile = ssh_pcap_file_new();
178 if (ssh_pcap_file_open(
179 m_pcapFile, m_pParameter->GetPcapFile().toStdString().c_str())
181 qCritical(log) <<
"SSH failed: Error opening pcap file: "
182 << m_pParameter->GetPcapFile();
183 ssh_pcap_file_free(m_pcapFile);
184 m_pcapFile =
nullptr;
187 ssh_set_pcap_file(m_Session, m_pcapFile);
189 szErr = tr(
"SSH failed: ssh_pcap_file_new: ")
190 + ssh_get_error(m_Session);
191 qCritical(log) << szErr;
195 nRet = ssh_connect(m_Session);
197 szErr = tr(
"SSH failed: ssh connect ")
199 +
":" + QString::number(net.GetPort())
201 + ssh_get_error(m_Session);
203 qCritical(log) << szErr;
204 setErrorString(szErr);
208 nRet = verifyKnownhost(m_Session);
213 auto &user = m_pParameter->m_Net.
m_User;
214 if(((user.GetUsedType() == CParameterUser::TYPE::UserPassword)
215 && (user.GetPassword().isEmpty() || user.GetUser().isEmpty()))
216 || ((user.GetUsedType() == CParameterUser::TYPE::PublicKey)
217 && user.GetPassphrase().isEmpty())
220 if(QDialog::Accepted != nRet)
222 setErrorString(tr(
"User cancel"));
227 int nMeth = SSH_AUTH_METHOD_PUBLICKEY;
228 switch(user.GetUsedType()) {
229 case CParameterUser::TYPE::UserPassword:
230 nMeth = SSH_AUTH_METHOD_PASSWORD;
232 case CParameterUser::TYPE::PublicKey:
233 nMeth = SSH_AUTH_METHOD_PUBLICKEY;
240 user.GetPassphrase(),
244 nRet = forward(m_Session);
252 ssh_disconnect(m_Session);
258void CChannelSSHTunnel::close()
260 qDebug(log) <<
"CChannelSSHTunnel::close()";
262 if(!isOpen())
return;
285 if(ssh_channel_is_open(m_Channel)) {
286 ssh_channel_close(m_Channel);
288 ssh_channel_free(m_Channel);
293 if(ssh_is_connected(m_Session))
294 ssh_disconnect(m_Session);
301 ssh_pcap_file_close(m_pcapFile);
302 ssh_pcap_file_free(m_pcapFile);
303 m_pcapFile =
nullptr;
309int CChannelSSHTunnel::verifyKnownhost(ssh_session session)
313 ssh_key srv_pubkey = NULL;
315 auto &net = m_pParameter->m_Net;
317 nRet = ssh_get_server_publickey(session, &srv_pubkey);
319 szErr = tr(
"SSH failed: Get server public key.") +
" " + net.GetHost() +
"; [";
320 szErr += ssh_get_error(session);
322 qCritical(log) << szErr;
323 setErrorString(szErr);
326 unsigned char *hash = NULL;
328 nRet = ssh_get_publickey_hash(srv_pubkey,
329 SSH_PUBLICKEY_HASH_SHA1,
332 ssh_key_free(srv_pubkey);
334 szErr = tr(
"SSH failed: Get public key hash value fail.");
335 qCritical(log) << szErr;
336 setErrorString(szErr);
339 QByteArray baHash((
const char*)hash, nLen);
340 QString szHash = baHash.toHex(
':').toStdString().c_str();
341 ssh_clean_pubkey_hash(&hash);
343 QMessageBox::StandardButton btRet = QMessageBox::Yes;
344 bool checkBox =
false;
345 enum ssh_known_hosts_e state = ssh_session_is_known_server(session);
347 case SSH_KNOWN_HOSTS_OK:
350 case SSH_KNOWN_HOSTS_CHANGED:
352 szErr = net.GetHost() +
" " + tr(
"the host key for server changed. it is now:") +
"\n";
353 szErr += szHash +
"\n";
354 szErr += tr(
"For security reasons, connection will be stopped.") +
"\n";
355 szErr += tr(
"Please look at the OpenSSL documentation on "
356 "how to add a private CA to the store.");
357 qCritical(log) << szErr;
358 setErrorString(szErr);
360 case SSH_KNOWN_HOSTS_OTHER:
362 szErr = net.GetHost() +
" " + tr(
"the host key for this server was not found but an other type of key exists.") +
"\n";
363 szErr += tr(
"An attacker might change the default server key to "
364 "confuse your client into thinking the key does not exist") +
"\n";
365 szErr += tr(
"For security reasons, connection will be stopped.") +
"\n";
366 szErr += tr(
"Please look at the OpenSSL documentation on "
367 "how to add a private CA to the store.");
368 qCritical(log) << szErr;
369 setErrorString(szErr);
371 case SSH_KNOWN_HOSTS_NOT_FOUND:
373 szErr = net.GetHost() +
" " + tr(
"is not find in known host file.") +
"\n";
374 szErr += tr(
"If you accept the host key here, the file will be "
375 "automatically created.") +
"\n";
376 szErr += tr(
"Host key hash:") +
"\n" + szHash;
377 qDebug(log) << szErr;
378 if(!m_pConnect)
break;
383 QMessageBox::No | QMessageBox::Ignore,
385 if(QMessageBox::Yes == btRet) {
386 nRet = ssh_session_update_known_hosts(session);
389 qCritical(log) <<
"ssh_session_update_known_hosts fail."
390 << ssh_get_error(session);
392 }
if(QMessageBox::Ignore == btRet)
395 setErrorString(tr(
"Reject the host key"));
397 case SSH_KNOWN_HOSTS_UNKNOWN:
399 szErr = net.GetHost() +
" " + tr(
"is unknown. Do you trust the host key?") +
"\n";
400 szErr += tr(
"Host key hash:") +
"\n" + szHash;
401 qDebug(log) << szErr;
402 if(!m_pConnect)
break;
407 QMessageBox::No | QMessageBox::Ignore,
409 if(QMessageBox::Yes == btRet) {
410 nRet = ssh_session_update_known_hosts(session);
411 if (SSH_OK != nRet) {
412 qCritical(log) <<
"ssh_session_update_known_hosts fail."
413 << ssh_get_error(session);
415 }
if(QMessageBox::Ignore == btRet)
418 setErrorString(tr(
"Reject the host key"));
420 case SSH_KNOWN_HOSTS_ERROR:
422 szErr = tr(
"Error:") + ssh_get_error(session) +
"\n";
423 szErr += net.GetHost() +
" " + tr(
"the host key hash:") +
"\n" + szHash +
"\n";
424 szErr += tr(
"Will be stopped.");
425 qCritical(log) << szErr;
426 setErrorString(szErr);
437 const QString szUser,
438 const QString szPassword,
439 const QString szPassphrase,
443 int nServerMethod = nMethod;
445 qDebug(log) <<
"Authentication method:" << nMethod;
447 nRet = ssh_userauth_none(session,
448 szUser.toStdString().c_str());
449 qDebug(log) <<
"ssh_userauth_none:" << nRet;
450 if(SSH_AUTH_SUCCESS == nRet)
453 char *banner =
nullptr;
454 banner = ssh_get_issue_banner(session);
457 qDebug(log) <<
"banner:" << banner;
461 nServerMethod = ssh_userauth_list(session,
462 szUser.toStdString().c_str());
463 qDebug(log) <<
"ssh_userauth_list:" << nServerMethod;
466 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PUBLICKEY) {
467 auto &user = m_pParameter->m_Net.
m_User;
468 if(user.GetUseSystemFile()) {
469 qDebug(log) <<
"User authentication with ssh_userauth_publickey_auto";
470 nRet = ssh_userauth_publickey_auto(session,
471 szUser.toStdString().c_str(),
472 szPassphrase.toStdString().c_str());
473 if(SSH_AUTH_SUCCESS == nRet)
475 QString szErr = tr(
"SSH failed: Failed authenticating with publickey:")
476 + ssh_get_error(m_Session);
477 qCritical(log) << szErr;
478 setErrorString(szErr);
480 qDebug(log) <<
"User authentication with publickey";
481 nRet = authenticationPublicKey(
484 user.GetPublicKeyFile(),
485 user.GetPrivateKeyFile(),
486 user.GetPassphrase());
487 if(SSH_AUTH_SUCCESS == nRet)
492 if(nServerMethod & nMethod & SSH_AUTH_METHOD_PASSWORD) {
493 qDebug(log) <<
"User authentication with password";
495 nRet = ssh_userauth_password(session,
496 szUser.toStdString().c_str(),
497 szPassword.toStdString().c_str());
499 QString szErr = tr(
"Failed authenticating with password. User: ")
501 + ssh_get_error(session);
502 qCritical(log) << szErr;
503 setErrorString(szErr);
511int CChannelSSHTunnel::authenticationPublicKey(
513 const QString szUser,
514 const QString szPublicKeyFile,
515 const QString szPrivateKeyFile,
516 const QString szPassphrase)
520 ssh_key publicKey = NULL;
521 ssh_key privateKey = NULL;
524 if(szPublicKeyFile.isEmpty())
526 szErr = tr(
"SSH failed: There is not set public key file.");
527 qCritical(log) << szErr;
528 setErrorString(szErr);
531 nRet = ssh_pki_import_pubkey_file(
532 szPublicKeyFile.toStdString().c_str(),
535 szErr = tr(
"SSH failed: Import public key fail.") + szPublicKeyFile;
537 szErr +=
"\n" + tr(
"The file doesn't exist or permission denied:");
538 qCritical(log) << szErr;
539 setErrorString(szErr);
543 nRet = ssh_userauth_try_publickey(
545 szUser.toStdString().c_str(),
547 if(SSH_AUTH_SUCCESS != nRet)
549 szErr = tr(
"SSH failed: Authentication failed. User:") + szUser +
"\n";
550 szErr += ssh_get_error(session);
551 qCritical(log) << szErr;
552 setErrorString(szErr);
556 if(szPrivateKeyFile.isEmpty())
558 szErr = tr(
"SSH failed: There is not set private key file.");
559 qCritical(log) << szErr;
560 setErrorString(szErr);
563 nRet = ssh_pki_import_privkey_file(
564 szPrivateKeyFile.toStdString().c_str(),
565 szPassphrase.toStdString().c_str(),
566 NULL, NULL, &privateKey);
569 szErr = tr(
"SSH failed: Import private key fail.") + szPrivateKeyFile;
571 szErr +=
"\n" + tr(
"The file doesn't exist or permission denied:");
572 qCritical(log) << szErr;
573 setErrorString(szErr);
577 nRet = ssh_userauth_publickey(
579 szUser.toStdString().c_str(),
581 if(SSH_AUTH_SUCCESS != nRet) {
582 szErr = tr(
"SSH failed: Authentication failed. User:") + szUser +
"\n";
583 szErr += ssh_get_error(session);
584 qCritical(log) << szErr;
585 setErrorString(szErr);
591 ssh_key_free(publicKey);
593 ssh_key_free(privateKey);
598int CChannelSSHTunnel::forward(ssh_session session)
604 m_Channel = ssh_channel_new(session);
605 if(NULL == m_Channel) {
606 qCritical(log) <<
"ssh_channel_new fail." << ssh_get_error(session);
610 nRet = ssh_channel_open_forward(
612 m_pRemoteNet->GetHost().toStdString().c_str(),
613 m_pRemoteNet->GetPort(),
614 m_pParameter->GetSourceHost().toStdString().c_str(),
615 m_pParameter->GetSourcePort());
617 ssh_channel_free(m_Channel);
621 szErr = tr(
"SSH failed: open forward.") + ssh_get_error(session);
622 szErr +=
"(" + m_pRemoteNet->GetHost()
623 +
":" + QString::number(m_pRemoteNet->GetPort()) +
")";
624 qCritical(log) << szErr;
625 setErrorString(szErr);
629 qInfo(log) <<
"Connected:"
630 << m_pRemoteNet->GetHost()
631 +
":" + QString::number(m_pRemoteNet->GetPort())
632 <<
"with ssh turnnel:"
633 << m_pParameter->m_Net.GetHost()
634 +
":" + QString::number(m_pParameter->m_Net.GetPort());
651 if(!m_Channel || !ssh_channel_is_open(m_Channel)
652 || ssh_channel_is_eof(m_Channel)) {
653 QString szErr =
"The channel is not open";
654 qCritical(log) << szErr;
655 setErrorString(szErr);
659 struct timeval timeout = {0, 5000000};
660 ssh_channel channels[2], channel_out[2];
661 channels[0] = m_Channel;
662 channels[1] =
nullptr;
666 socket_t fd = SSH_INVALID_SOCKET;
668 fd = m_pEvent->GetFd();
669 if(SSH_INVALID_SOCKET != fd)
673 nRet = ssh_select(channels, channel_out, fd + 1, &set, &timeout);
680 szErr =
"ssh_channel_select failed: " + QString::number(nRet);
681 szErr += ssh_get_error(m_Session);
682 qCritical(log) << szErr;
683 setErrorString(szErr);
687 if(SSH_INVALID_SOCKET != fd && FD_ISSET(fd, &set)) {
690 nRet = m_pEvent->Reset();
695 if(!channel_out[0]) {
700 if(ssh_channel_is_eof(m_Channel)) {
701 qWarning(log) <<
"Channel is eof";
702 setErrorString(tr(
"The channel is eof"));
708 nRet = ssh_channel_poll(m_Channel, 0);
710 if(SSH_ERROR == nRet)
713 szErr =
"ssh_channel_poll failed. nRet:";
714 szErr += QString::number(nRet);
715 szErr += ssh_get_error(m_Session);
716 setErrorString(szErr);
717 qCritical(log) << szErr;
719 }
else if(SSH_EOF == nRet) {
722 }
else if(0 > nRet) {
724 szErr =
"ssh_channel_poll failed. nRet:";
725 szErr += QString::number(nRet);
726 szErr += ssh_get_error(m_Session);
727 setErrorString(szErr);
728 qCritical(log) << szErr;
731 }
else if(0 == nRet) {
742qint64 CChannelSSHTunnel::readData(
char *data, qint64 maxlen)
748 Q_ASSERT(data && maxlen >= 0);
749 if(
nullptr == data || 0 > maxlen) {
750 qCritical(log) << Q_FUNC_INFO <<
"The parameters is invalid" << maxlen;
755 qCritical(log) << Q_FUNC_INFO <<
"maxlen:" << maxlen;
759 if(!m_Channel || !ssh_channel_is_open(m_Channel))
762 szErr =
"The channel is not opened";
763 qCritical(log) << szErr;
764 setErrorString(szErr);
768 nRet = ssh_channel_read_nonblocking(m_Channel, data, maxlen, 0);
769 if(SSH_AGAIN == nRet) {
770 qDebug(log) << Q_FUNC_INFO <<
"ssh again read";
772 }
else if(0 > nRet) {
774 szErr =
"Read data from channel failed. nRet:";
775 szErr += QString::number(nRet);
776 szErr += ssh_get_error(m_Session);
777 qCritical(log) << szErr;
784qint64 CChannelSSHTunnel::writeData(
const char *data, qint64 len)
788 Q_ASSERT(data && len >= 0);
789 if(
nullptr == data || 0 > len) {
790 qCritical(log) << Q_FUNC_INFO <<
"The parameters is invalid" << len;
795 qCritical(log) << Q_FUNC_INFO <<
"len:" << len;
799 if(!m_Channel || !ssh_channel_is_open(m_Channel) || ssh_channel_is_eof(m_Channel))
802 szErr =
"The channel is not opened";
803 qCritical(log) << szErr;
804 setErrorString(szErr);
808 nRet = ssh_channel_write(m_Channel, data, len);
809 if(SSH_AGAIN == nRet) {
810 qDebug(log) << Q_FUNC_INFO <<
"ssh again write";
812 }
else if(nRet < 0) {
814 szErr =
"Write data from channel failed:";
815 szErr += ssh_get_error(m_Session);
816 qCritical(log) << szErr;
817 setErrorString(szErr);
824int CChannelSSHTunnel::DoWait(
bool bWrite,
int timeout)
827 if(!m_Channel || !ssh_channel_is_open(m_Channel)
828 || ssh_channel_is_eof(m_Channel)) {
829 QString szErr =
"The channel is not open";
830 qCritical(log) << szErr;
831 setErrorString(szErr);
838 struct timeval tm = {0, timeout};
839 ssh_channel channels[2], channel_out[2];
840 channels[0] = m_Channel;
841 channels[1] =
nullptr;
844 socket_t fd = SSH_INVALID_SOCKET;
847 if(SSH_INVALID_SOCKET != fd)
849 nRet = select(fd + 1,
nullptr, &set,
nullptr, &tm);
850 if(0 > nRet)
return nRet;
855 nRet = ssh_select(channels, channel_out, 1, &set, &tm);
862 szErr =
"ssh_channel_select failed: " + QString::number(nRet);
863 szErr += ssh_get_error(m_Session);
864 qCritical(log) << szErr;
865 setErrorString(szErr);
869 if(!channel_out[0]) {
874 if(ssh_channel_is_eof(m_Channel)) {
875 qWarning(log) <<
"Channel is eof";
876 setErrorString(tr(
"The channel is eof"));
882 nRet = ssh_channel_poll(m_Channel, 0);
884 if(SSH_ERROR == nRet)
887 szErr =
"ssh_channel_poll failed. nRet:";
888 szErr += QString::number(nRet);
889 szErr += ssh_get_error(m_Session);
890 setErrorString(szErr);
891 qCritical(log) << szErr;
893 }
else if(SSH_EOF == nRet) {
896 }
else if(0 > nRet) {
898 szErr =
"ssh_channel_poll failed. nRet:";
899 szErr += QString::number(nRet);
900 szErr += ssh_get_error(m_Session);
901 setErrorString(szErr);
902 qCritical(log) << szErr;
905 }
else if(0 == nRet) {
virtual QString GetDetails() override
Depend on information.
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)
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
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.
Basic network parameters.
CParameterUser m_User
[Instance user]