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