Package coro :: Module backdoor
[hide private]
[frames] | no frames]

Source Code for Module coro.backdoor

  1  # -*- Mode: Python -*- 
  2   
  3  # Copyright 1999, 2000 by eGroups, Inc. 
  4  # 
  5  #                         All Rights Reserved 
  6  # 
  7  # Permission to use, copy, modify, and distribute this software and 
  8  # its documentation for any purpose and without fee is hereby 
  9  # granted, provided that the above copyright notice appear in all 
 10  # copies and that both that copyright notice and this permission 
 11  # notice appear in supporting documentation, and that the name of 
 12  # eGroups not be used in advertising or publicity pertaining to 
 13  # distribution of the software without specific, written prior 
 14  # permission. 
 15  # 
 16  # EGROUPS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 
 17  # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN 
 18  # NO EVENT SHALL EGROUPS BE LIABLE FOR ANY SPECIAL, INDIRECT OR 
 19  # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 
 20  # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 
 21  # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
 22  # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 23   
 24  """Backdoor Python access. 
 25   
 26  This module implements a server that allows one to telnet to a socket and get a 
 27  Python prompt in a process. 
 28   
 29  Simply spawn a thread running the `serve` function to start a backdoor server. 
 30  """ 
 31   
 32  VERSION_STRING = '$Id: //prod/main/ap/shrapnel/coro/backdoor.py#6 $' 
 33   
 34  import coro 
 35  import cStringIO 
 36  import fcntl 
 37  import sys 
 38  import traceback 
 39  import os 
 40   
 41  # Originally, this object implemented the file-output api, and set 
 42  # sys.stdout and sys.stderr to 'self'.  However, if any other 
 43  # coroutine ran, it would see the captured definition of sys.stdout, 
 44  # and would send its output here, instead of the expected place.  Now 
 45  # the code captures all output using StringIO.  A little less 
 46  # flexible, a little less efficient, but much less surprising! 
 47  # [Note: this is exactly the same problem addressed by Scheme's 
 48  #  dynamic-wind facility] 
 49   
