|
@@ -11,7 +11,7 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
|
|
|
|
|
|
'''
|
|
'''
|
|
|
|
|
|
-import socket, optparse, time, os
|
|
|
|
|
|
+import socket, optparse, time, os, sys, subprocess
|
|
from select import select
|
|
from select import select
|
|
from websocket import WebSocketServer
|
|
from websocket import WebSocketServer
|
|
|
|
|
|
@@ -38,12 +38,102 @@ Traffic Legend:
|
|
"""
|
|
"""
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
def __init__(self, *args, **kwargs):
|
|
- # Save off the target host:port
|
|
|
|
- self.target_host = kwargs.pop('target_host')
|
|
|
|
- self.target_port = kwargs.pop('target_port')
|
|
|
|
|
|
+ # Save off proxy specific options
|
|
|
|
+ self.target_host = kwargs.pop('target_host')
|
|
|
|
+ self.target_port = kwargs.pop('target_port')
|
|
|
|
+ self.wrap_cmd = kwargs.pop('wrap_cmd')
|
|
|
|
+ self.wrap_mode = kwargs.pop('wrap_mode')
|
|
|
|
+ # Last 3 timestamps command was run
|
|
|
|
+ self.wrap_times = [0, 0, 0]
|
|
|
|
+
|
|
|
|
+ if self.wrap_cmd:
|
|
|
|
+ rebinder_path = ['./', os.path.dirname(sys.argv[0])]
|
|
|
|
+ self.rebinder = None
|
|
|
|
+
|
|
|
|
+ for rdir in rebinder_path:
|
|
|
|
+ rpath = os.path.join(rdir, "rebind.so")
|
|
|
|
+ if os.path.exists(rpath):
|
|
|
|
+ self.rebinder = rpath
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+ if not self.rebinder:
|
|
|
|
+ raise Exception("rebind.so not found, perhaps you need to run make")
|
|
|
|
+
|
|
|
|
+ self.target_host = "127.0.0.1" # Loopback
|
|
|
|
+ # Find a free high port
|
|
|
|
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
+ sock.bind(('', 0))
|
|
|
|
+ self.target_port = sock.getsockname()[1]
|
|
|
|
+ sock.close()
|
|
|
|
+
|
|
|
|
+ os.environ.update({
|
|
|
|
+ "LD_PRELOAD": self.rebinder,
|
|
|
|
+ "REBIND_OLD_PORT": str(kwargs['listen_port']),
|
|
|
|
+ "REBIND_NEW_PORT": str(self.target_port)})
|
|
|
|
+
|
|
WebSocketServer.__init__(self, *args, **kwargs)
|
|
WebSocketServer.__init__(self, *args, **kwargs)
|
|
|
|
|
|
- def handler(self, client):
|
|
|
|
|
|
+ def run_wrap_cmd(self):
|
|
|
|
+ print "Starting '%s'" % " ".join(self.wrap_cmd)
|
|
|
|
+ self.wrap_times.append(time.time())
|
|
|
|
+ self.wrap_times.pop(0)
|
|
|
|
+ self.cmd = subprocess.Popen(
|
|
|
|
+ self.wrap_cmd, env=os.environ)
|
|
|
|
+ self.spawn_message = True
|
|
|
|
+
|
|
|
|
+ def started(self):
|
|
|
|
+ """
|
|
|
|
+ Called after Websockets server startup (i.e. after daemonize)
|
|
|
|
+ """
|
|
|
|
+ # Need to call wrapped command after daemonization so we can
|
|
|
|
+ # know when the wrapped command exits
|
|
|
|
+ if self.wrap_cmd:
|
|
|
|
+ print " - proxying from %s:%s to '%s' (port %s)\n" % (
|
|
|
|
+ self.listen_host, self.listen_port,
|
|
|
|
+ " ".join(self.wrap_cmd), self.target_port)
|
|
|
|
+ self.run_wrap_cmd()
|
|
|
|
+ else:
|
|
|
|
+ print " - proxying from %s:%s to %s:%s\n" % (
|
|
|
|
+ self.listen_host, self.listen_port,
|
|
|
|
+ self.target_host, self.target_port)
|
|
|
|
+
|
|
|
|
+ def poll(self):
|
|
|
|
+ # If we are wrapping a command, check it's status
|
|
|
|
+
|
|
|
|
+ if self.wrap_cmd and self.cmd:
|
|
|
|
+ ret = self.cmd.poll()
|
|
|
|
+ if ret != None:
|
|
|
|
+ self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
|
|
|
|
+ self.cmd = None
|
|
|
|
+
|
|
|
|
+ if self.wrap_cmd and self.cmd == None:
|
|
|
|
+ # Response to wrapped command being gone
|
|
|
|
+ if self.wrap_mode == "ignore":
|
|
|
|
+ pass
|
|
|
|
+ elif self.wrap_mode == "exit":
|
|
|
|
+ sys.exit(ret)
|
|
|
|
+ elif self.wrap_mode == "respawn":
|
|
|
|
+ now = time.time()
|
|
|
|
+ avg = sum(self.wrap_times)/len(self.wrap_times)
|
|
|
|
+ if (now - avg) < 10:
|
|
|
|
+ # 3 times in the last 10 seconds
|
|
|
|
+ if self.spawn_message:
|
|
|
|
+ print "Command respawning too fast"
|
|
|
|
+ self.spawn_message = False
|
|
|
|
+ else:
|
|
|
|
+ self.run_wrap_cmd()
|
|
|
|
+
|
|
|
|
+ #
|
|
|
|
+ # Routines above this point are run in the master listener
|
|
|
|
+ # process.
|
|
|
|
+ #
|
|
|
|
+
|
|
|
|
+ #
|
|
|
|
+ # Routines below this point are connection handler routines and
|
|
|
|
+ # will be run in a separate forked process for each connection.
|
|
|
|
+ #
|
|
|
|
+
|
|
|
|
+ def new_client(self, client):
|
|
"""
|
|
"""
|
|
Called after a new WebSocket connection has been established.
|
|
Called after a new WebSocket connection has been established.
|
|
"""
|
|
"""
|
|
@@ -155,16 +245,18 @@ Traffic Legend:
|
|
cpartial = cpartial + buf
|
|
cpartial = cpartial + buf
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if __name__ == '__main__':
|
|
- usage = "%prog [--record FILE]"
|
|
|
|
|
|
+ usage = "\n %prog [options]"
|
|
usage += " [source_addr:]source_port target_addr:target_port"
|
|
usage += " [source_addr:]source_port target_addr:target_port"
|
|
|
|
+ usage += "\n %prog [options]"
|
|
|
|
+ usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
|
|
parser = optparse.OptionParser(usage=usage)
|
|
parser = optparse.OptionParser(usage=usage)
|
|
parser.add_option("--verbose", "-v", action="store_true",
|
|
parser.add_option("--verbose", "-v", action="store_true",
|
|
help="verbose messages and per frame traffic")
|
|
help="verbose messages and per frame traffic")
|
|
parser.add_option("--record",
|
|
parser.add_option("--record",
|
|
help="record sessions to FILE.[session_number]", metavar="FILE")
|
|
help="record sessions to FILE.[session_number]", metavar="FILE")
|
|
- parser.add_option("--foreground", "-f",
|
|
|
|
- dest="daemon", default=True, action="store_false",
|
|
|
|
- help="stay in foreground, do not daemonize")
|
|
|
|
|
|
+ parser.add_option("--daemon", "-D",
|
|
|
|
+ dest="daemon", action="store_true",
|
|
|
|
+ help="become a daemon (background process)")
|
|
parser.add_option("--cert", default="self.pem",
|
|
parser.add_option("--cert", default="self.pem",
|
|
help="SSL certificate file")
|
|
help="SSL certificate file")
|
|
parser.add_option("--key", default=None,
|
|
parser.add_option("--key", default=None,
|
|
@@ -173,30 +265,43 @@ if __name__ == '__main__':
|
|
help="disallow non-encrypted connections")
|
|
help="disallow non-encrypted connections")
|
|
parser.add_option("--web", default=None, metavar="DIR",
|
|
parser.add_option("--web", default=None, metavar="DIR",
|
|
help="run webserver on same port. Serve files from DIR.")
|
|
help="run webserver on same port. Serve files from DIR.")
|
|
|
|
+ parser.add_option("--wrap-mode", default="exit", metavar="MODE",
|
|
|
|
+ choices=["exit", "ignore", "respawn"],
|
|
|
|
+ help="action to take when the wrapped program exits "
|
|
|
|
+ "or daemonizes: exit (default), ignore, respawn")
|
|
(opts, args) = parser.parse_args()
|
|
(opts, args) = parser.parse_args()
|
|
|
|
|
|
# Sanity checks
|
|
# Sanity checks
|
|
- if len(args) > 2: parser.error("Too many arguments")
|
|
|
|
- if len(args) < 2: parser.error("Too few arguments")
|
|
|
|
|
|
+ if len(args) < 2:
|
|
|
|
+ parser.error("Too few arguments")
|
|
|
|
+ if sys.argv.count('--'):
|
|
|
|
+ opts.wrap_cmd = args[1:]
|
|
|
|
+ else:
|
|
|
|
+ opts.wrap_cmd = None
|
|
|
|
+ if len(args) > 2:
|
|
|
|
+ parser.error("Too many arguments")
|
|
|
|
|
|
if opts.ssl_only and not os.path.exists(opts.cert):
|
|
if opts.ssl_only and not os.path.exists(opts.cert):
|
|
parser.error("SSL only and %s not found" % opts.cert)
|
|
parser.error("SSL only and %s not found" % opts.cert)
|
|
- elif not os.path.exists(opts.cert):
|
|
|
|
- print "Warning: %s not found" % opts.cert
|
|
|
|
|
|
|
|
# Parse host:port and convert ports to numbers
|
|
# Parse host:port and convert ports to numbers
|
|
if args[0].count(':') > 0:
|
|
if args[0].count(':') > 0:
|
|
opts.listen_host, opts.listen_port = args[0].split(':')
|
|
opts.listen_host, opts.listen_port = args[0].split(':')
|
|
else:
|
|
else:
|
|
opts.listen_host, opts.listen_port = '', args[0]
|
|
opts.listen_host, opts.listen_port = '', args[0]
|
|
- if args[1].count(':') > 0:
|
|
|
|
- opts.target_host, opts.target_port = args[1].split(':')
|
|
|
|
- else:
|
|
|
|
- parser.error("Error parsing target")
|
|
|
|
try: opts.listen_port = int(opts.listen_port)
|
|
try: opts.listen_port = int(opts.listen_port)
|
|
except: parser.error("Error parsing listen port")
|
|
except: parser.error("Error parsing listen port")
|
|
- try: opts.target_port = int(opts.target_port)
|
|
|
|
- except: parser.error("Error parsing target port")
|
|
|
|
|
|
+
|
|
|
|
+ if opts.wrap_cmd:
|
|
|
|
+ opts.target_host = None
|
|
|
|
+ opts.target_port = None
|
|
|
|
+ else:
|
|
|
|
+ if args[1].count(':') > 0:
|
|
|
|
+ opts.target_host, opts.target_port = args[1].split(':')
|
|
|
|
+ else:
|
|
|
|
+ parser.error("Error parsing target")
|
|
|
|
+ try: opts.target_port = int(opts.target_port)
|
|
|
|
+ except: parser.error("Error parsing target port")
|
|
|
|
|
|
# Create and start the WebSockets proxy
|
|
# Create and start the WebSockets proxy
|
|
server = WebSocketProxy(**opts.__dict__)
|
|
server = WebSocketProxy(**opts.__dict__)
|