Repo-Init
Loading...
Searching...
No Matches
TelnetSession Class Reference

#include <TelnetServer.hpp>

Inheritance diagram for TelnetSession:
Collaboration diagram for TelnetSession:

Public Member Functions

 TelnetSession (Socket ClientSocket, std::shared_ptr< TelnetServer > tServer)
 Constructor for session.
void sendLine (std::string data)
 Send a line of data to the Telnet Server.
void closeClient ()
 Finish the session.
bool checkTimeout () const
 Checks the connection timeout.
void markTimeout ()
 Marks timeout to close session.

Protected Member Functions

void initialise ()
 Initialise session.
void update ()
 Called every frame/loop by the Terminal Server.

Private Member Functions

std::string getPeerIP () const
void sendPromptAndBuffer ()
void eraseLine ()
void echoBack (const char *buffer, unsigned long length)
bool processTab (std::string &buffer)
void addToHistory (const std::string &line)
bool processCommandHistory (std::string &buffer)
bool editInput (std::string &buffer)

Static Private Member Functions

static void stripNVT (std::string &buffer)
static void stripEscapeCharacters (std::string &buffer)
static bool processBackspace (std::string &buffer)
static std::vector< std::string > getCompleteLines (std::string &buffer)

Private Attributes

TelnetSessionStats stats
 Statistics variables.
std::chrono::system_clock::time_point lastSeenTime
Socket m_socket
std::shared_ptr< TelnetServer > m_telnetServer
std::string m_buffer
std::list< std::string > m_history
std::list< std::string >::iterator m_historyCursor
friend TelnetServer

Detailed Description

Session class for manage connections

Definition at line 56 of file TelnetServer.hpp.

Constructor & Destructor Documentation

◆ TelnetSession()

TelnetSession::TelnetSession ( Socket ClientSocket,
std::shared_ptr< TelnetServer > tServer )
inline

Constructor for session.

Definition at line 59 of file TelnetServer.hpp.

60 : m_socket(ClientSocket), m_telnetServer(std::move(tServer))
61 {
63 };
std::shared_ptr< TelnetServer > m_telnetServer
std::list< std::string > m_history
std::list< std::string >::iterator m_historyCursor

Member Function Documentation

◆ addToHistory()

void TelnetSession::addToHistory ( const std::string & line)
private

Definition at line 366 of file TelnetServer.cpp.

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}
constexpr int TELNET_HISTORY_LIMIT
Here is the caller graph for this function:

◆ checkTimeout()

bool TelnetSession::checkTimeout ( ) const

Checks the connection timeout.

Definition at line 196 of file TelnetServer.cpp.

197{
198 return (llabs(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - lastSeenTime)
199 .count()) > TELNET_TIMEOUT);
200}
constexpr int TELNET_TIMEOUT
std::chrono::system_clock::time_point lastSeenTime

◆ closeClient()

void TelnetSession::closeClient ( )

Finish the session.

Definition at line 182 of file TelnetServer.cpp.

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}
TelnetSessionStats stats
Statistics variables.
std::string getPeerIP() const
Here is the call graph for this function:

◆ echoBack()

void TelnetSession::echoBack ( const char * buffer,
unsigned long length )
private

Definition at line 207 of file TelnetServer.cpp.

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}
constexpr int ASCII_NBSP
Here is the caller graph for this function:

◆ editInput()

bool TelnetSession::editInput ( std::string & buffer)
private

Definition at line 526 of file TelnetServer.cpp.

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}
bool processTab(std::string &buffer)
bool processCommandHistory(std::string &buffer)
static bool processBackspace(std::string &buffer)
static void stripEscapeCharacters(std::string &buffer)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ eraseLine()

void TelnetSession::eraseLine ( )
private

Definition at line 144 of file TelnetServer.cpp.

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}
const std::string ANSI_ERASE_LINE("\x1b[2K")
Here is the call graph for this function:
Here is the caller graph for this function:

◆ getCompleteLines()

std::vector< std::string > TelnetSession::getCompleteLines ( std::string & buffer)
staticprivate

Definition at line 439 of file TelnetServer.cpp.

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}
Here is the caller graph for this function:

