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)
 |