Rabbit Remote Control 0.1.0-bate10
Loading...
Searching...
No Matches
BackendSftpServer.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <QHostAddress>
4#include <QLoggingCategory>
5#include <QDir>
6#include <QFile>
7
8// Windows 平台需要包含不同的头文件
9#if defined(Q_OS_WIN)
10 #include <winsock2.h>
11 #include <ws2tcpip.h>
12#else
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <arpa/inet.h>
16#endif
17#include "Stats.h"
18#include "BackendSftpServer.h"
19
20#define DEF_STR_SIZE 1024
21char authorizedkeys[DEF_STR_SIZE] = {0};
22static Q_LOGGING_CATEGORY(log, "Backend.SftpServer")
23
24// 获取客户端 IP 地址的通用函数
25static QString GetClientAddress(ssh_session session)
26{
27 int sock_fd = ssh_get_fd(session);
28 if (sock_fd < 0) {
29 return QString();
30 }
31
32 struct sockaddr_storage addr;
33 socklen_t addr_len = sizeof(addr);
34
35 if (getpeername(sock_fd, (struct sockaddr*)&addr, &addr_len) == 0) {
36 char ip_str[INET6_ADDRSTRLEN];
37
38 if (addr.ss_family == AF_INET) {
39 // IPv4
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) {
44 // IPv6
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);
48 }
49 }
50
51 return QString();
52}
53
54// 同时获取 IP 和端口的函数
55static bool GetClientAddressAndPort(ssh_session session, QString& ip, quint16& port)
56{
57 int sock_fd = ssh_get_fd(session);
58 if (sock_fd < 0) {
59 return false;
60 }
61
62 struct sockaddr_storage addr;
63 socklen_t addr_len = sizeof(addr);
64
65 if (getpeername(sock_fd, (struct sockaddr*)&addr, &addr_len) == 0) {
66 char ip_str[INET6_ADDRSTRLEN];
67
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);
73 return true;
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);
79 return true;
80 }
81 }
82
83 return false;
84}
85
86CBackendSftpServer::CBackendSftpServer(COperateSftpServer *pOperate, bool bStopSignal)
87 : CBackendServer(pOperate, bStopSignal)
88 , m_pPara(pOperate->GetParameter())
89 , m_event(nullptr)
90{
91 qDebug(log) << Q_FUNC_INFO;
92}
93
94CBackendSftpServer::~CBackendSftpServer()
95{
96 qDebug(log) << Q_FUNC_INFO;
97}
98
99CBackend::OnInitReturnValue CBackendSftpServer::OnInit()
100{
101 OnInitReturnValue ret = OnInitReturnValue::Fail;
102 if(!m_pPara) return ret;
103
104 m_event = ssh_event_new();
105 if(!m_event)
106 return ret;
107
108 QDir root(m_pPara->GetRoot());
109 if (!root.exists() && !root.mkpath(".")) {
110 qCritical(log) << "Failed to create root directory";
111 return ret;
112 }
113
114 qint16 port = m_pPara->m_Net.GetPort();
115 if(m_pPara->GetListenAll()) {
116 int nRet = Listen("0.0.0.0", port);
117 if(0 == nRet)
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()) {
122 Listen(add, port);
123 }
124 qInfo(log) << "SFTP Server is listend on port:"
125 << port << m_pPara->GetListen()
126 << "; Root path:" << root.absolutePath();
127 } else
128 return ret;
129
130 if(m_pPara->GetAnonymousLogin())
131 emit sigSecurityLevel(CSecurityLevel::Level::SecureChannel);
132 else if(!m_pPara->m_Net.m_User.GetPassword().isEmpty())
133 emit sigSecurityLevel(CSecurityLevel::Level::SecureChannel
134 | CSecurityLevel::Level::Authentication);
135
136 return OnInitReturnValue::UseOnProcess;
137}
138
140{
141 int nRet = 0;
142
143 foreach(auto d, m_lstListen) {
144 ssh_bind_free(d->sshBind);
145 delete d->pListenNotifier;
146 delete d;
147 }
148 m_lstListen.clear();
149
150 foreach (auto c, m_lstClients) {
151 DeleteClient(c);
152 }
153 m_lstClients.clear();
154
155 if(m_event) {
156 ssh_event_free(m_event);
157 m_event = nullptr;
158 }
159
160 return nRet;
161}
162
164{
165 int nRet = 0;
166 return nRet;
167}
168
185{
186 int nRet = 0;
187
188 if(m_lstClients.isEmpty())
189 return 10;
190
191 QList<sClientData*> toRemove;
192
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);
202 }
203 foreach(auto pData, toRemove) {
204 m_lstClients.removeAll(pData);
205 DeleteClient(pData);
206 }
207
208 return 0;
209 }
210
211 foreach(auto pData, m_lstClients) {
212 if(!pData) {
213 toRemove.push_back(pData);
214 continue;
215 }
216
217 if(nullptr == pData->channel) {
218 // Authenticate
219 if(pData->pPara->GetAuthenticateAttempts() < pData->m_nAuthAttempts
220 || pData->pPara->GetAuthenticateTime()
221 < pData->m_Time.msecsTo(QTime::currentTime())) {
222 toRemove.push_back(pData);
223 continue;
224 }
225 } else if(pData->m_Time.isValid()) {
226 ssh_set_channel_callbacks(pData->channel, &pData->channel_cb);
227 pData->m_Time = QTime();
228 }
229
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);
234 }
235 }
236
237 foreach(auto pData, toRemove) {
238 m_lstClients.removeAll(pData);
239 DeleteClient(pData);
240 }
241
242 return 0;
243}
244
245bool CBackendSftpServer::InitHostKey(ssh_bind ssdBind)
246{
247 qDebug(log) << Q_FUNC_INFO;
248 if(!ssdBind) return false;
249
250 // 检查主机密钥文件
251 QString szPath;
252 szPath = m_pPara->GetHostKeyFile();
253 if (!QFile::exists(szPath)) {
254 qCritical(log) << "Host key not found:" << szPath;
255 return false;
256 }
257
258 // 尝试加载 RSA 密钥
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);
262 return false;
263 }
264
265 return true;
266}
267
268int CBackendSftpServer::Listen(const QString &szIp, qint16 nPort)
269{
270 int ret = -1;
271 sBindData* pData = new sBindData();
272 if(!pData) return ret;
273 do {
274 auto sshBind = ssh_bind_new();
275 if (!sshBind) {
276 qCritical(log) << "Failed to create SSH bind";
277 return ret;
278 }
279
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());
284
285 if (!InitHostKey(sshBind)) {
286 ssh_bind_free(sshBind);
287 return ret;
288 }
289
290 if (ssh_bind_listen(sshBind) < 0) {
291 qCritical(log) << "Failed to bind:" << ssh_get_error(sshBind);
292 ssh_bind_free(sshBind);
293 return ret;
294 }
295
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);
301 Q_ASSERT(check);
302 }
303 pData->sshBind = sshBind;
304 pData->pListenNotifier = pListenNotifier;
305 m_lstListen.push_back(pData);
306 return 0;
307 } while(0);
308 delete pData;
309 return -1;
310}
311
312ssh_bind CBackendSftpServer::GetSshBind(QSocketNotifier* pNotifier)
313{
314 foreach(auto d, m_lstListen) {
315 if(d->pListenNotifier == pNotifier)
316 return d->sshBind;
317 }
318 return nullptr;
319}
320
321void CBackendSftpServer::slotNewConnection()
322{
323 qDebug(log) << Q_FUNC_INFO;
324 int nRet = SSH_ERROR;
325
326 QSocketNotifier* pSocketNotifier = qobject_cast<QSocketNotifier*>(sender());
327 if(!pSocketNotifier) return;
328
329 // 接受新连接
330 ssh_session session = ssh_new();
331 if (!session) {
332 qCritical(log) << "Failed to create SSH session";
333 return;
334 }
335
336 struct sClientData* pData = new sClientData();
337 do {
338 memset(pData, 0, sizeof(struct sClientData));
339 pData->session = session;
340 pData->m_Time = QTime::currentTime();
341 pData->pPara = m_pPara;
342
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);
348
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);
354
355 if (authorizedkeys[0])
356 {
357 pData->server_cb.auth_pubkey_function = cbAuthPublickey;
358 ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY);
359 }
360 else
361 ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
362
363 nRet = ssh_set_server_callbacks(session, &pData->server_cb);
364 if(SSH_OK != nRet)
365 qCritical(log) << "Failed: Set server callbacks";
366
367 // 设置为非阻塞模式
368 ssh_set_blocking(session, 0);
369
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);
373 break;
374 }
375
376 // Check filter
377 bool bRet = GetClientAddressAndPort(session, pData->ip, pData->port);
378 if(!bRet) break;
379 qDebug(log) << "IP:" << pData->ip << "Port:" << pData->port;
380 auto& white = m_pPara->m_WhiteFilter;
381 auto& black = m_pPara->m_BlackFilter;
382 bool bFilte = false;
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)) {
389 bInWhite = true;
390 return -1;
391 }
392 return 0;
393 }, true);
394 }
395
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";
402 bFilte = true;
403 return -1;
404 }
405 return 0;
406 }, true);
407 }
408
409 if(bFilte)
410 break;
411
412 if (ssh_handle_key_exchange(session) != SSH_OK)
413 {
414 qCritical(log) << "Failed: Key exchange:" << ssh_get_error(session);
415 //return;
416 }
417
418 nRet = ssh_event_add_session(m_event, session);
419 if(SSH_OK != nRet)
420 qCritical(log) << "Failed: event add session";
421
422 m_lstClients.push_back(pData);
423
424 emit sigConnected(pData->ip, pData->port);
425 return;
426 } while(0);
427
428 if(pData)
429 delete pData;
430 if(session) {
431 ssh_disconnect(session);
432 ssh_free(session);
433 }
434}
435
436int CBackendSftpServer::cbAuthPassword(ssh_session session, const char *userName,
437 const char *pass, void *userdata)
438{
439 qDebug(log) << Q_FUNC_INFO;
440 struct sClientData *pData = (struct sClientData *)userdata;
441 Q_ASSERT(pData);
442
443 CParameterSftpServer* para = pData->pPara;
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()))
448 {
449 SendBanner(pData, userName);
450 return SSH_AUTH_SUCCESS;
451 }
452 pData->m_nAuthAttempts++;
453 return SSH_AUTH_DENIED;
454}
455
456int CBackendSftpServer::cbAuthPublickey(ssh_session session,
457 const char *user,
458 struct ssh_key_struct *pubkey,
459 char signature_state,
460 void *userdata)
461{
462 qDebug(log) << Q_FUNC_INFO;
463 struct sClientData *pData = (struct sClientData *)userdata;
464
465 if (signature_state == SSH_PUBLICKEY_STATE_NONE)
466 {
467 SendBanner(pData, user);
468 return SSH_AUTH_SUCCESS;
469 }
470
471 if (signature_state != SSH_PUBLICKEY_STATE_VALID)
472 {
473 return SSH_AUTH_DENIED;
474 }
475
476 /*
477 // valid so far. Now look through authorized keys for a match
478 if (authorizedkeys[0])
479 {
480 ssh_key key = NULL;
481 int result;
482 struct stat buf;
483
484 if (stat(authorizedkeys, &buf) == 0)
485 {
486 result = ssh_pki_import_pubkey_file(authorizedkeys, &key);
487 if ((result != SSH_OK) || (key == NULL))
488 {
489 qDebug(log) << "Unable to import public key file:" << authorizedkeys;
490 }
491 else
492 {
493 result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC);
494 ssh_key_free(key);
495 if (result == 0)
496 {
497 SendBanner(session);
498 return SSH_AUTH_SUCCESS;
499 }
500 }
501 }
502 }//*/
503
504 // no matches
505 return SSH_AUTH_DENIED;
506}
507
508ssh_channel CBackendSftpServer::cbChannelOpen(ssh_session session, void *userdata)
509{
510 qDebug(log) << Q_FUNC_INFO;
511 if(!session || !userdata) {
512 return nullptr;
513 }
514 struct sClientData *pData = (struct sClientData *)userdata;
515 if(!pData)
516 return nullptr;
517 pData->channel = ssh_channel_new(session);
518 return pData->channel;
519}
520
521int CBackendSftpServer::DeleteClient(sClientData *pClient)
522{
523 qDebug(log) << Q_FUNC_INFO;
524 int nRet = 0;
525 if(!pClient)
526 return -1;
527
528 if(pClient->channel && ssh_channel_is_open(pClient->channel)) {
529 ssh_channel_send_eof(pClient->channel);
530 ssh_channel_close(pClient->channel);
531 }
532
533 if(pClient->session) {
534 if(ssh_is_connected(pClient->session))
535 ssh_disconnect(pClient->session);
536 if(m_event)
537 ssh_event_remove_session(m_event, pClient->session);
538 ssh_free(pClient->session);
539 }
540
541 emit sigDisconnected(pClient->ip, pClient->port);
542
543 delete pClient;
544 return nRet;
545}
546
547void CBackendSftpServer::SendBanner(sClientData *pClient, const QString& user)
548{
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());
556 if (banner) {
557 ssh_send_issue_banner(pClient->session, banner);
558 ssh_string_free(banner);
559 }
560}
561
562void CBackendSftpServer::slotDisconnect(const QString &szIp, const quint16 port)
563{
564 foreach(auto c, m_lstClients) {
565 if(c->ip == szIp && c->port == port) {
566 m_lstClients.removeAll(c);
567 DeleteClient(c);
568 break;
569 }
570 }
571}
Server interface.
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]