50 -class backdoor:
51
52 - def __init__ (self, sock, line_separator='\r\n', welcome_message=None, global_dict=None):
53 self.sock = sock 54 self.buffer = '' 55 self.lines = [] 56 self.multilines = [] 57 self.line_separator = line_separator 58 self.welcome_message = welcome_message 59 self.global_dict = global_dict 60 61 # allow the user to change the prompts: 62 if not sys.__dict__.has_key('ps1'): 63 sys.ps1 = '>>> ' 64 if not sys.__dict__.has_key('ps2'): 65 sys.ps2 = '... '
66
67 - def send (self, data):
68 try: 69 self.sock.send (data) 70 except: 71 pass
72
73 - def prompt (self):
74 if self.multilines: 75 self.send (sys.ps2) 76 else: 77 self.send (sys.ps1)
78
79 - def read_line (self):
80 if self.lines: 81 l = self.lines[0] 82 self.lines = self.lines[1:] 83 return l 84 else: 85 while not self.lines: 86 block = self.sock.recv (8192) 87 if not block: 88 return None 89 elif block == '\004': 90 self.sock.close() 91 return None 92 else: 93 self.buffer = self.buffer + block 94 lines = self.buffer.split (self.line_separator) 95 for l in lines[:-1]: 96 self.lines.append (l) 97 self.buffer = lines[-1] 98 return self.read_line()
99
100 - def send_welcome_message(self):
101 self.send ('Python ' + sys.version + self.line_separator) 102 self.send (sys.copyright + self.line_separator) 103 if self.welcome_message is not None: 104 # make '\n' into the right line separator and terminate with 105 # a line separator 106 lines = self.welcome_message.split ('\n') 107 if lines[-1] != '': 108 lines.append('') 109 self.send (self.line_separator.join (lines))
110
111 - def read_eval_print_loop (self):
112 self.send_welcome_message() 113 114 if self.global_dict is None: 115 # this does the equivalent of 'from __main__ import *' 116 env = sys.modules['__main__'].__dict__.copy() 117 else: 118 env = self.global_dict.copy() 119 120 while 1: 121 self.prompt() 122 line = self.read_line() 123 if line is None: 124 break 125 elif self.multilines: 126 self.multilines.append(line) 127 if line == '': 128 code = '\n'.join (self.multilines) 129 self.parse(code, env) 130 # we do this after the parsing so parse() knows not to do 131 # a second round of multiline input if it really is an 132 # unexpected EOF 133 self.multilines = [] 134 else: 135 self.parse(line, env)
136
137 - def parse(self, line, env):
138 save = sys.stdout, sys.stderr 139 output = cStringIO.StringIO() 140 try: 141 try: 142 sys.stdout = sys.stderr = output 143 co = compile (line, repr(self), 'eval') 144 result = eval (co, env) 145 if result is not None: 146 print repr(result) 147 env['_'] = result 148 except SyntaxError: 149 try: 150 co = compile (line, repr(self), 'exec') 151 exec co in env 152 except SyntaxError, msg: 153 # this is a hack, but it is a righteous hack: 154 if not self.multilines and msg[0] == 'unexpected EOF while parsing': 155 self.multilines.append(line) 156 else: 157 traceback.print_exc() 158 except: 159 traceback.print_exc() 160 except: 161 traceback.print_exc() 162 finally: 163 sys.stdout, sys.stderr = save 164 self.send (output.getvalue()) 165 del output
166
167 -def client (conn, addr, welcome_message=None, global_dict=None):
168 b = backdoor (conn, welcome_message=welcome_message, global_dict=global_dict) 169 b.read_eval_print_loop()
170
171 -def serve (port=None, ip='', unix_path=None, welcome_message=None, global_dict=None):
172 """Backdoor server function. 173 174 This function will listen on the backdoor socket and spawn new threads for 175 each connection. 176 177 :Parameters: 178 - `port`: The IPv4 port to listen on (defaults to automatically choose 179 an unused port between 8023->8033). May also be a list of ports. 180 - `ip`: The IP to listen on. Defaults to all IP's. 181 - `unix_path`: The unix path to listen on. If this is specified, then 182 it will use unix-domain sockets, otherwise it will use IPv4 sockets. 183 - `welcome_message`: A welcome message to display when a user logs in. 184 - `global_dict`: The global dictionary to use for client sessions. 185 """ 186 import errno 187 if unix_path: 188 try: 189 os.remove (unix_path) 190 except OSError, why: 191 if why[0]==errno.ENOENT: 192 pass 193 else: 194 raise 195 s = coro.make_socket (coro.PF.LOCAL, coro.SOCK.STREAM) 196 s.bind (unix_path) 197 coro.print_stderr('Backdoor started on unix socket %s\n' % unix_path) 198 else: 199 s = coro.make_socket (coro.PF.INET, coro.SOCK.STREAM) 200 s.set_reuse_addr() 201 if port is None: 202 ports = xrange(8023, 8033) 203 else: 204 if type(port) is int: 205 ports = [port] 206 else: 207 ports = port 208 for i in ports: 209 try: 210 s.bind ((ip, i)) 211 coro.print_stderr('Backdoor started on port %d\n' % i) 212 break 213 except OSError, why: 214 if why[0] != errno.EADDRINUSE: 215 raise OSError, why 216 else: 217 raise Exception, "couldn't bind a port (try not specifying a port)" 218 219 flags = fcntl.fcntl(s.fileno(), fcntl.F_GETFD) 220 fcntl.fcntl(s.fileno(), fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) 221 222 s.listen (1024) 223 while 1: 224 conn, addr = s.accept() 225 coro.print_stderr ('incoming connection from %r\n' % (conn.getsockname(),)) 226 thread = coro.spawn (client, conn, addr, welcome_message, global_dict) 227 thread.set_name('backdoor session')
228 229 if __name__ == '__main__': 230 thread = coro.spawn (serve, welcome_message='Testing backdoor.py') 231 thread.set_name('backdoor server') 232 coro.event_loop (30.0) 233