7#include <spdlog/spdlog.h>
43 {
"clear",
"Clears the terminal screen"},
44 {
"disable log",
"Resets logger level"},
45 {
"enable log", R
"(Enable specified logger level. Level can be "v" (info), "vv" (debug) and "vvv" (trace))"},
46 {"help",
"Prints available commands"},
47 {
"ping",
"Pings the server"},
48 {
"status",
"Checks the internal status"},
49 {
"version",
"Displays the current version"},
57 {
"quit",
"Ends the connection"}};
111 sockaddr_in client_info{};
112 memset(&client_info, 0,
sizeof(client_info));
113 socklen_t addrsize =
sizeof(client_info);
114 getpeername(
m_socket,
reinterpret_cast<sockaddr *
>(&client_info), &addrsize);
116 std::array<char, INET_ADDRSTRLEN> ipAddr{};
117 inet_ntop(AF_INET, &client_info.sin_addr, ipAddr.data(), INET_ADDRSTRLEN);
119 return ipAddr.data();
153 const std::string moveBack =
"\x1b[80D";
154 sendBytes = send(
m_socket, moveBack.c_str(), moveBack.length(), 0);
170 if (
auto sendBytes = send(
m_socket, data.c_str(), data.length(), 0) > 0)
183 spdlog::info(
"Telnet connection to {} closed",
getPeerIP());
197 return (llabs(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() -
lastSeenTime)
203 lastSeenTime = std::chrono::system_clock::time_point(std::chrono::duration<int>(0));
209 if (
static_cast<uint8_t
>(*buffer) ==
ASCII_NBSP)
214 const ssize_t sendBytes = send(
m_socket, buffer, length, 0);
224 spdlog::info(
"Telnet connection received from {}",
getPeerIP());
229 unsigned long iMode = 1;
233 const std::array<uint8_t, 3> willEcho{0xff, 0xfb, 0x01};
234 ssize_t sendBytes = send(
m_socket, willEcho.data(), 3, 0);
241 const std::array<uint8_t, 3> dontEcho{0xff, 0xfe, 0x01};
242 sendBytes = send(
m_socket, dontEcho.data(), 3, 0);
249 const std::array<uint8_t, 3> willSGA{0xff, 0xfb, 0x03};
250 sendBytes = send(
m_socket, willSGA.data(), 3, 0);
270 found = buffer.find_first_of(
static_cast<char>(
ASCII_NBSP));
271 if (found != std::string::npos && (found + 2) <= buffer.length() - 1)
273 buffer.erase(found, 3);
275 else if ((found + 2) >= buffer.length())
279 }
while (found != std::string::npos);
287 for (
const auto &cursor : cursors)
291 found = buffer.find(cursor);
292 if (found != std::string::npos)
294 buffer.erase(found, cursor.length());
296 }
while (found != std::string::npos);
302 bool foundBackspaces =
false;
308 found = buffer.find_first_of(
'\x7f');
309 if (found == std::string::npos)
311 found = buffer.find_first_of(
'\b');
314 if (found != std::string::npos)
316 if (buffer.length() > 1 && (found > 0))
318 buffer.erase(found - 1, 2);
324 foundBackspaces =
true;
326 }
while (found != std::string::npos);
327 return foundBackspaces;
332 bool foundTabs =
false;
337 found = buffer.find_first_of(
'\t');
338 if (found == std::string::npos)
346 buffer.erase(found, 1);
353 const std::string retCommand =
m_telnetServer->tabCallback()(shared_from_this(), buffer.substr(0, found));
354 if (!retCommand.empty())
356 buffer.erase(0, found);
357 buffer.insert(0, retCommand);
360 }
while (found != std::string::npos);
392 ssize_t sendBytes = 0;
409 ssize_t sendBytes = 0;
431 std::vector<std::string> lines;
436 found = buffer.find(
"\r\n");
437 if (found != std::string::npos)
439 lines.push_back(buffer.substr(0, found));
440 buffer.erase(0, found + 2);
442 }
while (found != std::string::npos);
449 ssize_t readBytes = 0;
450 std::array<char, DEFAULT_BUFLEN> recvbuf{};
462 if (readBytes < 0 && errno != EAGAIN)
466 else if (readBytes > 0)
474 echoBack(recvbuf.data(),
static_cast<size_t>(readBytes));
479 recvbuf.begin(), recvbuf.begin() + readBytes, [](
char chr) { return chr == ASCII_NULL; },
ASCII_LF);
482 m_buffer.append(recvbuf.data(),
static_cast<unsigned int>(readBytes));
486 bool requirePromptReprint =
false;
492 requirePromptReprint =
true;
499 requirePromptReprint =
true;
504 requirePromptReprint =
true;
510 for (
const auto &line : lines)
537 catch (
const std::exception &e)
541 spdlog::error(
"Telnet server destructor thrown an exception: {}", e.what());
543 catch (
const std::exception &e2)
545 std::cerr <<
"Telnet server destructor and also logger thrown an exception: " << e.what() <<
'\n'
546 << e2.what() <<
'\n';
552 std::string promptString,
const std::shared_ptr<prometheus::Registry> ®,
553 const std::string &prependName)
565 memset(&hints, 0,
sizeof(hints));
567 hints.ai_family = AF_INET;
568 hints.ai_socktype = SOCK_STREAM;
569 hints.ai_protocol = IPPROTO_TCP;
570 hints.ai_flags = AI_PASSIVE;
573 addrinfo *result =
nullptr;
574 if (getaddrinfo(
nullptr, std::to_string(
m_listenPort).c_str(), &hints, &result) != 0)
580 m_listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
583 freeaddrinfo(result);
587 if (
int optionVal = 1; setsockopt(
m_listenSocket, SOL_SOCKET, SO_REUSEADDR, &optionVal,
sizeof(optionVal)) < 0)
589 freeaddrinfo(result);
595 if (bind(
m_listenSocket, result->ai_addr, result->ai_addrlen) < 0)
597 freeaddrinfo(result);
601 freeaddrinfo(result);
612 m_stats = std::make_unique<TelnetStats>(reg, listenPort, prependName);
632 const auto session = std::make_shared<TelnetSession>(ClientSocket, shared_from_this());
633 session->initialise();
635 session->sendLine(
"Too many active connections. Please try again later. \r\nClosing...");
636 session->closeClient();
640 const auto session = std::make_shared<TelnetSession>(ClientSocket, shared_from_this());
642 session->initialise();
648 spdlog::info(
"Telnet server started");
659 catch (
const std::exception &e)
661 spdlog::error(
"Telnet server failed: {}", e.what());
665 spdlog::info(
"Telnet server stopped");
678 bool newConnectionAccept =
false;
679 bool newConnectionRefused =
false;
685 if (select(
m_listenSocket + 1, &readSet,
nullptr,
nullptr, &timeout) > 0)
689 newConnectionAccept =
true;
693 newConnectionRefused =
true;
698 for (
size_t idx = 0; idx <
m_sessions.size(); ++idx)
729 m_stats->consumeStats(serverStats);
738 tSession->closeClient();
758 session->sendLine(
"");
759 session->sendLine(
"Available commands:");
760 session->sendLine(
"");
763 std::array<char, BUFSIZ> buffer{
'\0'};
764 if (snprintf(buffer.data(), BUFSIZ,
"%-25s : %s", command.c_str(), info.c_str()) > 0)
766 session->sendLine(buffer.data());
773 session->sendLine(
"\r\n"
774 "𝑲𝒆𝒆𝒑 𝒚𝒐𝒖𝒓 𝒆𝒚𝒆𝒔 𝒐𝒏 𝒕𝒉𝒆 𝒔𝒕𝒂𝒓𝒔 "
775 "𝒂𝒏𝒅 𝒚𝒐𝒖𝒓 𝒇𝒆𝒆𝒕 𝒐𝒏 𝒕𝒉𝒆 𝒈𝒓𝒐𝒖𝒏𝒅 "
782 spdlog::trace(
"Received message {}", line);
785 session->sendLine(line);
796 session->sendLine(
"OK");
802 session->sendLine(
"Default log mode enabled");
803 spdlog::set_level(spdlog::level::info);
806 session->sendLine(
"Disabling all logs");
807 spdlog::set_level(spdlog::level::off);
810 session->sendLine(
"Info log mode enabled");
811 spdlog::set_level(spdlog::level::info);
814 session->sendLine(
"Debug log mode enabled");
815 spdlog::set_level(spdlog::level::debug);
818 session->sendLine(
"Trace log mode enabled");
819 spdlog::set_level(spdlog::level::trace);
822 session->sendLine(
"pong");
825 session->sendLine(PROJECT_FULL_VERSION_STRING);
831 for (
const auto &[service, statusFlag] :
vCheckFlag)
833 std::ostringstream oss;
834 oss << std::left << std::setfill(
'.') << std::setw(
KEY_WIDTH) << service +
" " << std::setw(
VAL_WIDTH)
835 << std::right << (statusFlag->_M_i ?
" OK" :
" Not Active");
836 session->sendLine(oss.str());
847 session->sendLine(
"Closing connection");
848 session->sendLine(
"Goodbye!");
849 session->markTimeout();
852 session->sendLine(
"Unknown command received");
862 std::ostringstream sStream;
865 if (command.rfind(line, 0) == 0)
869 sStream << command << std::setw(
KEY_WIDTH);
873 if (ctr != 1 && (!sStream.str().empty()))
875 session->sendLine(sStream.str());
std::vector< std::pair< std::string, std::shared_ptr< std::atomic_flag > > > vCheckFlag
Global variable to check if the servers are running.
constexpr size_t constHasher(const char *s)
const std::string ANSI_UNDERLINE_OFF("\x1b[24m")
const std::string ANSI_FG_BLACK("\x1b[30m")
const std::string ANSI_ITALCIS_OFF("\x1b[23m")
const std::string ANSI_ITALICS_ON("\x1b[3m")
const std::string ANSI_BG_YELLOW("\x1b[43m")
const std::string ANSI_BG_BLUE("\x1b[44m")
const std::string TELNET_ERASE_LINE("\xff\xf8")
const std::string ANSI_FG_DEFAULT("\x1b[39m")
void TelnetConnectedCallback(const SP_TelnetSession &session)
const std::string ANSI_ARROW_RIGHT("\x1b\x5b\x43")
void TelnetPrintAvailableCommands(const SP_TelnetSession &session)
const std::string ANSI_FG_MAGENTA("\x1b[35m")
const std::string ANSI_INVERSE_OFF("\x1b[27m")
const std::string ANSI_ARROW_DOWN("\x1b\x5b\x42")
const std::string ANSI_STRIKETHROUGH_ON("\x1b[9m")
const std::string ANSI_ARROW_UP("\x1b\x5b\x41")
const std::string ANSI_FG_CYAN("\x1b[36m")
const std::string ANSI_UNDERLINE_ON("\x1b[4m")
const std::string ANSI_ERASE_SCREEN("\x1b[2J")
const std::string ANSI_FG_BLUE("\x1b[34m")
const std::string ANSI_DOUBLE_HORIZONTAL_TAB("\t\t")
const std::string ANSI_FG_RED("\x1b[31m")
constexpr int SLEEP_INTERVAL_MS
const std::string ANSI_FG_YELLOW("\x1b[33m")
const std::string ANSI_HORIZONTAL_TAB("\t")
const std::string ANSI_BG_RED("\x1b[41m")
const std::string ANSI_FG_GREEN("\x1b[32m")
const std::string ANSI_BOLD_ON("\x1b[1m")
const std::string ANSI_BG_WHITE("\x1b[47m")
const std::string ANSI_STRIKETHROUGH_OFF("\x1b[29m")
const std::string ANSI_BG_GREEN("\x1b[42m")
const std::string ANSI_BG_DEFAULT("\x1b[49m")
std::string TelnetTabCallback(const SP_TelnetSession &session, std::string_view line)
constexpr int DEFAULT_BUFLEN
constexpr int MAX_AVAILABLE_SESSION
constexpr int INVALID_SOCKET
const std::string TELNET_CLEAR_SCREEN("\033[2J")
const std::string ANSI_BG_CYAN("\x1b[46m")
const std::string ANSI_BG_MAGENTA("\x1b[45m")
const std::string ANSI_BOLD_OFF("\x1b[22m")
const std::string ANSI_ARROW_LEFT("\x1b\x5b\x44")
const std::string ANSI_BG_BLACK("\x1b[40m")
const std::vector< std::pair< std::string, std::string > > telnetCommands
constexpr int TELNET_TIMEOUT
constexpr int TELNET_HISTORY_LIMIT
const std::string ANSI_INVERSE_ON("\x1b[7m")
const std::string ANSI_FG_WHITE("\x1b[37m")
const std::string ANSI_ERASE_LINE("\x1b[2K")
bool TelnetMessageCallback(const SP_TelnetSession &session, const std::string &line)
std::shared_ptr< TelnetSession > SP_TelnetSession
std::unique_ptr< TelnetStats > m_stats
unsigned long m_listenPort
std::atomic_flag m_shouldStop
std::string m_promptString
std::unique_ptr< std::thread > m_serverThread
bool initialise(unsigned long listenPort, const std::shared_ptr< std::atomic_flag > &checkFlag, std::string promptString="", const std::shared_ptr< prometheus::Registry > ®=nullptr, const std::string &prependName="")
void shutdown()
Closes the Telnet Server.
VEC_SP_TelnetSession m_sessions
void threadFunc() noexcept
std::shared_ptr< std::atomic_flag > m_checkFlag
const std::string & promptString() const
~TelnetServer()
Destructor for server.
void update()
Process new connections and messages.
void update()
Called every frame/loop by the Terminal Server.
TelnetSessionStats stats
Statistics variables.
bool processTab(std::string &buffer)
void markTimeout()
Marks timeout to close session.
bool processCommandHistory(std::string &buffer)
std::chrono::system_clock::time_point lastSeenTime
static bool processBackspace(std::string &buffer)
static void stripEscapeCharacters(std::string &buffer)
static void stripNVT(std::string &buffer)
bool checkTimeout() const
Checks the connection timeout.
void sendLine(std::string data)
Send a line of data to the Telnet Server.
std::shared_ptr< TelnetServer > m_telnetServer
void sendPromptAndBuffer()
static std::vector< std::string > getCompleteLines(std::string &buffer)
void closeClient()
Finish the session.
std::list< std::string > m_history
void initialise()
Initialise session.
void echoBack(const char *buffer, unsigned long length)
void addToHistory(const std::string &line)
std::string getPeerIP() const
std::list< std::string >::iterator m_historyCursor
std::chrono::high_resolution_clock::time_point processingTimeStart
Processing time start.
uint64_t activeConnectionCtr
Number of active connections.
std::chrono::high_resolution_clock::time_point processingTimeEnd
Processing time end.
uint64_t refusedConnectionCtr
Number of refused connections.
uint64_t acceptedConnectionCtr
Number of accepted connections.
std::chrono::high_resolution_clock::time_point disconnectTime
Connection end time.
uint64_t failCmdCtr
Failed commands.
uint64_t successCmdCtr
Successful commands.
size_t downloadBytes
Downloaded bytes.
std::chrono::high_resolution_clock::time_point connectTime
Connection start time.
size_t uploadBytes
Uploaded bytes.