commit 56c2ea8c33e63456dc1316f3936439478e12cc03 Author: Damian Johnson atagar@torproject.org Date: Mon Nov 21 09:21:21 2011 -0800
Function to query PROTOCOLINFO via control socket
Same as before, implementation and integ sanity check for making a PROTOCOLINFO query via a control socket. This has the common bits between it, the control port function, and a bit of the PROTOCOLINFO response parsing delegated to helper functions. --- stem/connection.py | 122 +++++++++++++++++++++++---------- test/integ/connection/protocolinfo.py | 29 ++++++-- test/unit/connection/protocolinfo.py | 12 ++-- 3 files changed, 114 insertions(+), 49 deletions(-)
diff --git a/stem/connection.py b/stem/connection.py index 3e97e9e..476c9c3 100644 --- a/stem/connection.py +++ b/stem/connection.py @@ -1,5 +1,17 @@ """ Functions for connecting and authenticating to the tor process. + +get_protocolinfo_by_port - PROTOCOLINFO query via a control port. +get_protocolinfo_by_socket - PROTOCOLINFO query via a control socket. +ProtocolInfoResponse - Reply from a PROTOCOLINFO query. + |- Attributes: + | |- protocol_version + | |- tor_version + | |- auth_methods + | |- unknown_auth_methods + | |- cookie_path + | +- socket + +- convert - parses a ControlMessage, turning it into a ProtocolInfoResponse """
import Queue @@ -28,7 +40,7 @@ LOGGER = logging.getLogger("stem")
AuthMethod = stem.util.enum.Enum("NONE", "PASSWORD", "COOKIE", "UNKNOWN")
-def get_protocolinfo_port(control_addr = "127.0.0.1", control_port = 9051, keep_alive = False): +def get_protocolinfo_by_port(control_addr = "127.0.0.1", control_port = 9051, keep_alive = False): """ Issues a PROTOCOLINFO query to a control port, getting information about the tor process running on it. @@ -49,12 +61,50 @@ def get_protocolinfo_port(control_addr = "127.0.0.1", control_port = 9051, keep_ """
control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + connection_args = (control_addr, control_port) + protocolinfo_response = _get_protocolinfo_impl(control_socket, connection_args, keep_alive) + + # attempt to expand relative cookie paths using our port to infer the pid + protocolinfo_response.cookie_path = _expand_cookie_path(protocolinfo_response.cookie_path, stem.util.system.get_pid_by_port, control_port) + + return protocolinfo_response + +def get_protocolinfo_by_socket(socket_path = "/var/run/tor/control", keep_alive = False): + """ + Issues a PROTOCOLINFO query to a control socket, getting information about + the tor process running on it. + + Arguments: + socket_path (str) - path where the control socket is located + keep_alive (bool) - keeps the socket used to issue the PROTOCOLINFO query + open if True, closes otherwise + + Raises: + stem.types.ProtocolError if the PROTOCOLINFO response is malformed + stem.types.SocketError if problems arise in establishing or using the + socket + """ + + control_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + protocolinfo_response = _get_protocolinfo_impl(control_socket, socket_path, keep_alive) + + # attempt to expand relative cookie paths using our socket to infer the pid + protocolinfo_response.cookie_path = _expand_cookie_path(protocolinfo_response.cookie_path, stem.util.system.get_pid_by_open_file, socket_path) + + return protocolinfo_response + +def _get_protocolinfo_impl(control_socket, connection_args, keep_alive): + """ + Common implementation behind the get_protocolinfo_by_* functions. This + connects the given socket and issues a PROTOCOLINFO query with it. + """ + control_socket_file = control_socket.makefile() protocolinfo_response, raised_exc = None, None
try: # initiates connection - control_socket.connect((control_addr, control_port)) + control_socket.connect(connection_args)
# issues the PROTOCOLINFO query control_socket_file.write("PROTOCOLINFO 1\r\n") @@ -79,26 +129,36 @@ def get_protocolinfo_port(control_addr = "127.0.0.1", control_port = 9051, keep_ # if we're keeping the socket open then attach it to the response protocolinfo_response.socket = control_socket
- if raised_exc: - raise raised_exc - else: - # if we have an unresolved cookie path then try to expand it again, using - # our port to infer a pid - - cookie_path = protocolinfo_response.cookie_file - if cookie_path and control_addr == "127.0.0.1" and stem.util.system.is_relative_path(cookie_path): - try: - tor_pid = stem.util.system.get_pid_by_port(control_port) - if not tor_pid: raise IOError("pid lookup failed") - - tor_cwd = stem.util.system.get_cwd(tor_pid) - if not tor_cwd: raise IOError("cwd lookup failed") - - protocolinfo_response.cookie_file = stem.util.system.expand_path(cookie_path, tor_cwd) - except IOError, exc: - LOGGER.debug("unable to expand relative tor cookie path by port: %s" % exc) - - return protocolinfo_response + if raised_exc: raise raised_exc + else: return protocolinfo_response + +def _expand_cookie_path(cookie_path, pid_resolver, pid_resolution_arg): + """ + Attempts to expand a relative cookie path with the given pid resolver. This + returns the input path if it's already absolute, None, or the system calls + fail. + """ + + if cookie_path and stem.util.system.is_relative_path(cookie_path): + try: + tor_pid = pid_resolver(pid_resolution_arg) + if not tor_pid: raise IOError("pid lookup failed") + + tor_cwd = stem.util.system.get_cwd(tor_pid) + if not tor_cwd: raise IOError("cwd lookup failed") + + cookie_path = stem.util.system.expand_path(cookie_path, tor_cwd) + except IOError, exc: + resolver_labels = { + stem.util.system.get_pid_by_name: " by name", + stem.util.system.get_pid_by_port: " by port", + stem.util.system.get_pid_by_open_file: " by socket file", + } + + pid_resolver_label = resolver_labels.get(pid_resolver, "") + LOGGER.debug("unable to expand relative tor cookie path%s: %s" % (pid_resolver_label, exc)) + + return cookie_path
class ProtocolInfoResponse(stem.types.ControlMessage): """ @@ -119,7 +179,7 @@ class ProtocolInfoResponse(stem.types.ControlMessage): tor_version (stem.types.Version) - version of the tor process auth_methods (tuple) - AuthMethod types that tor will accept unknown_auth_methods (tuple) - strings of unrecognized auth methods - cookie_file (str) - path of tor's authentication cookie + cookie_path (str) - path of tor's authentication cookie socket (socket.socket) - socket used to make the query """
@@ -155,7 +215,7 @@ class ProtocolInfoResponse(stem.types.ControlMessage):
self.protocol_version = None self.tor_version = None - self.cookie_file = None + self.cookie_path = None self.socket = None
auth_methods, unknown_auth_methods = [], [] @@ -225,20 +285,10 @@ class ProtocolInfoResponse(stem.types.ControlMessage):
# parse optional COOKIEFILE mapping (quoted and can have escapes) if line.is_next_mapping("COOKIEFILE", True, True): - self.cookie_file = line.pop_mapping(True, True)[1] + self.cookie_path = line.pop_mapping(True, True)[1]
# attempt to expand relative cookie paths - if stem.util.system.is_relative_path(self.cookie_file): - try: - tor_pid = stem.util.system.get_pid_by_name("tor") - if not tor_pid: raise IOError("pid lookup failed") - - tor_cwd = stem.util.system.get_cwd(tor_pid) - if not tor_cwd: raise IOError("cwd lookup failed") - - self.cookie_file = stem.util.system.expand_path(self.cookie_file, tor_cwd) - except IOError, exc: - LOGGER.debug("unable to expand relative tor cookie path by name: %s" % exc) + self.cookie_path = _expand_cookie_path(self.cookie_path, stem.util.system.get_pid_by_name, "tor") elif line_type == "VERSION": # Line format: # VersionLine = "250-VERSION" SP "Tor=" TorVersion OptArguments CRLF diff --git a/test/integ/connection/protocolinfo.py b/test/integ/connection/protocolinfo.py index 73a7884..037f6dd 100644 --- a/test/integ/connection/protocolinfo.py +++ b/test/integ/connection/protocolinfo.py @@ -46,20 +46,35 @@ class TestProtocolInfo(unittest.TestCase): self.assertEqual(None, protocolinfo_response.socket) self.assert_protocolinfo_attr(protocolinfo_response, connection_type)
- def test_get_protocolinfo_port(self): + def test_get_protocolinfo_by_port(self): """ - Exercises the stem.connection.get_protocolinfo_port function. + Exercises the stem.connection.get_protocolinfo_by_port function. """
connection_type = test.runner.get_runner().get_connection_type()
if test.runner.OPT_PORT in test.runner.CONNECTION_OPTS[connection_type]: - protocolinfo_response = stem.connection.get_protocolinfo_port(control_port = test.runner.CONTROL_PORT) + protocolinfo_response = stem.connection.get_protocolinfo_by_port(control_port = test.runner.CONTROL_PORT) self.assertEqual(None, protocolinfo_response.socket) self.assert_protocolinfo_attr(protocolinfo_response, connection_type) else: # we don't have a control port - self.assertRaises(stem.types.SocketError, stem.connection.get_protocolinfo_port, "127.0.0.1", test.runner.CONTROL_PORT) + self.assertRaises(stem.types.SocketError, stem.connection.get_protocolinfo_by_port, "127.0.0.1", test.runner.CONTROL_PORT) + + def test_get_protocolinfo_by_socket(self): + """ + Exercises the stem.connection.get_protocolinfo_by_socket function. + """ + + connection_type = test.runner.get_runner().get_connection_type() + + if test.runner.OPT_SOCKET in test.runner.CONNECTION_OPTS[connection_type]: + protocolinfo_response = stem.connection.get_protocolinfo_by_socket(socket_path = test.runner.CONTROL_SOCKET_PATH) + self.assertEqual(None, protocolinfo_response.socket) + self.assert_protocolinfo_attr(protocolinfo_response, connection_type) + else: + # we don't have a control socket + self.assertRaises(stem.types.SocketError, stem.connection.get_protocolinfo_by_socket, test.runner.CONTROL_SOCKET_PATH)
def assert_protocolinfo_attr(self, protocolinfo_response, connection_type): """ @@ -86,9 +101,9 @@ class TestProtocolInfo(unittest.TestCase): self.assertEqual((), protocolinfo_response.unknown_auth_methods) self.assertEqual(auth_methods, protocolinfo_response.auth_methods)
+ auth_cookie_path = None if test.runner.OPT_COOKIE in test.runner.CONNECTION_OPTS[connection_type]: auth_cookie_path = test.runner.get_runner().get_auth_cookie_path() - self.assertEqual(auth_cookie_path, protocolinfo_response.cookie_file) - else: - self.assertEqual(None, protocolinfo_response.cookie_file) + + self.assertEqual(auth_cookie_path, protocolinfo_response.cookie_path)
diff --git a/test/unit/connection/protocolinfo.py b/test/unit/connection/protocolinfo.py index fc9ab64..3014cbb 100644 --- a/test/unit/connection/protocolinfo.py +++ b/test/unit/connection/protocolinfo.py @@ -89,7 +89,7 @@ class TestProtocolInfoResponse(unittest.TestCase): self.assertEquals(stem.types.Version("0.2.1.30"), control_message.tor_version) self.assertEquals((stem.connection.AuthMethod.NONE, ), control_message.auth_methods) self.assertEquals((), control_message.unknown_auth_methods) - self.assertEquals(None, control_message.cookie_file) + self.assertEquals(None, control_message.cookie_path) self.assertEquals(None, control_message.socket)
def test_password_auth(self): @@ -110,7 +110,7 @@ class TestProtocolInfoResponse(unittest.TestCase): control_message = stem.types.read_message(StringIO.StringIO(COOKIE_AUTH)) stem.connection.ProtocolInfoResponse.convert(control_message) self.assertEquals((stem.connection.AuthMethod.COOKIE, ), control_message.auth_methods) - self.assertEquals("/tmp/my data\"dir//control_auth_cookie", control_message.cookie_file) + self.assertEquals("/tmp/my data\"dir//control_auth_cookie", control_message.cookie_path)
def test_multiple_auth(self): """ @@ -120,7 +120,7 @@ class TestProtocolInfoResponse(unittest.TestCase): control_message = stem.types.read_message(StringIO.StringIO(MULTIPLE_AUTH)) stem.connection.ProtocolInfoResponse.convert(control_message) self.assertEquals((stem.connection.AuthMethod.COOKIE, stem.connection.AuthMethod.PASSWORD), control_message.auth_methods) - self.assertEquals("/home/atagar/.tor/control_auth_cookie", control_message.cookie_file) + self.assertEquals("/home/atagar/.tor/control_auth_cookie", control_message.cookie_path)
def test_unknown_auth(self): """ @@ -145,7 +145,7 @@ class TestProtocolInfoResponse(unittest.TestCase): self.assertEquals(None , control_message.tor_version) self.assertEquals((), control_message.auth_methods) self.assertEquals((), control_message.unknown_auth_methods) - self.assertEquals(None, control_message.cookie_file) + self.assertEquals(None, control_message.cookie_path) self.assertEquals(None, control_message.socket)
def test_relative_cookie(self): @@ -169,7 +169,7 @@ class TestProtocolInfoResponse(unittest.TestCase):
control_message = stem.types.read_message(StringIO.StringIO(RELATIVE_COOKIE_PATH)) stem.connection.ProtocolInfoResponse.convert(control_message) - self.assertEquals("/tmp/foo/tor-browser_en-US/Data/control_auth_cookie", control_message.cookie_file) + self.assertEquals("/tmp/foo/tor-browser_en-US/Data/control_auth_cookie", control_message.cookie_path)
# exercise cookie expansion where both calls fail (should work, just # leaving the path unexpanded) @@ -177,7 +177,7 @@ class TestProtocolInfoResponse(unittest.TestCase): stem.util.system.CALL_MOCKING = lambda cmd: None control_message = stem.types.read_message(StringIO.StringIO(RELATIVE_COOKIE_PATH)) stem.connection.ProtocolInfoResponse.convert(control_message) - self.assertEquals("./tor-browser_en-US/Data/control_auth_cookie", control_message.cookie_file) + self.assertEquals("./tor-browser_en-US/Data/control_auth_cookie", control_message.cookie_path)
# reset system call mocking stem.util.system.CALL_MOCKING = None
tor-commits@lists.torproject.org