[or-cvs] r23059: {arm} change: updating torctl version to git head release (in arm/dependencies: . TorCtl)
Damian Johnson
atagar1 at gmail.com
Thu Aug 26 15:24:29 UTC 2010
Author: atagar
Date: 2010-08-26 15:24:29 +0000 (Thu, 26 Aug 2010)
New Revision: 23059
Modified:
arm/dependencies/TorCtl/TorCtl.py
arm/dependencies/notes.txt
Log:
change: updating torctl version to git head release
Modified: arm/dependencies/TorCtl/TorCtl.py
===================================================================
--- arm/dependencies/TorCtl/TorCtl.py 2010-08-26 11:44:44 UTC (rev 23058)
+++ arm/dependencies/TorCtl/TorCtl.py 2010-08-26 15:24:29 UTC (rev 23059)
@@ -18,12 +18,19 @@
This package also contains a helper class for representing Routers, and
classes and constants for each event.
+
+To quickly fetch a TorCtl instance to experiment with use the following:
+>>> import TorCtl
+>>> conn = TorCtl.connect()
+>>> conn.get_info("version")["version"]
+'0.2.1.24'
+
"""
-__all__ = ["EVENT_TYPE", "TorCtlError", "TorCtlClosed", "ProtocolError",
- "ErrorReply", "NetworkStatus", "ExitPolicyLine", "Router",
- "RouterVersion", "Connection", "parse_ns_body",
+__all__ = ["EVENT_TYPE", "connect", "TorCtlError", "TorCtlClosed",
+ "ProtocolError", "ErrorReply", "NetworkStatus", "ExitPolicyLine",
+ "Router", "RouterVersion", "Connection", "parse_ns_body",
"EventHandler", "DebugEventHandler", "NetworkStatusEvent",
"NewDescEvent", "CircuitEvent", "StreamEvent", "ORConnEvent",
"StreamBwEvent", "LogEvent", "AddrMapEvent", "BWEvent",
@@ -39,6 +46,7 @@
import datetime
import traceback
import socket
+import getpass
import binascii
import types
import time
@@ -79,6 +87,62 @@
POSTLISTEN="POSTLISTEN",
DONE="DONE")
+# Types of control port authentication
+AUTH_TYPE = Enum2(
+ NONE="NONE",
+ PASSWORD="PASSWORD",
+ COOKIE="COOKIE")
+
+INCORRECT_PASSWORD_MSG = "Provided passphrase was incorrect"
+
+def connect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None):
+ """
+ Convenience function for quickly getting a TorCtl connection. This is very
+ handy for debugging or CLI setup, handling setup and prompting for a password
+ if necessary (if either none is provided as input or it fails). If any issues
+ arise this prints a description of the problem and returns None.
+
+ Arguments:
+ controlAddr - ip address belonging to the controller
+ controlPort - port belonging to the controller
+ passphrase - authentication passphrase (if defined this is used rather
+ than prompting the user)
+ """
+
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((controlAddr, controlPort))
+ conn = Connection(s)
+ authType, authValue = conn.get_auth_type(), ""
+
+ if authType == AUTH_TYPE.PASSWORD:
+ # password authentication, promting for the password if it wasn't provided
+ if passphrase: authValue = passphrase
+ else:
+ try: authValue = getpass.getpass()
+ except KeyboardInterrupt: return None
+ elif authType == AUTH_TYPE.COOKIE:
+ authValue = conn.get_auth_cookie_path()
+
+ conn.authenticate(authValue)
+ return conn
+ except socket.error, exc:
+ if "Connection refused" in exc.args:
+ # most common case - tor control port isn't available
+ print "Connection refused. Is the ControlPort enabled?"
+ else: print "Failed to establish socket: %s" % exc
+
+ return None
+ except Exception, exc:
+ if passphrase and str(exc) == "Unable to authenticate: password incorrect":
+ # provide a warning that the provided password didn't work, then try
+ # again prompting for the user to enter it
+ print INCORRECT_PASSWORD_MSG
+ return connect(controlAddr, controlPort)
+ else:
+ print exc
+ return None
+
class TorCtlError(Exception):
"Generic error raised by TorControl code."
pass
@@ -323,6 +387,17 @@
for kw, reg in desc_re.iteritems():
desc_re[kw] = re.compile(reg)
+def partition(string, delimiter):
+ """ Implementation of string.partition-like function for Python <
+ 2.5. Returns a tuple (first, rest), where first is the text up to
+ the first delimiter, and rest is the text after the first delimiter.
+ """
+ sp = string.split(delimiter, 1)
+ if len(sp) > 1:
+ return sp[0], sp[1]
+ else:
+ return sp[0], ""
+
class Router:
"""
Class to represent a router from a descriptor. Can either be
@@ -384,12 +459,12 @@
for line in desc:
# Pull off the keyword...
- kw, _, rest = line.partition(" ")
+ kw, rest = partition(line, " ")
# ...and if it's "opt", extend it by the next keyword
# so we get "opt hibernating" as one keyword.
if kw == "opt":
- okw, _, rest = rest.partition(" ")
+ okw, rest = partition(rest, " ")
kw += " " + okw
# try to match the descriptor line by keyword.
@@ -487,7 +562,64 @@
self._eventQueue = Queue.Queue()
self._s = BufSock(sock)
self._debugFile = None
+
+ # authentication information (lazily fetched so None if still unknown)
+ self._authType = None
+ self._cookiePath = None
+ def get_auth_type(self):
+ """
+ Provides the authentication type used for the control port (a member of
+ the AUTH_TYPE enumeration). This raises an IOError if this fails to query
+ the PROTOCOLINFO.
+ """
+
+ if self._authType: return self._authType
+ else:
+ # check PROTOCOLINFO for authentication type
+ try:
+ authInfo = self.sendAndRecv("PROTOCOLINFO\r\n")[1][1]
+ except ErrorReply, exc:
+ raise IOError("Unable to query PROTOCOLINFO for the authentication type: %s" % exc)
+
+ authType, cookiePath = None, None
+ if authInfo.startswith("AUTH METHODS=NULL"):
+ # no authentication required
+ authType = AUTH_TYPE.NONE
+ elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
+ # password authentication
+ authType = AUTH_TYPE.PASSWORD
+ elif authInfo.startswith("AUTH METHODS=COOKIE"):
+ # cookie authentication, parses authentication cookie path
+ authType = AUTH_TYPE.COOKIE
+
+ start = authInfo.find("COOKIEFILE=\"") + 12
+ end = authInfo.find("\"", start)
+ cookiePath = authInfo[start:end]
+ else:
+ # not of a recognized authentication type (new addition to the
+ # control-spec?)
+ raise IOError("Unrecognized authentication type: %s" % authInfo)
+
+ self._authType = authType
+ self._cookiePath = cookiePath
+ return self._authType
+
+ def get_auth_cookie_path(self):
+ """
+ Provides the path of tor's authentication cookie. If the connection isn't
+ using cookie authentication then this provides None. This raises an IOError
+ if PROTOCOLINFO can't be queried.
+ """
+
+ # fetches authentication type and cookie path if still unloaded
+ if self._authType == None: self.get_auth_type()
+
+ if self._authType == AUTH_TYPE.COOKIE:
+ return self._cookiePath
+ else:
+ return None
+
def set_close_handler(self, handler):
"""Call 'handler' when the Tor process has closed its connection or
given us an exception. If we close normally, no arguments are
@@ -765,6 +897,61 @@
return lines
def authenticate(self, secret=""):
+ """
+ Authenticates to the control port. If an issue arises this raises either of
+ the following:
+ - IOError for failures in reading an authentication cookie or querying
+ PROTOCOLINFO.
+ - TorCtl.ErrorReply for authentication failures or if the secret is
+ undefined when using password authentication
+ """
+
+ # fetches authentication type and cookie path if still unloaded
+ if self._authType == None: self.get_auth_type()
+
+ # validates input
+ if self._authType == AUTH_TYPE.PASSWORD and secret == "":
+ raise ErrorReply("Unable to authenticate: no passphrase provided")
+
+ authCookie = None
+ try:
+ if self._authType == AUTH_TYPE.NONE:
+ self.authenticate_password("")
+ elif self._authType == AUTH_TYPE.PASSWORD:
+ self.authenticate_password(secret)
+ else:
+ authCookie = open(self._cookiePath, "r")
+ self.authenticate_cookie(authCookie)
+ authCookie.close()
+ except ErrorReply, exc:
+ if authCookie: authCookie.close()
+ issue = str(exc)
+
+ # simplifies message if the wrong credentials were provided (common
+ # mistake)
+ if issue.startswith("515 Authentication failed: "):
+ if issue[27:].startswith("Password did not match"):
+ issue = "password incorrect"
+ elif issue[27:] == "Wrong length on authentication cookie.":
+ issue = "cookie value incorrect"
+
+ raise ErrorReply("Unable to authenticate: %s" % issue)
+ except IOError, exc:
+ if authCookie: authCookie.close()
+ issue = None
+
+ # cleaner message for common errors
+ if str(exc).startswith("[Errno 13] Permission denied"):
+ issue = "permission denied"
+ elif str(exc).startswith("[Errno 2] No such file or directory"):
+ issue = "file doesn't exist"
+
+ # if problem's recognized give concise message, otherwise print exception
+ # string
+ if issue: raise IOError("Failed to read authentication cookie (%s): %s" % (issue, self._cookiePath))
+ else: raise IOError("Failed to read authentication cookie: %s" % exc)
+
+ def authenticate_password(self, secret=""):
"""Sends an authenticating secret (password) to Tor. You'll need to call
this method (or authenticate_cookie) before Tor can start.
"""
Modified: arm/dependencies/notes.txt
===================================================================
--- arm/dependencies/notes.txt 2010-08-26 11:44:44 UTC (rev 23058)
+++ arm/dependencies/notes.txt 2010-08-26 15:24:29 UTC (rev 23059)
@@ -2,5 +2,6 @@
Last Updated: 8/22/10 (c514a0a7105cebe7cc5fa199750b90369b820bfb):
To update run the following:
git clone git://git.torproject.org/pytorctl.git
+ cd pytorctl
git archive master | tar -x -C /path/to/dependences/TorCtl/
More information about the tor-commits
mailing list