| #!/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]+'<img src="http://www.noah.org/cursor.gif">'+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<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 |
| |
| 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) |
| |