119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
from __future__ import print_function, unicode_literals
|
|
|
|
import sys
|
|
|
|
from attr import attrib, attrs
|
|
from twisted.internet.defer import inlineCallbacks, returnValue
|
|
from twisted.internet.endpoints import clientFromString
|
|
from zope.interface.declarations import directlyProvides
|
|
|
|
from . import _interfaces, errors
|
|
from .timing import DebugTiming
|
|
|
|
try:
|
|
import txtorcon
|
|
except ImportError:
|
|
txtorcon = None
|
|
|
|
|
|
@attrs
|
|
class SocksOnlyTor(object):
|
|
_reactor = attrib()
|
|
|
|
def stream_via(self, host, port, tls=False):
|
|
return txtorcon.TorClientEndpoint(
|
|
host,
|
|
port,
|
|
socks_endpoint=None, # tries localhost:9050 and 9150
|
|
tls=tls,
|
|
reactor=self._reactor,
|
|
)
|
|
|
|
|
|
@inlineCallbacks
|
|
def get_tor(reactor,
|
|
launch_tor=False,
|
|
tor_control_port=None,
|
|
timing=None,
|
|
stderr=sys.stderr):
|
|
"""
|
|
If launch_tor=True, I will try to launch a new Tor process, ask it
|
|
for its SOCKS and control ports, and use those for outbound
|
|
connections (and inbound onion-service listeners, if necessary).
|
|
|
|
Otherwise if tor_control_port is provided, I will attempt to connect
|
|
to an existing Tor's control port at the endpoint it specifies. I'll
|
|
ask that Tor for its SOCKS port.
|
|
|
|
With no arguments, I will try to connect to an existing Tor's control
|
|
port at the usual places: [unix:/var/run/tor/control,
|
|
tcp:127.0.0.1:9051, tcp:127.0.0.1:9151]. If any are successful, I'll
|
|
ask that Tor for its SOCKS port. If none are successful, I'll
|
|
attempt to do SOCKS to the usual places: [tcp:127.0.0.1:9050,
|
|
tcp:127.0.0.1:9150].
|
|
|
|
If I am unable to make a SOCKS connection, the initial connection to
|
|
the Rendezvous Server will fail, and the program will terminate.
|
|
|
|
Control-port connections can only succeed if I can authenticate (by
|
|
reading a cookie file named by the Tor process), so the current user
|
|
must have permission to read that file (either they started Tor, e.g.
|
|
TorBrowser, or they are in a unix group that's been given access,
|
|
e.g. debian-tor).
|
|
"""
|
|
# rationale: launching a new Tor takes a long time, so only do it if
|
|
# the user specifically asks for it with --launch-tor. Using an
|
|
# existing Tor should be much faster, but still requires general
|
|
# permission via --tor.
|
|
|
|
if not txtorcon:
|
|
raise errors.NoTorError()
|
|
|
|
if not isinstance(launch_tor, bool): # note: False is int
|
|
raise TypeError("launch_tor= must be boolean")
|
|
if not isinstance(tor_control_port, (type(""), type(None))):
|
|
raise TypeError("tor_control_port= must be str or None")
|
|
assert tor_control_port != ""
|
|
if launch_tor and tor_control_port is not None:
|
|
raise ValueError("cannot combine --launch-tor and --tor-control-port=")
|
|
timing = timing or DebugTiming()
|
|
|
|
# Connect to an existing Tor, or create a new one. If we need to
|
|
# launch an onion service, then we need a working control port (and
|
|
# authentication cookie). If we're only acting as a client, we don't
|
|
# need the control port.
|
|
|
|
if launch_tor:
|
|
print(
|
|
" launching a new Tor process, this may take a while..",
|
|
file=stderr)
|
|
with timing.add("launch tor"):
|
|
tor = yield txtorcon.launch(reactor,
|
|
# data_directory=,
|
|
# tor_binary=,
|
|
)
|
|
elif tor_control_port:
|
|
with timing.add("find tor"):
|
|
control_ep = clientFromString(reactor, tor_control_port)
|
|
tor = yield txtorcon.connect(reactor, control_ep) # might raise
|
|
print(
|
|
" using Tor via control port at %s" % tor_control_port,
|
|
file=stderr)
|
|
else:
|
|
# Let txtorcon look through a list of usual places. If that fails,
|
|
# we'll arrange to attempt the default SOCKS port
|
|
with timing.add("find tor"):
|
|
try:
|
|
tor = yield txtorcon.connect(reactor)
|
|
print(" using Tor via default control port", file=stderr)
|
|
except Exception:
|
|
# TODO: make this more specific. I think connect() is
|
|
# likely to throw a reactor.connectTCP -type error, like
|
|
# ConnectionFailed or ConnectionRefused or something
|
|
print(
|
|
" unable to find default Tor control port, using SOCKS",
|
|
file=stderr)
|
|
tor = SocksOnlyTor(reactor)
|
|
directlyProvides(tor, _interfaces.ITorManager)
|
|
returnValue(tor)
|