1# -*- coding: utf-8 -*- 2 3from __future__ import absolute_import 4from __future__ import print_function 5from __future__ import with_statement 6 7from twisted.web.iweb import IAgentEndpointFactory 8from twisted.web.client import Agent 9from twisted.internet.defer import inlineCallbacks, returnValue, Deferred 10from twisted.internet.endpoints import TCP4ClientEndpoint, UNIXClientEndpoint 11 12from zope.interface import implementer 13 14from txtorcon.socks import TorSocksEndpoint 15from txtorcon.log import txtorlog 16from txtorcon.util import SingleObserver 17 18 19@implementer(IAgentEndpointFactory) 20class _AgentEndpointFactoryUsingTor(object): 21 def __init__(self, reactor, tor_socks_endpoint): 22 self._reactor = reactor 23 self._proxy_ep = SingleObserver() 24 # if _proxy_ep is Deferred, but we get called twice, we must 25 # remember the resolved object here 26 if isinstance(tor_socks_endpoint, Deferred): 27 tor_socks_endpoint.addCallback(self._set_proxy) 28 else: 29 self._proxy_ep.fire(tor_socks_endpoint) 30 31 def _set_proxy(self, p): 32 self._proxy_ep.fire(p) 33 return p 34 35 def endpointForURI(self, uri): 36 return TorSocksEndpoint( 37 self._proxy_ep.when_fired(), 38 uri.host, 39 uri.port, 40 tls=(uri.scheme == b'https'), 41 ) 42 43 44@implementer(IAgentEndpointFactory) 45class _AgentEndpointFactoryForCircuit(object): 46 def __init__(self, reactor, tor_socks_endpoint, circ): 47 self._reactor = reactor 48 self._socks_ep = tor_socks_endpoint 49 self._circ = circ 50 51 def endpointForURI(self, uri): 52 """IAgentEndpointFactory API""" 53 torsocks = TorSocksEndpoint( 54 self._socks_ep, 55 uri.host, uri.port, 56 tls=uri.scheme == b'https', 57 ) 58 from txtorcon.circuit import TorCircuitEndpoint 59 return TorCircuitEndpoint( 60 self._reactor, self._circ._torstate, self._circ, torsocks, 61 ) 62 63 64def tor_agent(reactor, socks_endpoint, circuit=None, pool=None): 65 """ 66 This is the low-level method used by 67 :meth:`txtorcon.Tor.web_agent` and 68 :meth:`txtorcon.Circuit.web_agent` -- probably you should call one 69 of those instead. 70 71 :returns: a Deferred that fires with an object that implements 72 :class:`twisted.web.iweb.IAgent` and is thus suitable for passing 73 to ``treq`` as the ``agent=`` kwarg. Of course can be used 74 directly; see `using Twisted web cliet 75 <http://twistedmatrix.com/documents/current/web/howto/client.html>`_. 76 77 :param reactor: the reactor to use 78 79 :param circuit: If supplied, a particular circuit to use 80 81 :param socks_endpoint: Deferred that fires w/ 82 IStreamClientEndpoint (or IStreamClientEndpoint instance) 83 which points at a SOCKS5 port of our Tor 84 85 :param pool: passed on to the Agent (as ``pool=``) 86 """ 87 88 if socks_endpoint is None: 89 raise ValueError( 90 "Must provide socks_endpoint as Deferred or IStreamClientEndpoint" 91 ) 92 if circuit is not None: 93 factory = _AgentEndpointFactoryForCircuit(reactor, socks_endpoint, circuit) 94 else: 95 factory = _AgentEndpointFactoryUsingTor(reactor, socks_endpoint) 96 return Agent.usingEndpointFactory(reactor, factory, pool=pool) 97 98 99@inlineCallbacks 100def agent_for_socks_port(reactor, torconfig, socks_config, pool=None): 101 """ 102 This returns a Deferred that fires with an object that implements 103 :class:`twisted.web.iweb.IAgent` and is thus suitable for passing 104 to ``treq`` as the ``agent=`` kwarg. Of course can be used 105 directly; see `using Twisted web cliet 106 <http://twistedmatrix.com/documents/current/web/howto/client.html>`_. If 107 you have a :class:`txtorcon.Tor` instance already, the preferred 108 API is to call :meth:`txtorcon.Tor.web_agent` on it. 109 110 :param torconfig: a :class:`txtorcon.TorConfig` instance. 111 112 :param socks_config: anything valid for Tor's ``SocksPort`` 113 option. This is generally just a TCP port (e.g. ``9050``), but 114 can also be a unix path like so ``unix:/path/to/socket`` (Tor 115 has restrictions on the ownership/permissions of the directory 116 containing ``socket``). If the given SOCKS option is not 117 already available in the underlying Tor instance, it is 118 re-configured to add the SOCKS option. 119 """ 120 # :param tls: True (the default) will use Twisted's default options 121 # with the hostname in the URI -- that is, TLS verification 122 # similar to a Browser. Otherwise, you can pass whatever Twisted 123 # returns for `optionsForClientTLS 124 # <https://twistedmatrix.com/documents/current/api/twisted.internet.ssl.optionsForClientTLS.html>`_ 125 126 socks_config = str(socks_config) # sadly, all lists are lists-of-strings to Tor :/ 127 if socks_config not in torconfig.SocksPort: 128 txtorlog.msg("Adding SOCKS port '{}' to Tor".format(socks_config)) 129 torconfig.SocksPort.append(socks_config) 130 try: 131 yield torconfig.save() 132 except Exception as e: 133 raise RuntimeError( 134 "Failed to reconfigure Tor with SOCKS port '{}': {}".format( 135 socks_config, str(e) 136 ) 137 ) 138 139 if socks_config.startswith('unix:'): 140 socks_ep = UNIXClientEndpoint(reactor, socks_config[5:]) 141 else: 142 if ':' in socks_config: 143 host, port = socks_config.split(':', 1) 144 else: 145 host = '127.0.0.1' 146 port = int(socks_config) 147 socks_ep = TCP4ClientEndpoint(reactor, host, port) 148 149 returnValue( 150 Agent.usingEndpointFactory( 151 reactor, 152 _AgentEndpointFactoryUsingTor(reactor, socks_ep), 153 pool=pool, 154 ) 155 ) 156