Permalink
Cannot retrieve contributors at this time
524 lines (400 sloc)
17.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| # -*- coding: utf-8 -*- | |
| import sys | |
| import socket | |
| import string | |
| import traceback | |
| import fteproxy.conf | |
| import fteproxy.defs | |
| import fteproxy.record_layer | |
| import fteproxy.regex2dfa | |
| import fte.encoder | |
| class InvalidRoleException(Exception): | |
| pass | |
| class NegotiationFailedException(Exception): | |
| pass | |
| class ChannelNotReadyException(Exception): | |
| pass | |
| class NegotiateTimeoutException(Exception): | |
| """Raised when negotiation fails to complete after """ + str(fteproxy.conf.getValue('runtime.fteproxy.negotiate.timeout')) + """ seconds. | |
| """ | |
| pass | |
| def fatal_error(msg): | |
| if fteproxy.conf.getValue('runtime.loglevel') in [1,2,3]: | |
| print 'ERROR:', msg | |
| sys.exit(1) | |
| def warn(msg): | |
| if fteproxy.conf.getValue('runtime.loglevel') in [2,3]: | |
| print 'WARN:', msg | |
| def info(msg): | |
| if fteproxy.conf.getValue('runtime.loglevel') in [3]: | |
| print 'INFO:', msg | |
| class NegotiateCell(object): | |
| _CELL_SIZE = 64 | |
| _PADDING_LEN = 32 | |
| _PADDING_CHAR = '\x00' | |
| _DATE_FORMAT = 'YYYYMMDD' | |
| def __init__(self): | |
| self._def_file = "" | |
| self._language = "" | |
| def setDefFile(self, def_file): | |
| self._def_file = def_file | |
| def getDefFile(self): | |
| return self._def_file | |
| def setLanguage(self, language): | |
| self._language = language | |
| def getLanguage(self): | |
| return self._language | |
| def toString(self): | |
| retval = '' | |
| retval += self._def_file | |
| retval += self._language | |
| retval = string.rjust( | |
| retval, NegotiateCell._CELL_SIZE, NegotiateCell._PADDING_CHAR) | |
| assert retval[:NegotiateCell._PADDING_LEN] == NegotiateCell._PADDING_CHAR * \ | |
| NegotiateCell._PADDING_LEN | |
| return retval | |
| def fromString(self, negotiate_cell_str): | |
| assert len(negotiate_cell_str) == NegotiateCell._CELL_SIZE | |
| assert negotiate_cell_str[ | |
| :NegotiateCell._PADDING_LEN] == NegotiateCell._PADDING_CHAR * NegotiateCell._PADDING_LEN | |
| negotiate_cell_str = negotiate_cell_str.strip( | |
| NegotiateCell._PADDING_CHAR) | |
| # 8==len(YYYYMMDD) | |
| def_file = negotiate_cell_str[:len(NegotiateCell._DATE_FORMAT)] | |
| language = negotiate_cell_str[len(NegotiateCell._DATE_FORMAT):] | |
| negotiate_cell = NegotiateCell() | |
| negotiate_cell.setDefFile(def_file) | |
| negotiate_cell.setLanguage(language) | |
| return negotiate_cell | |
| class NegotiationManager(object): | |
| def __init__(self, K1, K2): | |
| self._negotiationComplete = False | |
| self._K1 = K1 | |
| self._K2 = K2 | |
| def getNegotiationComplete(self): | |
| return self._negotiationComplete | |
| def _acceptNegotiation(self, data): | |
| languages = fteproxy.defs.load_definitions() | |
| for incoming_language in languages.keys(): | |
| try: | |
| if incoming_language.endswith('response'): | |
| continue | |
| incoming_regex = fteproxy.defs.getRegex(incoming_language) | |
| incoming_fixed_slice = fteproxy.defs.getFixedSlice( | |
| incoming_language) | |
| incoming_decoder = fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(incoming_regex), | |
| incoming_fixed_slice, self._K1, self._K2) | |
| decoder = fteproxy.record_layer.Decoder(decoder=incoming_decoder) | |
| decoder.push(data) | |
| negotiate_cell = decoder.pop(oneCell=True) | |
| NegotiateCell().fromString(negotiate_cell) | |
| return [negotiate_cell, decoder._buffer] | |
| except Exception as e: | |
| fteproxy.info('Failed to decode first message as '+incoming_language+': '+str(e)) | |
| raise NegotiationFailedException() | |
| def _init_encoders(self, | |
| outgoing_regex, outgoing_fixed_slice, | |
| incoming_regex, incoming_fixed_slice): | |
| encoder = None | |
| decoder = None | |
| if outgoing_regex != None and outgoing_fixed_slice != -1: | |
| outgoing_encoder = fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(outgoing_regex), | |
| outgoing_fixed_slice, self._K1, self._K2) | |
| encoder = fteproxy.record_layer.Encoder(encoder=outgoing_encoder) | |
| if incoming_regex != None and incoming_fixed_slice != -1: | |
| incoming_decoder = fte.encoder.DfaEncoder(fteproxy.regex2dfa.regex2dfa(incoming_regex), | |
| incoming_fixed_slice, self._K1, self._K2) | |
| decoder = fteproxy.record_layer.Decoder(decoder=incoming_decoder) | |
| return [encoder, decoder] | |
| def _makeNegotiationCell(self, encoder): | |
| negotiate_cell = NegotiateCell() | |
| def_file = fteproxy.conf.getValue('fteproxy.defs.release') | |
| negotiate_cell.setDefFile(def_file) | |
| language = fteproxy.conf.getValue('runtime.state.upstream_language') | |
| language = language[:-len('-request')] | |
| negotiate_cell.setLanguage(language) | |
| encoder.push(negotiate_cell.toString()) | |
| data = encoder.pop() | |
| return data | |
| def makeClientNegotiationCell(self, | |
| outgoing_regex, outgoing_fixed_slice, | |
| incoming_regex, incoming_fixed_slice): | |
| [encoder, decoder] = self._init_encoders( | |
| outgoing_regex, outgoing_fixed_slice, incoming_regex, incoming_fixed_slice) | |
| return self._makeNegotiationCell(encoder) | |
| def doServerSideNegotiation(self, data): | |
| [negotiate_cell, remaining_buffer] = self._acceptNegotiation(data) | |
| negotiate = NegotiateCell().fromString(negotiate_cell) | |
| outgoing_language = negotiate.getLanguage() + '-response' | |
| incoming_language = negotiate.getLanguage() + '-request' | |
| outgoing_regex = fteproxy.defs.getRegex(outgoing_language) | |
| outgoing_fixed_slice = fteproxy.defs.getFixedSlice(outgoing_language) | |
| incoming_regex = fteproxy.defs.getRegex(incoming_language) | |
| incoming_fixed_slice = fteproxy.defs.getFixedSlice(incoming_language) | |
| [encoder, decoder] = self._init_encoders( | |
| outgoing_regex, outgoing_fixed_slice, incoming_regex, incoming_fixed_slice) | |
| decoder.push(remaining_buffer) | |
| return [encoder, decoder] | |
| class FTEHelper(object): | |
| def _processRecv(self, data): | |
| retval = data | |
| if self._isServer and not self._negotiationComplete: | |
| try: | |
| self._preNegotiationBuffer_incoming += data | |
| [encoder, decoder] = self._negotiation_manager.doServerSideNegotiation( | |
| self._preNegotiationBuffer_incoming) | |
| self._encoder = encoder | |
| self._decoder = decoder | |
| self._preNegotiationBuffer_incoming = '' | |
| self._negotiationComplete = True | |
| retval = '' | |
| except Exception as e: | |
| raise ChannelNotReadyException() | |
| return retval | |
| def _processSend(self): | |
| retval = '' | |
| if self._isClient and not self._negotiationComplete: | |
| [encoder, decoder] = self._negotiation_manager._init_encoders( | |
| self._outgoing_regex, | |
| self._outgoing_fixed_slice, | |
| self._incoming_regex, | |
| self._incoming_fixed_slice) | |
| self._encoder = encoder | |
| self._decoder = decoder | |
| negotiation_cell = self._negotiation_manager.makeClientNegotiationCell( | |
| self._outgoing_regex, self._outgoing_fixed_slice, | |
| self._incoming_regex, self._incoming_fixed_slice) | |
| retval = negotiation_cell | |
| self._negotiationComplete = True | |
| return retval | |
| class _FTESocketWrapper(FTEHelper, object): | |
| def __init__(self, _socket, | |
| outgoing_regex=None, outgoing_fixed_slice=-1, | |
| incoming_regex=None, incoming_fixed_slice=-1, | |
| K1=None, K2=None): | |
| self._socket = _socket | |
| self._outgoing_regex = outgoing_regex | |
| self._outgoing_fixed_slice = outgoing_fixed_slice | |
| self._incoming_regex = incoming_regex | |
| self._incoming_fixed_slice = incoming_fixed_slice | |
| self._K1 = K1 | |
| self._K2 = K2 | |
| self._negotiation_manager = NegotiationManager(K1, K2) | |
| self._negotiationComplete = False | |
| self._isServer = (outgoing_regex is None and incoming_regex is None) | |
| self._isClient = ( | |
| outgoing_regex is not None and incoming_regex is not None) | |
| self._incoming_buffer = '' | |
| self._preNegotiationBuffer_outgoing = '' | |
| self._preNegotiationBuffer_incoming = '' | |
| def fileno(self): | |
| return self._socket.fileno() | |
| def recv(self, bufsize): | |
| # <HACK> | |
| # Required to deal with case when client attempts to recv | |
| # before sending. This checks to ensure that a negotiate | |
| # cell is sent no matter what the client does first. | |
| to_send = self._processSend() | |
| if to_send: | |
| numbytes = self._socket.send(to_send) | |
| assert numbytes == len(to_send) | |
| # </HACK> | |
| try: | |
| while True: | |
| data = self._socket.recv(bufsize) | |
| noData = (data == '') | |
| data = self._processRecv(data) | |
| if noData and not self._incoming_buffer and not self._decoder._buffer: | |
| return '' | |
| self._decoder.push(data) | |
| while True: | |
| frag = self._decoder.pop() | |
| if not frag: | |
| break | |
| self._incoming_buffer += frag | |
| if self._incoming_buffer: | |
| break | |
| retval = self._incoming_buffer | |
| self._incoming_buffer = '' | |
| except ChannelNotReadyException: | |
| raise socket.timeout | |
| return retval | |
| def send(self, data): | |
| to_send = self._processSend() | |
| if to_send: | |
| self._socket.sendall(to_send) | |
| self._encoder.push(data) | |
| while True: | |
| to_send = self._encoder.pop() | |
| if not to_send: | |
| break | |
| self._socket.sendall(to_send) | |
| return len(data) | |
| def sendall(self, data): | |
| return self.send(data) | |
| def gettimeout(self): | |
| return self._socket.gettimeout() | |
| def settimeout(self, val): | |
| return self._socket.settimeout(val) | |
| def shutdown(self, flags): | |
| return self._socket.shutdown(flags) | |
| def close(self): | |
| return self._socket.close() | |
| def connect(self, addr): | |
| return self._socket.connect(addr) | |
| def accept(self): | |
| conn, addr = self._socket.accept() | |
| conn = _FTESocketWrapper(conn, | |
| self._outgoing_regex, self._outgoing_fixed_slice, | |
| self._incoming_regex, self._incoming_fixed_slice, | |
| self._K1, self._K2) | |
| return conn, addr | |
| def bind(self, addr): | |
| return self._socket.bind(addr) | |
| def listen(self, N): | |
| return self._socket.listen(N) | |
| def wrap_socket(sock, | |
| outgoing_regex=None, outgoing_fixed_slice=-1, | |
| incoming_regex=None, incoming_fixed_slice=-1, | |
| K1=None, K2=None): | |
| """``fteproxy.wrap_socket`` turns an existing socket into an fteproxy socket. | |
| The input parameter ``sock`` is the socket to wrap. | |
| The parameter ``outgoing_regex`` specifies the format of the messages | |
| to send via the socket. The ``outgoing_fixed_slice`` parameter specifies the | |
| maximum length of the strings in ``outgoing_regex``. | |
| The parameters ``incoming_regex`` and ``incoming_fixed_slice`` are defined | |
| similarly. | |
| The optional parameters ``K1`` and ``K2`` specify 128-bit keys to be used | |
| in FTE's underlying AE scheme. If specified, these values must be 16-byte | |
| hex strings. | |
| """ | |
| assert K1 == None or len(K1) == 16 | |
| assert K2 == None or len(K2) == 16 | |
| socket_wrapped = _FTESocketWrapper( | |
| sock, | |
| outgoing_regex, outgoing_fixed_slice, | |
| incoming_regex, incoming_fixed_slice, | |
| K1, K2) | |
| return socket_wrapped | |
| import obfsproxy.network.network as network | |
| import obfsproxy.network.socks as socks | |
| import obfsproxy.network.extended_orport as extended_orport | |
| import obfsproxy.transports.base | |
| import twisted.internet | |
| class FTETransport(FTEHelper, obfsproxy.transports.base.BaseTransport): | |
| def __init__(self, pt_config=None): | |
| self.circuit = None | |
| self._isClient = (fteproxy.conf.getValue('runtime.mode') == 'client') | |
| self._isServer = not self._isClient | |
| if self._isClient: | |
| outgoing_language = fteproxy.conf.getValue( | |
| 'runtime.state.upstream_language') | |
| incoming_language = fteproxy.conf.getValue( | |
| 'runtime.state.downstream_language') | |
| self._outgoing_regex = fteproxy.defs.getRegex(outgoing_language) | |
| self._outgoing_fixed_slice = fteproxy.defs.getFixedSlice( | |
| outgoing_language) | |
| self._incoming_regex = fteproxy.defs.getRegex(incoming_language) | |
| self._incoming_fixed_slice = fteproxy.defs.getFixedSlice( | |
| incoming_language) | |
| else: | |
| self._outgoing_regex = None | |
| self._outgoing_fixed_slice = -1 | |
| self._incoming_regex = None | |
| self._incoming_fixed_slice = -1 | |
| self._encoder = None | |
| self._decoder = None | |
| self._negotiation_manager = NegotiationManager(K1, K2) | |
| self._negotiationComplete = False | |
| self._incoming_buffer = '' | |
| self._preNegotiationBuffer_outgoing = '' | |
| self._preNegotiationBuffer_incoming = '' | |
| def receivedDownstream(self, data, circuit=None): | |
| """decode fteproxy stream""" | |
| circuit = self.circuit if circuit == None else circuit | |
| try: | |
| data = data.read() | |
| data = self._processRecv(data) | |
| if self._decoder: | |
| self._decoder.push(data) | |
| while True: | |
| frag = self._decoder.pop() | |
| if not frag: | |
| break | |
| circuit.upstream.write(frag) | |
| except ChannelNotReadyException: | |
| pass | |
| def receivedUpstream(self, data, circuit=None): | |
| """encode fteproxy stream""" | |
| circuit = self.circuit if circuit == None else circuit | |
| to_send = self._processSend() | |
| if to_send: | |
| circuit.downstream.write(to_send) | |
| data = data.read() | |
| if self._encoder: | |
| self._encoder.push(data) | |
| while True: | |
| to_send = self._encoder.pop() | |
| if not to_send: | |
| break | |
| circuit.downstream.write(to_send) | |
| class FTETransportClient(FTETransport): | |
| pass | |
| class FTETransportServer(FTETransport): | |
| pass | |
| def launch_transport_listener(transport, bindaddr, role, remote_addrport, pt_config, ext_or_cookie_file=None): | |
| """ | |
| Launch a listener for 'transport' in role 'role' (socks/client/server/ext_server). | |
| If 'bindaddr' is set, then listen on bindaddr. Otherwise, listen | |
| on an ephemeral port on localhost. | |
| 'remote_addrport' is the TCP/IP address of the other end of the | |
| circuit. It's not used if we are in 'socks' role. | |
| 'pt_config' contains configuration options (such as the state location) | |
| which are of interest to the pluggable transport. | |
| 'ext_or_cookie_file' is the filesystem path where the Extended | |
| ORPort Authentication cookie is stored. It's only used in | |
| 'ext_server' mode. | |
| Return a tuple (addr, port) representing where we managed to bind. | |
| Throws obfsproxy.transports.transports.TransportNotFound if the | |
| transport could not be found. | |
| Throws twisted.internet.error.CannotListenError if the listener | |
| could not be set up. | |
| """ | |
| listen_host = bindaddr[0] if bindaddr else 'localhost' | |
| listen_port = int(bindaddr[1]) if bindaddr else 0 | |
| if role == 'socks': | |
| transport_class = FTETransportClient | |
| if hasattr(socks, "OBFSSOCKSv5Factory"): | |
| # obfsproxy >= 0.2.7 provides SOCKS5. | |
| factory = socks.OBFSSOCKSv5Factory(transport_class, pt_config) | |
| pt_config.fte_client_socks_version = 5 | |
| elif hasattr(socks, "SOCKSv4Factory"): | |
| # obfsproxy < 0.2.7 provides SOCKS4. | |
| factory = socks.SOCKSv4Factory(transport_class, pt_config) | |
| pt_config.fte_client_socks_version = 4 | |
| else: | |
| # This will only happen if the obfsproxy people change the socks | |
| # code again. This really is a dependency issue, so raise an | |
| # ImportError. | |
| raise ImportError("Failed to setup an obfsproxy SOCKS server factory") | |
| elif role == 'ext_server': | |
| assert(remote_addrport and ext_or_cookie_file) | |
| transport_class = FTETransportServer | |
| factory = extended_orport.ExtORPortServerFactory( | |
| remote_addrport, ext_or_cookie_file, transport, transport_class, pt_config) | |
| elif role == 'client': | |
| assert(remote_addrport) | |
| transport_class = FTETransportClient | |
| factory = network.StaticDestinationServerFactory( | |
| remote_addrport, role, transport_class, pt_config) | |
| elif role == 'server': | |
| assert(remote_addrport) | |
| transport_class = FTETransportServer | |
| factory = network.StaticDestinationServerFactory( | |
| remote_addrport, role, transport_class, pt_config) | |
| else: | |
| raise InvalidRoleException() | |
| addrport = twisted.internet.reactor.listenTCP( | |
| listen_port, factory, interface=listen_host) | |
| return (addrport.getHost().host, addrport.getHost().port) |