◆ getPeerIP()

std::string TelnetSession::getPeerIP ( ) const
private

Definition at line 110 of file TelnetServer.cpp.

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}
Here is the caller graph for this function:

◆ initialise()

void TelnetSession::initialise ( )
protected

Initialise session.

Definition at line 222 of file TelnetServer.cpp.

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}
Here is the call graph for this function:

◆ markTimeout()

void TelnetSession::markTimeout ( )

Marks timeout to close session.

Definition at line 202 of file TelnetServer.cpp.

203{
204 lastSeenTime = std::chrono::system_clock::time_point(std::chrono::duration<int>(0));
205}

◆ processBackspace()

bool TelnetSession::processBackspace ( std::string & buffer)
staticprivate

Definition at line 301 of file TelnetServer.cpp.

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}
Here is the caller graph for this function:

◆ processCommandHistory()

bool TelnetSession::processCommandHistory ( std::string & buffer)
private

Definition at line 380 of file TelnetServer.cpp.

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}
const std::string ANSI_ARROW_RIGHT("\x1b\x5b\x43")
const std::string ANSI_ARROW_DOWN("\x1b\x5b\x42")
const std::string ANSI_ARROW_UP("\x1b\x5b\x41")
const std::string ANSI_ARROW_LEFT("\x1b\x5b\x44")
Here is the call graph for this function:
Here is the caller graph for this function:

◆ processTab()

bool TelnetSession::processTab ( std::string & buffer)
private

Definition at line 332 of file TelnetServer.cpp.

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}
Here is the caller graph for this function:

◆ sendLine()

void TelnetSession::sendLine ( std::string data)

Send a line of data to the Telnet Server.

Definition at line 162 of file TelnetServer.cpp.

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}
std::string m_buffer
void sendPromptAndBuffer()
Here is the call graph for this function:

◆ sendPromptAndBuffer()

void TelnetSession::sendPromptAndBuffer ( )
private

Definition at line 123 of file TelnetServer.cpp.

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}
Here is the caller graph for this function:

◆ stripEscapeCharacters()

void TelnetSession::stripEscapeCharacters ( std::string & buffer)
staticprivate

Definition at line 283 of file TelnetServer.cpp.

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}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ stripNVT()

void TelnetSession::stripNVT ( std::string & buffer)
staticprivate

Definition at line 266 of file TelnetServer.cpp.

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}
Here is the caller graph for this function:

◆ update()

void TelnetSession::update ( )
protected

Called every frame/loop by the Terminal Server.

Definition at line 458 of file TelnetServer.cpp.

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}
constexpr int DEFAULT_BUFLEN
constexpr int ASCII_LF
bool editInput(std::string &buffer)
static void stripNVT(std::string &buffer)
static std::vector< std::string > getCompleteLines(std::string &buffer)
void echoBack(const char *buffer, unsigned long length)
void addToHistory(const std::string &line)
Here is the call graph for this function:

Member Data Documentation

◆ lastSeenTime

std::chrono::system_clock::time_point TelnetSession::lastSeenTime
private

Definition at line 110 of file TelnetServer.hpp.

◆ m_buffer

std::string TelnetSession::m_buffer
private

Definition at line 116 of file TelnetServer.hpp.

◆ m_history

std::list<std::string> TelnetSession::m_history
private

Definition at line 118 of file TelnetServer.hpp.

◆ m_historyCursor

std::list<std::string>::iterator TelnetSession::m_historyCursor
private

Definition at line 120 of file TelnetServer.hpp.

◆ m_socket

Socket TelnetSession::m_socket
private

Definition at line 112 of file TelnetServer.hpp.

◆ m_telnetServer

std::shared_ptr<TelnetServer> TelnetSession::m_telnetServer
private

Definition at line 114 of file TelnetServer.hpp.

◆ stats

TelnetSessionStats TelnetSession::stats
private

Statistics variables.

Definition at line 108 of file TelnetServer.hpp.

◆ TelnetServer

friend TelnetSession::TelnetServer
private

Definition at line 122 of file TelnetServer.hpp.


The documentation for this class was generated from the following files: