#!/usr/bin/env python # Copyright (c) 2006 Guido Wesdorp. All rights reserved. # This software is distributed under the terms of the RIPShell # License. See LICENSE.txt for license text. """simple socket server to remotely execute Python code the client can either send a full string of Python code and have it executed, or have the server present a Python prompt; in both cases stdin, stdout and stderr are redirected from the server to the client """ import socket import code import sys import thread import traceback class config: server_ip = '192.168.1.103' client_ips = ['192.168.1.4', '127.0.0.1'] port = 8002 __version__ = '0.2 SVN' class STDFilePointers: """proxy for file pointers can be used to redirect sys.stdin, sys.stdout and sys.stderr to a socket """ def __init__(self, conn): self.conn = conn def write(self, s): self.conn.send(s) def read(self, l): r = self.conn.recv(l) #if not r: # raise IOError('Connection closed') return r or ' ' def readline(self): data = [] while 1: c = self.read(1) if c == '\n': return ''.join(data) + '\n' data.append(c) class RIPServer: """server that processes Python code from the client locally either executes file content with stdin, stdout and stderr redirected from and to the client, or runs an interactive interpreter loop """ banner = ('Python %s\n' 'Remote Interactive Python Shell v%s\n' 'Type "help", "copyright", "credits" or "license" ' 'for more information.' % (sys.version, __version__)) def __init__(self, config): """open a socket and start waiting for connections""" self.config = config self.sock = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.bind((config.server_ip, config.port)) s.listen(1) while 1: conn, addr = s.accept() if addr[0] not in config.client_ips: print 'Connection refused' return print 'Connection from', addr self.handle(conn, addr) conn.close() print 'Connection closed' finally: print 'Closing' s.close() def handle(self, conn, addr): """handle a new connection""" stdfps = STDFilePointers(conn) sys.stdin = stdfps sys.stdout = stdfps sys.stderr = stdfps try: try: command = conn.recv(1) # dispatch depending on command (first char sent should be '-' # for the interactive interpreter loop, 'x' for executing code) if command == '-': self.interpreterloop(conn, addr) elif command == 'x': self.readandexec(conn, addr) else: print 'Unexpected input, exiting...' except SystemExit, e: # raise a SystemExit with message 'quit' to stop the server # from a client if str(e) == 'quit': print 'Stopping server' raise # kill the server print 'SystemExit' except: exc, e, tb = sys.exc_info() try: print '%s - %s' % (exc, e) print '\n'.join(traceback.format_tb(tb)) except: pass del tb print >>sys.__stdout__, 'Exception:', exc, '-', e finally: sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ def interpreterloop(self, conn, addr): """standard code interpreter loop""" code.interact(self.banner) def readandexec(self, conn, addr): """execute file data""" data = [] while 1: c = conn.recv(1) if not c: raise IOError('Connection closed') return if c == '\0': print ''.join(data) exec ''.join(data) in globals() return data.append(c) class RIPClient: """sends code to the server to execute and prints returned data thin client, basically only presents stdout and stderr from, and sends stdin to, the server """ def __init__(self, config, filedata=None): """connect to the server and start the session if filedata resolves to False, the interactive interpreter loop is started; if not it should be a string containing Python code, which will be executed on the server """ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((config.server_ip, config.port)) if filedata: self.execfile(s, filedata) else: self.interpreterloop(s) finally: s.close() def interpreterloop(self, sock): """interpreter""" sock.send('-') # tell the server we want to get a prompt thread.start_new_thread(self.readloop, (sock,)) self.writeloop(sock) def execfile(self, sock, filedata): """execute file data""" sock.send('x') # tell the server file data is coming thread.start_new_thread(self.readloop, (sock,)) sock.send(filedata) sock.send('\0') # tell the server we're done... self.writeloop(sock) def readloop(self, sock): while 1: try: sock.send(sys.stdin.read(1)) except socket.error: return def writeloop(self, sock): while 1: c = sock.recv(1) if not c: break sys.stdout.write(c) sys.stdout.flush() if __name__ == '__main__': if len(sys.argv) == 1: print 'Starting server...' RIPServer(config) # blocks until done sys.exit(0) elif len(sys.argv) == 2 and sys.argv[1] not in ['-h', '?', '--help']: filedata = None if sys.argv[1] != '-': fp = open(sys.argv[1]) try: filedata = fp.read() finally: fp.close() RIPClient(config, filedata) # blocks until done sys.exit(0) print 'Usage: %s [-|]' % (sys.argv[0],) print print ('Without any command line arguments, the server is started, which\n' 'allows executing Python code from connected clients. When the\n' 'first argument is a dash (-), the client connects to the server\n' 'and requests it to start an interactive loop. Any other\n' 'argument will be considered a path to a Python script, which\n' 'will be executed on the server.\n')