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