7#include <spdlog/spdlog.h>
44 {
"clear",
"Clears the terminal screen"},
45 {
"disable log",
"Resets logger level"},
46 {
"enable log", R
"(Enable specified logger level. Level can be "v" (info), "vv" (debug) and "vvv" (trace))"},
47 {"help",
"Prints available commands"},
48 {
"ping",
"Pings the server"},
49 {
"status",
"Checks the internal status"},
50 {
"version",
"Displays the current version"},
58 {
"quit",
"Ends the connection"}};
112 sockaddr_in client_info{};
113 memset(&client_info, 0,
sizeof(client_info));
114 socklen_t addrsize =
sizeof(client_info);
115 getpeername(
m_socket, std::bit_cast<sockaddr *>(&client_info), &addrsize);
117 std::array<char, INET_ADDRSTRLEN> ipAddr{};
118 inet_ntop(AF_INET, &client_info.sin_addr, ipAddr.data(), INET_ADDRSTRLEN);
120 return ipAddr.data();
130 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
139 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
150 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
154 const std::string moveBack =
"\x1b[80D";
155 sendBytes = send(
m_socket, moveBack.c_str(), moveBack.length(), 0);
158 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
171 if (
const auto sendBytes = send(
m_socket, data.c_str(), data.length(), 0); sendBytes > 0)
173 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
184 spdlog::info(
"Telnet connection to {} closed",
getPeerIP());
193 stats.disconnectTime = std::chrono::high_resolution_clock::now();
198 return (llabs(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() -
lastSeenTime)
204 lastSeenTime = std::chrono::system_clock::time_point(std::chrono::duration<int>(0));
210 if (
static_cast<uint8_t
>(*buffer) ==
ASCII_NBSP)
215 const ssize_t sendBytes = send(
m_socket, buffer, length, 0);
218 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
225 spdlog::info(
"Telnet connection received from {}",
getPeerIP());
227 stats.connectTime = std::chrono::high_resolution_clock::now();
230 unsigned long iMode = 1;
234 const std::array<uint8_t, 3> willEcho{0xff, 0xfb, 0x01};
235 ssize_t sendBytes = send(
m_socket, willEcho.data(), 3, 0);
238 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
242 const std::array<uint8_t, 3> dontEcho{0xff, 0xfe, 0x01};
243 sendBytes = send(
m_socket, dontEcho.data(), 3, 0);
246 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
250 const std::array<uint8_t, 3> willSGA{0xff, 0xfb, 0x03};
251 sendBytes = send(
m_socket, willSGA.data(), 3, 0);
254 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
271 found = buffer.find_first_of(
static_cast<char>(
ASCII_NBSP));
272 if (found != std::string::npos && (found + 2) <= buffer.length() - 1)
274 buffer.erase(found, 3);
276 else if ((found + 2) >= buffer.length())
280 }
while (found != std::string::npos);
288 for (
const auto &cursor : cursors)
292 found = buffer.find(cursor);
293 if (found != std::string::npos)
295 buffer.erase(found, cursor.length());
297 }
while (found != std::string::npos);
303 bool foundBackspaces =
false;
309 found = buffer.find_first_of(
'\x7f');
310 if (found == std::string::npos)
312 found = buffer.find_first_of(
'\b');
315 if (found != std::string::npos)
319 buffer.erase(found - 1, 2);
326 foundBackspaces =
true;
328 }
while (found != std::string::npos);
329 return foundBackspaces;
334 bool foundTabs =
false;
339 found = buffer.find_first_of(
'\t');
340 if (found == std::string::npos)
348 buffer.erase(found, 1);
355 const std::string retCommand =
m_telnetServer->tabCallback()(shared_from_this(), buffer.substr(0, found));
356 if (!retCommand.empty())
358 buffer.erase(0, found);
359 buffer.insert(0, retCommand);
362 }
while (found != std::string::npos);
397 ssize_t sendBytes = 0;
402 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
421 ssize_t sendBytes = 0;
427 stats.uploadBytes +=
static_cast<size_t>(sendBytes);
442 std::vector<std::string> lines;
447 found = buffer.find(
"\r\n");
448 if (found != std::string::npos)
450 lines.push_back(buffer.substr(0, found));
451 buffer.erase(0, found + 2);
453 }
while (found != std::string::npos);
460 ssize_t readBytes = 0;
461 std::array<char, DEFAULT_BUFLEN> recvbuf{};
464 stats.uploadBytes = 0;
465 stats.downloadBytes = 0;
466 stats.successCmdCtr = 0;
467 stats.failCmdCtr = 0;
473 if (readBytes < 0 && errno != EAGAIN)
477 else if (readBytes > 0)
479 stats.downloadBytes +=
static_cast<size_t>(readBytes);
485 echoBack(recvbuf.data(),
static_cast<size_t>(readBytes));
490 recvbuf.begin(), recvbuf.begin() + readBytes, [](
char chr) { return chr == ASCII_NULL; },
ASCII_LF);
493 m_buffer.append(recvbuf.data(),
static_cast<unsigned int>(readBytes));
499 std::string pending = std::move(
m_buffer);
503 for (
auto line : lines)
552 catch (
const std::exception &e)
556 spdlog::error(
"Telnet server destructor thrown an exception: {}", e.what());
558 catch (
const std::exception &e2)
560 (void)std::fprintf(stderr,
"Telnet server destructor and also logger thrown an exception: %s %s\n",
561 e.what(), e2.what());
567 std::string
promptString,
const std::shared_ptr<prometheus::Registry> ®,
568 const std::string &prependName)
580 memset(&hints, 0,
sizeof(hints));
582 hints.ai_family = AF_INET;
583 hints.ai_socktype = SOCK_STREAM;
584 hints.ai_protocol = IPPROTO_TCP;
585 hints.ai_flags = AI_PASSIVE;
588 addrinfo *result =
nullptr;
589 if (getaddrinfo(
nullptr, std::to_string(
m_listenPort).c_str(), &hints, &result) != 0)
595 m_listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
598 freeaddrinfo(result);
602 if (
int optionVal = 1; setsockopt(
m_listenSocket, SOL_SOCKET, SO_REUSEADDR, &optionVal,
sizeof(optionVal)) < 0)
604 freeaddrinfo(result);
610 if (bind(
m_listenSocket, result->ai_addr, result->ai_addrlen) < 0)
612 freeaddrinfo(result);
616 freeaddrinfo(result);
627 m_stats = std::make_unique<TelnetStats>(reg, listenPort, prependName);
646 const auto session = std::make_shared<TelnetSession>(ClientSocket, shared_from_this());
647 session->initialise();
649 session->sendLine(
"Too many active connections. Please try again later. \r\nClosing...");
650 session->closeClient();
654 const auto session = std::make_shared<TelnetSession>(ClientSocket, shared_from_this());
656 session->initialise();
662 spdlog::info(
"Telnet server started");
663 while (!stopToken.stop_requested())
673 catch (
const std::exception &e)
675 spdlog::error(
"Telnet server failed: {}", e.what());
679 spdlog::info(
"Telnet server stopped");
692 bool newConnectionAccept =
false;
693 bool newConnectionRefused =
false;
699 if (select(
m_listenSocket + 1, &readSet,
nullptr,
nullptr, &timeout) > 0)
703 newConnectionAccept =
true;
707 newConnectionRefused =
true;
712 for (
size_t idx = 0; idx <
m_sessions.size(); ++idx)
743 m_stats->consumeStats(serverStats);
752 tSession->closeClient();
770 session->sendLine(
"");
771 session->sendLine(
"Available commands:");
772 session->sendLine(
"");
775 std::array<char, BUFSIZ> buffer{
'\0'};
776 if (std::format_to_n(buffer.data(), BUFSIZ,
"{:<25} : {}", command, info).size > 0)
778 session->sendLine(buffer.data());
785 session->sendLine(
"\r\n"
786 "𝑲𝒆𝒆𝒑 𝒚𝒐𝒖𝒓 𝒆𝒚𝒆𝒔 𝒐𝒏 𝒕𝒉𝒆 𝒔𝒕𝒂𝒓𝒔 "
787 "𝒂𝒏𝒅 𝒚𝒐𝒖𝒓 𝒇𝒆𝒆𝒕 𝒐𝒏 𝒕𝒉𝒆 𝒈𝒓𝒐𝒖𝒏𝒅 "
794 spdlog::trace(
"Received message {}", line);
797 session->sendLine(line);
808 session->sendLine(
"OK");
814 session->sendLine(
"Default log mode enabled");
816 spdlog::set_level(spdlog::level::warn);
818 spdlog::set_level(spdlog::level::info);
822 session->sendLine(
"Disabling all logs");
823 spdlog::set_level(spdlog::level::off);
826 session->sendLine(
"Info log mode enabled");
827 spdlog::set_level(spdlog::level::info);
830 session->sendLine(
"Debug log mode enabled");
831 spdlog::set_level(spdlog::level::debug);
834 session->sendLine(
"Trace log mode enabled");
835 spdlog::set_level(spdlog::level::trace);
838 session->sendLine(
"pong");
841 session->sendLine(PROJECT_FULL_VERSION_STRING);
847 for (
const auto &[service, statusFlag] :
vCheckFlag)
849 session->sendLine(std::format(
"{:.<{}}{:.>{}} ", service +
" ",
KEY_WIDTH,
850 (statusFlag->test() ?
" OK" :
" Not Active"),
VAL_WIDTH));
861 session->sendLine(
"Closing connection");
862 session->sendLine(
"Goodbye!");
863 session->markTimeout();
866 session->sendLine(
"Unknown command received");
876 std::ostringstream sStream;
879 if (command.starts_with(line))
883 sStream << command << std::setw(
KEY_WIDTH);
887 if (ctr != 1 && (!sStream.str().empty()))
889 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
void promptString(const std::string_view &prompt)
std::unique_ptr< TelnetStats > m_stats
unsigned long m_listenPort
std::string m_promptString
void threadFunc(const std::stop_token &stopToken) noexcept
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
std::shared_ptr< std::atomic_flag > m_checkFlag
~TelnetServer()
Destructor for server.
void update()
Process new connections and messages.
std::unique_ptr< std::jthread > m_serverThread
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)
bool editInput(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.