tor: socks.port is now a (restricted) endpoint string

Foolscap has limitations that prevent us from accepting anything but a
TCP endpoint, but that will change in the future, so make the tahoe.cfg
syntax accept an endpoint, but then reject non-TCP ones. See the ticket
for details: refs ticket:2813.

This depends upon the new `foolscap.connections.tor.socks_port(host,
port)` API in foolscap-0.12.2, so it bumps the dependency to that (the
previous commit depended upon 0.12.1, but I hadn't gotten around to
updating the dep before now).
This commit is contained in:
Brian Warner 2016-08-28 16:28:01 -07:00
parent bc079a71eb
commit a099b9237d
4 changed files with 37 additions and 32 deletions

View File

@ -402,14 +402,21 @@ specify it.
If False, this will disable the use of Tor entirely. The default of True
means the node will use Tor, if necessary, and if possible.
``socks.port = (string, optional, PORT, defaults to empty)``
``socks.port = (string, optional, endpoint specification string, defaults to empty)``
This tells the node that Tor connections should be routed to a SOCKS
proxy listening on the given port. The default (of an empty value) will
cause the node to first try localhost port 9050, then if that fails, try
localhost port 9150. These are the default listening ports of the
proxy listening on the given endpoint. The default (of an empty value)
will cause the node to first try localhost port 9050, then if that fails,
try localhost port 9150. These are the default listening ports of the
standard Tor daemon, and the Tor Browser Bundle, respectively.
While this nominally accepts an arbitrary endpoint string, internal
limitations prevent it from accepting anything but ``tcp:HOST:PORT``
(unfortunately, unix-domain sockets are not yet supported). See ticket
#2813 for details. Also note that using a HOST of anything other than
localhost is discouraged, because you would be revealing your IP address
to external (and possibly hostile) machines.
``control.port = (string, optional, endpoint specification string)``
This tells the node to connect to a pre-existing Tor daemon on the given
@ -434,8 +441,8 @@ There are 5 valid combinations of these configuration settings:
* 1: ``(empty)``: use SOCKS on port 9050/9150
* 2: ``launch = true``: launch a new Tor
* 3: ``socks.port = HOST:PORT``: use an existing Tor on the given SOCKS port
* 4: ``control.port = PORT``: use an existing Tor at the given control port
* 3: ``socks.port = tcp:HOST:PORT``: use an existing Tor on the given SOCKS port
* 4: ``control.port = ENDPOINT``: use an existing Tor at the given control port
* 5: ``enable = false``: no Tor at all
1 is the default, and should work for any Linux host with the system Tor

View File

@ -39,7 +39,8 @@ install_requires = [
# * foolscap 0.8.0 generates 2048-bit RSA-with-SHA-256 signatures,
# rather than 1024-bit RSA-with-MD5. This also allows us to work
# with a FIPS build of OpenSSL.
"foolscap >= 0.10.1",
# * foolscap >= 0.12.2 provides tcp/tor/i2p connection handlers we need
"foolscap >= 0.12.2",
# Needed for SFTP.
# pycrypto 2.2 doesn't work due to <https://bugs.launchpad.net/pycrypto/+bug/620253>

View File

@ -187,25 +187,22 @@ class Node(service.MultiService):
socksport = self.get_config("tor", "socks.port", None)
if socksport:
# foolscap.connections.tor.socks_port() in Foolscap-0.12.1 only
# allows the use of SOCKS port on localhost, to discourage unsafe
# connections to remote SOCKS ports. Allow the HOST:PORT syntax,
# but refuse to use anything other than 127.0.0.1 . Also accept
# just PORT.
if ":" in socksport:
host, port = socksport.split(":")
if host != "127.0.0.1":
raise ValueError("'tahoe.cfg [tor] socks.port' = "
"must be '127.0.0.1:PORT' or just PORT, "
"not '%s'" % (socksport,))
else:
port = socksport
# this is nominally and endpoint string, but txtorcon requires
# TCP host and port. So parse it now, and reject non-TCP
# endpoints.
pieces = socksport.split(":")
if pieces[0] != "tcp" or len(pieces) != 3:
raise ValueError("'tahoe.cfg [tor] socks.port' = "
"is currently limited to 'tcp:HOST:PORT', "
"not '%s'" % (socksport,))
host = pieces[1]
try:
port = int(port)
port = int(pieces[2])
except ValueError:
raise ValueError("'tahoe.cfg [tor] socks.port' used "
"non-numeric PORT value '%s'" % (port,))
return tor.socks_port(port)
"non-numeric PORT value '%s'" % (pieces[2],))
return tor.socks_port(host, port)
controlport = self.get_config("tor", "control.port", None)
if controlport:

View File

@ -65,30 +65,30 @@ class Tor(unittest.TestCase):
self.assertIdentical(h, h1)
def test_socksport(self):
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = 1234\n")
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:127.0.0.1:1234\n")
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.socks_port",
return_value=h1) as f:
h = n._make_tor_handler()
self.assertEqual(f.mock_calls, [mock.call(1234)])
self.assertEqual(f.mock_calls, [mock.call("127.0.0.1", 1234)])
self.assertIdentical(h, h1)
def test_socksport_localhost(self):
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = 127.0.0.1:1234\n")
def test_socksport_otherhost(self):
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:otherhost:1234\n")
h1 = mock.Mock()
with mock.patch("foolscap.connections.tor.socks_port",
return_value=h1) as f:
h = n._make_tor_handler()
self.assertEqual(f.mock_calls, [mock.call(1234)])
self.assertEqual(f.mock_calls, [mock.call("otherhost", 1234)])
self.assertIdentical(h, h1)
def test_socksport_bad_host(self):
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = example.com:1234\n")
def test_socksport_bad_endpoint(self):
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = unix:unsupported\n")
e = self.assertRaises(ValueError, n._make_tor_handler)
self.assertIn("must be '127.0.0.1:PORT'", str(e))
self.assertIn("is currently limited to 'tcp:HOST:PORT'", str(e))
def test_socksport_not_integer(self):
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = kumquat\n")
n = FakeNode(BASECONFIG+"[tor]\nsocks.port = tcp:localhost:kumquat\n")
e = self.assertRaises(ValueError, n._make_tor_handler)
self.assertIn("used non-numeric PORT value", str(e))