diff options
author | noah <noah@656d521f-e311-0410-88e0-e7920216d269> | 2006-08-24 20:52:46 +0000 |
---|---|---|
committer | noah <noah@656d521f-e311-0410-88e0-e7920216d269> | 2006-08-24 20:52:46 +0000 |
commit | 310f342ac89810990643740f94c713c74c20ee73 (patch) | |
tree | 387293750f5c55ac2a299e9338ff4d0e7f7a111f | |
parent | a0410c953f9423063640050ad8fd542cf111214d (diff) | |
download | pexpect-310f342ac89810990643740f94c713c74c20ee73.tar.gz |
Merged client and server into a single script.
git-svn-id: http://pexpect.svn.sourceforge.net/svnroot/pexpect/trunk@419 656d521f-e311-0410-88e0-e7920216d269
-rwxr-xr-x | pexpect/examples/bd_client.cgi | 407 |
1 files changed, 340 insertions, 67 deletions
diff --git a/pexpect/examples/bd_client.cgi b/pexpect/examples/bd_client.cgi index 66341b1..73e7955 100755 --- a/pexpect/examples/bd_client.cgi +++ b/pexpect/examples/bd_client.cgi @@ -1,9 +1,339 @@ #!/usr/bin/env python -# #sys.path.insert (0,"/var/www/cgi-bin") -#sys.path.insert (0,"/usr/local/apache/cgi-bin") +"""Back door shell server -import sys, os, socket, random, string, traceback, cgi +This exposes a shell terminal on a socket. + --hostname : sets the remote host name to open an ssh connection to. + --username : sets the user name to login with + --password : (optional) sets the password to login with + --port : set the local port for the server to listen on + --watch : show the virtual screen after each client request + +This project is probably not the most security concious thing I've ever built. +This should be considered an experimental tool -- at best. +""" +import sys,os +sys.path.insert (0,os.getcwd()) +import socket, random, string, traceback, cgi, time, getopt, getpass, threading +import pxssh, pexpect, ANSI +import pxssh, pexpect, ANSI + +def exit_with_usage(exit_code=1): + print globals()['__doc__'] + os._exit(exit_code) + +def client (command, host='localhost', port=-1): + """This sends a request to the server and returns the response. + If port is <= 0 then host is assumed to be the filename of a Unix domain socket. + If port is greater 0 then host is an inet hostname. + """ + if port <= 0: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(host) + else: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + s.send(command) + data = s.recv (2500) + s.close() + return data + +def server (hostname, username, password, socket_filename='/tmp/mysock', verbose=False): + """This starts and services requests from the client. + """ + if verbose: sys.stdout.write ('server started with pid %d\n' % os.getpid() ) + + virtual_screen = ANSI.ANSI (24,80) + child = pxssh.pxssh() + child.login (hostname, username, password) + if verbose: print 'created shell. command line prompt is', child.PROMPT + virtual_screen.write (child.before) + virtual_screen.write (child.after) + + if os.path.exists(socket_filename): os.remove(socket_filename) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + localhost = '127.0.0.1' + s.bind(socket_filename) + os.chmod(socket_filename,0777) + if verbose: print 'Listen' + s.listen(1) + #s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + #localhost = '127.0.0.1' + #s.bind((localhost, port)) + #print 'Listen' + #s.listen(1) + + r = roller (0.01, endless_poll, (child, child.PROMPT, virtual_screen)) + r.start() + if verbose: print "screen poll updater started in background thread" + sys.stdout.flush() + try: + while True: + conn, addr = s.accept() + if verbose: print 'Connected by', addr + data = conn.recv(1024) + if data[0]!=':': + cmd = ':sendline' + arg = data.strip() + else: + request = data.split(' ', 1) + if len(request)>1: + cmd = request[0].strip() + arg = request[1].strip() + else: + cmd = request[0].strip() + if cmd == ':exit': + r.cancel() + break + elif cmd == ':sendline': + child.sendline (arg) + #child.prompt(timeout=2) + time.sleep(0.2) + shell_window = str(virtual_screen) + elif cmd == ':send' or cmd==':xsend': + if cmd==':xsend': + arg = arg.decode("hex") + child.send (arg) + time.sleep(0.2) + shell_window = str(virtual_screen) + elif cmd == ':cursor': + shell_window = '%x%x' % (virtual_screen.cur_r, virtual_screen.cur_c) + elif cmd == ':refresh': + shell_window = str(virtual_screen) + + response = [] + response.append (shell_window) + #response = add_cursor_blink (response, row, col) + if verbose: print '\n'.join(response) + sent = conn.send('\n'.join(response)) + if sent < len (response): + if verbose: print "Sent is too short. Some data was cut off." + conn.close() + finally: + r.cancel() + if verbose: print "cleaning up socket" + s.close() + if os.path.exists(socket_filename): os.remove(socket_filename) + if verbose: print "server done!" + +class roller (threading.Thread): + """This class continuously loops a function in a thread. + """ + def __init__(self, interval, function, args=[], kwargs={}): + """The interval parameter defines time between each call to the function. + """ + threading.Thread.__init__(self) + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = threading.Event() + def cancel(self): + """Stop the roller.""" + self.finished.set() + def run(self): + while not self.finished.isSet(): + # self.finished.wait(self.interval) + self.function(*self.args, **self.kwargs) + +def endless_poll (child, prompt, screen, refresh_timeout=0.1): + """This keeps the screen updated with the output of the child. + This runs in a separate thread. + See roller class. + """ + #child.logfile_read = screen + try: + s = child.read_nonblocking(4000, 0.1) + screen.write(s) + except: + pass + +def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + '''This forks the current process into a daemon. + Almost none of this is necessary (or advisable) if your daemon + is being started by inetd. In that case, stdin, stdout and stderr are + all set up for you to refer to the network connection, and the fork()s + and session manipulation should not be done (to avoid confusing inetd). + Only the chdir() and umask() steps remain as useful. + + References: + UNIX Programming FAQ + 1.7 How do I get my program to act like a daemon? + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + + Advanced Programming in the Unix Environment + W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7. + + The stdin, stdout, and stderr arguments are file names that + will be opened and be used to replace the standard file descriptors + in sys.stdin, sys.stdout, and sys.stderr. + These arguments are optional and default to /dev/null. + Note that stderr is opened unbuffered, so + if it shares a file with stdout then interleaved output + may not appear in the order that you expect. + ''' + + # Do first fork. + try: + pid = os.fork() + if pid > 0: + sys.exit(0) # Exit first parent. + except OSError, e: + sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) ) + sys.exit(1) + + # Decouple from parent environment. + os.chdir("/") + os.umask(0) + os.setsid() + + # Do second fork. + try: + pid = os.fork() + if pid > 0: + sys.exit(0) # Exit second parent. + except OSError, e: + sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) ) + sys.exit(1) + + # Now I am a daemon! + + # Redirect standard file descriptors. + si = open(stdin, 'r') + so = open(stdout, 'a+') + se = open(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # I now return as the daemon + return 0 + +def random_sid (): + a=random.randint(0,65535) + b=random.randint(0,65535) + return '%04x%04x.sid' % (a,b) + +def parse_host_connect_string (hcs): + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. + """ + if '@' in hcs: + p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + else: + p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +def pretty_box (s, rows=24, cols=80): + """This puts an ASCII text box around the given string. + """ + top_bot = '+' + '-'*cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot + +def client_cgi (): + """This handles the request if this script was called as a cgi. + """ + print "Content-type: text/html;charset=utf-8\r\n" + sys.stderr = sys.stdout + ajax_mode = False + TITLE="Shell" + SHELL_OUTPUT="" + SID="NOT" + try: + form = cgi.FieldStorage() + if not form.has_key('sid'): + SID=random_sid() + else: + SID=form['sid'].value + if form.has_key('ajax'): + ajax_mode = True + ajax_cmd = form['ajax'].value + if ajax_cmd == 'send': + command = ':xsend' + arg = form['arg'].value.encode('hex') + result = client (command + ' ' + arg, '/tmp/mysock') + print result + elif ajax_cmd == 'refresh': + command = ':refresh' + result = client (command, '/tmp/mysock') + print result + elif ajax_cmd == 'cursor': + command = ':cursor' + result = client (command, '/tmp/mysock') + print result + elif form.has_key('command'): + command = form["command"].value + SHELL_OUTPUT = client (command, '/tmp/mysock') + print CGISH_HTML % locals() + else: + print CGISH_HTML % locals() + except: + tb_dump = traceback.format_exc() + if ajax_mode: + print str(tb_dump) + else: + SHELL_OUTPUT=str(tb_dump) + print CGISH_HTML % locals() + +def server_cli(): + """This is the command line interface to starting the server. + This handles things if the script was not called as a CGI + (if you run it from the command line). + """ + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch']) + except Exception, e: + print str(e) + exit_with_usage() + + command_line_options = dict(optlist) + options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + hostname = "127.0.0.1" + #port = 1664 + username = os.getenv('USER') + password = "" + daemon_mode = False + if '-d' in options: + daemon_mode = True + if '--watch' in options: + watch_mode = True + else: + watch_mode = False + if '--hostname' in options: + hostname = options['--hostname'] + if '--port' in options: + port = int(options['--port']) + if '--username' in options: + username = options['--username'] + if '--password' in options: + password = options['--password'] + else: + password = getpass.getpass('password: ') + + if daemon_mode: + print "daemonizing server" + daemonize() + #daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log') + + server (hostname, username, password) + +def main (): + if os.getenv('REQUEST_METHOD') is None: + server_cli() + else: + client_cgi() + +# It's mostly HTML and Javascript from here on out. CGISH_HTML="""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> @@ -112,7 +442,6 @@ function query_cursor() { loadurl('bd_client.cgi?ajax=cursor'); } - function type_key (chars) { var ch = '?'; @@ -243,7 +572,7 @@ function KeyCheck(e) <body onload="init()"> <form id="form" name="form" action="/cgi-bin/bd_client.cgi" method="POST"> <input name="sid" value="%(SID)s" type="hidden"> -<textarea name="screen_text" cols="80" rows="24">%(SHELL_OUTPUT)s</textarea> +<textarea name="screen_text" cols="81" rows="25">%(SHELL_OUTPUT)s</textarea> <hr noshade="1"> <input name="command" id="command" type="text" size="80"><br> <p align="left"> @@ -251,7 +580,6 @@ function KeyCheck(e) <tr> <td width="86%%" align="center"> <input name="submit" type="submit" value="Enter"> - <input name="esc" type="submit" value="ESC"> <input name="refresh" type="button" value="REFRESH" onclick="refresh_screen()"> <input name="refresh" type="button" value="CURSOR" onclick="query_cursor()"> <br> @@ -326,66 +654,11 @@ function KeyCheck(e) </html> """ -def random_sid (): - a=random.randint(0,65535) - b=random.randint(0,65535) - return 'pxssh%04x%04x' % (a,b) - -def bd_client (command, host='localhost', port=-1): - """This sends a request to the server and returns the response. - If port is less than 0 then host is assumed - to be the filename of a Unix domain socket. - """ - if port < 0: - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.connect(host) - else: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((host, port)) - s.send(command) - data = s.recv (2500) - s.close() - return data - -print "Content-type: text/html;charset=utf-8\r\n" -sys.stderr = sys.stdout -ajax_mode = False -TITLE="Shell" -SHELL_OUTPUT="" -SID="NOT" -try: - form = cgi.FieldStorage() - if not form.has_key('sid'): - SID=random_sid() - else: - SID=form['sid'].value - if form.has_key('ajax'): - ajax_mode = True - ajax_cmd = form['ajax'].value - if ajax_cmd == 'send': - command = ':xsend' - arg = form['arg'].value.encode('hex') - result = bd_client (command + ' ' + arg, "/tmp/mysock") - print result - elif ajax_cmd == 'refresh': - command = ':refresh' - result = bd_client (command, "/tmp/mysock") - print result - elif ajax_cmd == 'cursor': - command = ':cursor' - result = bd_client (command, "/tmp/mysock") - print result - elif form.has_key('command'): - command = form["command"].value - SHELL_OUTPUT = bd_client (command, "/tmp/mysock") - print CGISH_HTML % locals() - else: - print CGISH_HTML % locals() -except: - tb_dump = traceback.format_exc() - if ajax_mode: +if __name__ == "__main__": + try: + main() + except Exception, e: + print str(e) + tb_dump = traceback.format_exc() print str(tb_dump) - else: - SHELL_OUTPUT=str(tb_dump) - print CGISH_HTML % locals() |