| #!/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 |
| import pexpect |
| import ANSI |
| import time |
| import sys |
| import os |
| import getopt |
| import getpass |
| import traceback |
| import threading |
| import 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 as 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 as 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 as 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', 0o777) |
| 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 as e: |
| print str(e) |
| tb_dump = traceback.format_exc() |
| print str(tb_dump) |