├── .gitignore ├── .project ├── .pydevproject ├── COPYING.txt ├── MANIFEST.in ├── README.rst ├── setup.py ├── telnetsrv ├── __init__.py ├── evtlet.py ├── green.py ├── paramiko_ssh.py ├── telnetsrvlib.py ├── test_rsa.key └── threaded.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | 29 | .DS_Store 30 | 31 | MANIFEST 32 | 33 | telnetsrv/server_rsa.key 34 | 35 | server_rsa.key 36 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | telnetsrv 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | python 2.7 6 | Default 7 | 8 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include COPYING.txt 3 | 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | telnetsrvlib 2 | ============ 3 | 4 | Telnet server using gevent or threading. 5 | 6 | Copied from http://pytelnetsrvlib.sourceforge.net/ 7 | and modified to support gevent and eventlet, better input handling, clean asynchronous messages and much more. 8 | Licensed under the LGPL, as per the SourceForge notes. 9 | 10 | This library allows you to easily create a Telnet or SSH server powered by your Python code. 11 | The library negotiates with a Telnet client, parses commands, provides an automated 12 | help command, optionally provides login queries, then allows you to define your own 13 | commands. An optional SSH handler is provided to wrap the defined Telnet handler into 14 | an SSH handler. 15 | 16 | You use the library to create your own handler, then pass that handler to a StreamServer 17 | or TCPServer to perform the actual connection tasks. 18 | 19 | This library includes two flavors of the server handler, one uses separate threads, 20 | the other uses greenlets (green pseudo-threads) via gevent or eventlet. 21 | 22 | The threaded version uses a separate thread to process the input buffer and 23 | semaphores reading and writing. The provided test server only handles a single 24 | connection at a time. 25 | 26 | The green version moves the input buffer processing into a greenlet to allow 27 | cooperative multi-processing. This results in significantly less memory usage 28 | and nearly no idle processing. The provided test server handles a large number of connections. 29 | 30 | 31 | Install 32 | ------- 33 | 34 | telnetsrv is available through the Cheeseshop. You can use easy_install or pip to perform the installation. 35 | 36 | :: 37 | 38 | easy_install telnetsrv 39 | 40 | or 41 | 42 | :: 43 | 44 | pip install telnetsrv 45 | 46 | Note that there are no dependancies defined, but if you want to use the green version, you must also install gevent or eventlet. 47 | If you wish to use the SSH server, you must also install paramiko. 48 | 49 | To Use 50 | ------ 51 | 52 | Import the ``TelnetHandler`` base class and ``command`` function decorator from either the green class, evtlet class or threaded class, 53 | then subclass ``TelnetHandler`` to add your own commands which are methods decorated with ``@command``. 54 | 55 | Threaded 56 | ++++++++ 57 | 58 | .. code:: python 59 | 60 | from telnetsrv.threaded import TelnetHandler, command 61 | class MyHandler(TelnetHandler): 62 | ... 63 | 64 | Green 65 | +++++ 66 | 67 | .. code:: python 68 | 69 | from telnetsrv.green import TelnetHandler, command 70 | class MyHandler(TelnetHandler): 71 | ... 72 | 73 | Eventlet 74 | ++++++++ 75 | 76 | .. code:: python 77 | 78 | from telnetsrv.evtlet import TelnetHandler, command 79 | class MyHandler(TelnetHandler): 80 | ... 81 | 82 | Adding Commands 83 | --------------- 84 | 85 | Commands can be defined by using the ``command`` function decorator. 86 | 87 | .. code:: python 88 | 89 | @command('echo') 90 | def command_echo(self, params): 91 | ... 92 | 93 | Old Style 94 | +++++++++ 95 | 96 | Commands can also be defined by prefixing any method with "cmd". For example, 97 | this also creates an ``echo`` command: 98 | 99 | .. code:: python 100 | 101 | def cmdECHO(self, params): 102 | ... 103 | 104 | *This method is less flexible and may not be supported in future versions.* 105 | 106 | Command Parameters 107 | ++++++++++++++++++ 108 | 109 | Any command parameters will be passed to this function automatically. The parameters are 110 | contained in a list. The user input is parsed similar to the way Bash parses text: space delimited, 111 | quoted parameters are kept together and default behavior can be modified with the ``\`` character. 112 | If you need to access the raw text input, inspect the self.input.raw variable. 113 | 114 | :: 115 | 116 | Telnet Server> echo 1 "2 3" 117 | 118 | .. code:: python 119 | 120 | params == ['1', '2 3'] 121 | self.input.raw == 'echo 1 "2 3"\n' 122 | 123 | :: 124 | 125 | Telnet Server> echo 1 \ 126 | ... 2 "3 127 | ... 4" "5\ 128 | ... 6" 129 | 130 | .. code:: python 131 | 132 | params == ['1', '2', '3\n4', '56'] 133 | 134 | :: 135 | 136 | Telnet Server> echo 1\ 2 137 | 138 | .. code:: python 139 | 140 | params == ['1 2'] 141 | 142 | Command Help Text 143 | +++++++++++++++++ 144 | 145 | The command's docstring is used for generating the console help information, and must be formatted 146 | with at least 3 lines: 147 | 148 | - Line 0: Command parameter(s) if any. (Can be blank line) 149 | - Line 1: Short descriptive text. (Mandatory) 150 | - Line 2+: Long descriptive text. (Can be blank line) 151 | 152 | If there is no line 2, line 1 will be used for the long description as well. 153 | 154 | .. code:: python 155 | 156 | @command('echo') 157 | def command_echo(self, params): 158 | ''' 159 | Echo text back to the console. 160 | This command simply echos the provided text 161 | back to the console. 162 | ''' 163 | pass 164 | 165 | 166 | :: 167 | 168 | Telnet Server> help 169 | ? [] - Display help 170 | BYE - Exit the command shell 171 | ECHO - Echo text back to the console. 172 | ... 173 | 174 | 175 | Telnet Server> help echo 176 | ECHO 177 | 178 | This command simply echos the provided text 179 | back to the console. 180 | Telnet Server> 181 | 182 | 183 | Command Aliases 184 | +++++++++++++++ 185 | 186 | To create an alias for the new command, set the method's name to a list: 187 | 188 | .. code:: python 189 | 190 | @command(['echo', 'copy']) 191 | def command_echo(self, params): 192 | ... 193 | 194 | The decorator may be stacked, which adds each list to the aliases: 195 | 196 | .. code:: python 197 | 198 | @command('echo') 199 | @command(['copy', 'repeat']) 200 | @command('ditto') 201 | def command_echo(self, params): 202 | ... 203 | 204 | 205 | 206 | Hidden Commands 207 | +++++++++++++++ 208 | 209 | To hide the command (and any alias for that command) from the help text output, pass in hidden=True to the decorator: 210 | 211 | .. code:: python 212 | 213 | @command('echo', hidden=True) 214 | def command_echo(self, params): 215 | ... 216 | 217 | The command will not show when the user invokes ``help`` by itself, but the detailed help text will show if 218 | the user invokes ``help echo``. 219 | 220 | When stacking decorators, any one of the stack may define the hidden parameter to hide the command. 221 | 222 | Console Information 223 | ------------------- 224 | 225 | These will be provided for inspection. 226 | 227 | ``TERM`` 228 | String ID describing the currently connected terminal 229 | 230 | ``WIDTH`` 231 | Integer describing the width of the terminal at connection time. 232 | 233 | ``HEIGHT`` 234 | Integer describing the height of the terminal at connection time. 235 | 236 | ``username`` 237 | Set after authentication succeeds, name of the logged in user. 238 | If no authentication was requested, will be ``None``. 239 | 240 | ``history`` 241 | List containing the command history. This can be manipulated directly. 242 | 243 | 244 | .. code:: python 245 | 246 | @command('info') 247 | def command_info(self, params): 248 | ''' 249 | Provides some information about the current terminal. 250 | ''' 251 | self.writeresponse( "Username: %s, terminal type: %s" % (self.username, self.TERM) ) 252 | self.writeresponse( "Width: %s, height: %s" % (self.WIDTH, self.HEIGHT) ) 253 | self.writeresponse( "Command history:" ) 254 | for c in self.history: 255 | self.writeresponse(" %r" % c) 256 | 257 | 258 | Console Communication 259 | --------------------- 260 | 261 | Send Text to the Client 262 | +++++++++++++++++++++++ 263 | 264 | Lower level functions: 265 | 266 | ``self.writeline( TEXT )`` 267 | 268 | ``self.write( TEXT )`` 269 | 270 | Higher level functions: 271 | 272 | ``self.writemessage( TEXT )`` - for clean, asynchronous writing. Any interrupted input is rebuilt. 273 | 274 | ``self.writeresponse( TEXT )`` - to emit a line of expected output 275 | 276 | ``self.writeerror( TEXT )`` - to emit error messages 277 | 278 | The ``writemessage`` method is intended to send messages to the console without 279 | interrupting any current input. If the user has entered text at the prompt, 280 | the prompt and text will be seamlessly regenerated following the message. 281 | It is ideal for asynchronous messages that aren't generated from the direct user input. 282 | 283 | Receive Text from the Client 284 | ++++++++++++++++++++++++++++ 285 | 286 | ``self.readline( prompt=TEXT )`` 287 | 288 | Setting the prompt is important to recreate the user input following a ``writemessage`` 289 | interruption. 290 | 291 | When requesting sensitive information from the user (such as requesting a new password) the input should 292 | not be shown nor should the input line be written to the command history. ``readline`` accepts 293 | two optional parameters to control this, ``echo`` and ``use_history``. 294 | 295 | ``self.readline( prompt=TEXT, echo=False, use_history=False )`` 296 | 297 | When ``echo`` is set to False, the input will not echo back to the user. When ``use_history`` is set 298 | to False, the user will not have access to the command history (up arrow) nor will the entered data 299 | be stored in the command history. 300 | 301 | Handler Options 302 | --------------- 303 | 304 | Override these class members to change the handler's behavior. 305 | 306 | ``PROMPT`` 307 | Default: ``"Telnet Server> "`` 308 | 309 | ``CONTINUE_PROMPT`` 310 | Default: ``"... "`` 311 | 312 | ``WELCOME`` 313 | Displayed after a successful connection, after the username/password is accepted, if configured. 314 | 315 | Default: ``"You have connected to the telnet server."`` 316 | 317 | ``session_start(self)`` 318 | Called after the ``WELCOME`` text is displayed. 319 | 320 | Default: pass 321 | 322 | ``session_end(self)`` 323 | Called after the console is disconnected. 324 | 325 | Default: pass 326 | 327 | ``authCallback(self, username, password)`` 328 | Reference to authentication function. If 329 | this is not defined, no username or password is requested. Should 330 | raise an exception if authentication fails 331 | 332 | Default: None 333 | 334 | ``authNeedUser`` 335 | Should a username be requested? 336 | 337 | Default: ``False`` 338 | 339 | ``authNeedPass`` 340 | Should a password be requested? 341 | 342 | Default: ``False`` 343 | 344 | 345 | Handler Display Modification 346 | ---------------------------- 347 | 348 | If you want to change how the output is displayed, override one or all of the 349 | write classes. Make sure you call back to the base class when doing so. 350 | This is a good way to provide color to your console by using ANSI color commands. 351 | See http://en.wikipedia.org/wiki/ANSI_escape_code 352 | 353 | - writemessage( TEXT ) 354 | - writeresponse( TEXT ) 355 | - writeerror( TEXT ) 356 | 357 | .. code:: python 358 | 359 | def writeerror(self, text): 360 | '''Write errors in red''' 361 | TelnetHandler.writeerror(self, "\x1b[91m%s\x1b[0m" % text ) 362 | 363 | Serving the Handler 364 | ------------------- 365 | 366 | Now you have a shiny new handler class, but it doesn't serve itself - it must be called 367 | from an appropriate server. The server will create an instance of the TelnetHandler class 368 | for each new connection. The handler class will work with either a gevent StreamServer instance 369 | (for the green version) or with a SocketServer.TCPServer instance (for the threaded version). 370 | 371 | Threaded 372 | ++++++++ 373 | 374 | .. code:: python 375 | 376 | import SocketServer 377 | class TelnetServer(SocketServer.TCPServer): 378 | allow_reuse_address = True 379 | 380 | server = TelnetServer(("0.0.0.0", 8023), MyHandler) 381 | server.serve_forever() 382 | 383 | Green 384 | +++++ 385 | 386 | The TelnetHandler class includes a streamserver_handle class method to translate the 387 | required fields from a StreamServer, allowing use with the gevent StreamServer (and possibly 388 | others). 389 | 390 | .. code:: python 391 | 392 | import gevent.server 393 | server = gevent.server.StreamServer(("", 8023), MyHandler.streamserver_handle) 394 | server.serve_forever() 395 | 396 | 397 | Short Example 398 | ------------- 399 | 400 | .. code:: python 401 | 402 | import gevent, gevent.server 403 | from telnetsrv.green import TelnetHandler, command 404 | 405 | class MyTelnetHandler(TelnetHandler): 406 | WELCOME = "Welcome to my server." 407 | 408 | @command(['echo', 'copy', 'repeat']) 409 | def command_echo(self, params): 410 | ''' 411 | Echo text back to the console. 412 | 413 | ''' 414 | self.writeresponse( ' '.join(params) ) 415 | 416 | @command('timer') 417 | def command_timer(self, params): 418 | '''