Repo-Init
Loading...
Searching...
No Matches
TelnetServer.cpp
Go to the documentation of this file.
2
3#include "Version.h"
5#include "utils/Hasher.hpp"
6
7#include <spdlog/spdlog.h>
8
9#include <algorithm>
10#include <array>
11#include <ctime>
12#include <format>
13#include <iomanip>
14#include <sstream>
15#include <string>
16#include <utility>
17
18#include <sys/time.h>
19
20// Invalid socket identifier for readability
21constexpr int INVALID_SOCKET = -1;
22// Receive buffer length
23constexpr int DEFAULT_BUFLEN = 512;
24// Timeout to automatic close session
25constexpr int TELNET_TIMEOUT = 120;
26// Maximum number of concurrent sessions
27constexpr int MAX_AVAILABLE_SESSION = 5;
28// History limit for Telnet session
29constexpr int TELNET_HISTORY_LIMIT = 50;
30// Sleep interval for the server (to control rate limiting)
31constexpr int SLEEP_INTERVAL_MS = 50;
32
33// Status table widths
34constexpr int KEY_WIDTH = 30;
35constexpr int VAL_WIDTH = 15;
36
37// Telnet ASCII constants
38constexpr int ASCII_LF = 0x0A;
39constexpr int ASCII_NULL = 0x00;
40constexpr int ASCII_NBSP = 0xFF;
41
42// NOLINTBEGIN
43const std::vector<std::pair<std::string, std::string>> telnetCommands = {
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"},
51 /* ################################################################################### */
52 /* ############################# MAKE MODIFICATIONS HERE ############################# */
53 /* ################################################################################### */
54
55 /* ################################################################################### */
56 /* ################################ END MODIFICATIONS ################################ */
57 /* ################################################################################### */
58 {"quit", "Ends the connection"}};
59
60const std::string ANSI_FG_BLACK("\x1b[30m");
61const std::string ANSI_FG_RED("\x1b[31m");
62const std::string ANSI_FG_GREEN("\x1b[32m");
63const std::string ANSI_FG_YELLOW("\x1b[33m");
64const std::string ANSI_FG_BLUE("\x1b[34m");
65const std::string ANSI_FG_MAGENTA("\x1b[35m");
66const std::string ANSI_FG_CYAN("\x1b[36m");
67const std::string ANSI_FG_WHITE("\x1b[37m");
68const std::string ANSI_FG_DEFAULT("\x1b[39m");
69
70const std::string ANSI_BG_BLACK("\x1b[40m");
71const std::string ANSI_BG_RED("\x1b[41m");
72const std::string ANSI_BG_GREEN("\x1b[42m");
73const std::string ANSI_BG_YELLOW("\x1b[43m");
74const std::string ANSI_BG_BLUE("\x1b[44m");
75const std::string ANSI_BG_MAGENTA("\x1b[45m");
76const std::string ANSI_BG_CYAN("\x1b[46m");
77const std::string ANSI_BG_WHITE("\x1b[47m");
78const std::string ANSI_BG_DEFAULT("\x1b[49m");
79
80const std::string ANSI_BOLD_ON("\x1b[1m");
81const std::string ANSI_BOLD_OFF("\x1b[22m");
82
83const std::string ANSI_ITALICS_ON("\x1b[3m");
84const std::string ANSI_ITALCIS_OFF("\x1b[23m");
85
86const std::string ANSI_UNDERLINE_ON("\x1b[4m");
87const std::string ANSI_UNDERLINE_OFF("\x1b[24m");
88
89const std::string ANSI_INVERSE_ON("\x1b[7m");
90const std::string ANSI_INVERSE_OFF("\x1b[27m");
91
92const std::string ANSI_STRIKETHROUGH_ON("\x1b[9m");
93const std::string ANSI_STRIKETHROUGH_OFF("\x1b[29m");
94
95const std::string ANSI_ERASE_LINE("\x1b[2K");
96const std::string ANSI_ERASE_SCREEN("\x1b[2J");
97
98const std::string ANSI_ARROW_UP("\x1b\x5b\x41");
99const std::string ANSI_ARROW_DOWN("\x1b\x5b\x42");
100const std::string ANSI_ARROW_RIGHT("\x1b\x5b\x43");
101const std::string ANSI_ARROW_LEFT("\x1b\x5b\x44");
102
103const std::string ANSI_DOUBLE_HORIZONTAL_TAB("\t\t");
104const std::string ANSI_HORIZONTAL_TAB("\t");
105
106const std::string TELNET_ERASE_LINE("\xff\xf8");
107const std::string TELNET_CLEAR_SCREEN("\033[2J");
108// NOLINTEND
109
110std::string TelnetSession::getPeerIP() const
111{
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);
116
117 std::array<char, INET_ADDRSTRLEN> ipAddr{};
118 inet_ntop(AF_INET, &client_info.sin_addr, ipAddr.data(), INET_ADDRSTRLEN);
119
120 return ipAddr.data();
121}
122
124{
125 // Output the prompt
126 ssize_t sendBytes =
127 send(m_socket, m_telnetServer->promptString().c_str(), m_telnetServer->promptString().length(), 0);
128 if (sendBytes > 0)
129 {
130 stats.uploadBytes += static_cast<size_t>(sendBytes);
131 }
132
133 // Resend the buffer
134 if (!m_buffer.empty())
135 {
136 sendBytes = send(m_socket, m_buffer.c_str(), m_buffer.length(), 0);
137 if (sendBytes > 0)
138 {
139 stats.uploadBytes += static_cast<size_t>(sendBytes);
140 }
141 }
142}
143
145{
146 // Send an erase line
147 ssize_t sendBytes = send(m_socket, ANSI_ERASE_LINE.c_str(), ANSI_ERASE_LINE.length(), 0);
148 if (sendBytes > 0)
149 {
150 stats.uploadBytes += static_cast<size_t>(sendBytes);
151 }
152
153 // Move the cursor to the beginning of the line
154 const std::string moveBack = "\x1b[80D";
155 sendBytes = send(m_socket, moveBack.c_str(), moveBack.length(), 0);
156 if (sendBytes > 0)
157 {
158 stats.uploadBytes += static_cast<size_t>(sendBytes);
159 }
160}
161
162void TelnetSession::sendLine(std::string data)
163{
164 // If is something is on the prompt, wipe it off
165 if (m_telnetServer->interactivePrompt() || !m_buffer.empty())
166 {
167 eraseLine();
168 }
169
170 data.append("\r\n");
171 if (const auto sendBytes = send(m_socket, data.c_str(), data.length(), 0); sendBytes > 0)
172 {
173 stats.uploadBytes += static_cast<size_t>(sendBytes);
174 }
175
176 if (m_telnetServer->interactivePrompt())
177 {
179 }
180}
181
183{
184 spdlog::info("Telnet connection to {} closed", getPeerIP());
185
186 // Attempt to cleanly shutdown the connection since we're done
187 shutdown(m_socket, SHUT_WR);
188
189 // Cleanup
190 close(m_socket);
191
192 // Set disconnect time
193 stats.disconnectTime = std::chrono::high_resolution_clock::now();
194}
195
197{
198 return (llabs(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - lastSeenTime)
199 .count()) > TELNET_TIMEOUT);
200}
201
203{
204 lastSeenTime = std::chrono::system_clock::time_point(std::chrono::duration<int>(0));
205}
206
207void TelnetSession::echoBack(const char *buffer, unsigned long length)
208{
209 // If you are an NVT command (i.e. first it of data is 255) then ignore the echo back
210 if (static_cast<uint8_t>(*buffer) == ASCII_NBSP)
211 {
212 return;
213 }
214
215 const ssize_t sendBytes = send(m_socket, buffer, length, 0);
216 if (sendBytes > 0)
217 {
218 stats.uploadBytes += static_cast<size_t>(sendBytes);
219 }
220}
221
223{
224 // Get details of connection
225 spdlog::info("Telnet connection received from {}", getPeerIP());
226
227 stats.connectTime = std::chrono::high_resolution_clock::now();
228
229 // Set the connection to be non-blocking
230 unsigned long iMode = 1;
231 ioctl(m_socket, FIONBIO, &iMode);
232
233 // Set NVT mode to say that I will echo back characters.
234 const std::array<uint8_t, 3> willEcho{0xff, 0xfb, 0x01};
235 ssize_t sendBytes = send(m_socket, willEcho.data(), 3, 0);
236 if (sendBytes > 0)
237 {
238 stats.uploadBytes += static_cast<size_t>(sendBytes);
239 }
240
241 // Set NVT requesting that the remote system not/dont echo back characters
242 const std::array<uint8_t, 3> dontEcho{0xff, 0xfe, 0x01};
243 sendBytes = send(m_socket, dontEcho.data(), 3, 0);
244 if (sendBytes > 0)
245 {
246 stats.uploadBytes += static_cast<size_t>(sendBytes);
247 }
248
249 // Set NVT mode to say that I will suppress go-ahead. Stops remote clients from doing local linemode.
250 const std::array<uint8_t, 3> willSGA{0xff, 0xfb, 0x03};
251 sendBytes = send(m_socket, willSGA.data(), 3, 0);
252 if (sendBytes > 0)
253 {
254 stats.uploadBytes += static_cast<size_t>(sendBytes);
255 }
256
257 if (m_telnetServer->connectedCallback())
258 {
259 m_telnetServer->connectedCallback()(shared_from_this());
260 }
261
262 // Set last seen
263 lastSeenTime = std::chrono::system_clock::now();
264}
265
266void TelnetSession::stripNVT(std::string &buffer)
267{
268 size_t found = 0;
269 do
270 {
271 found = buffer.find_first_of(static_cast<char>(ASCII_NBSP));
272 if (found != std::string::npos && (found + 2) <= buffer.length() - 1)
273 {
274 buffer.erase(found, 3);
275 }
276 else if ((found + 2) >= buffer.length())
277 {
278 break;
279 }
280 } while (found != std::string::npos);
281}
282
284{
285 size_t found = 0;
286 const std::array<std::string, 4> cursors = {ANSI_ARROW_UP, ANSI_ARROW_DOWN, ANSI_ARROW_RIGHT, ANSI_ARROW_LEFT};
287
288 for (const auto &cursor : cursors)
289 {
290 do
291 {
292 found = buffer.find(cursor);
293 if (found != std::string::npos)
294 {
295 buffer.erase(found, cursor.length());
296 }
297 } while (found != std::string::npos);
298 }
299}
300
301bool TelnetSession::processBackspace(std::string &buffer)
302{
303 bool foundBackspaces = false;
304
305 size_t found = 0;
306 do
307 {
308 // Need to handle both \x7f and \b backspaces
309 found = buffer.find_first_of('\x7f');
310 if (found == std::string::npos)
311 {
312 found = buffer.find_first_of('\b');
313 }
314
315 if (found != std::string::npos)
316 {
317 if (found > 0)
318 {
319 buffer.erase(found - 1, 2);
320 }
321 else
322 {
323 // Nothing to delete before it, drop only the backspace itself
324 buffer.erase(0, 1);
325 }
326 foundBackspaces = true;
327 }
328 } while (found != std::string::npos);
329 return foundBackspaces;
330}
331
332bool TelnetSession::processTab(std::string &buffer)
333{
334 bool foundTabs = false;
335
336 size_t found = 0;
337 do
338 {
339 found = buffer.find_first_of('\t');
340 if (found == std::string::npos)
341 {
342 continue;
343 }
344
345 // Remove single tab
346 if (!buffer.empty())
347 {
348 buffer.erase(found, 1);
349 }
350 foundTabs = true;
351
352 // Process
353 if (m_telnetServer->tabCallback())
354 {
355 const std::string retCommand = m_telnetServer->tabCallback()(shared_from_this(), buffer.substr(0, found));
356 if (!retCommand.empty())
357 {
358 buffer.erase(0, found);
359 buffer.insert(0, retCommand);
360 }
361 }
362 } while (found != std::string::npos);
363 return foundTabs;
364}
365
366void TelnetSession::addToHistory(const std::string &line)
367{
368 // Add it to the history
369 if (line != (!m_history.empty() ? m_history.back() : "") && !line.empty())
370 {
371 m_history.push_back(line);
372 if (m_history.size() > TELNET_HISTORY_LIMIT)
373 {
374 m_history.pop_front();
375 }
376 }
378}
379
381{
382 if (!m_telnetServer->interactivePrompt())
383 {
384 return false;
385 }
386
387 // Handle up and down arrow actions
388 if (buffer.find(ANSI_ARROW_UP) != std::string::npos && !m_history.empty())
389 {
390 if (m_historyCursor != m_history.begin())
391 {
393 }
394 buffer = *m_historyCursor;
395
396 // Issue a cursor command to counter it
397 ssize_t sendBytes = 0;
398 if ((sendBytes = send(m_socket, ANSI_ARROW_DOWN.c_str(), ANSI_ARROW_DOWN.length(), 0)) < 0)
399 {
400 return false;
401 }
402 stats.uploadBytes += static_cast<size_t>(sendBytes);
403 return true;
404 }
405 if (buffer.find(ANSI_ARROW_DOWN) != std::string::npos && !m_history.empty())
406 {
407 if (m_historyCursor == m_history.end())
408 {
409 buffer.clear();
410 }
411 else
412 {
413 if (next(m_historyCursor) != m_history.end())
414 {
416 }
417 buffer = *m_historyCursor;
418 }
419
420 // Issue a cursor command to counter it
421 ssize_t sendBytes = 0;
422 if ((sendBytes = send(m_socket, ANSI_ARROW_UP.c_str(), ANSI_ARROW_UP.length(), 0)) < 0)
423 {
424 return false;
425 }
426
427 stats.uploadBytes += static_cast<size_t>(sendBytes);
428 return true;
429 }
430
431 // Ignore left and right and just reprint buffer
432 if (buffer.find(ANSI_ARROW_LEFT) != std::string::npos || buffer.find(ANSI_ARROW_RIGHT) != std::string::npos)
433 {
434 return true;
435 }
436 return false;
437}
438
439std::vector<std::string> TelnetSession::getCompleteLines(std::string &buffer)
440{
441 // Now find all new lines (<CR><LF>) and place in a vector and delete from buffer
442 std::vector<std::string> lines;
443
444 size_t found = 0;
445 do
446 {
447 found = buffer.find("\r\n");
448 if (found != std::string::npos)
449 {
450 lines.push_back(buffer.substr(0, found));
451 buffer.erase(0, found + 2);
452 }
453 } while (found != std::string::npos);
454
455 return lines;
456}
457
459{
460 ssize_t readBytes = 0;
461 std::array<char, DEFAULT_BUFLEN> recvbuf{};
462
463 // Reset stats
464 stats.uploadBytes = 0;
465 stats.downloadBytes = 0;
466 stats.successCmdCtr = 0;
467 stats.failCmdCtr = 0;
468
469 // Receive
470 readBytes = recv(m_socket, recvbuf.data(), DEFAULT_BUFLEN, 0);
471
472 // Check for errors from the read
473 if (readBytes < 0 && errno != EAGAIN)
474 {
475 close(m_socket);
476 }
477 else if (readBytes > 0)
478 {
479 stats.downloadBytes += static_cast<size_t>(readBytes);
480
481 // Update last seen
482 lastSeenTime = std::chrono::system_clock::now();
483
484 // Echo it back to the sender
485 echoBack(recvbuf.data(), static_cast<size_t>(readBytes));
486
487 // we've got to be careful here. Telnet client might send null characters for New Lines mid-data block. We need
488 // to swap these out. recv is not null terminated, so its cool
489 std::replace_if(
490 recvbuf.begin(), recvbuf.begin() + readBytes, [](char chr) { return chr == ASCII_NULL; }, ASCII_LF);
491
492 // Add it to the received buffer
493 m_buffer.append(recvbuf.data(), static_cast<unsigned int>(readBytes));
494 // Remove telnet negotiation sequences
496
497 // Several commands can arrive in a single recv. Split them first so line editing (history, backspace, tab)
498 // is applied to each line separately instead of the whole chunk
499 std::string pending = std::move(m_buffer);
500 m_buffer.clear();
501
502 const auto lines = getCompleteLines(pending);
503 for (auto line : lines)
504 {
505 editInput(line);
506
507 if (!m_telnetServer->newLineCallBack())
508 {
509 break;
510 }
511
512 m_telnetServer->newLineCallBack()(shared_from_this(), line) ? ++stats.successCmdCtr : ++stats.failCmdCtr;
513 addToHistory(line);
514 }
515
516 // Remaining incomplete line stays in the buffer
517 m_buffer = std::move(pending);
518 if (editInput(m_buffer) && m_telnetServer->interactivePrompt())
519 {
520 eraseLine();
522 }
523 }
524}
525
526bool TelnetSession::editInput(std::string &buffer)
527{
528 if (!m_telnetServer->interactivePrompt())
529 {
530 return false;
531 }
532
533 // Read up and down arrow keys and scroll through history
534 bool changed = processCommandHistory(buffer);
535 stripEscapeCharacters(buffer);
536
537 // Remove characters
538 changed |= processBackspace(buffer);
539 // Complete commands
540 changed |= processTab(buffer);
541 return changed;
542}
543
544/* ------------------ Telnet Server -------------------*/
545
547{
548 try
549 {
550 shutdown();
551 }
552 catch (const std::exception &e)
553 {
554 try
555 {
556 spdlog::error("Telnet server destructor thrown an exception: {}", e.what());
557 }
558 catch (const std::exception &e2)
559 {
560 (void)std::fprintf(stderr, "Telnet server destructor and also logger thrown an exception: %s %s\n",
561 e.what(), e2.what());
562 }
563 }
564}
565
566bool TelnetServer::initialise(unsigned long listenPort, const std::shared_ptr<std::atomic_flag> &checkFlag,
567 std::string promptString, const std::shared_ptr<prometheus::Registry> &reg,
568 const std::string &prependName)
569{
570 if (m_initialised)
571 {
572 return false;
573 }
574
575 m_checkFlag = checkFlag;
576 m_listenPort = listenPort;
577 m_promptString = std::move(promptString);
578
579 addrinfo hints{};
580 memset(&hints, 0, sizeof(hints));
581
582 hints.ai_family = AF_INET;
583 hints.ai_socktype = SOCK_STREAM;
584 hints.ai_protocol = IPPROTO_TCP;
585 hints.ai_flags = AI_PASSIVE;
586
587 // Resolve the server address and port
588 addrinfo *result = nullptr;
589 if (getaddrinfo(nullptr, std::to_string(m_listenPort).c_str(), &hints, &result) != 0)
590 {
591 return false;
592 }
593
594 // Create a SOCKET for connecting to server
595 m_listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
597 {
598 freeaddrinfo(result);
599 return false;
600 }
601
602 if (int optionVal = 1; setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, &optionVal, sizeof(optionVal)) < 0)
603 {
604 freeaddrinfo(result);
605 close(m_listenSocket);
606 return false;
607 }
608
609 // Setup the TCP listening socket
610 if (bind(m_listenSocket, result->ai_addr, result->ai_addrlen) < 0)
611 {
612 freeaddrinfo(result);
613 close(m_listenSocket);
614 return false;
615 }
616 freeaddrinfo(result);
617
618 if (listen(m_listenSocket, SOMAXCONN) < 0)
619 {
620 close(m_listenSocket);
621 return false;
622 }
623
624 // If prometheus registry is provided prepare statistics
625 if (reg)
626 {
627 m_stats = std::make_unique<TelnetStats>(reg, listenPort, prependName);
628 }
629
630 m_serverThread = std::make_unique<std::jthread>([this](const std::stop_token &sToken) { threadFunc(sToken); });
631
632 m_initialised = true;
633 return true;
634}
635
637{
638 const Socket ClientSocket = accept(m_listenSocket, nullptr, nullptr);
639 if (ClientSocket == INVALID_SOCKET)
640 {
641 return false;
642 }
643 if (m_sessions.size() >= MAX_AVAILABLE_SESSION)
644 {
645 // Create for only sending error
646 const auto session = std::make_shared<TelnetSession>(ClientSocket, shared_from_this());
647 session->initialise();
648
649 session->sendLine("Too many active connections. Please try again later. \r\nClosing...");
650 session->closeClient();
651 return false;
652 }
653
654 const auto session = std::make_shared<TelnetSession>(ClientSocket, shared_from_this());
655 m_sessions.push_back(session);
656 session->initialise();
657 return true;
658}
659
660void TelnetServer::threadFunc(const std::stop_token &stopToken) noexcept
661{
662 spdlog::info("Telnet server started");
663 while (!stopToken.stop_requested())
664 {
665 try
666 {
667 update();
668 if (m_checkFlag)
669 {
670 m_checkFlag->test_and_set();
671 }
672 }
673 catch (const std::exception &e)
674 {
675 spdlog::error("Telnet server failed: {}", e.what());
676 }
677 std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_INTERVAL_MS));
678 }
679 spdlog::info("Telnet server stopped");
680}
681
683{
684 // See if connection pending on the listening socket
685 fd_set readSet;
686 FD_ZERO(&readSet);
687 FD_SET(m_listenSocket, &readSet);
688 timeval timeout{};
689 timeout.tv_sec = 0; // Zero timeout (poll)
690 timeout.tv_usec = 0;
691
692 bool newConnectionAccept = false;
693 bool newConnectionRefused = false;
694
695 TelnetServerStats serverStats;
696 serverStats.processingTimeStart = std::chrono::high_resolution_clock::now();
697
698 // If there is a connection pending, accept it.
699 if (select(m_listenSocket + 1, &readSet, nullptr, nullptr, &timeout) > 0)
700 {
701 if (acceptConnection())
702 {
703 newConnectionAccept = true;
704 }
705 else
706 {
707 newConnectionRefused = true;
708 }
709 }
710
711 // Update all the telnet Sessions that are currently in flight.
712 for (size_t idx = 0; idx < m_sessions.size(); ++idx)
713 {
714 // Update session
715 m_sessions[idx]->update();
716
717 // Close session if needed
718 if (m_sessions[idx]->checkTimeout())
719 {
720 m_sessions[idx]->closeClient();
721 if (m_stats)
722 {
723 m_stats->consumeStats(m_sessions[idx]->stats, true);
724 }
725 m_sessions.erase(m_sessions.begin() + static_cast<int>(idx));
726 --idx;
727 }
728 else
729 {
730 if (m_stats)
731 {
732 m_stats->consumeStats(m_sessions[idx]->stats, false);
733 }
734 }
735 }
736
737 serverStats.activeConnectionCtr = m_sessions.size();
738 serverStats.acceptedConnectionCtr = static_cast<uint64_t>(newConnectionAccept);
739 serverStats.refusedConnectionCtr = static_cast<uint64_t>(newConnectionRefused);
740 serverStats.processingTimeEnd = std::chrono::high_resolution_clock::now();
741 if (m_stats)
742 {
743 m_stats->consumeStats(serverStats);
744 }
745}
746
748{
749 // Attempt to cleanly close every telnet session in flight.
750 for (const SP_TelnetSession &tSession : m_sessions)
751 {
752 tSession->closeClient();
753 }
754 m_sessions.clear();
755
756 // No longer need server socket so close it.
757 close(m_listenSocket);
759 m_initialised = false;
760
761 if (m_serverThread)
762 {
763 m_serverThread.reset();
764 }
765}
766
768{
769 // Print available commands
770 session->sendLine("");
771 session->sendLine("Available commands:");
772 session->sendLine("");
773 for (const auto &[command, info] : telnetCommands)
774 {
775 std::array<char, BUFSIZ> buffer{'\0'};
776 if (std::format_to_n(buffer.data(), BUFSIZ, "{:<25} : {}", command, info).size > 0)
777 {
778 session->sendLine(buffer.data());
779 }
780 }
781}
782
784{
785 session->sendLine("\r\n"
786 "𝑲𝒆𝒆𝒑 𝒚𝒐𝒖𝒓 𝒆𝒚𝒆𝒔 𝒐𝒏 𝒕𝒉𝒆 𝒔𝒕𝒂𝒓𝒔 "
787 "𝒂𝒏𝒅 𝒚𝒐𝒖𝒓 𝒇𝒆𝒆𝒕 𝒐𝒏 𝒕𝒉𝒆 𝒈𝒓𝒐𝒖𝒏𝒅 "
788 "\r\n");
790}
791
792bool TelnetMessageCallback(const SP_TelnetSession &session, const std::string &line)
793{
794 spdlog::trace("Received message {}", line);
795
796 // Send received message for user terminal
797 session->sendLine(line);
798
799 if (line.empty())
800 {
801 return true;
802 }
803
804 // Process received message
805 switch (constHasher(line.c_str()))
806 {
807 case constHasher("Test Message"):
808 session->sendLine("OK");
809 return true;
810 case constHasher("help"):
812 return true;
813 case constHasher("disable log"):
814 session->sendLine("Default log mode enabled");
815#ifdef NDEBUG
816 spdlog::set_level(spdlog::level::warn);
817#else
818 spdlog::set_level(spdlog::level::info);
819#endif
820 return true;
821 case constHasher("disable log all"): // Internal use only
822 session->sendLine("Disabling all logs");
823 spdlog::set_level(spdlog::level::off);
824 return true;
825 case constHasher("enable log v"):
826 session->sendLine("Info log mode enabled");
827 spdlog::set_level(spdlog::level::info);
828 return true;
829 case constHasher("enable log vv"):
830 session->sendLine("Debug log mode enabled");
831 spdlog::set_level(spdlog::level::debug);
832 return true;
833 case constHasher("enable log vvv"):
834 session->sendLine("Trace log mode enabled");
835 spdlog::set_level(spdlog::level::trace);
836 return true;
837 case constHasher("ping"):
838 session->sendLine("pong");
839 return true;
840 case constHasher("version"):
841 session->sendLine(PROJECT_FULL_VERSION_STRING);
842 return true;
843 case constHasher("clear"):
844 session->sendLine(TELNET_CLEAR_SCREEN);
845 return true;
846 case constHasher("status"):
847 for (const auto &[service, statusFlag] : vCheckFlag)
848 {
849 session->sendLine(std::format("{:.<{}}{:.>{}} ", service + " ", KEY_WIDTH,
850 (statusFlag->test() ? " OK" : " Not Active"), VAL_WIDTH));
851 }
852 return true;
853 /* ################################################################################### */
854 /* ############################# MAKE MODIFICATIONS HERE ############################# */
855 /* ################################################################################### */
856
857 /* ################################################################################### */
858 /* ################################ END MODIFICATIONS ################################ */
859 /* ################################################################################### */
860 case constHasher("quit"):
861 session->sendLine("Closing connection");
862 session->sendLine("Goodbye!");
863 session->markTimeout();
864 return true;
865 default:
866 session->sendLine("Unknown command received");
867 return false;
868 }
869}
870
871std::string TelnetTabCallback(const SP_TelnetSession &session, std::string_view line)
872{
873 std::string retval;
874
875 size_t ctr = 0;
876 std::ostringstream sStream;
877 for (const auto &[command, info] : telnetCommands)
878 {
879 if (command.starts_with(line))
880 {
881 ++ctr;
882 retval = command;
883 sStream << command << std::setw(KEY_WIDTH);
884 }
885 }
886 // Send suggestions if found any. If there is only one command retval will invoke completion
887 if (ctr != 1 && (!sStream.str().empty()))
888 {
889 session->sendLine(sStream.str());
890 retval = "";
891 }
892
893 return retval;
894}
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)
Definition Hasher.hpp:13
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")
constexpr int VAL_WIDTH
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")
constexpr int ASCII_NULL
const std::string ANSI_STRIKETHROUGH_ON("\x1b[9m")
constexpr int KEY_WIDTH
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")
constexpr int ASCII_LF
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")
constexpr int ASCII_NBSP
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)
int Socket
std::shared_ptr< TelnetSession > SP_TelnetSession
void promptString(const std::string_view &prompt)
std::unique_ptr< TelnetStats > m_stats
bool acceptConnection()
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 > &reg=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.
Socket m_listenSocket
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)
std::string m_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.