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