├── ExampleApplication ├── exampleTelnetServerApp.hpp ├── stdafx.cpp ├── targetver.h ├── stdafx.h ├── helper.hpp ├── exampleTelnetServerApp.sln ├── exampleTelnetServerApp.cpp ├── helper.cpp ├── exampleTelnetServerApp.vcxproj.filters └── exampleTelnetServerApp.vcxproj ├── .gitignore ├── LICENSE ├── README └── TelnetServLib ├── telnetservlib.hpp └── telnetservlib.cpp /ExampleApplication/exampleTelnetServerApp.hpp: -------------------------------------------------------------------------------- 1 | #include "..\TelnetServLib\telnetservlib.hpp" 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TelnetApplication/Debug/ 2 | *.ipch 3 | ExampleApplication/Debug/ 4 | ExampleApplication/Release/ 5 | ExampleApplication/exampleTelnetServerApp.sdf 6 | -------------------------------------------------------------------------------- /ExampleApplication/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // TelnetApplication.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /ExampleApplication/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /ExampleApplication/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #include 11 | #include 12 | 13 | 14 | 15 | // TODO: reference additional headers your program requires here 16 | -------------------------------------------------------------------------------- /ExampleApplication/helper.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | HELPER.H 4 | ======== 5 | (c) Paul Griffiths, 1999 6 | Email: mail@paulgriffiths.net 7 | 8 | Interface to socket helper functions. 9 | 10 | Many of these functions are adapted from, inspired by, or 11 | otherwise shamelessly plagiarised from "Unix Network 12 | Programming", W Richard Stevens (Prentice Hall). 13 | 14 | */ 15 | 16 | 17 | #ifndef PG_SOCK_HELP 18 | #define PG_SOCK_HELP 19 | 20 | #define LISTENQ (1024) /* Backlog for listen() */ 21 | 22 | 23 | /* Function declarations */ 24 | 25 | size_t Readline(int fd, void *vptr, size_t maxlen); 26 | size_t Writeline(int fc, const void *vptr, size_t maxlen); 27 | 28 | 29 | #endif /* PG_SOCK_HELP */ 30 | 31 | -------------------------------------------------------------------------------- /ExampleApplication/exampleTelnetServerApp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exampleTelnetServerApp", "exampleTelnetServerApp.vcxproj", "{883EEBB2-8324-42F2-81A7-CB5E70090DA0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {883EEBB2-8324-42F2-81A7-CB5E70090DA0}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {883EEBB2-8324-42F2-81A7-CB5E70090DA0}.Debug|Win32.Build.0 = Debug|Win32 16 | {883EEBB2-8324-42F2-81A7-CB5E70090DA0}.Release|Win32.ActiveCfg = Release|Win32 17 | {883EEBB2-8324-42F2-81A7-CB5E70090DA0}.Release|Win32.Build.0 = Release|Win32 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /ExampleApplication/exampleTelnetServerApp.cpp: -------------------------------------------------------------------------------- 1 | // TelnetApplication.cpp : Defines the entry point for the console application. 2 | // 3 | 4 | #undef UNICODE 5 | 6 | #define WIN32_LEAN_AND_MEAN 7 | 8 | #include "exampleTelnetServerApp.hpp" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void myConnected(SP_TelnetSession session) 15 | { 16 | std::cout << "myConnected got called\n"; 17 | session->sendLine("Welcome to the Telnet Server."); 18 | } 19 | 20 | void myNewLine(SP_TelnetSession session, std::string line) 21 | { 22 | std::cout << "myNewLine got called with line: " << line << "\n"; 23 | session->sendLine("Copy that."); 24 | } 25 | 26 | int _tmain(int argc, _TCHAR* argv[]) 27 | { 28 | // Do unit tests 29 | TelnetSession::UNIT_TEST(); 30 | 31 | // Create a terminal server which 32 | auto ts = std::make_shared < TelnetServer >(); 33 | 34 | ts->initialise(27015); 35 | ts->connectedCallback(myConnected); 36 | ts->newLineCallback(myNewLine); 37 | 38 | // Our loop 39 | do 40 | { 41 | ts->update(); 42 | Sleep(16); 43 | } 44 | while (true); 45 | 46 | ts->shutdown(); 47 | WSACleanup(); 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | Copyright (c) 2015, Luke Malcolm 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | The views and conclusions contained in the software and documentation are those 27 | of the authors and should not be interpreted as representing official policies, 28 | either expressed or implied, of the FreeBSD Project. -------------------------------------------------------------------------------- /ExampleApplication/helper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | HELPER.C 4 | ======== 5 | (c) Paul Griffiths, 1999 6 | Email: mail@paulgriffiths.net 7 | 8 | Implementation of sockets helper functions. 9 | 10 | Many of these functions are adapted from, inspired by, or 11 | otherwise shamelessly plagiarised from "Unix Network 12 | Programming", W Richard Stevens (Prentice Hall). 13 | 14 | */ 15 | 16 | #include "helper.h" 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | /* Read a line from a socket */ 23 | 24 | ssize_t Readline(int sockd, void *vptr, size_t maxlen) { 25 | ssize_t n, rc; 26 | char c, *buffer; 27 | 28 | buffer = vptr; 29 | 30 | for ( n = 1; n < maxlen; n++ ) { 31 | 32 | if ( (rc = read(sockd, &c, 1)) == 1 ) { 33 | *buffer++ = c; 34 | if ( c == '\n' ) 35 | break; 36 | } 37 | else if ( rc == 0 ) { 38 | if ( n == 1 ) 39 | return 0; 40 | else 41 | break; 42 | } 43 | else { 44 | if ( errno == EINTR ) 45 | continue; 46 | return -1; 47 | } 48 | } 49 | 50 | *buffer = 0; 51 | return n; 52 | } 53 | 54 | 55 | /* Write a line to a socket */ 56 | 57 | ssize_t Writeline(int sockd, const void *vptr, size_t n) { 58 | size_t nleft; 59 | ssize_t nwritten; 60 | const char *buffer; 61 | 62 | buffer = vptr; 63 | nleft = n; 64 | 65 | while ( nleft > 0 ) { 66 | if ( (nwritten = write(sockd, buffer, nleft)) <= 0 ) { 67 | if ( errno == EINTR ) 68 | nwritten = 0; 69 | else 70 | return -1; 71 | } 72 | nleft -= nwritten; 73 | buffer += nwritten; 74 | } 75 | 76 | return n; 77 | } 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /ExampleApplication/exampleTelnetServerApp.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 10 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 11 | 12 | 13 | {a76a5e0e-623e-45f0-83e5-58ceeacce86a} 14 | 15 | 16 | 17 | 18 | Source Files\TelnetServLib 19 | 20 | 21 | Source Files 22 | 23 | 24 | Source Files 25 | 26 | 27 | 28 | 29 | Source Files 30 | 31 | 32 | Source Files\TelnetServLib 33 | 34 | 35 | 36 | 37 | Source Files\TelnetServLib 38 | 39 | 40 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | TelnetServLib is a very light ANSI Telnet Server library for use in apps with 'game 2 | loops': i.e. update, render. It utilises TCP select to enable it to operate in the 3 | main thread. 4 | 5 | There are two classes: TelnetServer, TelnetSession 6 | 7 | TelnetServer 8 | ============ 9 | TelnetServer is responsible for setting up a listening port and accepting 10 | incoming connections. It also is responsible for keeping TelnetSessions up-to- 11 | date. 12 | 13 | Start a TelnetServer with the following code: 14 | 15 | // Create your TelnetServer with a shared pointer. 16 | std::shared_ptr ts = std::make_shared < TelnetServer >(); 17 | 18 | // Start your TelnetServer on a given port and present an interactive prompt: py> 19 | ts->initialise(27015, "py> "); 20 | 21 | Once your TelnetServer is created you can hook external functions to call when 22 | certain events happen in TelnetSessions. 23 | 24 | // Call back after initial telnet client initialised (used for MOTD etc) 25 | ts->connectedCallback(&MyClass::myConnectedFunction); 26 | 27 | // Call back made after every carriage return from the telnet client 28 | ts->newLineCallback(&MyClass::myNewLineFunction); 29 | 30 | These callback functions should be structured as follows: 31 | 32 | void myConnectedFunction(SP_TelnetSession session); 33 | void myNewLineFunction (SP_TelnetSession session, std::string line); 34 | 35 | SP_TelnetSession is a type definition to a shared pointer to the TelnetSession. With 36 | access to the TelnetSession you can send responses etc. 37 | 38 | TelnetSession 39 | ============= 40 | TelnetSessions are currently open telnet sessions with clients. 41 | 42 | TelnetSessions can be accessed in two ways: 43 | - It is passed to the callback functions 44 | - A list of active sessions can be retrieved from TelnetServer::sessions() 45 | 46 | The key functions: 47 | void sendLine(std::string data); // Send a line of data to the Telnet Server 48 | void closeClient(); // Finish the session 49 | 50 | NB: sendline does not require a closing newline. 51 | 52 | License 53 | ======= 54 | Copyright (c) 2015, Luke Malcolm 55 | All rights reserved. 56 | 57 | Redistribution and use in source and binary forms, with or without 58 | modification, are permitted provided that the following conditions are met: 59 | 60 | 1. Redistributions of source code must retain the above copyright notice, this 61 | list of conditions and the following disclaimer. 62 | 2. Redistributions in binary form must reproduce the above copyright notice, 63 | this list of conditions and the following disclaimer in the documentation 64 | and/or other materials provided with the distribution. 65 | 66 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 67 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 68 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 69 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 70 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 71 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 72 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 73 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 74 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 75 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 76 | 77 | The views and conclusions contained in the software and documentation are those 78 | of the authors and should not be interpreted as representing official policies, 79 | either expressed or implied, of the FreeBSD Project. 80 | 81 | -------------------------------------------------------------------------------- /ExampleApplication/exampleTelnetServerApp.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {883EEBB2-8324-42F2-81A7-CB5E70090DA0} 15 | Win32Proj 16 | TelnetApplication 17 | 18 | 19 | 20 | Application 21 | true 22 | v120 23 | Unicode 24 | 25 | 26 | Application 27 | false 28 | v120 29 | true 30 | Unicode 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | true 44 | 45 | 46 | false 47 | 48 | 49 | 50 | NotUsing 51 | Level3 52 | Disabled 53 | WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 54 | 55 | 56 | Console 57 | true 58 | 59 | 60 | 61 | 62 | Level3 63 | NotUsing 64 | MaxSpeed 65 | true 66 | true 67 | WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) 68 | 69 | 70 | Console 71 | true 72 | true 73 | true 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /TelnetServLib/telnetservlib.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | TelnetServLib 3 | ============= 4 | 5 | TelnetServLib is a very light ANSI Telnet Server library for use in apps with 'game 6 | loops': i.e. update, render. It utilises TCP select to enable it to operate in the 7 | main thread. 8 | 9 | License 10 | ======= 11 | 12 | Copyright (c) 2015, Luke Malcolm 13 | All rights reserved. 14 | 15 | Redistribution and use in source and binary forms, with or without 16 | modification, are permitted provided that the following conditions are met: 17 | 18 | 1. Redistributions of source code must retain the above copyright notice, this 19 | list of conditions and the following disclaimer. 20 | 2. Redistributions in binary form must reproduce the above copyright notice, 21 | this list of conditions and the following disclaimer in the documentation 22 | and/or other materials provided with the distribution. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 25 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 28 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | The views and conclusions contained in the software and documentation are those 36 | of the authors and should not be interpreted as representing official policies, 37 | either expressed or implied, of the FreeBSD Project. 38 | */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | class TelnetServer; 50 | class TelnetSession; 51 | 52 | const std::string ANSI_FG_BLACK ("\x1b[30m"); 53 | const std::string ANSI_FG_RED ("\x1b[31m"); 54 | const std::string ANSI_FG_GREEN ("\x1b[32m"); 55 | const std::string ANSI_FG_YELLOW ("\x1b[33m"); 56 | const std::string ANSI_FG_BLUE ("\x1b[34m"); 57 | const std::string ANSI_FG_MAGENTA ("\x1b[35m"); 58 | const std::string ANSI_FG_CYAN ("\x1b[36m"); 59 | const std::string ANSI_FG_WHITE ("\x1b[37m"); 60 | const std::string ANSI_FG_DEFAULT ("\x1b[39m"); 61 | 62 | const std::string ANSI_BG_BLACK ("\x1b[40m"); 63 | const std::string ANSI_BG_RED ("\x1b[41m"); 64 | const std::string ANSI_BG_GREEN ("\x1b[42m"); 65 | const std::string ANSI_BG_YELLOW ("\x1b[43m"); 66 | const std::string ANSI_BG_BLUE ("\x1b[44m"); 67 | const std::string ANSI_BG_MAGENTA ("\x1b[45m"); 68 | const std::string ANSI_BG_CYAN ("\x1b[46m"); 69 | const std::string ANSI_BG_WHITE ("\x1b[47m"); 70 | const std::string ANSI_BG_DEFAULT ("\x1b[49m"); 71 | 72 | const std::string ANSI_BOLD_ON ("\x1b[1m"); 73 | const std::string ANSI_BOLD_OFF ("\x1b[22m"); 74 | 75 | const std::string ANSI_ITALICS_ON ("\x1b[3m"); 76 | const std::string ANSI_ITALCIS_OFF ("\x1b[23m"); 77 | 78 | const std::string ANSI_UNDERLINE_ON ("\x1b[4m"); 79 | const std::string ANSI_UNDERLINE_OFF ("\x1b[24m"); 80 | 81 | const std::string ANSI_INVERSE_ON ("\x1b[7m"); 82 | const std::string ANSI_INVERSE_OFF ("\x1b[27m"); 83 | 84 | const std::string ANSI_STRIKETHROUGH_ON ("\x1b[9m"); 85 | const std::string ANSI_STRIKETHROUGH_OFF ("\x1b[29m"); 86 | 87 | const std::string ANSI_ERASE_LINE ("\x1b[2K"); 88 | const std::string ANSI_ERASE_SCREEN ("\x1b[2J"); 89 | 90 | const std::string ANSI_ARROW_UP("\x1b\x5b\x41"); 91 | const std::string ANSI_ARROW_DOWN("\x1b\x5b\x42"); 92 | const std::string ANSI_ARROW_RIGHT("\x1b\x5b\x43"); 93 | const std::string ANSI_ARROW_LEFT("\x1b\x5b\x44"); 94 | 95 | 96 | const std::string TELNET_ERASE_LINE ("\xff\xf8"); 97 | 98 | 99 | class TelnetSession : public std::enable_shared_from_this < TelnetSession > 100 | { 101 | public: 102 | TelnetSession(SOCKET ClientSocket, std::shared_ptr ts) : m_socket(ClientSocket), m_telnetServer(ts) 103 | { 104 | m_historyCursor = m_history.end(); 105 | }; 106 | 107 | public: 108 | void sendLine(std::string data); // Send a line of data to the Telnet Server 109 | void closeClient(); // Finish the session 110 | 111 | static void UNIT_TEST(); 112 | 113 | protected: 114 | void initialise(); // 115 | void update(); // Called every frame/loop by the Terminal Server 116 | 117 | private: 118 | void sendPromptAndBuffer(); // Write the prompt and any data sat in the input buffer 119 | void eraseLine(); // Erase all characters on the current line and move prompt back to beginning of line 120 | void echoBack(char * buffer, u_long length); 121 | static void stripNVT(std::string &buffer); 122 | static void stripEscapeCharacters(std::string &buffer); // Remove all escape characters from the line 123 | static bool processBackspace(std::string &buffer); // Takes backspace commands and removes them and the preceeding character from the m_buffer. // Handles arrow key actions for history management. Returns true if the input buffer was changed. 124 | void addToHistory(std::string line); // Add a command into the command history 125 | bool processCommandHistory(std::string &buffer); // Handles arrow key actions for history management. Returns true if the input buffer was changed. 126 | static std::vector getCompleteLines(std::string &buffer); 127 | 128 | private: 129 | SOCKET m_socket; // The Winsock socket 130 | std::shared_ptr m_telnetServer; // Parent TelnetServer class 131 | std::string m_buffer; // Buffer of input data (mid line) 132 | std::list m_history; // A history of all completed commands 133 | std::list::iterator m_historyCursor; 134 | 135 | friend TelnetServer; 136 | }; 137 | 138 | typedef std::shared_ptr SP_TelnetSession; 139 | typedef std::vector < SP_TelnetSession > VEC_SP_TelnetSession; 140 | 141 | typedef std::function< void(SP_TelnetSession) > FPTR_ConnectedCallback; 142 | typedef std::function< void(SP_TelnetSession, std::string) > FPTR_NewLineCallback; 143 | 144 | class TelnetServer : public std::enable_shared_from_this < TelnetServer > 145 | { 146 | public: 147 | TelnetServer() : m_initialised(false), m_promptString("") {}; 148 | 149 | bool initialise(u_long listenPort, std::string promptString = ""); 150 | void update(); 151 | void shutdown(); 152 | 153 | public: 154 | void connectedCallback(FPTR_ConnectedCallback f) { m_connectedCallback = f; } 155 | FPTR_ConnectedCallback connectedCallback() const { return m_connectedCallback; } 156 | 157 | void newLineCallback(FPTR_NewLineCallback f) { m_newlineCallback = f; } 158 | FPTR_NewLineCallback newLineCallBack() const { return m_newlineCallback; } 159 | 160 | VEC_SP_TelnetSession sessions() const { return m_sessions; } 161 | 162 | bool interactivePrompt() const { return m_promptString.length() > 0; } 163 | void promptString(std::string prompt) { m_promptString = prompt; } 164 | std::string promptString() const { return m_promptString; } 165 | 166 | private: 167 | void acceptConnection(); 168 | 169 | private: 170 | u_long m_listenPort; 171 | SOCKET m_listenSocket; 172 | VEC_SP_TelnetSession m_sessions; 173 | bool m_initialised; 174 | std::string m_promptString; // A string that denotes the current prompt 175 | 176 | protected: 177 | FPTR_ConnectedCallback m_connectedCallback; // Called after the telnet session is initialised. function(SP_TelnetSession) {} 178 | FPTR_NewLineCallback m_newlineCallback; // Called after every new line (from CR or LF) function(SP_TelnetSession, std::string) {} 179 | }; -------------------------------------------------------------------------------- /TelnetServLib/telnetservlib.cpp: -------------------------------------------------------------------------------- 1 | #include "telnetservlib.hpp" 2 | #include "iostream" 3 | #include 4 | #include 5 | #include 6 | 7 | // Need to link with Ws2_32.lib 8 | #pragma comment (lib, "Ws2_32.lib") 9 | // #pragma comment (lib, "Mswsock.lib") 10 | 11 | #define DEFAULT_BUFLEN 512 12 | 13 | void TelnetSession::sendPromptAndBuffer() 14 | { 15 | // Output the prompt 16 | u_long iSendResult; 17 | iSendResult = send(m_socket, m_telnetServer->promptString().c_str(), (u_long)m_telnetServer->promptString().length(), 0); 18 | 19 | if (m_buffer.length() > 0) 20 | { 21 | // resend the buffer 22 | iSendResult = send(m_socket, m_buffer.c_str(), (u_long)m_buffer.length(), 0); 23 | } 24 | } 25 | 26 | void TelnetSession::eraseLine() 27 | { 28 | u_long iSendResult; 29 | // send an erase line 30 | iSendResult = send(m_socket, ANSI_ERASE_LINE.c_str(), (u_long)ANSI_ERASE_LINE.length(), 0); 31 | 32 | // Move the cursor to the beginning of the line 33 | std::string moveBack = "\x1b[80D"; 34 | iSendResult = send(m_socket, moveBack.c_str(), (u_long)moveBack.length(), 0); 35 | } 36 | 37 | void TelnetSession::sendLine(std::string data) 38 | { 39 | u_long iSendResult; 40 | // If is something is on the prompt, wipe it off 41 | if (m_telnetServer->interactivePrompt() || m_buffer.length() > 0) 42 | { 43 | eraseLine(); 44 | } 45 | 46 | data.append("\r\n"); 47 | iSendResult = send(m_socket, data.c_str(), (u_long)data.length(), 0); 48 | 49 | if (m_telnetServer->interactivePrompt()) 50 | sendPromptAndBuffer(); 51 | } 52 | 53 | void TelnetSession::closeClient() 54 | { 55 | u_long iResult; 56 | 57 | // attempt to cleanly shutdown the connection since we're done 58 | iResult = shutdown(m_socket, SD_SEND); 59 | if (iResult == SOCKET_ERROR) { 60 | printf("shutdown failed with error: %d\n", WSAGetLastError()); 61 | return; 62 | } 63 | 64 | // cleanup 65 | closesocket(m_socket); 66 | } 67 | 68 | void TelnetSession::echoBack(char * buffer, u_long length) 69 | { 70 | // Echo the buffer back to the sender 71 | 72 | // If you are an NVT command (i.e. first it of data is 255) then ignore the echo back 73 | unsigned char firstItem = * buffer; 74 | if (firstItem == 0xff) 75 | return; 76 | 77 | u_long iSendResult; 78 | iSendResult = send(m_socket, buffer, length, 0); 79 | 80 | if (iSendResult == SOCKET_ERROR && iSendResult != WSAEWOULDBLOCK) { 81 | printf("Send failed with Winsock error: %d\n", WSAGetLastError()); 82 | std::cout << "Closing session and socket.\r\n"; 83 | closesocket(m_socket); 84 | return; 85 | } 86 | } 87 | 88 | void TelnetSession::initialise() 89 | { 90 | // get details of connection 91 | SOCKADDR_IN client_info = { 0 }; 92 | int addrsize = sizeof(client_info); 93 | getpeername(m_socket, (struct sockaddr*)&client_info, &addrsize); 94 | 95 | char ip[16]; 96 | inet_ntop(AF_INET, &client_info.sin_addr, &ip[0], 16); 97 | 98 | std::cout << "Client " << ip << " connected...\n"; 99 | 100 | // Set the connection to be non-blocking 101 | u_long iMode = 1; 102 | ioctlsocket(m_socket, FIONBIO, &iMode); 103 | 104 | // Set NVT mode to say that I will echo back characters. 105 | u_long iSendResult; 106 | unsigned char willEcho[3] = { 0xff, 0xfb, 0x01 }; 107 | iSendResult = send(m_socket, (char *)willEcho, 3, 0); 108 | 109 | // Set NVT requesting that the remote system not/dont echo back characters 110 | unsigned char dontEcho[3] = { 0xff, 0xfe, 0x01 }; 111 | iSendResult = send(m_socket, (char *)dontEcho, 3, 0); 112 | 113 | // Set NVT mode to say that I will supress go-ahead. Stops remote clients from doing local linemode. 114 | unsigned char willSGA[3] = { 0xff, 0xfb, 0x03 }; 115 | iSendResult = send(m_socket, (char *)willSGA, 3, 0); 116 | 117 | if (m_telnetServer->connectedCallback()) 118 | m_telnetServer->connectedCallback()(shared_from_this()); 119 | } 120 | 121 | void TelnetSession::stripNVT(std::string &buffer) 122 | { 123 | size_t found; 124 | do 125 | { 126 | unsigned char findChar = 0xff; 127 | found = buffer.find_first_of((char)findChar); 128 | if (found != std::string::npos && (found + 2) <= buffer.length() - 1) 129 | { 130 | buffer.erase(found, 3); 131 | } 132 | } while (found != std::string::npos); 133 | } 134 | 135 | void TelnetSession::stripEscapeCharacters(std::string &buffer) 136 | { 137 | size_t found; 138 | 139 | std::array cursors = { ANSI_ARROW_UP, ANSI_ARROW_DOWN, ANSI_ARROW_RIGHT, ANSI_ARROW_LEFT }; 140 | 141 | for (auto c : cursors) 142 | { 143 | do 144 | { 145 | found = buffer.find(c); 146 | if (found != std::string::npos) 147 | { 148 | buffer.erase(found, c.length()); 149 | } 150 | } while (found != std::string::npos); 151 | } 152 | } 153 | 154 | bool TelnetSession::processBackspace(std::string &buffer) 155 | { 156 | bool foundBackspaces = false; 157 | size_t found; 158 | do 159 | { 160 | // Need to handle both \x7f and \b backspaces 161 | unsigned char findChar = '\x7f'; 162 | found = buffer.find_first_of((char)findChar); 163 | if (found == std::string::npos) 164 | { 165 | findChar = '\b'; 166 | found = buffer.find_first_of((char)findChar); 167 | } 168 | 169 | if (found != std::string::npos) 170 | { 171 | if (buffer.length() > 1) 172 | buffer.erase(found - 1, 2); 173 | else 174 | buffer = ""; 175 | foundBackspaces = true; 176 | } 177 | } while (found != std::string::npos); 178 | return foundBackspaces; 179 | } 180 | 181 | void TelnetSession::addToHistory(std::string line) 182 | { 183 | // Add it to the history 184 | if (line != (m_history.size() > 0 ? m_history.back() : "") && line != "") 185 | { 186 | m_history.push_back(line); 187 | if (m_history.size() > 50) 188 | m_history.pop_front(); 189 | } 190 | m_historyCursor = m_history.end(); 191 | } 192 | 193 | bool TelnetSession::processCommandHistory(std::string &buffer) 194 | { 195 | // Handle up and down arrow actions 196 | if (m_telnetServer->interactivePrompt()) 197 | { 198 | if (buffer.find(ANSI_ARROW_UP) != std::string::npos && m_history.size() > 0) 199 | { 200 | if (m_historyCursor != m_history.begin()) 201 | { 202 | m_historyCursor--; 203 | } 204 | buffer = *m_historyCursor; 205 | 206 | // Issue a cursor command to counter it 207 | u_long iSendResult; 208 | iSendResult = send(m_socket, ANSI_ARROW_DOWN.c_str(), (u_long)ANSI_ARROW_DOWN.length(), 0); 209 | return true; 210 | } 211 | if (buffer.find(ANSI_ARROW_DOWN) != std::string::npos && m_history.size() > 0) 212 | { 213 | if (next(m_historyCursor) != m_history.end()) 214 | { 215 | m_historyCursor++; 216 | } 217 | buffer = *m_historyCursor; 218 | 219 | // Issue a cursor command to counter it 220 | u_long iSendResult; 221 | iSendResult = send(m_socket, ANSI_ARROW_UP.c_str(), (u_long)ANSI_ARROW_UP.length(), 0); 222 | return true; 223 | } 224 | if (buffer.find(ANSI_ARROW_LEFT) != std::string::npos || buffer.find(ANSI_ARROW_RIGHT) != std::string::npos) 225 | { 226 | // Ignore left and right and just reprint buffer 227 | return true; 228 | } 229 | } 230 | return false; 231 | } 232 | 233 | std::vector TelnetSession::getCompleteLines(std::string &buffer) 234 | { 235 | // Now find all new lines () and place in a vector and delete from buffer 236 | 237 | char CRLF[2] = { 0x0D, 0x0A }; 238 | std::vector lines; 239 | size_t found; 240 | do 241 | { 242 | found = buffer.find("\r\n"); 243 | if (found != std::string::npos) 244 | { 245 | lines.push_back(buffer.substr(0, found)); 246 | buffer.erase(0, found + 2); 247 | } 248 | } while (found != std::string::npos); 249 | 250 | return lines; 251 | } 252 | 253 | void TelnetSession::update() 254 | { 255 | int readBytes; 256 | char recvbuf[DEFAULT_BUFLEN]; 257 | u_long recvbuflen = DEFAULT_BUFLEN; 258 | 259 | readBytes = recv(m_socket, recvbuf, recvbuflen, 0); 260 | 261 | // Check for errors from the read 262 | int error = WSAGetLastError(); 263 | if (error != WSAEWOULDBLOCK && error != 0) 264 | { 265 | std::cout << "Receive failed with Winsock error code: " << error << "\r\n"; 266 | std::cout << "Closing session and socket.\r\n"; 267 | closesocket(m_socket); 268 | return; 269 | } 270 | 271 | if (readBytes > 0) { 272 | // Echo it back to the sender 273 | echoBack(recvbuf, readBytes); 274 | 275 | // we've got to be careful here. Telnet client might send null characters for New Lines mid-data block. We need to swap these out. recv is not null terminated, so its cool 276 | for (int i = 0; i < readBytes; i++) 277 | { 278 | if (recvbuf[i] == 0x00) 279 | recvbuf[i] = 0x0A; // New Line 280 | } 281 | 282 | // Add it to the received buffer 283 | m_buffer.append(recvbuf, readBytes); 284 | 285 | stripNVT(m_buffer); // Remove telnet negotiation sequences 286 | 287 | bool requirePromptReprint = false; 288 | 289 | if (m_telnetServer->interactivePrompt()) 290 | { 291 | if (processCommandHistory(m_buffer)) // Read up and down arrow keys and scroll through history 292 | requirePromptReprint = true; 293 | stripEscapeCharacters(m_buffer); 294 | 295 | if (processBackspace(m_buffer)) 296 | requirePromptReprint = true; 297 | } 298 | 299 | auto lines = getCompleteLines(m_buffer); 300 | for (auto line : lines) 301 | { 302 | if (m_telnetServer->newLineCallBack()) 303 | m_telnetServer->newLineCallBack()(shared_from_this(), line); 304 | 305 | addToHistory(line); 306 | } 307 | 308 | if (m_telnetServer->interactivePrompt() && requirePromptReprint) 309 | { 310 | eraseLine(); 311 | sendPromptAndBuffer(); 312 | } 313 | } 314 | } 315 | 316 | void TelnetSession::UNIT_TEST() 317 | { 318 | /* stripNVT */ 319 | std::cout << "TEST: stripNVT\n"; 320 | std::string origData = "12345"; 321 | std::string data = origData; 322 | unsigned char toStrip[3] = { 255, 251, 1 }; 323 | data.insert(2, (char *)toStrip, 3); 324 | TelnetSession::stripNVT(data); 325 | 326 | assert(origData == data); 327 | 328 | /* processBackspace */ 329 | std::cout << "TEST: handleBackspace\n"; 330 | std::string bkData = "123455\x7f"; 331 | bool bkResult = TelnetSession::processBackspace(bkData); 332 | assert(bkData == "12345"); 333 | assert(bkResult == true); 334 | 335 | /* getCompleteLines */ 336 | std::cout << "TEST: getCompleteLines\n"; 337 | std::string multiData = "LINE1\r\nLINE2\r\nLINE3\r\n"; 338 | auto lines = TelnetSession::getCompleteLines(multiData); 339 | 340 | assert(lines.size() == 3); 341 | assert(lines[0] == "LINE1"); 342 | assert(lines[1] == "LINE2"); 343 | assert(lines[2] == "LINE3"); 344 | } 345 | 346 | /* ------------------ Telnet Server -------------------*/ 347 | bool TelnetServer::initialise(u_long listenPort, std::string promptString) 348 | { 349 | if (m_initialised) 350 | { 351 | std::cout << "This Telnet Server instance has already been initialised. Please shut it down before reinitialising it."; 352 | return false; 353 | } 354 | 355 | m_listenPort = listenPort; 356 | m_promptString = promptString; 357 | 358 | std::cout << "Starting Telnet Server on port " << std::to_string(m_listenPort) << "\n"; 359 | 360 | WSADATA wsaData; 361 | int iResult; 362 | 363 | m_listenSocket = INVALID_SOCKET; 364 | 365 | struct addrinfo *result = NULL; 366 | struct addrinfo hints; 367 | 368 | // Initialize Winsock 369 | iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 370 | if (iResult != 0) { 371 | printf("WSAStartup failed with error: %d\n", iResult); 372 | return false; 373 | } 374 | 375 | ZeroMemory(&hints, sizeof(hints)); 376 | hints.ai_family = AF_INET; 377 | hints.ai_socktype = SOCK_STREAM; 378 | hints.ai_protocol = IPPROTO_TCP; 379 | hints.ai_flags = AI_PASSIVE; 380 | 381 | // Resolve the server address and port 382 | iResult = getaddrinfo(NULL, std::to_string(m_listenPort).c_str(), &hints, &result); 383 | if (iResult != 0) { 384 | printf("getaddrinfo failed with error: %d\n", iResult); 385 | return false; 386 | } 387 | 388 | // Create a SOCKET for connecting to server 389 | m_listenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); 390 | if (m_listenSocket == INVALID_SOCKET) { 391 | printf("socket failed with error: %ld\n", WSAGetLastError()); 392 | freeaddrinfo(result); 393 | return false; 394 | } 395 | 396 | // Setup the TCP listening socket 397 | iResult = bind(m_listenSocket, result->ai_addr, (int)result->ai_addrlen); 398 | if (iResult == SOCKET_ERROR) { 399 | printf("bind failed with error: %d\n", WSAGetLastError()); 400 | freeaddrinfo(result); 401 | closesocket(m_listenSocket); 402 | return false; 403 | } 404 | 405 | freeaddrinfo(result); 406 | 407 | iResult = listen(m_listenSocket, SOMAXCONN); 408 | if (iResult == SOCKET_ERROR) { 409 | printf("listen failed with error: %d\n", WSAGetLastError()); 410 | closesocket(m_listenSocket); 411 | return false; 412 | } 413 | 414 | m_initialised = true; 415 | return true; 416 | } 417 | 418 | void TelnetServer::acceptConnection() 419 | { 420 | SOCKET ClientSocket = INVALID_SOCKET; 421 | ClientSocket = accept(m_listenSocket, NULL, NULL); 422 | if (ClientSocket == INVALID_SOCKET) { 423 | printf("accept failed with error: %d\n", WSAGetLastError()); 424 | closesocket(m_listenSocket); 425 | return; 426 | } 427 | else 428 | { 429 | SP_TelnetSession s = std::make_shared < TelnetSession >(ClientSocket, shared_from_this()); 430 | m_sessions.push_back(s); 431 | s->initialise(); 432 | } 433 | } 434 | 435 | void TelnetServer::update() 436 | { 437 | // See if connection pending on the listening socket 438 | fd_set readSet; 439 | FD_ZERO(&readSet); 440 | FD_SET(m_listenSocket, &readSet); 441 | timeval timeout; 442 | timeout.tv_sec = 0; // Zero timeout (poll) 443 | timeout.tv_usec = 0; 444 | 445 | if (select(m_listenSocket, &readSet, NULL, NULL, &timeout) == 1) 446 | { 447 | // There is a connection pending, so accept it. 448 | acceptConnection(); 449 | } 450 | 451 | // Update all the telnet Sessions that are currently in flight. 452 | for (SP_TelnetSession ts : m_sessions) 453 | { 454 | ts->update(); 455 | } 456 | } 457 | 458 | void TelnetServer::shutdown() 459 | { 460 | // Attempt to cleanly close every telnet session in flight. 461 | for (SP_TelnetSession ts : m_sessions) 462 | { 463 | ts->closeClient(); 464 | } 465 | m_sessions.clear(); 466 | 467 | // No longer need server socket so close it. 468 | closesocket(m_listenSocket); 469 | m_initialised = false; 470 | } --------------------------------------------------------------------------------