diff --git a/utils/sqlwebsockify.config.template b/utils/sqlwebsockify.config.template new file mode 100644 index 00000000..e10a32c2 --- /dev/null +++ b/utils/sqlwebsockify.config.template @@ -0,0 +1,11 @@ +#MySQL Configuration File +# +# database connection settings +db=YOURDB +user=YOURUSER +passwd=YOURPASS +# query to validate the token +# _TOKEN_ will be replaced with the token +# should return 0 rows if the token does not match, +# and the first field should be the IP:Port they are authenticated to connect to +match_query=select as_data, substr(as_data, 1, locate(':',as_data) - 1) as ip, substr(as_data, locate(':',as_data)+1) as port from appsession where concat(as_sid,'_',as_location)='_TOKEN_' diff --git a/utils/websockify b/utils/websockify index 1154d925..a3ac0ba4 100755 --- a/utils/websockify +++ b/utils/websockify @@ -1,5 +1,5 @@ #!/usr/bin/env python - + ''' A WebSocket to TCP socket proxy with support for "wss://" encryption. Copyright 2011 Joel Martin @@ -11,6 +11,8 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates ''' +import MySQLdb +from MySQLdb.constants import FIELD_TYPE import signal, socket, optparse, time, os, sys, subprocess from select import select import websocket @@ -50,9 +52,11 @@ Traffic Legend: self.wrap_mode = kwargs.pop('wrap_mode', None) self.unix_target = kwargs.pop('unix_target', None) self.ssl_target = kwargs.pop('ssl_target', None) + self.target_sql_cfg = kwargs.pop('target_sql_cfg', None) self.target_cfg = kwargs.pop('target_cfg', None) # Last 3 timestamps command was run self.wrap_times = [0, 0, 0] + #logging.info("NEW_SCORE: %s", score) if self.wrap_cmd: rebinder_path = ['./', os.path.dirname(sys.argv[0])] @@ -83,6 +87,9 @@ Traffic Legend: if self.target_cfg: self.target_cfg = os.path.abspath(self.target_cfg) + if self.target_sql_cfg: + self.target_sql_cfg = os.path.abspath(self.target_sql_cfg) + websocket.WebSocketServer.__init__(self, *args, **kwargs) def run_wrap_cmd(self): @@ -109,6 +116,9 @@ Traffic Legend: if self.target_cfg: msg = " - proxying from %s:%s to targets in %s" % ( self.listen_host, self.listen_port, self.target_cfg) + elif self.target_sql_cfg: + msg = " - proxying from %s:%s to targets via database lookups defined in %s" % ( + self.listen_host, self.listen_port, self.target_sql_cfg) else: msg = " - proxying from %s:%s to %s" % ( self.listen_host, self.listen_port, dst_string) @@ -166,6 +176,9 @@ Traffic Legend: if self.target_cfg: (self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path) + if self.target_sql_cfg: + (self.target_host, self.target_port) = self.get_sql_target(self.target_sql_cfg, self.path) + # Connect to the target if self.wrap_cmd: msg = "connecting to command: '%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port) @@ -235,6 +248,55 @@ Traffic Legend: else: raise self.EClose("Token '%s' not found" % token) + def get_sql_target(self, target_sql_cfg, path): + """ + Parses the path, extracts a token, and performs a database + query to validate the token. database information is read + from the configuration file. Sets + target_host and target_port if successful + """ + sqlconfig = {} + my_conv = { FIELD_TYPE.LONG: int } + # The files in target contain the lines + # in the form of setting=value + # + # db=YOURDB + # user=YOURUSER + # passwd=YOURPASS + # match_query=YOUR_QUERY + # + # _TOKEN_ in the match_query will be replaced with the token + # should return 0 rows if the token does not match, + # and the first field should be the IP:Port they are authenticated to connect to + + # Extract the token parameter from url + args = parse_qs(urlparse(path)[4]) # 4 is the query from url + + if not args.has_key('token') or not len(args['token']): + raise self.EClose("Token not present") + + for line in [l.strip() for l in file(target_sql_cfg).readlines()]: + if line and not line.startswith('#'): + line = line.split('=',1) + sqlconfig[line[0]] = line[1] + + #print sqlconfig + token = args['token'][0].rstrip('\n') + targets = {} + db=MySQLdb.connect(passwd=sqlconfig['passwd'],db=sqlconfig['db'],user=sqlconfig['user']) + c=db.cursor() + c.execute(sqlconfig['match_query'].replace('_TOKEN_', db.escape_string(token))) + results = c.fetchone() + targets[token] = results[0] + c.close() + + self.vmsg("SQL Query Result: %s" % repr(targets)) + + if targets.has_key(token): + return targets[token].split(':') + else: + raise self.EClose("Token '%s' not found" % token) + def do_proxy(self, target): """ Proxy client WebSocket to normal target socket. @@ -345,10 +407,16 @@ def websockify_init(): help="Configuration file containing valid targets " "in the form 'token: host:port' or, alternatively, a " "directory containing configuration files of this form") + parser.add_option("--sql-target-config", metavar="FILE", + dest="target_sql_cfg", + help="Configuration file containing valid connection " + "information and a squery that will need to validate " + "the token. The first field in the response from " + "the database need in the form of IP:Port") (opts, args) = parser.parse_args() # Sanity checks - if len(args) < 2 and not (opts.target_cfg or opts.unix_target): + if len(args) < 2 and not (opts.target_cfg or opts.unix_target or opts.target_sql_cfg): parser.error("Too few arguments") if sys.argv.count('--'): opts.wrap_cmd = args[1:] @@ -373,7 +441,7 @@ def websockify_init(): try: opts.listen_port = int(opts.listen_port) except: parser.error("Error parsing listen port") - if opts.wrap_cmd or opts.unix_target or opts.target_cfg: + if opts.wrap_cmd or opts.unix_target or opts.target_cfg or opts.target_sql_cfg: opts.target_host = None opts.target_port = None else: