Project SkyFire Core
SkyFire 5.4.8 server core API documentation
Loading...
Searching...
No Matches
RASocket.cpp
Go to the documentation of this file.
1/*
2* This file is part of Project SkyFire https://www.projectskyfire.org.
3* See LICENSE.md file for Copyright information
4*/
5
9
10#include "AccountMgr.h"
11#include "Common.h"
14#include "Log.h"
16#include "RASocket.h"
17#include "SRP6.h"
18#include "World.h"
19#include <boost/asio/buffer.hpp>
20#include <boost/asio/error.hpp>
21#include <boost/asio/read_until.hpp>
22#include <boost/asio/write.hpp>
23#include <boost/system/error_code.hpp>
24#include <chrono>
25#include <cstring>
26#include <istream>
27#include <sstream>
28#include <utility>
29
30RASocket::RASocket(std::shared_ptr<Skyfire::Asio::IoContextExecutor> executor, std::unique_ptr<RASocketHandle> socket, std::string const& remoteAddress) :
31 _executor(std::move(executor)),
32 _socket(std::move(socket)),
33 _subnegotiationTimer(_executor->GetIoContext()),
34 _remoteAddress(remoteAddress),
38 _writeInProgress(false),
39 _closed(false),
42 _user(),
43 _pass(),
44 _minLevel(3),
45 _commandExecuting(false),
47 _commandComplete(false),
50{
51 _minLevel = uint8(sConfigMgr->GetIntDefault("RA.MinLevel", 3));
52}
53
55{
56 close();
57}
58
60{
61 std::shared_ptr<RASocket> self = shared_from_this();
62 _executor->Post([self]
63 {
64 self->start_subnegotiation();
65 });
66}
67
69{
70 if (_closed.exchange(true))
71 return;
72
73 SF_LOG_INFO("commands.ra", "Closing connection");
74
76
77 if (_socket && _socket->is_open())
79}
80
82{
83 return !_closed && _socket && _socket->is_open();
84}
85
87{
88 if (!is_open())
89 {
90 close();
91 return;
92 }
93
94 _subnegotiationDone = false;
96
97 std::shared_ptr<RASocket> self = shared_from_this();
98 _subnegotiationTimer.expires_after(std::chrono::milliseconds(1000));
99 _subnegotiationTimer.async_wait([self](boost::system::error_code const& error)
100 {
101 if (error || self->_subnegotiationDone)
102 return;
103
104 self->_subnegotiationDone = true;
105 self->_subnegotiationTimedOut = true;
106
107 boost::system::error_code ignored;
108 self->_socket->cancel(ignored);
109 self->finish_subnegotiation();
110 });
111
112 _socket->async_read_some(boost::asio::buffer(_subnegotiationBuffer),
113 [self](boost::system::error_code const& error, size_t transferredBytes)
114 {
115 self->handle_subnegotiation_read(error, transferredBytes);
116 });
117}
118
119void RASocket::handle_subnegotiation_read(boost::system::error_code const& error, size_t transferredBytes)
120{
122 return;
123
124 if (error == boost::asio::error::operation_aborted && _subnegotiationTimedOut)
125 return;
126
127 _subnegotiationDone = true;
128
130
131 if (error)
132 {
133 close();
134 return;
135 }
136
137 if (transferredBytes == 0)
138 {
140 return;
141 }
142
143 if (transferredBytes >= _subnegotiationBuffer.size())
144 {
145 SF_LOG_DEBUG("commands.ra", "RASocket::subnegotiate: allocated buffer 1024 bytes was too small for negotiation packet, size: %u", uint32(transferredBytes));
146 close();
147 return;
148 }
149
150#ifdef _DEBUG
151 for (size_t i = 0; i < transferredBytes;)
152 {
154 if (iac == 0xFF) // "Interpret as Command" (IAC)
155 {
156 if (i + 2 >= transferredBytes)
157 {
158 close();
159 return;
160 }
161
162 uint8 command = uint8(_subnegotiationBuffer[++i]);
163 std::stringstream ss;
164 switch (command)
165 {
166 case 0xFB: // WILL
167 ss << "WILL ";
168 break;
169 case 0xFC: // WON'T
170 ss << "WON'T ";
171 break;
172 case 0xFD: // DO
173 ss << "DO ";
174 break;
175 case 0xFE: // DON'T
176 ss << "DON'T ";
177 break;
178 default:
179 close();
180 return;
181 }
182
183 uint8 param = uint8(_subnegotiationBuffer[++i]);
184 ss << uint32(param);
185 SF_LOG_DEBUG("commands.ra", "%s", ss.str().c_str());
186 }
187 ++i;
188 }
189#endif
190
191 uint8 const reply[2] = { 0xFF, 0xF0 };
192 std::shared_ptr<RASocket> self = shared_from_this();
193 queue_write(std::string(reinterpret_cast<char const*>(reply), sizeof(reply)), [self](bool success)
194 {
195 if (success)
196 self->finish_subnegotiation();
197 else
198 self->close();
199 });
200}
201
206
208{
209 std::shared_ptr<RASocket> self = shared_from_this();
210 queue_write("Authentication required\r\n", [self](bool success)
211 {
212 if (success)
213 self->prompt_username();
214 else
215 self->close();
216 });
217}
218
220{
221 std::shared_ptr<RASocket> self = shared_from_this();
222 queue_write("Username: ", [self](bool success)
223 {
224 if (!success)
225 {
226 self->close();
227 return;
228 }
229
230 self->async_read_line([self](bool read, std::string line)
231 {
232 if (!read)
233 {
234 self->close();
235 return;
236 }
237
238 self->_user = std::move(line);
239 self->prompt_password();
240 });
241 });
242}
243
245{
246 std::shared_ptr<RASocket> self = shared_from_this();
247 queue_write("Password: ", [self](bool success)
248 {
249 if (!success)
250 {
251 self->close();
252 return;
253 }
254
255 self->async_read_line([self](bool read, std::string line)
256 {
257 if (!read)
258 {
259 self->close();
260 return;
261 }
262
263 self->_pass = std::move(line);
264 self->handle_credentials();
265 });
266 });
267}
268
270{
271 SF_LOG_INFO("commands.ra", "Login attempt for user: %s", _user.c_str());
272
273 if (check_access_level(_user) == -1 || check_password(_user, _pass) == -1)
274 {
275 std::shared_ptr<RASocket> self = shared_from_this();
276 queue_write("Authentication failed\r\n", [self](bool)
277 {
278 self->close();
279 });
280 return;
281 }
282
283 SF_LOG_INFO("commands.ra", "User login: %s", _user.c_str());
284
285 std::shared_ptr<RASocket> self = shared_from_this();
286 queue_write(std::string(sWorld->GetMotd()) + "\r\n", [self](bool success)
287 {
288 if (success)
289 self->prompt_command();
290 else
291 self->close();
292 });
293}
294
296{
297 std::shared_ptr<RASocket> self = shared_from_this();
298 queue_write("TC> ", [self](bool success)
299 {
300 if (!success)
301 {
302 self->close();
303 return;
304 }
305
306 self->async_read_line([self](bool read, std::string line)
307 {
308 if (!read)
309 {
310 self->close();
311 return;
312 }
313
314 self->handle_command(line);
315 });
316 });
317}
318
319void RASocket::handle_command(std::string const& command)
320{
321 if (command.empty())
322 {
324 return;
325 }
326
327 SF_LOG_INFO("commands.ra", "Received command: %s", command.c_str());
328
329 if (command == "quit" || command == "exit" || command == "logout")
330 {
331 std::shared_ptr<RASocket> self = shared_from_this();
332 queue_write("Bye\r\n", [self](bool)
333 {
334 self->close();
335 });
336 return;
337 }
338
339 start_command(command);
340}
341
342void RASocket::start_command(std::string const& command)
343{
344 _commandExecuting = true;
345
346 {
347 std::lock_guard<std::mutex> guard(_commandLock);
348 _commandComplete = false;
350 std::queue<std::string> empty;
351 std::swap(_commandOutput, empty);
352 _commandSelf = shared_from_this();
353 }
354
356 sWorld->QueueCliCommand(cmd);
357}
358
359void RASocket::queue_write(std::string data, WriteCallback callback)
360{
361 if (_closed)
362 {
363 if (callback)
364 callback(false);
365 return;
366 }
367
368 bool startWrite = !_writeInProgress && _writeQueue.empty();
369 _writeQueue.push_back(PendingWrite{ std::move(data), std::move(callback) });
370
371 if (startWrite)
373}
374
376{
377 if (_closed || _writeInProgress || _writeQueue.empty())
378 return;
379
380 _writeInProgress = true;
381
382 std::shared_ptr<RASocket> self = shared_from_this();
383 boost::asio::async_write(*_socket, boost::asio::buffer(_writeQueue.front().Data),
384 [self](boost::system::error_code const& error, size_t)
385 {
386 self->handle_async_write(error);
387 });
388}
389
390void RASocket::handle_async_write(boost::system::error_code const& error)
391{
392 WriteCallback callback;
393
394 if (!_writeQueue.empty())
395 {
396 callback = std::move(_writeQueue.front().Callback);
397 _writeQueue.pop_front();
398 }
399
400 _writeInProgress = false;
401
402 if (error)
403 {
404 if (callback)
405 callback(false);
406
407 close();
408 return;
409 }
410
411 if (callback)
412 callback(true);
413
414 if (!_writeQueue.empty())
416}
417
419{
420 if (_closed)
421 {
422 callback(false, std::string());
423 return;
424 }
425
426 std::shared_ptr<RASocket> self = shared_from_this();
427 boost::asio::async_read_until(*_socket, _lineBuffer, '\n',
428 [self, callback = std::move(callback)](boost::system::error_code const& error, size_t transferredBytes) mutable
429 {
430 self->handle_read_line(error, transferredBytes, std::move(callback));
431 });
432}
433
434void RASocket::handle_read_line(boost::system::error_code const& error, size_t /*transferredBytes*/, ReadLineCallback callback)
435{
436 if (error)
437 {
438 callback(false, std::string());
439 close();
440 return;
441 }
442
443 std::istream input(&_lineBuffer);
444 std::string line;
445 std::getline(input, line);
446
447 if (!line.empty() && line.back() == '\r')
448 line.pop_back();
449
450 callback(true, std::move(line));
451}
452
454{
455 if (_closed)
456 {
457 std::lock_guard<std::mutex> guard(_commandLock);
459 _commandSelf.reset();
460 return;
461 }
462
463 std::string output;
464 bool hasOutput = false;
465 bool commandComplete = false;
466
467 {
468 std::lock_guard<std::mutex> guard(_commandLock);
470 return;
471
472 if (!_commandOutput.empty())
473 {
474 output = _commandOutput.front();
475 _commandOutput.pop();
477 hasOutput = true;
478 }
479 else if (_commandComplete)
480 {
481 commandComplete = true;
482 _commandSelf.reset();
483 }
484 }
485
486 if (hasOutput)
487 {
488 std::shared_ptr<RASocket> self = shared_from_this();
489 queue_write(std::move(output), [self](bool success)
490 {
491 {
492 std::lock_guard<std::mutex> guard(self->_commandLock);
493 self->_commandOutputDrainInProgress = false;
494 }
495
496 if (success)
497 self->drain_command_output();
498 else
499 self->close();
500 });
501 return;
502 }
503
504 if (commandComplete)
506}
507
508int RASocket::check_access_level(const std::string& user)
509{
510 std::string safeUser = user;
511
513
514 PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_SEL_ACCOUNT_ACCESS);
515 stmt->setString(0, safeUser);
516 PreparedQueryResult result = LoginDatabase.Query(stmt);
517
518 if (!result)
519 {
520 SF_LOG_INFO("commands.ra", "User %s does not exist in database", user.c_str());
521 return -1;
522 }
523
524 Field* fields = result->Fetch();
525
526 if (fields[1].GetUInt8() < _minLevel)
527 {
528 SF_LOG_INFO("commands.ra", "User %s has no privilege to login", user.c_str());
529 return -1;
530 }
531 else if (fields[2].GetInt32() != -1)
532 {
533 SF_LOG_INFO("commands.ra", "User %s has to be assigned on all realms (with RealmID = '-1')", user.c_str());
534 return -1;
535 }
536
537 return 0;
538}
539
540int RASocket::check_password(const std::string& user, const std::string& pass)
541{
542 std::string safe_user = user;
544
545 std::string safe_pass = pass;
547
549
550 stmt->setString(0, safe_user);
551 if (PreparedQueryResult result = LoginDatabase.Query(stmt))
552 {
555
556 if (SkyFire::Crypto::SRP6::CheckLogin(safe_user, safe_pass, salt, verifier))
557 return 0;
558 }
559
560 SF_LOG_INFO("commands.ra", "Wrong password for user: %s", user.c_str());
561 return -1;
562}
563
564void RASocket::zprint(void* callbackArg, const char* szText)
565{
566 if (!szText || !callbackArg)
567 return;
568
569 RASocket* socket = static_cast<RASocket*>(callbackArg);
570 if (socket->_closed)
571 return;
572
573 size_t sz = strlen(szText);
574 std::shared_ptr<RASocket> self;
575
576 {
577 std::lock_guard<std::mutex> guard(socket->_commandLock);
578 socket->_commandOutput.push(std::string(szText, sz));
579 self = socket->_commandSelf;
580 }
581
582 if (self)
583 self->_executor->Post([self]
584 {
585 self->drain_command_output();
586 });
587}
588
589void RASocket::commandFinished(void* callbackArg, bool /*success*/)
590{
591 if (!callbackArg)
592 return;
593
594 RASocket* socket = static_cast<RASocket*>(callbackArg);
595 std::shared_ptr<RASocket> self;
596
597 {
598 std::lock_guard<std::mutex> guard(socket->_commandLock);
599 socket->_commandComplete = true;
600 socket->_commandExecuting = false;
601 self = socket->_commandSelf;
602
603 if (socket->_closed)
604 {
605 socket->_commandSelf.reset();
606 return;
607 }
608 }
609
610 if (self)
611 self->_executor->Post([self]
612 {
613 self->drain_command_output();
614 });
615}
#define sConfigMgr
Definition Config.h:64
std::uint8_t uint8
Definition Define.h:79
std::uint32_t uint32
Definition Define.h:77
#define SF_LOG_DEBUG(filterType__,...)
Definition Log.h:134
#define SF_LOG_INFO(filterType__,...)
Definition Log.h:137
@ LOGIN_SEL_CHECK_PASSWORD_BY_NAME
@ LOGIN_SEL_ACCOUNT_ACCESS
Skyfire::AutoPtr< PreparedResultSet, Skyfire::Mutex > PreparedQueryResult
Definition QueryResult.h:94
static bool normalizeString(std::string &utf8String)
Definition Field.h:16
void setString(const uint8 index, const std::string &value)
void prompt_command()
Definition RASocket.cpp:295
void handle_async_write(boost::system::error_code const &error)
Definition RASocket.cpp:390
bool _commandOutputDrainInProgress
Definition RASocket.h:92
std::unique_ptr< RASocketHandle > _socket
Definition RASocket.h:75
void queue_write(std::string data, WriteCallback callback=WriteCallback())
Definition RASocket.cpp:359
boost::asio::streambuf _lineBuffer
Definition RASocket.h:79
void send_authentication_required()
Definition RASocket.cpp:207
std::function< void(bool, std::string)> ReadLineCallback
Definition RASocket.h:41
void async_read_line(ReadLineCallback callback)
Definition RASocket.cpp:418
std::string _remoteAddress
Definition RASocket.h:77
void start_command(std::string const &command)
Definition RASocket.cpp:342
void finish_subnegotiation()
Definition RASocket.cpp:202
void start()
Definition RASocket.cpp:59
std::shared_ptr< Skyfire::Asio::IoContextExecutor > _executor
Definition RASocket.h:74
uint8 _minLevel
Minimum security level required to connect.
Definition RASocket.h:87
RASocket(std::shared_ptr< Skyfire::Asio::IoContextExecutor > executor, std::unique_ptr< RASocketHandle > socket, std::string const &remoteAddress)
Definition RASocket.cpp:30
std::function< void(bool)> WriteCallback
Definition RASocket.h:40
void handle_subnegotiation_read(boost::system::error_code const &error, size_t transferredBytes)
Definition RASocket.cpp:119
boost::asio::steady_timer _subnegotiationTimer
Definition RASocket.h:76
int check_password(const std::string &user, const std::string &pass)
Definition RASocket.cpp:540
virtual ~RASocket()
Definition RASocket.cpp:54
std::string _pass
Definition RASocket.h:86
bool _subnegotiationTimedOut
Definition RASocket.h:84
void drain_command_output()
Definition RASocket.cpp:453
void start_async_write()
Definition RASocket.cpp:375
std::shared_ptr< RASocket > _commandSelf
Definition RASocket.h:93
void prompt_password()
Definition RASocket.cpp:244
std::string _user
Definition RASocket.h:85
void close()
Definition RASocket.cpp:68
std::atomic< bool > _commandExecuting
Definition RASocket.h:88
void prompt_username()
Definition RASocket.cpp:219
static void zprint(void *callbackArg, const char *szText)
Definition RASocket.cpp:564
bool _subnegotiationDone
Definition RASocket.h:83
int check_access_level(const std::string &user)
Definition RASocket.cpp:508
void handle_command(std::string const &command)
Definition RASocket.cpp:319
static void commandFinished(void *callbackArg, bool success)
Definition RASocket.cpp:589
std::mutex _commandLock
Definition RASocket.h:89
std::array< char, 1024 > _subnegotiationBuffer
Definition RASocket.h:78
void handle_credentials()
Definition RASocket.cpp:269
bool _writeInProgress
Definition RASocket.h:81
void start_subnegotiation()
Used by telnet protocol RFC 854 / 855.
Definition RASocket.cpp:86
bool is_open() const
Definition RASocket.cpp:81
bool _commandComplete
Definition RASocket.h:91
void handle_read_line(boost::system::error_code const &error, size_t transferredBytes, ReadLineCallback callback)
Definition RASocket.cpp:434
std::queue< std::string > _commandOutput
Definition RASocket.h:90
std::atomic< bool > _closed
Definition RASocket.h:82
std::deque< PendingWrite > _writeQueue
Definition RASocket.h:80
std::array< uint8, SALT_LENGTH > Salt
Definition SRP6.h:18
static bool CheckLogin(std::string const &username, std::string const &password, Salt const &salt, Verifier const &verifier)
Definition SRP6.h:30
std::array< uint8, VERIFIER_LENGTH > Verifier
Definition SRP6.h:20
static constexpr size_t VERIFIER_LENGTH
Definition SRP6.h:19
static constexpr size_t SALT_LENGTH
Definition SRP6.h:17
#define sWorld
Definition World.h:910
void CloseTcpSocket(boost::asio::ip::tcp::socket &socket)
void CancelTimer(Timer &timer)
LoginDatabaseWorkerPool LoginDatabase
Definition Main.cpp:54
Storage class for commands issued for delayed execution.
Definition World.h:524