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