317 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			317 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/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)
 | 
						|
 |