4#include <QLoggingCategory>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
18#include "BackendSftpServer.h"
20#define DEF_STR_SIZE 1024
21char authorizedkeys[DEF_STR_SIZE] = {0};
22static Q_LOGGING_CATEGORY(log,
"Backend.SftpServer")
25static QString GetClientAddress(ssh_session session)
27 int sock_fd = ssh_get_fd(session);
32 struct sockaddr_storage addr;
33 socklen_t addr_len =
sizeof(addr);
35 if (getpeername(sock_fd, (
struct sockaddr*)&addr, &addr_len) == 0) {
36 char ip_str[INET6_ADDRSTRLEN];
38 if (addr.ss_family == AF_INET) {
40 struct sockaddr_in* ipv4 = (
struct sockaddr_in*)&addr;
41 inet_ntop(AF_INET, &(ipv4->sin_addr), ip_str,
sizeof(ip_str));
42 return QString::fromUtf8(ip_str);
43 }
else if (addr.ss_family == AF_INET6) {
45 struct sockaddr_in6* ipv6 = (
struct sockaddr_in6*)&addr;
46 inet_ntop(AF_INET6, &(ipv6->sin6_addr), ip_str,
sizeof(ip_str));
47 return QString::fromUtf8(ip_str);
55static bool GetClientAddressAndPort(ssh_session session, QString& ip, quint16& port)
57 int sock_fd = ssh_get_fd(session);
62 struct sockaddr_storage addr;
63 socklen_t addr_len =
sizeof(addr);
65 if (getpeername(sock_fd, (
struct sockaddr*)&addr, &addr_len) == 0) {
66 char ip_str[INET6_ADDRSTRLEN];
68 if (addr.ss_family == AF_INET) {
69 struct sockaddr_in* ipv4 = (
struct sockaddr_in*)&addr;
70 inet_ntop(AF_INET, &(ipv4->sin_addr), ip_str,
sizeof(ip_str));
71 ip = QString::fromUtf8(ip_str);
72 port = ntohs(ipv4->sin_port);
74 }
else if (addr.ss_family == AF_INET6) {
75 struct sockaddr_in6* ipv6 = (
struct sockaddr_in6*)&addr;
76 inet_ntop(AF_INET6, &(ipv6->sin6_addr), ip_str,
sizeof(ip_str));
77 ip = QString::fromUtf8(ip_str);
78 port = ntohs(ipv6->sin6_port);
88 , m_pPara(pOperate->GetParameter())
91 qDebug(log) << Q_FUNC_INFO;
94CBackendSftpServer::~CBackendSftpServer()
96 qDebug(log) << Q_FUNC_INFO;
101 OnInitReturnValue ret = OnInitReturnValue::Fail;
102 if(!m_pPara)
return ret;
104 m_event = ssh_event_new();
108 QDir root(m_pPara->GetRoot());
109 if (!root.exists() && !root.mkpath(
".")) {
110 qCritical(log) <<
"Failed to create root directory";
114 qint16 port = m_pPara->m_Net.GetPort();
115 if(m_pPara->GetListenAll()) {
116 int nRet = Listen(
"0.0.0.0", port);
118 qInfo(log) <<
"SFTP Server is listend on port:" << port
119 <<
"; Root path:" << root.absolutePath();
120 }
else if(!m_pPara->GetListen().isEmpty()){
121 foreach (
auto add, m_pPara->GetListen()) {
124 qInfo(log) <<
"SFTP Server is listend on port:"
125 << port << m_pPara->GetListen()
126 <<
"; Root path:" << root.absolutePath();
130 if(m_pPara->GetAnonymousLogin())
132 else if(!m_pPara->m_Net.
m_User.GetPassword().isEmpty())
134 | CSecurityLevel::Level::Authentication);
136 return OnInitReturnValue::UseOnProcess;
143 foreach(
auto d, m_lstListen) {
144 ssh_bind_free(d->sshBind);
145 delete d->pListenNotifier;
150 foreach (
auto c, m_lstClients) {
153 m_lstClients.clear();
156 ssh_event_free(m_event);
188 if(m_lstClients.isEmpty())
191 QList<sClientData*> toRemove;
193 nRet = ssh_event_dopoll(m_event, 100);
194 if (SSH_ERROR == nRet) {
195 qDebug(log) <<
"Failed: ssh_event_dopoll. nRet:" << nRet;
196 foreach (
auto pData, m_lstClients) {
197 qDebug(log) <<
"ip:" << pData->ip <<
"port:" << pData->port
198 <<
"error code:" << ssh_get_error_code(pData->session)
199 << ssh_get_error(pData->session);
200 if(ssh_get_error_code(pData->session) != SSH_OK)
201 toRemove.push_back(pData);
203 foreach(
auto pData, toRemove) {
204 m_lstClients.removeAll(pData);
211 foreach(
auto pData, m_lstClients) {
213 toRemove.push_back(pData);
217 if(
nullptr == pData->channel) {
219 if(pData->pPara->GetAuthenticateAttempts() < pData->m_nAuthAttempts
220 || pData->pPara->GetAuthenticateTime()
221 < pData->m_Time.msecsTo(QTime::currentTime())) {
222 toRemove.push_back(pData);
225 }
else if(pData->m_Time.isValid()) {
226 ssh_set_channel_callbacks(pData->channel, &pData->channel_cb);
227 pData->m_Time = QTime();
230 if(pData->channel && (!ssh_channel_is_open(pData->channel)
231 || ssh_channel_is_eof(pData->channel))) {
232 qWarning(log) <<
"Then channel is not open";
233 toRemove.push_back(pData);
237 foreach(
auto pData, toRemove) {
238 m_lstClients.removeAll(pData);
245bool CBackendSftpServer::InitHostKey(ssh_bind ssdBind)
247 qDebug(log) << Q_FUNC_INFO;
248 if(!ssdBind)
return false;
252 szPath = m_pPara->GetHostKeyFile();
253 if (!QFile::exists(szPath)) {
254 qCritical(log) <<
"Host key not found:" << szPath;
259 if (ssh_bind_options_set(ssdBind, SSH_BIND_OPTIONS_HOSTKEY,
260 szPath.toStdString().c_str()) < 0) {
261 qCritical(log) <<
"Failed to set RSA host key" << ssh_get_error(ssdBind);
268int CBackendSftpServer::Listen(
const QString &szIp, qint16 nPort)
271 sBindData* pData =
new sBindData();
272 if(!pData)
return ret;
274 auto sshBind = ssh_bind_new();
276 qCritical(log) <<
"Failed to create SSH bind";
280 auto &net = m_pPara->m_Net;
281 quint16 port = net.GetPort();
282 ssh_bind_options_set(sshBind, SSH_BIND_OPTIONS_BINDPORT, &port);
283 ssh_bind_options_set(sshBind, SSH_BIND_OPTIONS_BINDADDR, szIp.toStdString().c_str());
285 if (!InitHostKey(sshBind)) {
286 ssh_bind_free(sshBind);
290 if (ssh_bind_listen(sshBind) < 0) {
291 qCritical(log) <<
"Failed to bind:" << ssh_get_error(sshBind);
292 ssh_bind_free(sshBind);
296 auto fd = ssh_bind_get_fd(sshBind);
297 auto pListenNotifier =
new QSocketNotifier(fd, QSocketNotifier::Read,
this);
298 if(pListenNotifier) {
299 bool check = connect(pListenNotifier, &QSocketNotifier::activated,
300 this, &CBackendSftpServer::slotNewConnection);
303 pData->sshBind = sshBind;
304 pData->pListenNotifier = pListenNotifier;
305 m_lstListen.push_back(pData);
312ssh_bind CBackendSftpServer::GetSshBind(QSocketNotifier* pNotifier)
314 foreach(
auto d, m_lstListen) {
315 if(d->pListenNotifier == pNotifier)
321void CBackendSftpServer::slotNewConnection()
323 qDebug(log) << Q_FUNC_INFO;
324 int nRet = SSH_ERROR;
326 QSocketNotifier* pSocketNotifier = qobject_cast<QSocketNotifier*>(sender());
327 if(!pSocketNotifier)
return;
330 ssh_session session = ssh_new();
332 qCritical(log) <<
"Failed to create SSH session";
336 struct sClientData* pData =
new sClientData();
338 memset(pData, 0,
sizeof(
struct sClientData));
339 pData->session = session;
340 pData->m_Time = QTime::currentTime();
341 pData->pPara = m_pPara;
343 memset(&pData->channel_cb, 0,
sizeof(ssh_channel_callbacks_struct));
344 pData->channel_cb.userdata = &(pData->sftp);
345 pData->channel_cb.channel_data_function = sftp_channel_default_data_callback;
346 pData->channel_cb.channel_subsystem_request_function = sftp_channel_default_subsystem_request;
347 ssh_callbacks_init(&pData->channel_cb);
349 memset(&pData->server_cb, 0,
sizeof(ssh_server_callbacks_struct));
350 pData->server_cb.userdata = pData;
351 pData->server_cb.auth_password_function = cbAuthPassword;
352 pData->server_cb.channel_open_request_session_function = cbChannelOpen;
353 ssh_callbacks_init(&pData->server_cb);
355 if (authorizedkeys[0])
357 pData->server_cb.auth_pubkey_function = cbAuthPublickey;
358 ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY);
361 ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
363 nRet = ssh_set_server_callbacks(session, &pData->server_cb);
365 qCritical(log) <<
"Failed: Set server callbacks";
368 ssh_set_blocking(session, 0);
370 ssh_bind sshBind = GetSshBind(pSocketNotifier);
371 if (ssh_bind_accept(sshBind, session) == SSH_ERROR) {
372 qCritical(log) <<
"Failed to accept connection:" << ssh_get_error(sshBind);
377 bool bRet = GetClientAddressAndPort(session, pData->ip, pData->port);
379 qDebug(log) <<
"IP:" << pData->ip <<
"Port:" << pData->port;
380 auto& white = m_pPara->m_WhiteFilter;
381 auto& black = m_pPara->m_BlackFilter;
383 bool bInWhite =
false;
384 if(!white.isEmpty()) {
385 white.OnProcess([&](
const QString& szKey)->
int {
386 QHostAddress addr(pData->ip);
387 auto sub = QHostAddress::parseSubnet(szKey);
388 if(addr.isInSubnet(sub.first, sub.second)) {
396 if(!bInWhite && !black.isEmpty()) {
397 black.OnProcess([&](
const QString& szKey)->int {
398 QHostAddress addr(pData->ip);
399 auto sub = QHostAddress::parseSubnet(szKey);
400 if(addr.isInSubnet(sub.first, sub.second)) {
401 qInfo(log) <<
"Filtered" << pData->ip <<
"in blacklist";
412 if (ssh_handle_key_exchange(session) != SSH_OK)
414 qCritical(log) <<
"Failed: Key exchange:" << ssh_get_error(session);
418 nRet = ssh_event_add_session(m_event, session);
420 qCritical(log) <<
"Failed: event add session";
422 m_lstClients.push_back(pData);
431 ssh_disconnect(session);
436int CBackendSftpServer::cbAuthPassword(ssh_session session,
const char *userName,
437 const char *pass,
void *userdata)
439 qDebug(log) << Q_FUNC_INFO;
440 struct sClientData *pData = (
struct sClientData *)userdata;
444 if(!para)
return SSH_AUTH_DENIED;
445 auto &user = para->m_Net.
m_User;
446 if (para->GetAnonymousLogin()
447 || (userName == user.GetUser() && pass == user.GetPassword()))
449 SendBanner(pData, userName);
450 return SSH_AUTH_SUCCESS;
452 pData->m_nAuthAttempts++;
453 return SSH_AUTH_DENIED;
456int CBackendSftpServer::cbAuthPublickey(ssh_session session,
458 struct ssh_key_struct *pubkey,
459 char signature_state,
462 qDebug(log) << Q_FUNC_INFO;
463 struct sClientData *pData = (
struct sClientData *)userdata;
465 if (signature_state == SSH_PUBLICKEY_STATE_NONE)
467 SendBanner(pData, user);
468 return SSH_AUTH_SUCCESS;
471 if (signature_state != SSH_PUBLICKEY_STATE_VALID)
473 return SSH_AUTH_DENIED;
505 return SSH_AUTH_DENIED;
508ssh_channel CBackendSftpServer::cbChannelOpen(ssh_session session,
void *userdata)
510 qDebug(log) << Q_FUNC_INFO;
511 if(!session || !userdata) {
514 struct sClientData *pData = (
struct sClientData *)userdata;
517 pData->channel = ssh_channel_new(session);
518 return pData->channel;
521int CBackendSftpServer::DeleteClient(sClientData *pClient)
523 qDebug(log) << Q_FUNC_INFO;
528 if(pClient->channel && ssh_channel_is_open(pClient->channel)) {
529 ssh_channel_send_eof(pClient->channel);
530 ssh_channel_close(pClient->channel);
533 if(pClient->session) {
534 if(ssh_is_connected(pClient->session))
535 ssh_disconnect(pClient->session);
537 ssh_event_remove_session(m_event, pClient->session);
538 ssh_free(pClient->session);
547void CBackendSftpServer::SendBanner(sClientData *pClient,
const QString& user)
549 QString szBanner =
"\n" + tr(
"Welcome to \"Rabbit Remote Control - SFTP Server\"") +
"\n";
550 szBanner +=
" * " + tr(
"Version") +
": " + QString(SftpServer_VERSION) +
"\n";
551 szBanner +=
" * " + tr(
"Home page") +
": https://github.com/KangLin/RabbitRemoteControl.git\n";
552 szBanner +=
" * " + tr(
"Author: Kang Lin") +
" <kl222@126.com>\n";
553 szBanner +=
" * " + tr(
"Support") +
": https://github.com/KangLin/RabbitRemoteControl/issues\n";
554 szBanner += tr(
"User \"%1\" logged in from %2 at %3").arg(user, pClient->ip, QDateTime::currentDateTime().toString()) +
"\n\n";
555 ssh_string banner = ssh_string_from_char(szBanner.toStdString().c_str());
557 ssh_send_issue_banner(pClient->session, banner);
558 ssh_string_free(banner);
562void CBackendSftpServer::slotDisconnect(
const QString &szIp,
const quint16 port)
564 foreach(
auto c, m_lstClients) {
565 if(c->ip == szIp && c->port == port) {
566 m_lstClients.removeAll(c);
void sigConnected(const QString &szIp, const quint16 port)
emit when new client connected
void sigDisconnected(const QString &szIp, const quint16 port)
emit when the client disconnected
virtual int OnClean() override
Clean.
virtual OnInitReturnValue OnInit() override
Initialization.
virtual int OnProcess() override
Specific operation processing of plug-in.
virtual int WakeUp() override
Wake up backend thread(background thread).
void sigSecurityLevel(CSecurityLevel::Levels level)
Triggered when the security level changes.
CParameterUser m_User
[Instance user]