commit 0612c1405d8e0d17b9cc82c530fa5a9557b49d6d Author: Quinn Jarrell qjarrell@gosynapsify.com Date: Fri Jul 4 23:49:50 2014 -0400
The files have been reorganized into a python and go project. --- .gitignore | 3 +- Makefile | 12 - fog-client/fog-client | 538 +++++++++++++++++++++++++++++++++++++++++++++ fog-client/fog/socks.py | 59 +++++ fog-client/fogrc | 17 ++ fog-client/setup.py | 23 ++ fog-client/torrc | 5 + fog-server/Makefile | 12 + fog-server/fog-server.go | 548 ++++++++++++++++++++++++++++++++++++++++++++++ fog-server/pt_test.go | 39 ++++ fog-server/stack.go | 57 +++++ fog-server/stack_test.go | 120 ++++++++++ fog-server/torrc | 5 + fog/socks.py | 59 ----- fogrc | 17 -- obfs-flash-client | 538 --------------------------------------------- obfs-flash-server.go | 548 ---------------------------------------------- pt_test.go | 39 ---- setup.py | 23 -- stack.go | 57 ----- stack_test.go | 120 ---------- torrc | 5 - torrc-server | 5 - 23 files changed, 1425 insertions(+), 1424 deletions(-)
diff --git a/.gitignore b/.gitignore index 452a1a4..8184caa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/mkii/obfs-flash-server +/fog-server/fog-server +*.pyc diff --git a/Makefile b/Makefile deleted file mode 100644 index b8a4d24..0000000 --- a/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -GOBUILDFLAGS = - -obfs-flash-server: obfs-flash-server.go stack.go - go build $(GOBUILDFLAGS) -o "$@" $^ - -test: - go test -v - -clean: - rm -f obfs-flash-server - -.PHONY: test clean diff --git a/fog-client/fog-client b/fog-client/fog-client new file mode 100755 index 0000000..9b2e7ef --- /dev/null +++ b/fog-client/fog-client @@ -0,0 +1,538 @@ +#!/usr/bin/python + +import argparse +import os +import sys + +from collections import namedtuple +from functools import partial + +# TODO(infinity0): this is temporary workaround until we do #10047 +if sys.platform == 'win32': + os.environ["KILL_CHILDREN_ON_DEATH"] = "1" +from pyptlib.util import parse_addr_spec +from pyptlib.util.subproc import auto_killall, Popen +from pyptlib.client import ClientTransportPlugin + +from subprocess import PIPE + +from twisted.internet.defer import Deferred, DeferredList +from twisted.internet.stdio import StandardIO +from twisted.internet.protocol import Factory, connectionDone +from twisted.internet.endpoints import TCP4ClientEndpoint +from twisted.protocols.basic import LineReceiver +from twisted.protocols.portforward import ProxyServer as _ProxyServer +from twisted.python import log +from txsocksx.client import SOCKS4ClientEndpoint, SOCKS5ClientEndpoint +from fog.socks import SOCKSv4InterceptorFactory + +import shlex + +import logging + +DEFAULT_CONFIG_FILE_NAME = os.path.dirname(os.path.realpath(__file__)) + '/fogrc' + +logger = None +def pt_setup_logger(): + global logger + logger = logging.getLogger('fog-logger') + logger.setLevel(logging.WARNING) + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + logger.addHandler(ch) + +def pt_child_env(managed_ver, env=os.environ): + """ + Prepare the environment for a child PT process, by clearing all TOR_PT_* + envvars except TOR_PT_STATE_LOCATION and TOR_PT_MANAGED_TRANSPORT_VER. + """ + exempt = ['TOR_PT_STATE_LOCATION'] + cur_env = [(k, v) for k, v in env.iteritems() + if not k.startswith('TOR_PT_') or k in exempt] + cur_env.append(('TOR_PT_MANAGED_TRANSPORT_VER', ','.join(managed_ver))) + return cur_env + +class MethodSpec(namedtuple('MethodSpec', 'name protocol addrport args opts')): + @classmethod + def fromLine(cls, line): + args = line.rstrip('\n').split(' ') + name = args[0] + protocol = args[1] + addrport = parse_addr_spec(args[2]) + args = args[3][-5:].split(',') if len(args) > 3 and args[3].startswith("ARGS=") else [] + opts = args[4][-9:].split(',') if len(args) > 4 and args[4].startswith("OPT-ARGS=") else [] + return MethodSpec(name, protocol, addrport, args, opts) + +def branch(parent): + """ + Returns a new Deferred that does not advance the callback-chain of the parent. + + See http://xph.us/2009/12/10/asynchronous-programming-in-python.html for motivation. + """ + d = Deferred() + parent.addCallback(lambda v: (v, d.callback(v))[0]) + parent.addErrback(lambda f: (f, d.errback(f))[1]) + return d + +class ManagedTransportProtocolV1(LineReceiver): + """ + A Twisted IProtocol to read PT output. + + See pt-spec.txt and others for details of the protocol. + """ + # TODO(infinity0): eventually this could be padded out and moved to pyptlib + + delimiter = os.linesep + protocol_version = "1" + + def __init__(self): + self.cmethods = {} + self._dCMethodsDone = Deferred() + self._dPluginError = Deferred() + # dPluginError triggers errors on all sub-events, not the other way round + # so fatal sub-events should call _abort rather than errback on their Deferreds + self._dPluginError.addErrback(lambda f: (f, self._fireCMethodsDone().errback(f))[0]) + # TODO(infinity0): call _abort if we don't recv CMETHODS DONE within n sec + + def whenCMethodsDone(self): + """ + Return a new Deferred that calls-back when CMETHODS DONE is received. + """ + return branch(self._dCMethodsDone) + + def whenPluginError(self): + """ + Return a new Deferred that errors-back when the remote plugin fails. + + Note: the success chain (callback) is never fired. + """ + return branch(self._dPluginError) + + def lineReceived(self, line): + if not line: return + + (kw, args) = line.split(' ', 1) + if kw == "VERSION": + version = args.strip() + if version != self.protocol_version: + self._abort(ValueError("child used unsupported managed transport version: %s" % version)) + elif kw == "CMETHOD": + cmethod = MethodSpec.fromLine(args) + self.cmethods[cmethod.name] = cmethod + elif kw == "CMETHODS" and args == "DONE": + self._fireCMethodsDone().callback(self.cmethods) + else: + pass # ignore unrecognised line + + def connectionLost(self, reason=connectionDone): + self._firePluginError().errback(reason) + + def _abort(self, exc): + self._firePluginError().errback(exc) + self.transport.loseConnection() + + def _fireCMethodsDone(self): + """Return dCMethodsDone or a dummy if it was already called.""" + if self._dCMethodsDone: + d = self._dCMethodsDone + self._dCMethodsDone = None + return d + return Deferred().addErrback(lambda *args: None) + + def _firePluginError(self): + """Return dPluginError or a dummy if it was already called.""" + if self._dPluginError: + d = self._dPluginError + self._dPluginError = None + return d + return Deferred().addErrback(lambda *args: None) + +# TODO(infinity0): remove this class when twisted update their side +class ProxyServer(_ProxyServer): + + def connectionMade(self): + # code copied from super class, except instead of connecting + # to a TCP endpoint we abstract that out to a child method + self.transport.pauseProducing() + + client = self.clientProtocolFactory() + client.setServer(self) + + if self.reactor is None: + from twisted.internet import reactor + self.reactor = reactor + + self.connectProxyClient(client) + + def connectProxyClient(self, client): + raise NotImplementedError() + +class OneUseSOCKSWrapper(ProxyServer): + + def connectProxyClient(self, client): + local_host, local_port = self.factory.method_spec.addrport + TCPPoint = TCP4ClientEndpoint( + self.reactor, + local_host, + local_port) + # Next PT may need either SOCKS4 or SOCKS5 so check its protocol and get the required class + socks_endpoint_class = self.getSocksEndpointClass() + SOCKSPoint = socks_endpoint_class( + self.factory.remote_host, + self.factory.remote_port, + TCPPoint) + # Store port for debugging messages before stopListening is called. + # listen_port will not have a port after stopListening is called. + stored_port = self.factory.listen_port.getHost().port + d_port_closed = self.factory.listen_port.stopListening() + d_port_closed.addCallback( + lambda x: logger.debug("Closed factory listener %s on port %s" % (self.factory, stored_port))) + d_port_closed.addErrback( + lambda x: logger.warn("Failed to close factory listener %s listening on port %s" % (self.factory, stored_port))) + d = SOCKSPoint.connect(client) + d.chainDeferred(self.factory.d_connected) + @d.addErrback + def _gotError(error): + log.err(error, "error connecting to SOCKS server") + + def getSocksEndpointClass(self): + """ + Checks self.factory.method_spec.protocol and returns the appropriate socks endpoint class. + """ + socks_endpoint_class = None + if self.factory.method_spec.protocol == 'socks4': + socks_endpoint_class = SOCKS4ClientEndpoint + elif self.factory.method_spec.protocol == 'socks5': + socks_endpoint_class = SOCKS5ClientEndpoint + else: + raise ValueError("Pluggable transport requires unknown protocol %s. Supported protocols are %s" % + (self.factory.method_spec.protocol, ('socks4', 'socks5'))) + return socks_endpoint_class + +class OneUseSOCKSFactory(Factory): + protocol = OneUseSOCKSWrapper + def __init__(self, method_spec, remote_host, remote_port): + self._connected_once = False + self.method_spec = method_spec + self.remote_host = remote_host + self.remote_port = remote_port + self.d_connected = Deferred() + self.listen_port = None + + def __str__(self): + return "OneUseSOCKSFactory connecting %s to %s:%s" % (self.method_spec, self.remote_host, self.remote_port) + + def __repr__(self): + return "OneUseSOCKSFactory(%s, %s, %s)" % (self.method_spec, self.remote_host, self.remote_port) + + def setListenPort(self, listen_port): + """ + Sets the listen_port object. + :param function listen_port: The function returned from a ListenTCP call. Used to shutdown the port when a connection is made. + """ + self.listen_port = listen_port + + def whenConnected(self): + """ + Returns a new Deferred that triggers when a connection is successfully made. + """ + return branch(self.d_connected) + + def buildProtocol(self, addr): + """ + Only allows one protocol to be created. After that it always returns None + :param twisted.internet.interfaces.IAddress addr: an object implementing L{twisted.internet.interfaces.IAddress} + """ + if self._connected_once: + return None + else: + self._connected_once = True + return Factory.buildProtocol(self, addr) + +if sys.platform == "win32": + # TODO(infinity0): push this upstream to Twisted + from twisted.internet import _pollingfile + import msvcrt + + _StandardIO = StandardIO + class StandardIO(_StandardIO): + + def __init__(self, proto, stdin=None, stdout=None, reactor=None): + """ + Start talking to standard IO with the given protocol. + + Also, put it stdin/stdout/stderr into binary mode. + """ + if reactor is None: + import twisted.internet.reactor + reactor = twisted.internet.reactor + + _pollingfile._PollingTimer.__init__(self, reactor) + self.proto = proto + + fdstdin = stdin or sys.stdin.fileno() + fdstdout = stdout or sys.stdout.fileno() + + for stdfd in (fdstdin, fdstdout): + msvcrt.setmode(stdfd, os.O_BINARY) + + hstdin = msvcrt.get_osfhandle(fdstdin) + self.stdin = _pollingfile._PollableReadPipe( + hstdin, self.dataReceived, self.readConnectionLost) + + hstdout = msvcrt.get_osfhandle(fdstdout) + self.stdout = _pollingfile._PollableWritePipe( + hstdout, self.writeConnectionLost) + + self._addPollableResource(self.stdin) + self._addPollableResource(self.stdout) + + self.proto.makeConnection(self) + +def pt_launch_child(reactor, client, methodnames, pt_method_name, cmdline): + """Launch a child PT and ensure it has the right transport methods.""" + cur_env = pt_child_env(ManagedTransportProtocolV1.protocol_version) + environment = dict(cur_env + { + "TOR_PT_CLIENT_TRANSPORTS": ",".join(methodnames), + }.items()) + sub_proc = Popen(cmdline, + stdout = PIPE, + env = environment, + ) + sub_protocol = ManagedTransportProtocolV1() + # we ought to pass reactor=reactor in below, but this breaks Twisted 12 + StandardIO(sub_protocol, stdin=sub_proc.stdout.fileno()) + methoddefers = [sub_protocol.whenCMethodsDone().addCallback( + partial(pt_require_child, client, name, pt_method_name)) + for name in methodnames] + return sub_proc, sub_protocol, methoddefers + +def pt_require_child(client, childmethod, pt_method_name, cmethods): + """Callback for checking a child PT has the right transport methods.""" + if childmethod not in cmethods: + client.reportMethodError(pt_method_name, "failed to start required child transport: %s" % childmethod) + raise ValueError() + return cmethods[childmethod] + +def pt_setup_socks_shim(pt_name, pt_chain, success_list, dest_address, dest_port, reactor, proxy_deferreds): + """ + Launches a socks proxy server to link two PTs together. + :param str pt_name: The name of the pt to send traffic to. + :param list pt_chain: The list of PTs in this chain. + :param list success_list: A list of tuples containing a launch status boolean, MethodSpec pairs. + Ex: [(True, MethodSpec(name='dummy', protocol='socks4', addrport=('127.0.0.1', 58982), args=[], opts=[])), + (True, MethodSpec(name='b64', protocol='socks4', addrport=('127.0.0.1', 58981), args=[], opts=[]))] + :param str dest_address: The address for the next PT to send its results to. + :param int dest_port: The port for the next PT to send to. + :param twisted.internet.interfaces.IReactor reactor: Reactor to attack the TCP server to. + + :param list proxy_deferreds: This list has each factorys' deferred appended to it. + + :returns twisted.internet.interfaces.IListeningPort: An IListeningPort used for shutting down a factory after a connection is made. + """ + methodspec = [r[1] for r in success_list if r[1].name == pt_name][0] # Returns the resulting methodspec. + factory = OneUseSOCKSFactory(methodspec, dest_address, dest_port) + # TODO switch to using endpoints instead of listenTCP + proxy_server = reactor.listenTCP(interface='127.0.0.1', port=0, factory=factory) + factory.setListenPort(proxy_server) + proxy_deferreds.append(factory.whenConnected()) + logger.debug("launched %s on port %s with dest %s:%s" % (pt_name, proxy_server.getHost().port, dest_address, dest_port)) + return proxy_server + +def pt_launch_chain(dest_address, dest_port, pt_chain, _chain_set_up, reactor, success_list): + """ + Launches a chain of pluggable transports by connecting each pt with SOCKS proxies. + :param str dest_address: The bridge address to connect to. + :param int dest_port: The bridge port to connect to. + :param list pt_chain: The list of pt names to launch. + :param function _chain_set_up: The function to call when the shims have been set up. + :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to. + :param list success_list: A list of tuples containing a launch status boolean, MethodSpec pairs. + Ex: [(True, MethodSpec(name='dummy', protocol='socks4', addrport=('127.0.0.1', 58982), args=[], opts=[])), + (True, MethodSpec(name='b64', protocol='socks4', addrport=('127.0.0.1', 58981), args=[], opts=[]))] + """ + proxy_deferreds = [] + last_pt_name = pt_chain[-1] + logger.debug("launching chain %s" % pt_chain) + # Initialize prev_server to the port picked by the last proxy server as that's the only one we know yet. + last_server = pt_setup_socks_shim(last_pt_name, pt_chain, success_list, dest_address, dest_port, + reactor, proxy_deferreds) + prev_server = last_server + for pt_name in reversed(pt_chain[:-1]): + # Loops through the pts linking them together through SOCKS proxies, skipping the last pt. + prev_server = pt_setup_socks_shim(pt_name, pt_chain, success_list, '127.0.0.1', prev_server.getHost().port, + reactor, proxy_deferreds) + def check_chain_all_connected(protocol_list): + """ + Checks all the shims launched to see if they successfully connected. + :param list protocol_list: A list of tuples containing status boolean, twisted.protocols.portforward.ProxyClient pairs. + Ex: [(True, <twisted.protocols.portforward.ProxyClient instance at 0x10b825518>), + (True, <twisted.protocols.portforward.ProxyClient instance at 0x10b829518>)] + """ + if all([result[0] for result in protocol_list]): + logger.debug("All PT shims connected correctly") + else: + # At this point the SOCKS protocol is in communication mode so no need to call makeReply(91) + # This assumes that the child pluggable transport will shut down the connection cleanly. + failed_protocols = [x[1] for x in protocol_list if x[0] == False] + logger.error("Shims %s failed to connect." % failed_protocols) + raise ValueError() + + finished = DeferredList(proxy_deferreds) + finished.addCallback(check_chain_all_connected) + _chain_set_up(prev_server.getHost().host, prev_server.getHost().port) + +def pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list): + """ + Launches a SOCKS interceptor. + :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to. + :param pyptlib.client.ClientTransportPlugin client: PT client API. + :param Config configuration: The configuration structure for this pair. + :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy" + :param list success_list: A list of tuples containing a launch status boolean, MethodSpec pairs. + Ex: [(True, MethodSpec(name='dummy', protocol='socks4', addrport=('127.0.0.1', 58982), args=[], opts=[])), + (True, MethodSpec(name='b64', protocol='socks4', addrport=('127.0.0.1', 58981), args=[], opts=[]))] + """ + logger.debug("launching interceptor") + pt_chain = configuration.alias_map[pt_method_name] + success = all(r[0] for r in success_list if r[1].name in pt_chain) + # failure was already reported by pt_require_child, just return + if not success: return + socks_interceptor = SOCKSv4InterceptorFactory(pt_method_name, + lambda dest_address, dest_port, pt_method_name, chain_finished: + pt_launch_chain(dest_address, dest_port, pt_chain, chain_finished, reactor, success_list)) + # TODO switch to using endpoints instead of listenTCP + interceptor = reactor.listenTCP(interface='127.0.0.1', port=0, factory=socks_interceptor) + interceptor_port = interceptor.getHost().port + client.reportMethodSuccess(pt_method_name, "socks4", ("127.0.0.1", interceptor_port)) + client.reportMethodsEnd() + +def pt_setup_transports(reactor, client, configuration, pt_method_name): + """ + Launches the PT processes. + :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to. + :param pyptlib.client.ClientTransportPlugin client: PT client API. + :param Config configuration: The configuration structure for this pair. + :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy" + """ + logger.debug("Setting up transports %s" % pt_method_name) + if pt_method_name in configuration.alias_map: + pt_chain = configuration.alias_map[pt_method_name] + else: + logger.error('Pluggable Transport Combination %s not found in configuration alias map.' % pt_method_name) + raise KeyError() + + defer_list = [] + + if len(pt_chain) < 2: + raise ValueError("PT Chain %s does not contain enough transports." % pt_chain) + + for pt in pt_chain: + if pt in configuration.transport_map: + pt_cmdline = configuration.transport_map[pt] + else: + raise ValueError("Pluggable transport %s not found in transport_map. Check your configuration file." % pt) + _, _, defer = pt_launch_child(reactor, client, [pt], pt_method_name, pt_cmdline) + defer_list.extend(defer) + whenAllDone = DeferredList(defer_list, consumeErrors=False) + whenAllDone.addCallback(lambda success_list: pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list)) + + +class Config(): + # Transport map links a pluggable transport name to the a commandline to launch it. + # Ex: {'b64' : 'exec obfsproxy managed'} + transport_map = None + + #Alias map links a pluggable transport chain name to a list of individual pluggable transports + # Ex: {'dummy_b64_dummy2' : ['dummy''b64''dummy2']} + alias_map = None + + def __init__(self, transport_map, alias_map): + self.transport_map = transport_map + self.alias_map = alias_map + + def __repr__(self): + return "Config(%s, %s)" % (self.transport_map, self.alias_map) + + def __str__(self): + return "Config Object with transport_map: %s, and alias_map %s." % (self.transport_map, self.alias_map) + + @classmethod + def parse(cls, config_string): + """ + Reads a configuration string and returns an instance of configuration. Uses shlex to parse configuration lines. + :param str config_string: The string which will be parsed to populate the transport_map and alias_map hash tables. + See the file example-fog-config for format. + """ + # TODO Add possibility of reading a ClientTransportPlugin with multiple transport types + # Ex: ClientTransportPlugin obfs3,scramblesuit obfsclient --option=value + + line_counter = 0 + lines = config_string.split('\n') + transport_map = {} + alias_map = {} + + for line in lines: + line_counter += 1 + if len(line) > 0 and line[0] != '#' : # Check for empty lines and comment tags on the first + line = line.strip() + delimited_tokens = shlex.split(line) + if len(delimited_tokens) > 1: + config_line_type = delimited_tokens[0] # This can be either Alias or ClientTransportPlugin + if config_line_type == 'ClientTransportPlugin': + cls.parse_transport_line(transport_map, delimited_tokens, line_counter) + elif config_line_type == 'Alias': + cls.parse_alias_line(alias_map, transport_map, delimited_tokens, line_counter) + else: + logger.warn("Configuration file has unknown line %s: '%s'" % (line_counter, line)) + return cls(transport_map, alias_map) + + @classmethod + def parse_transport_line(cls, transport_map, delimited_tokens, line_counter): + transport_name = delimited_tokens[1] + transport_cmdline = delimited_tokens[2:] + if transport_name in transport_map: + raise ValueError('Configuration file has duplicate ClientTransportPlugin lines. Duplicate line is at line number %s' % line_counter) + transport_map[transport_name] = transport_cmdline + + @classmethod + def parse_alias_line(cls, alias_map, transport_map, delimited_tokens, line_counter): + alias_name = delimited_tokens[1] # Example: "obfs3_flashproxy" + alias_path = delimited_tokens[2].split('|') # Example: "obfs3|flashproxy" + if alias_name in alias_map: + raise ValueError('Configuration file has duplicate Alias lines. Duplicate line is at line number %s' % line_counter) + for pt_name in alias_path: + if pt_name not in transport_map: + raise KeyError('Transport map is missing pluggable transport %s needed for chain %s. Check your configuration file for a ClientTransportPlugin line can launch %s' % (pt_name, alias_name, pt_name)) + alias_map[alias_name] = alias_path + +def main(*args): + parser = argparse.ArgumentParser() + parser.add_argument("-f", help="fog configuration file path", + metavar='FOGFILE', type=argparse.FileType('r'), default=DEFAULT_CONFIG_FILE_NAME) + + pt_setup_logger() + # TODO(infinity0): add an "external" mode, which would require us to run + # obfsproxy in external mode too. + + opts = parser.parse_args(args) + configuration = None + file_contents = opts.f.read() + configuration = Config.parse(file_contents) + pt_method_names = configuration.alias_map.keys() + client = ClientTransportPlugin() + client.init(pt_method_names) # Initialize our possible methods to all the chains listed by the fog file and stored in alias map. + if not client.getTransports(): + logger.error("no transports to serve. pt_method_names may be invalid.") + return 1 + + from twisted.internet import reactor + auto_killall(1, cleanup=reactor.stop) + #TODO Change from launching a single pair to launching multiple chains. + pt_setup_transports(reactor, client, configuration, pt_method_names[0]) + reactor.run(installSignalHandlers=0) + return 0 + +if __name__ == "__main__": + sys.exit(main(*sys.argv[1:])) + diff --git a/fog-client/fog/__init__.py b/fog-client/fog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fog-client/fog/socks.py b/fog-client/fog/socks.py new file mode 100644 index 0000000..b44135d --- /dev/null +++ b/fog-client/fog/socks.py @@ -0,0 +1,59 @@ +from twisted.protocols import socks +from twisted.internet.protocol import Factory +import logging + +logger = logging.getLogger('fog-logger') + +class SOCKSv4InterceptorProtocol(socks.SOCKSv4): + """ + A modified SOCKS protocol which extracts the requested ip and port + and redirects connections to the first pluggable transport in the chain. + """ + + def __init__(self, factory, pt_method_name): + """ + :param twisted.internet.protocol.factory factory: The factory that launched this protocol + :param pt_method_name: The name of the chain to be launched when a new connection is received + """ + self.factory = factory + self._pt_method_name = pt_method_name + socks.SOCKSv4.__init__(self) + + def _dataReceived2(self, server, user, version, code, port): + """ + Extracts the requested ip and port and redirects to a different address + """ + if code == 1: # CONNECT + assert version == 4, "Bad version code: %s" % version + if not self.authorize(code, server, port, user): + self.makeReply(91) + return + def _chain_set_up(remote_address, remote_port): + logger.debug("chain finished, connecting %s:%s" % (remote_address, remote_port)) + # Connect to our remote address instead of the requested one + d = self.connectClass(remote_address, remote_port, socks.SOCKSv4Outgoing, self) + d.addErrback(lambda result, self = self: self.makeReply(91)) + self.factory._new_conn_callback(server, port, self._pt_method_name, _chain_set_up) + assert self.buf == "", "hmm, still stuff in buffer... %s" % repr(self.buf) + else: + super(SOCKSv4InterceptorProtocol, self)._dataReceived2(server, user, version, code, port) + +class SOCKSv4InterceptorFactory(Factory): + + def __init__(self, pt_method_name, new_conn_callback): + """ + :param str pt_method_name: The name of the pt_method that this factory is launching. + :param function new_conn_callback: The function to be called when a connection is made. + def new_conn_callback + :param str server: The ip address requested by the SOCKS client. + :param int port: The port requested by the SOCKS client. + :param str pt_method_name: The name of the pt_method this factory is a part of. + :param function chain_set_up: The function to be called when the chain has finished setting up. + :param str remote_address: The address to relay the SOCKS request to. + :param int remote_port: The port to to send the SOCKS request to. + """ + self._pt_method_name = pt_method_name + self._new_conn_callback = new_conn_callback + + def buildProtocol(self, addr): + return SOCKSv4InterceptorProtocol(self, self._pt_method_name) \ No newline at end of file diff --git a/fog-client/fogrc b/fog-client/fogrc new file mode 100644 index 0000000..ee28514 --- /dev/null +++ b/fog-client/fogrc @@ -0,0 +1,17 @@ +#Based off of ticket #9744 +#Client transports are setup like so: +#ClientTransportPlugin name commandline +#For instance to launch obfs3, the client transport line should be this +#ClientTransportPlugin obfs3 obfsproxy managed +# +#For chaining transports together, an alias line is used. +#Alias chainname firsttransportname|secondtransportname +#tor expects alias to use underscores instead of pipes. So an alias links the tor version of a plugin chain to the actual plugins. See ticket #9580 + +ClientTransportPlugin obfs3 obfsproxy managed +ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :9000 +# If port 9000 cannot be portforwarded change it to a port that can be ported like so: +#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :3923 +# use a different facilitator +#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket -f http://siteb.fp-facilitator.org/fac/ --register —-register-methods=http 127.0.0.1:0 :3923 +Alias obfs3_flashproxy obfs3|flashproxy diff --git a/fog-client/setup.py b/fog-client/setup.py new file mode 100644 index 0000000..125da2d --- /dev/null +++ b/fog-client/setup.py @@ -0,0 +1,23 @@ +from distutils.core import setup +import py2exe + +# if py2exe complains "can't find P", try one of the following workarounds: +# +# a. py2exe doesn't support zipped eggs - http://www.py2exe.org/index.cgi/ExeWithEggs +# You should give the --always-unzip option to easy_install, or you can use setup.py directly +# $ python setup.py install --record install.log --single-version-externally-managed +# Don't forget to remove the previous zipped egg. +# +# b. Add an empty __init__.py to the P/ top-level directory, if it's missing +# - this is due to a bug (or misleading documentation) in python's imp.find_module() + +setup( + console=["fog-client"], + zipfile="py2exe-fog-client.zip", + options={ + "py2exe": { + "includes": ["pyptlib", "twisted", "txsocksx"], + "packages": ["ometa", "terml", "zope.interface"], + }, + }, +) diff --git a/fog-client/torrc b/fog-client/torrc new file mode 100644 index 0000000..98b1f47 --- /dev/null +++ b/fog-client/torrc @@ -0,0 +1,5 @@ +UseBridges 1 +Bridge obfs3_flashproxy 127.0.0.1:9000 +LearnCircuitBuildTimeout 0 +CircuitBuildTimeout 300 +ClientTransportPlugin obfs3_flashproxy exec ./fog-client diff --git a/fog-server/Makefile b/fog-server/Makefile new file mode 100644 index 0000000..c7d474b --- /dev/null +++ b/fog-server/Makefile @@ -0,0 +1,12 @@ +GOBUILDFLAGS = + +./fog-server: ./fog-server.go ./stack.go + go build $(GOBUILDFLAGS) -o "$@" $^ + +test: + go test -v + +clean: + rm -f ./fog-server + +.PHONY: test clean diff --git a/fog-server/fog-server.go b/fog-server/fog-server.go new file mode 100644 index 0000000..bb34219 --- /dev/null +++ b/fog-server/fog-server.go @@ -0,0 +1,548 @@ +package main + +import ( + "bufio" + "errors" + "flag" + "fmt" + "io" + "net" + "os" + "os/exec" + "os/signal" + "strings" + "sync" + "syscall" + "sort" + "time" +) + +import "git.torproject.org/pluggable-transports/goptlib.git" + +const connStackSize = 1000 +const subprocessWaitTimeout = 30 * time.Second + +var logFile = os.Stderr + +var ptInfo pt.ServerInfo + +// When a connection handler starts, +1 is written to this channel; when it +// ends, -1 is written. +var handlerChan = make(chan int) + +func usage() { + fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0]) + fmt.Printf("Chains websocket and obfsproxy server transports. pt-websocket-server and\n") + fmt.Printf("obfsproxy must be in PATH.\n") + fmt.Printf("\n") + fmt.Printf(" -h, --help show this help.\n") + fmt.Printf(" --log FILE log messages to FILE (default stderr).\n") + fmt.Printf(" --port PORT listen on PORT (overrides Tor's requested port).\n") +} + +var logMutex sync.Mutex + +func log(format string, v ...interface{}) { + dateStr := time.Now().Format("2006-01-02 15:04:05") + logMutex.Lock() + defer logMutex.Unlock() + msg := fmt.Sprintf(format, v...) + fmt.Fprintf(logFile, "%s %s\n", dateStr, msg) +} + +type ProcList []*os.Process + +func (procs ProcList) Signal(sig os.Signal) { + for _, p := range procs { + log("Sending signal %q to process with pid %d.", sig, p.Pid) + err := p.Signal(sig) + if err != nil { + log("Error sending signal %q to process with pid %d: %s.", sig, p.Pid, err) + } + } +} + +func (procs ProcList) Kill() { + for _, p := range procs { + log("Killing process with pid %d.", p.Pid) + err := p.Kill() + if err != nil { + log("Error killing process with pid %d: %s.", p.Pid, err) + continue + } + state, err := p.Wait() + if err != nil { + log("Error waiting on process with pid %d: %s.", state.Pid(), err) + continue + } + if !state.Exited() { + log("Process with pid %d didn't exit.", state.Pid()) + continue + } + } +} + +type Chain struct { + MethodName string + ExtLn, IntLn *net.TCPListener + ProcsAddr *net.TCPAddr + Procs ProcList + // This stack forwards external IP addresses to the extended ORPort. + Conns *Stack +} + +func (chain *Chain) CloseListeners() { + if chain.ExtLn != nil { + err := chain.ExtLn.Close() + if err != nil { + log("Error closing external listener: %s.", err) + } + } + if chain.IntLn != nil { + err := chain.IntLn.Close() + if err != nil { + log("Error closing internal listener: %s.", err) + } + } +} + +func (chain *Chain) Shutdown() { + chain.CloseListeners() + chain.Procs.Kill() + for { + elem, ok := chain.Conns.Pop() + if !ok { + break + } + conn := elem.(*net.TCPConn) + log("Closing stale connection from %s.", conn.RemoteAddr()) + err := conn.Close() + if err != nil { + } + } +} + +func findBindAddr(r io.Reader, methodName string) (*net.TCPAddr, error) { + br := bufio.NewReader(r) + for { + line, err := br.ReadString('\n') + if err != nil { + return nil, err + } + log("Received from sub-transport: %q.", line) + fields := strings.Fields(strings.TrimRight(line, "\n")) + if len(fields) < 1 { + continue + } + keyword := fields[0] + args := fields[1:] + if keyword == "SMETHOD" && len(args) >= 2 && args[0] == methodName { + bindaddr, err := net.ResolveTCPAddr("tcp", args[1]) + if err != nil { + return nil, err + } + return bindaddr, nil + } else if keyword == "SMETHODS" && len(args) == 1 && args[0] == "DONE" { + break + } + } + return nil, errors.New(fmt.Sprintf("no SMETHOD %s found before SMETHODS DONE", methodName)) +} + +// Escape a string for a ServerTransportOptions serialization. +func escape(s string) string { + repl := strings.NewReplacer(":", "\:", ";", "\;", "=", "\=", "\", "\\") + return repl.Replace(s) +} + +func encodeServerTransportOptions(methodName string, opts pt.Args) string { + if opts == nil { + return "" + } + keys := make([]string, 0, len(opts)) + for key, _ := range opts { + keys = append(keys, key) + } + sort.Strings(keys) + parts := make([]string, 0, len(keys)) + for _, key := range keys { + for _, value := range opts[key] { + parts = append(parts, escape(methodName) + ":" + escape(key) + "=" + escape(value)) + } + } + return strings.Join(parts, ";") +} + +// Represents a server transport plugin configuration like: +// ServerTransportPlugin MethodName exec Command +type ServerTransportPlugin struct { + MethodName string + Command []string + Options pt.Args +} + +func startProcesses(connectBackAddr net.Addr, plugins []ServerTransportPlugin) (bindAddr *net.TCPAddr, procs ProcList, err error) { + var stdout io.ReadCloser + + defer func() { + if err != nil { + // Kill subprocesses before returning error. + procs.Kill() + procs = procs[:0] + } + }() + + bindAddr = connectBackAddr.(*net.TCPAddr) + for _, plugin := range plugins { + // This plugin has its TOR_PT_ORPORT set to the previous + // bindAddr. + cmd := exec.Command(plugin.Command[0], plugin.Command[1:]...) + cmd.Env = []string{ + "TOR_PT_MANAGED_TRANSPORT_VER=1", + "TOR_PT_STATE_LOCATION=" + os.Getenv("TOR_PT_STATE_LOCATION"), + "TOR_PT_EXTENDED_SERVER_PORT=", + "TOR_PT_ORPORT=" + bindAddr.String(), + "TOR_PT_SERVER_TRANSPORTS=" + plugin.MethodName, + "TOR_PT_SERVER_BINDADDR=" + plugin.MethodName + "-127.0.0.1:0", + } + serverTransportOptions := encodeServerTransportOptions(plugin.MethodName, plugin.Options) + if serverTransportOptions != "" { + cmd.Env = append(cmd.Env, "TOR_PT_SERVER_TRANSPORT_OPTIONS=" + serverTransportOptions) + } + log("%s environment %q", cmd.Args[0], cmd.Env) + stdout, err = cmd.StdoutPipe() + if err != nil { + log("Failed to open %s stdout pipe: %s.", cmd.Args[0], err) + return + } + err = cmd.Start() + if err != nil { + log("Failed to start %s: %s.", cmd.Args[0], err) + return + } + log("Exec %s with args %q pid %d.", cmd.Path, cmd.Args, cmd.Process.Pid) + procs = append(procs, cmd.Process) + + bindAddr, err = findBindAddr(stdout, plugin.MethodName) + if err != nil { + log("Failed to find %s bindaddr: %s.", cmd.Args[0], err) + return + } + log("%s bindaddr is %s.", cmd.Args[0], bindAddr) + } + + return bindAddr, procs, err +} + +func acceptLoop(name string, ln *net.TCPListener, ch chan *net.TCPConn) { + for { + conn, err := ln.AcceptTCP() + if err != nil { + log("%s accept: %s.", name, err) + break + } + log("%s connection from %s.", name, conn.RemoteAddr()) + ch <- conn + } + close(ch) +} + +func copyLoop(a, b *net.TCPConn) error { + var wg sync.WaitGroup + + wg.Add(2) + + go func() { + n, err := io.Copy(b, a) + if err != nil { + log("After %d bytes from %s to %s: %s.", n, a.RemoteAddr(), b.RemoteAddr(), err) + } + a.CloseRead() + b.CloseWrite() + wg.Done() + }() + + go func() { + n, err := io.Copy(a, b) + if err != nil { + log("After %d bytes from %s to %s: %s.", n, b.RemoteAddr(), a.RemoteAddr(), err) + } + b.CloseRead() + a.CloseWrite() + wg.Done() + }() + + wg.Wait() + + return nil +} + +func handleExternalConnection(conn *net.TCPConn, chain *Chain) error { + handlerChan <- 1 + defer func() { + handlerChan <- -1 + }() + + chain.Conns.Push(conn) + log("handleExternalConnection: now %d conns buffered.", chain.Conns.Length()) + procsConn, err := net.DialTCP("tcp", nil, chain.ProcsAddr) + if err != nil { + log("error dialing proxy chain: %s.", err) + return err + } + err = copyLoop(conn, procsConn) + if err != nil { + log("error copying between ext and proxy chain: %s.", err) + return err + } + return nil +} + +func handleInternalConnection(conn *net.TCPConn, chain *Chain) error { + handlerChan <- 1 + defer func() { + handlerChan <- -1 + }() + + elem, ok := chain.Conns.Pop() + if !ok { + log("Underflow of connection stack, closing connection.") + err := conn.Close() + if err != nil { + log("Error in close: %s.", err) + } + return errors.New("connection stack underflow") + } + extConn := elem.(*net.TCPConn) + log("Connecting to ORPort using remote addr %s.", extConn.RemoteAddr()) + log("handleInternalConnection: now %d conns buffered.", chain.Conns.Length()) + or, err := pt.DialOr(&ptInfo, extConn.RemoteAddr().String(), chain.MethodName) + if err != nil { + log("Error connecting to ORPort: %s.", err) + return err + } + err = copyLoop(or, conn) + if err != nil { + log("Error copying between int and ORPort: %s.", err) + return err + } + return nil +} + +func listenerLoop(chain *Chain) { + extChan := make(chan *net.TCPConn) + intChan := make(chan *net.TCPConn) + go acceptLoop("external", chain.ExtLn, extChan) + go acceptLoop("internal", chain.IntLn, intChan) + +loop: + for { + select { + case conn, ok := <-extChan: + if !ok { + break loop + } + go handleExternalConnection(conn, chain) + case conn, ok := <-intChan: + if !ok { + break loop + } + go handleInternalConnection(conn, chain) + } + } +} + +func startChain(methodName string, bindaddr *net.TCPAddr, plugins []ServerTransportPlugin) (*Chain, error) { + chain := &Chain{} + var err error + + chain.MethodName = methodName + chain.Conns = NewStack(connStackSize) + + // Start internal listener (the proxy chain connects back to this). + chain.IntLn, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) + if err != nil { + log("Error opening internal listener: %s.", err) + chain.Shutdown() + return nil, err + } + log("Internal listener on %s.", chain.IntLn.Addr()) + + // Start subprocesses. + chain.ProcsAddr, chain.Procs, err = startProcesses(chain.IntLn.Addr(), plugins) + if err != nil { + log("Error starting proxy chain: %s.", err) + chain.Shutdown() + return nil, err + } + log("Proxy chain on %s.", chain.ProcsAddr) + + // Start external Internet listener (listens on bindaddr and connects to + // proxy chain). + chain.ExtLn, err = net.ListenTCP("tcp", bindaddr) + if err != nil { + log("Error opening external listener: %s.", err) + chain.Shutdown() + return nil, err + } + log("External listener on %s.", chain.ExtLn.Addr()) + + go listenerLoop(chain) + + return chain, nil +} + +type Configuration struct { + // Map from method names to command strings. + Transports map[string][]string + // Map from method names to ServerTransportOptions. + Options map[string]pt.Args + // Map from tor-friendly names like "obfs3_websocket" to systematic + // names like "obfs3|websocket". + Aliases map[string]string +} + +func (conf *Configuration) MethodNames() []string { + result := make([]string, 0) + // We understand all the single transports + for k, _ := range conf.Transports { + result = append(result, k) + } + // and aliases. + for k, _ := range conf.Aliases { + result = append(result, k) + } + return result +} + +// Parse a (possibly composed) method name into a slice of single method names. +func (conf *Configuration) ParseMethodName(methodName string) []string { + if name, ok := conf.Aliases[methodName]; ok { + methodName = name + } + return strings.Split(methodName, "|") +} + +func (conf *Configuration) PluginList(methodName string) ([]ServerTransportPlugin, error) { + names := conf.ParseMethodName(methodName) + stp := make([]ServerTransportPlugin, 0) + for _, name := range names { + command, ok := conf.Transports[name] + if !ok { + return nil, errors.New(fmt.Sprintf("no transport named %q", name)) + } + options := conf.Options[name] + stp = append(stp, ServerTransportPlugin{name, command, options}) + } + return stp, nil +} + +// Simulate loading a configuration file. +func getConfiguration() (conf *Configuration) { + conf = new(Configuration) + conf.Transports = make(map[string][]string) + conf.Aliases = make(map[string]string) + conf.Options = make(map[string]pt.Args) + conf.Transports["obfs3"] = []string{"obfsproxy", "managed"} + conf.Transports["websocket"] = []string{"pt-websocket-server"} + // conf.Options["obfs3"] = make(pt.Args) + // conf.Options["obfs3"]["secret"] = []string{"foo"} + conf.Aliases["obfs3_websocket"] = "obfs3|websocket" + return conf +} + +func main() { + var logFilename string + var port int + + flag.Usage = usage + flag.StringVar(&logFilename, "log", "", "log file to write to") + flag.IntVar(&port, "port", 0, "port to listen on if unspecified by Tor") + flag.Parse() + + if logFilename != "" { + f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error()) + os.Exit(1) + } + logFile = f + } + + log("Starting.") + + var err error + conf := getConfiguration() + ptInfo, err = pt.ServerSetup(conf.MethodNames()) + if err != nil { + log("Error in ServerSetup: %s", err) + os.Exit(1) + } + + chains := make([]*Chain, 0) + for _, bindaddr := range ptInfo.Bindaddrs { + // Override tor's requested port (which is 0 if this transport + // has not been run before) with the one requested by the --port + // option. + if port != 0 { + bindaddr.Addr.Port = port + } + + plugins, err := conf.PluginList(bindaddr.MethodName) + if err != nil { + pt.SmethodError(bindaddr.MethodName, err.Error()) + continue + } + + chain, err := startChain(bindaddr.MethodName, bindaddr.Addr, plugins) + if err != nil { + pt.SmethodError(bindaddr.MethodName, err.Error()) + continue + } + pt.Smethod(bindaddr.MethodName, chain.ExtLn.Addr()) + chains = append(chains, chain) + } + pt.SmethodsDone() + + var numHandlers int = 0 + var sig os.Signal + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + sig = nil + for sig == nil { + select { + case n := <-handlerChan: + numHandlers += n + case sig = <-sigChan: + } + } + log("Got first signal %q with %d running handlers.", sig, numHandlers) + for _, chain := range chains { + chain.CloseListeners() + chain.Procs.Signal(sig) + } + + if sig == syscall.SIGTERM { + log("Caught signal %q, exiting.", sig) + return + } + + sig = nil + for sig == nil && numHandlers != 0 { + select { + case n := <-handlerChan: + numHandlers += n + log("%d remaining handlers.", numHandlers) + case sig = <-sigChan: + } + } + if sig != nil { + log("Got second signal %q with %d running handlers.", sig, numHandlers) + for _, chain := range chains { + chain.Procs.Signal(sig) + } + } + + log("Exiting.") +} diff --git a/fog-server/pt_test.go b/fog-server/pt_test.go new file mode 100644 index 0000000..35d3aab --- /dev/null +++ b/fog-server/pt_test.go @@ -0,0 +1,39 @@ +package main + +import "testing" + +import "git.torproject.org/pluggable-transports/goptlib.git" + +func TestEncodeServerTransportOptions(t *testing.T) { + tests := [...]struct { + methodName string + opts pt.Args + expected string + }{ + { + "foo", + pt.Args{}, + "", + }, + { + "foo", + pt.Args{ + "key": []string{"value1", "value2"}, + "something": []string{"value1", "value2"}, + }, + "foo:key=value1;foo:key=value2;foo:something=value1;foo:something=value2", + }, + { + "m:m", + pt.Args{"k;k": []string{"v=v", "b\b"}}, + "m\:m:k\;k=v\=v;m\:m:k\;k=b\\b", + }, + } + + for _, test := range tests { + output := encodeServerTransportOptions(test.methodName, test.opts) + if output != test.expected { + t.Errorf("%q %q → %q (expected %q)", test.methodName, test.opts, output, test.expected) + } + } +} diff --git a/fog-server/stack.go b/fog-server/stack.go new file mode 100644 index 0000000..16cddd6 --- /dev/null +++ b/fog-server/stack.go @@ -0,0 +1,57 @@ +package main + +import "sync" + +// A fixed-size stack. If a push exceeds the capacity of the underlying slice, +// the least recently added element is lost. +type Stack struct { + buf []interface{} + base, head int + m sync.Mutex +} + +// Create a stack with the given capacity. +func NewStack(capacity int) *Stack { + return &Stack{buf: make([]interface{}, capacity+1)} +} + +func (s *Stack) clamp(x int) int { + x = x % len(s.buf) + if x < 0 { + x += len(s.buf) + } + return x +} + +func (s *Stack) Length() int { + s.m.Lock() + defer s.m.Unlock() + return s.clamp(s.head - s.base) +} + +// If this push causes the stack to overflow, the first return value is the +// discarded element and the second return value is false. Otherwise the second +// return value is true. +func (s *Stack) Push(x interface{}) (interface{}, bool) { + s.m.Lock() + defer s.m.Unlock() + s.buf[s.head] = x + s.head = s.clamp(s.head + 1) + if s.head == s.base { + s.base = s.clamp(s.base + 1) + return s.buf[s.head], false + } + return nil, true +} + +// The second return value is false if the stack was empty, and true otherwise. +// The first return value is defined only when the second is true. +func (s *Stack) Pop() (interface{}, bool) { + s.m.Lock() + defer s.m.Unlock() + if s.head == s.base { + return nil, false + } + s.head = s.clamp(s.head - 1) + return s.buf[s.head], true +} diff --git a/fog-server/stack_test.go b/fog-server/stack_test.go new file mode 100644 index 0000000..3188a2d --- /dev/null +++ b/fog-server/stack_test.go @@ -0,0 +1,120 @@ +package main + +import "testing" + +// Test operations on a zero-capacity stack. +func TestZeroCapacity(t *testing.T) { + var ok bool + + s := NewStack(0) + if s.Length() != 0 { + t.Fatal("initial length is not 0") + } + _, ok = s.Push("a") + if ok || s.Length() != 0 { + t.Fatal() + } + _, ok = s.Pop() + if ok || s.Length() != 0 { + t.Fatal() + } +} + +func TestPushPop(t *testing.T) { + var x interface{} + var ok bool + + s := NewStack(3) + + // Push elems. + if s.Length() != 0 { + t.Fatal("initial length is not 0") + } + _, ok = s.Push("a") + if !ok || s.Length() != 1 { + t.Fatal() + } + _, ok = s.Push("b") + if !ok || s.Length() != 2 { + t.Fatal() + } + + // Pop to empty. + x, ok = s.Pop() + if !ok || x != "b" || s.Length() != 1 { + t.Fatal() + } + x, ok = s.Pop() + if !ok || x != "a" || s.Length() != 0 { + t.Fatal() + } + // Pop one past empty. + x, ok = s.Pop() + if ok { + t.Fatal() + } + + // Push to capacity. + s.Push("c") + s.Push("d") + _, ok = s.Push("e") + if !ok || s.Length() != 3 { + t.Fatal("push to capacity is not at capacity") + } + // Push one past capacity. + x, ok = s.Push("f") + if ok || s.Length() != 3 { + t.Fatal() + } + if x != "c" { + t.Fatal("mismatch in overwritten element") + } + + // Pop to empty. + x, ok = s.Pop() + if !ok || x != "f" || s.Length() != 2 { + t.Fatal() + } + x, ok = s.Pop() + if !ok || x != "e" || s.Length() != 1 { + t.Fatal() + } + x, ok = s.Pop() + if !ok || x != "d" || s.Length() != 0 { + t.Fatal() + } + // Pop one past empty. + x, ok = s.Pop() + if ok { + t.Fatal() + } +} + +// Test underflow of an initially empty stack. +func TestUnderflowEmpty(t *testing.T) { + var ok bool + + s := NewStack(3) + _, ok = s.Pop() + if ok { + t.Fatal() + } +} + +// Test underflow of a stack that had been full. +func TestUnderflowFull(t *testing.T) { + var ok bool + + s := NewStack(3) + s.Push("a") + s.Push("b") + s.Push("c") + s.Push("d") + s.Pop() + s.Pop() + s.Pop() + _, ok = s.Pop() + if ok { + t.Fatal() + } +} diff --git a/fog-server/torrc b/fog-server/torrc new file mode 100644 index 0000000..f103ddc --- /dev/null +++ b/fog-server/torrc @@ -0,0 +1,5 @@ +ORPort 9999 +ExtORPort 5555 +BridgeRelay 1 +SocksPort 0 +ServerTransportPlugin obfs3_websocket exec ./bin/fog-server diff --git a/fog/__init__.py b/fog/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/fog/socks.py b/fog/socks.py deleted file mode 100644 index e3b483b..0000000 --- a/fog/socks.py +++ /dev/null @@ -1,59 +0,0 @@ -from twisted.protocols import socks -from twisted.internet.protocol import Factory -import logging - -logger = logging.getLogger('obfs-flash-logger') - -class SOCKSv4InterceptorProtocol(socks.SOCKSv4): - """ - A modified SOCKS protocol which extracts the requested ip and port - and redirects connections to the first pluggable transport in the chain. - """ - - def __init__(self, factory, pt_method_name): - """ - :param twisted.internet.protocol.factory factory: The factory that launched this protocol - :param pt_method_name: The name of the chain to be launched when a new connection is received - """ - self.factory = factory - self._pt_method_name = pt_method_name - socks.SOCKSv4.__init__(self) - - def _dataReceived2(self, server, user, version, code, port): - """ - Extracts the requested ip and port and redirects to a different address - """ - if code == 1: # CONNECT - assert version == 4, "Bad version code: %s" % version - if not self.authorize(code, server, port, user): - self.makeReply(91) - return - def _chain_set_up(remote_address, remote_port): - logger.debug("chain finished, connecting %s:%s" % (remote_address, remote_port)) - # Connect to our remote address instead of the requested one - d = self.connectClass(remote_address, remote_port, socks.SOCKSv4Outgoing, self) - d.addErrback(lambda result, self = self: self.makeReply(91)) - self.factory._new_conn_callback(server, port, self._pt_method_name, _chain_set_up) - assert self.buf == "", "hmm, still stuff in buffer... %s" % repr(self.buf) - else: - super(SOCKSv4InterceptorProtocol, self)._dataReceived2(server, user, version, code, port) - -class SOCKSv4InterceptorFactory(Factory): - - def __init__(self, pt_method_name, new_conn_callback): - """ - :param str pt_method_name: The name of the pt_method that this factory is launching. - :param function new_conn_callback: The function to be called when a connection is made. - def new_conn_callback - :param str server: The ip address requested by the SOCKS client. - :param int port: The port requested by the SOCKS client. - :param str pt_method_name: The name of the pt_method this factory is a part of. - :param function chain_set_up: The function to be called when the chain has finished setting up. - :param str remote_address: The address to relay the SOCKS request to. - :param int remote_port: The port to to send the SOCKS request to. - """ - self._pt_method_name = pt_method_name - self._new_conn_callback = new_conn_callback - - def buildProtocol(self, addr): - return SOCKSv4InterceptorProtocol(self, self._pt_method_name) \ No newline at end of file diff --git a/fogrc b/fogrc deleted file mode 100644 index ee28514..0000000 --- a/fogrc +++ /dev/null @@ -1,17 +0,0 @@ -#Based off of ticket #9744 -#Client transports are setup like so: -#ClientTransportPlugin name commandline -#For instance to launch obfs3, the client transport line should be this -#ClientTransportPlugin obfs3 obfsproxy managed -# -#For chaining transports together, an alias line is used. -#Alias chainname firsttransportname|secondtransportname -#tor expects alias to use underscores instead of pipes. So an alias links the tor version of a plugin chain to the actual plugins. See ticket #9580 - -ClientTransportPlugin obfs3 obfsproxy managed -ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :9000 -# If port 9000 cannot be portforwarded change it to a port that can be ported like so: -#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket --register 127.0.0.1:0 :3923 -# use a different facilitator -#ClientTransportPlugin flashproxy flashproxy-client --transport obfs3|websocket -f http://siteb.fp-facilitator.org/fac/ --register —-register-methods=http 127.0.0.1:0 :3923 -Alias obfs3_flashproxy obfs3|flashproxy diff --git a/obfs-flash-client b/obfs-flash-client deleted file mode 100755 index 18adff4..0000000 --- a/obfs-flash-client +++ /dev/null @@ -1,538 +0,0 @@ -#!/usr/bin/python - -import argparse -import os -import sys - -from collections import namedtuple -from functools import partial - -# TODO(infinity0): this is temporary workaround until we do #10047 -if sys.platform == 'win32': - os.environ["KILL_CHILDREN_ON_DEATH"] = "1" -from pyptlib.util import parse_addr_spec -from pyptlib.util.subproc import auto_killall, Popen -from pyptlib.client import ClientTransportPlugin - -from subprocess import PIPE - -from twisted.internet.defer import Deferred, DeferredList -from twisted.internet.stdio import StandardIO -from twisted.internet.protocol import Factory, connectionDone -from twisted.internet.endpoints import TCP4ClientEndpoint -from twisted.protocols.basic import LineReceiver -from twisted.protocols.portforward import ProxyServer as _ProxyServer -from twisted.python import log -from txsocksx.client import SOCKS4ClientEndpoint, SOCKS5ClientEndpoint -from fog.socks import SOCKSv4InterceptorFactory - -import shlex - -import logging - -DEFAULT_CONFIG_FILE_NAME = 'fogrc' - -logger = None -def pt_setup_logger(): - global logger - logger = logging.getLogger('obfs-flash-logger') - logger.setLevel(logging.WARNING) - ch = logging.StreamHandler() - ch.setLevel(logging.DEBUG) - logger.addHandler(ch) - -def pt_child_env(managed_ver, env=os.environ): - """ - Prepare the environment for a child PT process, by clearing all TOR_PT_* - envvars except TOR_PT_STATE_LOCATION and TOR_PT_MANAGED_TRANSPORT_VER. - """ - exempt = ['TOR_PT_STATE_LOCATION'] - cur_env = [(k, v) for k, v in env.iteritems() - if not k.startswith('TOR_PT_') or k in exempt] - cur_env.append(('TOR_PT_MANAGED_TRANSPORT_VER', ','.join(managed_ver))) - return cur_env - -class MethodSpec(namedtuple('MethodSpec', 'name protocol addrport args opts')): - @classmethod - def fromLine(cls, line): - args = line.rstrip('\n').split(' ') - name = args[0] - protocol = args[1] - addrport = parse_addr_spec(args[2]) - args = args[3][-5:].split(',') if len(args) > 3 and args[3].startswith("ARGS=") else [] - opts = args[4][-9:].split(',') if len(args) > 4 and args[4].startswith("OPT-ARGS=") else [] - return MethodSpec(name, protocol, addrport, args, opts) - -def branch(parent): - """ - Returns a new Deferred that does not advance the callback-chain of the parent. - - See http://xph.us/2009/12/10/asynchronous-programming-in-python.html for motivation. - """ - d = Deferred() - parent.addCallback(lambda v: (v, d.callback(v))[0]) - parent.addErrback(lambda f: (f, d.errback(f))[1]) - return d - -class ManagedTransportProtocolV1(LineReceiver): - """ - A Twisted IProtocol to read PT output. - - See pt-spec.txt and others for details of the protocol. - """ - # TODO(infinity0): eventually this could be padded out and moved to pyptlib - - delimiter = os.linesep - protocol_version = "1" - - def __init__(self): - self.cmethods = {} - self._dCMethodsDone = Deferred() - self._dPluginError = Deferred() - # dPluginError triggers errors on all sub-events, not the other way round - # so fatal sub-events should call _abort rather than errback on their Deferreds - self._dPluginError.addErrback(lambda f: (f, self._fireCMethodsDone().errback(f))[0]) - # TODO(infinity0): call _abort if we don't recv CMETHODS DONE within n sec - - def whenCMethodsDone(self): - """ - Return a new Deferred that calls-back when CMETHODS DONE is received. - """ - return branch(self._dCMethodsDone) - - def whenPluginError(self): - """ - Return a new Deferred that errors-back when the remote plugin fails. - - Note: the success chain (callback) is never fired. - """ - return branch(self._dPluginError) - - def lineReceived(self, line): - if not line: return - - (kw, args) = line.split(' ', 1) - if kw == "VERSION": - version = args.strip() - if version != self.protocol_version: - self._abort(ValueError("child used unsupported managed transport version: %s" % version)) - elif kw == "CMETHOD": - cmethod = MethodSpec.fromLine(args) - self.cmethods[cmethod.name] = cmethod - elif kw == "CMETHODS" and args == "DONE": - self._fireCMethodsDone().callback(self.cmethods) - else: - pass # ignore unrecognised line - - def connectionLost(self, reason=connectionDone): - self._firePluginError().errback(reason) - - def _abort(self, exc): - self._firePluginError().errback(exc) - self.transport.loseConnection() - - def _fireCMethodsDone(self): - """Return dCMethodsDone or a dummy if it was already called.""" - if self._dCMethodsDone: - d = self._dCMethodsDone - self._dCMethodsDone = None - return d - return Deferred().addErrback(lambda *args: None) - - def _firePluginError(self): - """Return dPluginError or a dummy if it was already called.""" - if self._dPluginError: - d = self._dPluginError - self._dPluginError = None - return d - return Deferred().addErrback(lambda *args: None) - -# TODO(infinity0): remove this class when twisted update their side -class ProxyServer(_ProxyServer): - - def connectionMade(self): - # code copied from super class, except instead of connecting - # to a TCP endpoint we abstract that out to a child method - self.transport.pauseProducing() - - client = self.clientProtocolFactory() - client.setServer(self) - - if self.reactor is None: - from twisted.internet import reactor - self.reactor = reactor - - self.connectProxyClient(client) - - def connectProxyClient(self, client): - raise NotImplementedError() - -class OneUseSOCKSWrapper(ProxyServer): - - def connectProxyClient(self, client): - local_host, local_port = self.factory.method_spec.addrport - TCPPoint = TCP4ClientEndpoint( - self.reactor, - local_host, - local_port) - # Next PT may need either SOCKS4 or SOCKS5 so check its protocol and get the required class - socks_endpoint_class = self.getSocksEndpointClass() - SOCKSPoint = socks_endpoint_class( - self.factory.remote_host, - self.factory.remote_port, - TCPPoint) - # Store port for debugging messages before stopListening is called. - # listen_port will not have a port after stopListening is called. - stored_port = self.factory.listen_port.getHost().port - d_port_closed = self.factory.listen_port.stopListening() - d_port_closed.addCallback( - lambda x: logger.debug("Closed factory listener %s on port %s" % (self.factory, stored_port))) - d_port_closed.addErrback( - lambda x: logger.warn("Failed to close factory listener %s listening on port %s" % (self.factory, stored_port))) - d = SOCKSPoint.connect(client) - d.chainDeferred(self.factory.d_connected) - @d.addErrback - def _gotError(error): - log.err(error, "error connecting to SOCKS server") - - def getSocksEndpointClass(self): - """ - Checks self.factory.method_spec.protocol and returns the appropriate socks endpoint class. - """ - socks_endpoint_class = None - if self.factory.method_spec.protocol == 'socks4': - socks_endpoint_class = SOCKS4ClientEndpoint - elif self.factory.method_spec.protocol == 'socks5': - socks_endpoint_class = SOCKS5ClientEndpoint - else: - raise ValueError("Pluggable transport requires unknown protocol %s. Supported protocols are %s" % - (self.factory.method_spec.protocol, ('socks4', 'socks5'))) - return socks_endpoint_class - -class OneUseSOCKSFactory(Factory): - protocol = OneUseSOCKSWrapper - def __init__(self, method_spec, remote_host, remote_port): - self._connected_once = False - self.method_spec = method_spec - self.remote_host = remote_host - self.remote_port = remote_port - self.d_connected = Deferred() - self.listen_port = None - - def __str__(self): - return "OneUseSOCKSFactory connecting %s to %s:%s" % (self.method_spec, self.remote_host, self.remote_port) - - def __repr__(self): - return "OneUseSOCKSFactory(%s, %s, %s)" % (self.method_spec, self.remote_host, self.remote_port) - - def setListenPort(self, listen_port): - """ - Sets the listen_port object. - :param function listen_port: The function returned from a ListenTCP call. Used to shutdown the port when a connection is made. - """ - self.listen_port = listen_port - - def whenConnected(self): - """ - Returns a new Deferred that triggers when a connection is successfully made. - """ - return branch(self.d_connected) - - def buildProtocol(self, addr): - """ - Only allows one protocol to be created. After that it always returns None - :param twisted.internet.interfaces.IAddress addr: an object implementing L{twisted.internet.interfaces.IAddress} - """ - if self._connected_once: - return None - else: - self._connected_once = True - return Factory.buildProtocol(self, addr) - -if sys.platform == "win32": - # TODO(infinity0): push this upstream to Twisted - from twisted.internet import _pollingfile - import msvcrt - - _StandardIO = StandardIO - class StandardIO(_StandardIO): - - def __init__(self, proto, stdin=None, stdout=None, reactor=None): - """ - Start talking to standard IO with the given protocol. - - Also, put it stdin/stdout/stderr into binary mode. - """ - if reactor is None: - import twisted.internet.reactor - reactor = twisted.internet.reactor - - _pollingfile._PollingTimer.__init__(self, reactor) - self.proto = proto - - fdstdin = stdin or sys.stdin.fileno() - fdstdout = stdout or sys.stdout.fileno() - - for stdfd in (fdstdin, fdstdout): - msvcrt.setmode(stdfd, os.O_BINARY) - - hstdin = msvcrt.get_osfhandle(fdstdin) - self.stdin = _pollingfile._PollableReadPipe( - hstdin, self.dataReceived, self.readConnectionLost) - - hstdout = msvcrt.get_osfhandle(fdstdout) - self.stdout = _pollingfile._PollableWritePipe( - hstdout, self.writeConnectionLost) - - self._addPollableResource(self.stdin) - self._addPollableResource(self.stdout) - - self.proto.makeConnection(self) - -def pt_launch_child(reactor, client, methodnames, pt_method_name, cmdline): - """Launch a child PT and ensure it has the right transport methods.""" - cur_env = pt_child_env(ManagedTransportProtocolV1.protocol_version) - environment = dict(cur_env + { - "TOR_PT_CLIENT_TRANSPORTS": ",".join(methodnames), - }.items()) - sub_proc = Popen(cmdline, - stdout = PIPE, - env = environment, - ) - sub_protocol = ManagedTransportProtocolV1() - # we ought to pass reactor=reactor in below, but this breaks Twisted 12 - StandardIO(sub_protocol, stdin=sub_proc.stdout.fileno()) - methoddefers = [sub_protocol.whenCMethodsDone().addCallback( - partial(pt_require_child, client, name, pt_method_name)) - for name in methodnames] - return sub_proc, sub_protocol, methoddefers - -def pt_require_child(client, childmethod, pt_method_name, cmethods): - """Callback for checking a child PT has the right transport methods.""" - if childmethod not in cmethods: - client.reportMethodError(pt_method_name, "failed to start required child transport: %s" % childmethod) - raise ValueError() - return cmethods[childmethod] - -def pt_setup_socks_shim(pt_name, pt_chain, success_list, dest_address, dest_port, reactor, proxy_deferreds): - """ - Launches a socks proxy server to link two PTs together. - :param str pt_name: The name of the pt to send traffic to. - :param list pt_chain: The list of PTs in this chain. - :param list success_list: A list of tuples containing a launch status boolean, MethodSpec pairs. - Ex: [(True, MethodSpec(name='dummy', protocol='socks4', addrport=('127.0.0.1', 58982), args=[], opts=[])), - (True, MethodSpec(name='b64', protocol='socks4', addrport=('127.0.0.1', 58981), args=[], opts=[]))] - :param str dest_address: The address for the next PT to send its results to. - :param int dest_port: The port for the next PT to send to. - :param twisted.internet.interfaces.IReactor reactor: Reactor to attack the TCP server to. - - :param list proxy_deferreds: This list has each factorys' deferred appended to it. - - :returns twisted.internet.interfaces.IListeningPort: An IListeningPort used for shutting down a factory after a connection is made. - """ - methodspec = [r[1] for r in success_list if r[1].name == pt_name][0] # Returns the resulting methodspec. - factory = OneUseSOCKSFactory(methodspec, dest_address, dest_port) - # TODO switch to using endpoints instead of listenTCP - proxy_server = reactor.listenTCP(interface='127.0.0.1', port=0, factory=factory) - factory.setListenPort(proxy_server) - proxy_deferreds.append(factory.whenConnected()) - logger.debug("launched %s on port %s with dest %s:%s" % (pt_name, proxy_server.getHost().port, dest_address, dest_port)) - return proxy_server - -def pt_launch_chain(dest_address, dest_port, pt_chain, _chain_set_up, reactor, success_list): - """ - Launches a chain of pluggable transports by connecting each pt with SOCKS proxies. - :param str dest_address: The bridge address to connect to. - :param int dest_port: The bridge port to connect to. - :param list pt_chain: The list of pt names to launch. - :param function _chain_set_up: The function to call when the shims have been set up. - :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to. - :param list success_list: A list of tuples containing a launch status boolean, MethodSpec pairs. - Ex: [(True, MethodSpec(name='dummy', protocol='socks4', addrport=('127.0.0.1', 58982), args=[], opts=[])), - (True, MethodSpec(name='b64', protocol='socks4', addrport=('127.0.0.1', 58981), args=[], opts=[]))] - """ - proxy_deferreds = [] - last_pt_name = pt_chain[-1] - logger.debug("launching chain %s" % pt_chain) - # Initialize prev_server to the port picked by the last proxy server as that's the only one we know yet. - last_server = pt_setup_socks_shim(last_pt_name, pt_chain, success_list, dest_address, dest_port, - reactor, proxy_deferreds) - prev_server = last_server - for pt_name in reversed(pt_chain[:-1]): - # Loops through the pts linking them together through SOCKS proxies, skipping the last pt. - prev_server = pt_setup_socks_shim(pt_name, pt_chain, success_list, '127.0.0.1', prev_server.getHost().port, - reactor, proxy_deferreds) - def check_chain_all_connected(protocol_list): - """ - Checks all the shims launched to see if they successfully connected. - :param list protocol_list: A list of tuples containing status boolean, twisted.protocols.portforward.ProxyClient pairs. - Ex: [(True, <twisted.protocols.portforward.ProxyClient instance at 0x10b825518>), - (True, <twisted.protocols.portforward.ProxyClient instance at 0x10b829518>)] - """ - if all([result[0] for result in protocol_list]): - logger.debug("All PT shims connected correctly") - else: - # At this point the SOCKS protocol is in communication mode so no need to call makeReply(91) - # This assumes that the child pluggable transport will shut down the connection cleanly. - failed_protocols = [x[1] for x in protocol_list if x[0] == False] - logger.error("Shims %s failed to connect." % failed_protocols) - raise ValueError() - - finished = DeferredList(proxy_deferreds) - finished.addCallback(check_chain_all_connected) - _chain_set_up(prev_server.getHost().host, prev_server.getHost().port) - -def pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list): - """ - Launches a SOCKS interceptor. - :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to. - :param pyptlib.client.ClientTransportPlugin client: PT client API. - :param Config configuration: The configuration structure for this pair. - :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy" - :param list success_list: A list of tuples containing a launch status boolean, MethodSpec pairs. - Ex: [(True, MethodSpec(name='dummy', protocol='socks4', addrport=('127.0.0.1', 58982), args=[], opts=[])), - (True, MethodSpec(name='b64', protocol='socks4', addrport=('127.0.0.1', 58981), args=[], opts=[]))] - """ - logger.debug("launching interceptor") - pt_chain = configuration.alias_map[pt_method_name] - success = all(r[0] for r in success_list if r[1].name in pt_chain) - # failure was already reported by pt_require_child, just return - if not success: return - socks_interceptor = SOCKSv4InterceptorFactory(pt_method_name, - lambda dest_address, dest_port, pt_method_name, chain_finished: - pt_launch_chain(dest_address, dest_port, pt_chain, chain_finished, reactor, success_list)) - # TODO switch to using endpoints instead of listenTCP - interceptor = reactor.listenTCP(interface='127.0.0.1', port=0, factory=socks_interceptor) - interceptor_port = interceptor.getHost().port - client.reportMethodSuccess(pt_method_name, "socks4", ("127.0.0.1", interceptor_port)) - client.reportMethodsEnd() - -def pt_setup_transports(reactor, client, configuration, pt_method_name): - """ - Launches the PT processes. - :param twisted.internet.interfaces.IReactor reactor: Reactor to install this PT to. - :param pyptlib.client.ClientTransportPlugin client: PT client API. - :param Config configuration: The configuration structure for this pair. - :param str pt_method_name: The name of the pt chain to launch. Ex: "obfs3_flashproxy" - """ - logger.debug("Setting up transports %s" % pt_method_name) - if pt_method_name in configuration.alias_map: - pt_chain = configuration.alias_map[pt_method_name] - else: - logger.error('Pluggable Transport Combination %s not found in configuration alias map.' % pt_method_name) - raise KeyError() - - defer_list = [] - - if len(pt_chain) < 2: - raise ValueError("PT Chain %s does not contain enough transports." % pt_chain) - - for pt in pt_chain: - if pt in configuration.transport_map: - pt_cmdline = configuration.transport_map[pt] - else: - raise ValueError("Pluggable transport %s not found in transport_map. Check your configuration file." % pt) - _, _, defer = pt_launch_child(reactor, client, [pt], pt_method_name, pt_cmdline) - defer_list.extend(defer) - whenAllDone = DeferredList(defer_list, consumeErrors=False) - whenAllDone.addCallback(lambda success_list: pt_launch_interceptor(reactor, client, configuration, pt_method_name, success_list)) - - -class Config(): - # Transport map links a pluggable transport name to the a commandline to launch it. - # Ex: {'b64' : 'exec obfsproxy managed'} - transport_map = None - - #Alias map links a pluggable transport chain name to a list of individual pluggable transports - # Ex: {'dummy_b64_dummy2' : ['dummy''b64''dummy2']} - alias_map = None - - def __init__(self, transport_map, alias_map): - self.transport_map = transport_map - self.alias_map = alias_map - - def __repr__(self): - return "Config(%s, %s)" % (self.transport_map, self.alias_map) - - def __str__(self): - return "Config Object with transport_map: %s, and alias_map %s." % (self.transport_map, self.alias_map) - - @classmethod - def parse(cls, config_string): - """ - Reads a configuration string and returns an instance of configuration. Uses shlex to parse configuration lines. - :param str config_string: The string which will be parsed to populate the transport_map and alias_map hash tables. - See the file example-fog-config for format. - """ - # TODO Add possibility of reading a ClientTransportPlugin with multiple transport types - # Ex: ClientTransportPlugin obfs3,scramblesuit obfsclient --option=value - - line_counter = 0 - lines = config_string.split('\n') - transport_map = {} - alias_map = {} - - for line in lines: - line_counter += 1 - if len(line) > 0 and line[0] != '#' : # Check for empty lines and comment tags on the first - line = line.strip() - delimited_tokens = shlex.split(line) - if len(delimited_tokens) > 1: - config_line_type = delimited_tokens[0] # This can be either Alias or ClientTransportPlugin - if config_line_type == 'ClientTransportPlugin': - cls.parse_transport_line(transport_map, delimited_tokens, line_counter) - elif config_line_type == 'Alias': - cls.parse_alias_line(alias_map, transport_map, delimited_tokens, line_counter) - else: - logger.warn("Configuration file has unknown line %s: '%s'" % (line_counter, line)) - return cls(transport_map, alias_map) - - @classmethod - def parse_transport_line(cls, transport_map, delimited_tokens, line_counter): - transport_name = delimited_tokens[1] - transport_cmdline = delimited_tokens[2:] - if transport_name in transport_map: - raise ValueError('Configuration file has duplicate ClientTransportPlugin lines. Duplicate line is at line number %s' % line_counter) - transport_map[transport_name] = transport_cmdline - - @classmethod - def parse_alias_line(cls, alias_map, transport_map, delimited_tokens, line_counter): - alias_name = delimited_tokens[1] # Example: "obfs3_flashproxy" - alias_path = delimited_tokens[2].split('|') # Example: "obfs3|flashproxy" - if alias_name in alias_map: - raise ValueError('Configuration file has duplicate Alias lines. Duplicate line is at line number %s' % line_counter) - for pt_name in alias_path: - if pt_name not in transport_map: - raise KeyError('Transport map is missing pluggable transport %s needed for chain %s. Check your configuration file for a ClientTransportPlugin line can launch %s' % (pt_name, alias_name, pt_name)) - alias_map[alias_name] = alias_path - -def main(*args): - parser = argparse.ArgumentParser() - parser.add_argument("-f", help="fog configuration file path", - metavar='FOGFILE', type=argparse.FileType('r'), default=DEFAULT_CONFIG_FILE_NAME) - - pt_setup_logger() - # TODO(infinity0): add an "external" mode, which would require us to run - # obfsproxy in external mode too. - - opts = parser.parse_args(args) - configuration = None - file_contents = opts.f.read() - configuration = Config.parse(file_contents) - pt_method_names = configuration.alias_map.keys() - client = ClientTransportPlugin() - client.init(pt_method_names) # Initialize our possible methods to all the chains listed by the fog file and stored in alias map. - if not client.getTransports(): - logger.error("no transports to serve. pt_method_names may be invalid.") - return 1 - - from twisted.internet import reactor - auto_killall(1, cleanup=reactor.stop) - #TODO Change from launching a single pair to launching multiple chains. - pt_setup_transports(reactor, client, configuration, pt_method_names[0]) - reactor.run(installSignalHandlers=0) - return 0 - -if __name__ == "__main__": - sys.exit(main(*sys.argv[1:])) - diff --git a/obfs-flash-server.go b/obfs-flash-server.go deleted file mode 100644 index bb34219..0000000 --- a/obfs-flash-server.go +++ /dev/null @@ -1,548 +0,0 @@ -package main - -import ( - "bufio" - "errors" - "flag" - "fmt" - "io" - "net" - "os" - "os/exec" - "os/signal" - "strings" - "sync" - "syscall" - "sort" - "time" -) - -import "git.torproject.org/pluggable-transports/goptlib.git" - -const connStackSize = 1000 -const subprocessWaitTimeout = 30 * time.Second - -var logFile = os.Stderr - -var ptInfo pt.ServerInfo - -// When a connection handler starts, +1 is written to this channel; when it -// ends, -1 is written. -var handlerChan = make(chan int) - -func usage() { - fmt.Printf("Usage: %s [OPTIONS]\n", os.Args[0]) - fmt.Printf("Chains websocket and obfsproxy server transports. pt-websocket-server and\n") - fmt.Printf("obfsproxy must be in PATH.\n") - fmt.Printf("\n") - fmt.Printf(" -h, --help show this help.\n") - fmt.Printf(" --log FILE log messages to FILE (default stderr).\n") - fmt.Printf(" --port PORT listen on PORT (overrides Tor's requested port).\n") -} - -var logMutex sync.Mutex - -func log(format string, v ...interface{}) { - dateStr := time.Now().Format("2006-01-02 15:04:05") - logMutex.Lock() - defer logMutex.Unlock() - msg := fmt.Sprintf(format, v...) - fmt.Fprintf(logFile, "%s %s\n", dateStr, msg) -} - -type ProcList []*os.Process - -func (procs ProcList) Signal(sig os.Signal) { - for _, p := range procs { - log("Sending signal %q to process with pid %d.", sig, p.Pid) - err := p.Signal(sig) - if err != nil { - log("Error sending signal %q to process with pid %d: %s.", sig, p.Pid, err) - } - } -} - -func (procs ProcList) Kill() { - for _, p := range procs { - log("Killing process with pid %d.", p.Pid) - err := p.Kill() - if err != nil { - log("Error killing process with pid %d: %s.", p.Pid, err) - continue - } - state, err := p.Wait() - if err != nil { - log("Error waiting on process with pid %d: %s.", state.Pid(), err) - continue - } - if !state.Exited() { - log("Process with pid %d didn't exit.", state.Pid()) - continue - } - } -} - -type Chain struct { - MethodName string - ExtLn, IntLn *net.TCPListener - ProcsAddr *net.TCPAddr - Procs ProcList - // This stack forwards external IP addresses to the extended ORPort. - Conns *Stack -} - -func (chain *Chain) CloseListeners() { - if chain.ExtLn != nil { - err := chain.ExtLn.Close() - if err != nil { - log("Error closing external listener: %s.", err) - } - } - if chain.IntLn != nil { - err := chain.IntLn.Close() - if err != nil { - log("Error closing internal listener: %s.", err) - } - } -} - -func (chain *Chain) Shutdown() { - chain.CloseListeners() - chain.Procs.Kill() - for { - elem, ok := chain.Conns.Pop() - if !ok { - break - } - conn := elem.(*net.TCPConn) - log("Closing stale connection from %s.", conn.RemoteAddr()) - err := conn.Close() - if err != nil { - } - } -} - -func findBindAddr(r io.Reader, methodName string) (*net.TCPAddr, error) { - br := bufio.NewReader(r) - for { - line, err := br.ReadString('\n') - if err != nil { - return nil, err - } - log("Received from sub-transport: %q.", line) - fields := strings.Fields(strings.TrimRight(line, "\n")) - if len(fields) < 1 { - continue - } - keyword := fields[0] - args := fields[1:] - if keyword == "SMETHOD" && len(args) >= 2 && args[0] == methodName { - bindaddr, err := net.ResolveTCPAddr("tcp", args[1]) - if err != nil { - return nil, err - } - return bindaddr, nil - } else if keyword == "SMETHODS" && len(args) == 1 && args[0] == "DONE" { - break - } - } - return nil, errors.New(fmt.Sprintf("no SMETHOD %s found before SMETHODS DONE", methodName)) -} - -// Escape a string for a ServerTransportOptions serialization. -func escape(s string) string { - repl := strings.NewReplacer(":", "\:", ";", "\;", "=", "\=", "\", "\\") - return repl.Replace(s) -} - -func encodeServerTransportOptions(methodName string, opts pt.Args) string { - if opts == nil { - return "" - } - keys := make([]string, 0, len(opts)) - for key, _ := range opts { - keys = append(keys, key) - } - sort.Strings(keys) - parts := make([]string, 0, len(keys)) - for _, key := range keys { - for _, value := range opts[key] { - parts = append(parts, escape(methodName) + ":" + escape(key) + "=" + escape(value)) - } - } - return strings.Join(parts, ";") -} - -// Represents a server transport plugin configuration like: -// ServerTransportPlugin MethodName exec Command -type ServerTransportPlugin struct { - MethodName string - Command []string - Options pt.Args -} - -func startProcesses(connectBackAddr net.Addr, plugins []ServerTransportPlugin) (bindAddr *net.TCPAddr, procs ProcList, err error) { - var stdout io.ReadCloser - - defer func() { - if err != nil { - // Kill subprocesses before returning error. - procs.Kill() - procs = procs[:0] - } - }() - - bindAddr = connectBackAddr.(*net.TCPAddr) - for _, plugin := range plugins { - // This plugin has its TOR_PT_ORPORT set to the previous - // bindAddr. - cmd := exec.Command(plugin.Command[0], plugin.Command[1:]...) - cmd.Env = []string{ - "TOR_PT_MANAGED_TRANSPORT_VER=1", - "TOR_PT_STATE_LOCATION=" + os.Getenv("TOR_PT_STATE_LOCATION"), - "TOR_PT_EXTENDED_SERVER_PORT=", - "TOR_PT_ORPORT=" + bindAddr.String(), - "TOR_PT_SERVER_TRANSPORTS=" + plugin.MethodName, - "TOR_PT_SERVER_BINDADDR=" + plugin.MethodName + "-127.0.0.1:0", - } - serverTransportOptions := encodeServerTransportOptions(plugin.MethodName, plugin.Options) - if serverTransportOptions != "" { - cmd.Env = append(cmd.Env, "TOR_PT_SERVER_TRANSPORT_OPTIONS=" + serverTransportOptions) - } - log("%s environment %q", cmd.Args[0], cmd.Env) - stdout, err = cmd.StdoutPipe() - if err != nil { - log("Failed to open %s stdout pipe: %s.", cmd.Args[0], err) - return - } - err = cmd.Start() - if err != nil { - log("Failed to start %s: %s.", cmd.Args[0], err) - return - } - log("Exec %s with args %q pid %d.", cmd.Path, cmd.Args, cmd.Process.Pid) - procs = append(procs, cmd.Process) - - bindAddr, err = findBindAddr(stdout, plugin.MethodName) - if err != nil { - log("Failed to find %s bindaddr: %s.", cmd.Args[0], err) - return - } - log("%s bindaddr is %s.", cmd.Args[0], bindAddr) - } - - return bindAddr, procs, err -} - -func acceptLoop(name string, ln *net.TCPListener, ch chan *net.TCPConn) { - for { - conn, err := ln.AcceptTCP() - if err != nil { - log("%s accept: %s.", name, err) - break - } - log("%s connection from %s.", name, conn.RemoteAddr()) - ch <- conn - } - close(ch) -} - -func copyLoop(a, b *net.TCPConn) error { - var wg sync.WaitGroup - - wg.Add(2) - - go func() { - n, err := io.Copy(b, a) - if err != nil { - log("After %d bytes from %s to %s: %s.", n, a.RemoteAddr(), b.RemoteAddr(), err) - } - a.CloseRead() - b.CloseWrite() - wg.Done() - }() - - go func() { - n, err := io.Copy(a, b) - if err != nil { - log("After %d bytes from %s to %s: %s.", n, b.RemoteAddr(), a.RemoteAddr(), err) - } - b.CloseRead() - a.CloseWrite() - wg.Done() - }() - - wg.Wait() - - return nil -} - -func handleExternalConnection(conn *net.TCPConn, chain *Chain) error { - handlerChan <- 1 - defer func() { - handlerChan <- -1 - }() - - chain.Conns.Push(conn) - log("handleExternalConnection: now %d conns buffered.", chain.Conns.Length()) - procsConn, err := net.DialTCP("tcp", nil, chain.ProcsAddr) - if err != nil { - log("error dialing proxy chain: %s.", err) - return err - } - err = copyLoop(conn, procsConn) - if err != nil { - log("error copying between ext and proxy chain: %s.", err) - return err - } - return nil -} - -func handleInternalConnection(conn *net.TCPConn, chain *Chain) error { - handlerChan <- 1 - defer func() { - handlerChan <- -1 - }() - - elem, ok := chain.Conns.Pop() - if !ok { - log("Underflow of connection stack, closing connection.") - err := conn.Close() - if err != nil { - log("Error in close: %s.", err) - } - return errors.New("connection stack underflow") - } - extConn := elem.(*net.TCPConn) - log("Connecting to ORPort using remote addr %s.", extConn.RemoteAddr()) - log("handleInternalConnection: now %d conns buffered.", chain.Conns.Length()) - or, err := pt.DialOr(&ptInfo, extConn.RemoteAddr().String(), chain.MethodName) - if err != nil { - log("Error connecting to ORPort: %s.", err) - return err - } - err = copyLoop(or, conn) - if err != nil { - log("Error copying between int and ORPort: %s.", err) - return err - } - return nil -} - -func listenerLoop(chain *Chain) { - extChan := make(chan *net.TCPConn) - intChan := make(chan *net.TCPConn) - go acceptLoop("external", chain.ExtLn, extChan) - go acceptLoop("internal", chain.IntLn, intChan) - -loop: - for { - select { - case conn, ok := <-extChan: - if !ok { - break loop - } - go handleExternalConnection(conn, chain) - case conn, ok := <-intChan: - if !ok { - break loop - } - go handleInternalConnection(conn, chain) - } - } -} - -func startChain(methodName string, bindaddr *net.TCPAddr, plugins []ServerTransportPlugin) (*Chain, error) { - chain := &Chain{} - var err error - - chain.MethodName = methodName - chain.Conns = NewStack(connStackSize) - - // Start internal listener (the proxy chain connects back to this). - chain.IntLn, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}) - if err != nil { - log("Error opening internal listener: %s.", err) - chain.Shutdown() - return nil, err - } - log("Internal listener on %s.", chain.IntLn.Addr()) - - // Start subprocesses. - chain.ProcsAddr, chain.Procs, err = startProcesses(chain.IntLn.Addr(), plugins) - if err != nil { - log("Error starting proxy chain: %s.", err) - chain.Shutdown() - return nil, err - } - log("Proxy chain on %s.", chain.ProcsAddr) - - // Start external Internet listener (listens on bindaddr and connects to - // proxy chain). - chain.ExtLn, err = net.ListenTCP("tcp", bindaddr) - if err != nil { - log("Error opening external listener: %s.", err) - chain.Shutdown() - return nil, err - } - log("External listener on %s.", chain.ExtLn.Addr()) - - go listenerLoop(chain) - - return chain, nil -} - -type Configuration struct { - // Map from method names to command strings. - Transports map[string][]string - // Map from method names to ServerTransportOptions. - Options map[string]pt.Args - // Map from tor-friendly names like "obfs3_websocket" to systematic - // names like "obfs3|websocket". - Aliases map[string]string -} - -func (conf *Configuration) MethodNames() []string { - result := make([]string, 0) - // We understand all the single transports - for k, _ := range conf.Transports { - result = append(result, k) - } - // and aliases. - for k, _ := range conf.Aliases { - result = append(result, k) - } - return result -} - -// Parse a (possibly composed) method name into a slice of single method names. -func (conf *Configuration) ParseMethodName(methodName string) []string { - if name, ok := conf.Aliases[methodName]; ok { - methodName = name - } - return strings.Split(methodName, "|") -} - -func (conf *Configuration) PluginList(methodName string) ([]ServerTransportPlugin, error) { - names := conf.ParseMethodName(methodName) - stp := make([]ServerTransportPlugin, 0) - for _, name := range names { - command, ok := conf.Transports[name] - if !ok { - return nil, errors.New(fmt.Sprintf("no transport named %q", name)) - } - options := conf.Options[name] - stp = append(stp, ServerTransportPlugin{name, command, options}) - } - return stp, nil -} - -// Simulate loading a configuration file. -func getConfiguration() (conf *Configuration) { - conf = new(Configuration) - conf.Transports = make(map[string][]string) - conf.Aliases = make(map[string]string) - conf.Options = make(map[string]pt.Args) - conf.Transports["obfs3"] = []string{"obfsproxy", "managed"} - conf.Transports["websocket"] = []string{"pt-websocket-server"} - // conf.Options["obfs3"] = make(pt.Args) - // conf.Options["obfs3"]["secret"] = []string{"foo"} - conf.Aliases["obfs3_websocket"] = "obfs3|websocket" - return conf -} - -func main() { - var logFilename string - var port int - - flag.Usage = usage - flag.StringVar(&logFilename, "log", "", "log file to write to") - flag.IntVar(&port, "port", 0, "port to listen on if unspecified by Tor") - flag.Parse() - - if logFilename != "" { - f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) - if err != nil { - fmt.Fprintf(os.Stderr, "Can't open log file %q: %s.\n", logFilename, err.Error()) - os.Exit(1) - } - logFile = f - } - - log("Starting.") - - var err error - conf := getConfiguration() - ptInfo, err = pt.ServerSetup(conf.MethodNames()) - if err != nil { - log("Error in ServerSetup: %s", err) - os.Exit(1) - } - - chains := make([]*Chain, 0) - for _, bindaddr := range ptInfo.Bindaddrs { - // Override tor's requested port (which is 0 if this transport - // has not been run before) with the one requested by the --port - // option. - if port != 0 { - bindaddr.Addr.Port = port - } - - plugins, err := conf.PluginList(bindaddr.MethodName) - if err != nil { - pt.SmethodError(bindaddr.MethodName, err.Error()) - continue - } - - chain, err := startChain(bindaddr.MethodName, bindaddr.Addr, plugins) - if err != nil { - pt.SmethodError(bindaddr.MethodName, err.Error()) - continue - } - pt.Smethod(bindaddr.MethodName, chain.ExtLn.Addr()) - chains = append(chains, chain) - } - pt.SmethodsDone() - - var numHandlers int = 0 - var sig os.Signal - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) - - sig = nil - for sig == nil { - select { - case n := <-handlerChan: - numHandlers += n - case sig = <-sigChan: - } - } - log("Got first signal %q with %d running handlers.", sig, numHandlers) - for _, chain := range chains { - chain.CloseListeners() - chain.Procs.Signal(sig) - } - - if sig == syscall.SIGTERM { - log("Caught signal %q, exiting.", sig) - return - } - - sig = nil - for sig == nil && numHandlers != 0 { - select { - case n := <-handlerChan: - numHandlers += n - log("%d remaining handlers.", numHandlers) - case sig = <-sigChan: - } - } - if sig != nil { - log("Got second signal %q with %d running handlers.", sig, numHandlers) - for _, chain := range chains { - chain.Procs.Signal(sig) - } - } - - log("Exiting.") -} diff --git a/pt_test.go b/pt_test.go deleted file mode 100644 index 35d3aab..0000000 --- a/pt_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import "testing" - -import "git.torproject.org/pluggable-transports/goptlib.git" - -func TestEncodeServerTransportOptions(t *testing.T) { - tests := [...]struct { - methodName string - opts pt.Args - expected string - }{ - { - "foo", - pt.Args{}, - "", - }, - { - "foo", - pt.Args{ - "key": []string{"value1", "value2"}, - "something": []string{"value1", "value2"}, - }, - "foo:key=value1;foo:key=value2;foo:something=value1;foo:something=value2", - }, - { - "m:m", - pt.Args{"k;k": []string{"v=v", "b\b"}}, - "m\:m:k\;k=v\=v;m\:m:k\;k=b\\b", - }, - } - - for _, test := range tests { - output := encodeServerTransportOptions(test.methodName, test.opts) - if output != test.expected { - t.Errorf("%q %q → %q (expected %q)", test.methodName, test.opts, output, test.expected) - } - } -} diff --git a/setup.py b/setup.py deleted file mode 100644 index 66f2262..0000000 --- a/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -from distutils.core import setup -import py2exe - -# if py2exe complains "can't find P", try one of the following workarounds: -# -# a. py2exe doesn't support zipped eggs - http://www.py2exe.org/index.cgi/ExeWithEggs -# You should give the --always-unzip option to easy_install, or you can use setup.py directly -# $ python setup.py install --record install.log --single-version-externally-managed -# Don't forget to remove the previous zipped egg. -# -# b. Add an empty __init__.py to the P/ top-level directory, if it's missing -# - this is due to a bug (or misleading documentation) in python's imp.find_module() - -setup( - console=["obfs-flash-client"], - zipfile="py2exe-obfs-flash-client.zip", - options={ - "py2exe": { - "includes": ["pyptlib", "twisted", "txsocksx"], - "packages": ["ometa", "terml", "zope.interface"], - }, - }, -) diff --git a/stack.go b/stack.go deleted file mode 100644 index 16cddd6..0000000 --- a/stack.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import "sync" - -// A fixed-size stack. If a push exceeds the capacity of the underlying slice, -// the least recently added element is lost. -type Stack struct { - buf []interface{} - base, head int - m sync.Mutex -} - -// Create a stack with the given capacity. -func NewStack(capacity int) *Stack { - return &Stack{buf: make([]interface{}, capacity+1)} -} - -func (s *Stack) clamp(x int) int { - x = x % len(s.buf) - if x < 0 { - x += len(s.buf) - } - return x -} - -func (s *Stack) Length() int { - s.m.Lock() - defer s.m.Unlock() - return s.clamp(s.head - s.base) -} - -// If this push causes the stack to overflow, the first return value is the -// discarded element and the second return value is false. Otherwise the second -// return value is true. -func (s *Stack) Push(x interface{}) (interface{}, bool) { - s.m.Lock() - defer s.m.Unlock() - s.buf[s.head] = x - s.head = s.clamp(s.head + 1) - if s.head == s.base { - s.base = s.clamp(s.base + 1) - return s.buf[s.head], false - } - return nil, true -} - -// The second return value is false if the stack was empty, and true otherwise. -// The first return value is defined only when the second is true. -func (s *Stack) Pop() (interface{}, bool) { - s.m.Lock() - defer s.m.Unlock() - if s.head == s.base { - return nil, false - } - s.head = s.clamp(s.head - 1) - return s.buf[s.head], true -} diff --git a/stack_test.go b/stack_test.go deleted file mode 100644 index 3188a2d..0000000 --- a/stack_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package main - -import "testing" - -// Test operations on a zero-capacity stack. -func TestZeroCapacity(t *testing.T) { - var ok bool - - s := NewStack(0) - if s.Length() != 0 { - t.Fatal("initial length is not 0") - } - _, ok = s.Push("a") - if ok || s.Length() != 0 { - t.Fatal() - } - _, ok = s.Pop() - if ok || s.Length() != 0 { - t.Fatal() - } -} - -func TestPushPop(t *testing.T) { - var x interface{} - var ok bool - - s := NewStack(3) - - // Push elems. - if s.Length() != 0 { - t.Fatal("initial length is not 0") - } - _, ok = s.Push("a") - if !ok || s.Length() != 1 { - t.Fatal() - } - _, ok = s.Push("b") - if !ok || s.Length() != 2 { - t.Fatal() - } - - // Pop to empty. - x, ok = s.Pop() - if !ok || x != "b" || s.Length() != 1 { - t.Fatal() - } - x, ok = s.Pop() - if !ok || x != "a" || s.Length() != 0 { - t.Fatal() - } - // Pop one past empty. - x, ok = s.Pop() - if ok { - t.Fatal() - } - - // Push to capacity. - s.Push("c") - s.Push("d") - _, ok = s.Push("e") - if !ok || s.Length() != 3 { - t.Fatal("push to capacity is not at capacity") - } - // Push one past capacity. - x, ok = s.Push("f") - if ok || s.Length() != 3 { - t.Fatal() - } - if x != "c" { - t.Fatal("mismatch in overwritten element") - } - - // Pop to empty. - x, ok = s.Pop() - if !ok || x != "f" || s.Length() != 2 { - t.Fatal() - } - x, ok = s.Pop() - if !ok || x != "e" || s.Length() != 1 { - t.Fatal() - } - x, ok = s.Pop() - if !ok || x != "d" || s.Length() != 0 { - t.Fatal() - } - // Pop one past empty. - x, ok = s.Pop() - if ok { - t.Fatal() - } -} - -// Test underflow of an initially empty stack. -func TestUnderflowEmpty(t *testing.T) { - var ok bool - - s := NewStack(3) - _, ok = s.Pop() - if ok { - t.Fatal() - } -} - -// Test underflow of a stack that had been full. -func TestUnderflowFull(t *testing.T) { - var ok bool - - s := NewStack(3) - s.Push("a") - s.Push("b") - s.Push("c") - s.Push("d") - s.Pop() - s.Pop() - s.Pop() - _, ok = s.Pop() - if ok { - t.Fatal() - } -} diff --git a/torrc b/torrc deleted file mode 100644 index 32c558e..0000000 --- a/torrc +++ /dev/null @@ -1,5 +0,0 @@ -UseBridges 1 -Bridge obfs3_flashproxy 127.0.0.1:9000 -LearnCircuitBuildTimeout 0 -CircuitBuildTimeout 300 -ClientTransportPlugin obfs3_flashproxy exec ./obfs-flash-client diff --git a/torrc-server b/torrc-server deleted file mode 100644 index 55b4a31..0000000 --- a/torrc-server +++ /dev/null @@ -1,5 +0,0 @@ -ORPort 9999 -ExtORPort 5555 -BridgeRelay 1 -SocksPort 0 -ServerTransportPlugin obfs3_websocket exec ./obfs-flash-server
tor-commits@lists.torproject.org