X-Git-Url: https://git.ucc.asn.au/?p=progcomp10.git;a=blobdiff_plain;f=src%2Flink%2Fpexpect%2Fexamples%2Fbd_serv.py;fp=src%2Flink%2Fpexpect%2Fexamples%2Fbd_serv.py;h=b7def9e140220ae9b03c3cdb90447fca30f87bf2;hp=0000000000000000000000000000000000000000;hb=cd42b53c196672694396e695ae17fd94ba7d58b4;hpb=e9a8105a8f22404f4ac550d79954eaa6b7f5d8ff diff --git a/src/link/pexpect/examples/bd_serv.py b/src/link/pexpect/examples/bd_serv.py new file mode 100755 index 0000000..b7def9e --- /dev/null +++ b/src/link/pexpect/examples/bd_serv.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python + +"""Back door shell server + +This exposes an 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 +""" + +# Having the password on the command line is not a good idea, but +# then this entire project is probably not the most security concious thing +# I've ever built. This should be considered an experimental tool -- at best. +import pxssh, pexpect, ANSI +import time, sys, os, getopt, getpass, traceback, threading, socket + +def exit_with_usage(exit_code=1): + + print globals()['__doc__'] + os._exit(exit_code) + +class roller (threading.Thread): + + """This runs a function in a loop 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(). """ + + #child.logfile_read = screen + try: + s = child.read_nonblocking(4000, 0.1) + screen.write(s) + except: + pass + #while True: + # #child.prompt (timeout=refresh_timeout) + # try: + # #child.read_nonblocking(1,timeout=refresh_timeout) + # child.read_nonblocking(4000, 0.1) + # 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 add_cursor_blink (response, row, col): + + i = (row-1) * 80 + col + return response[:i]+''+response[i:] + +def main (): + + 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'] + print "Login for %s@%s:%s" % (username, hostname, port) + 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') + + 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) + print 'created shell. command line prompt is', child.PROMPT + #child.sendline ('stty -echo') + #child.setecho(False) + virtual_screen.write (child.before) + virtual_screen.write (child.after) + + if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock") + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + localhost = '127.0.0.1' + s.bind('/tmp/mysock') + os.chmod('/tmp/mysock',0777) + print 'Listen' + s.listen(1) + print 'Accept' + #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() + print "screen poll updater started in background thread" + sys.stdout.flush() + + try: + while True: + conn, addr = s.accept() + 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) + sent = conn.send('\n'.join(response)) + if watch_mode: print '\n'.join(response) + if sent < len (response): + print "Sent is too short. Some data was cut off." + conn.close() + finally: + r.cancel() + print "cleaning up socket" + s.close() + if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock") + print "done!" + +def pretty_box (rows, cols, s): + + """This puts an ASCII text box around the given string, s. + """ + + top_bot = '+' + '-'*cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot + +def error_response (msg): + + response = [] + response.append ("""All commands start with : +:{REQUEST} {ARGUMENT} +{REQUEST} may be one of the following: + :sendline: Run the ARGUMENT followed by a line feed. + :send : send the characters in the ARGUMENT without a line feed. + :refresh : Use to catch up the screen with the shell if state gets out of sync. +Example: + :sendline ls -l +You may also leave off :command and it will be assumed. +Example: + ls -l +is equivalent to: + :sendline ls -l +""") + response.append (msg) + return '\n'.join(response) + +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[^@:]*)(:?)(?P.*)(?!\\)@(?P[^:]*):?(?P[0-9]*)') + else: + p = re.compile (r'(?P)(?P)(?P[^:]*):?(?P[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +if __name__ == "__main__": + + try: + start_time = time.time() + print time.asctime() + main() + print time.asctime() + print "TOTAL TIME IN MINUTES:", + print (time.time() - start_time) / 60.0 + except Exception, e: + print str(e) + tb_dump = traceback.format_exc() + print str(tb_dump) +