Ticket #1737: 0001-Convenience-functions-for-creating-and-authenticatin.patch

File 0001-Convenience-functions-for-creating-and-authenticatin.patch, 8.0 KB (added by atagar, 10 years ago)

git patch for torctl creating/authenticating convenience functions

  • TorCtl.py

    From 1051e193a58e0399cb8051347a0e1813dd9cb88f Mon Sep 17 00:00:00 2001
    From: Damian Johnson <atagar@torproject.org>
    Date: Sun, 22 Aug 2010 15:33:42 -0700
    Subject: [PATCH][BUILD] Convenience functions for creating and authenticating TorCtl connections.
    
    
    Signed-off-by: Damian Johnson <atagar@torproject.org>
    ---
     TorCtl.py |  163 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
     1 files changed, 155 insertions(+), 8 deletions(-)
    
    diff --git a/TorCtl.py b/TorCtl.py
    index 8aa498b..9dcd045 100755
    a b custom event handlers that you may extend off of those). 
    1919This package also contains a helper class for representing Routers, and
    2020classes and constants for each event.
    2121
     22To quickly fetch a TorCtl instance to experiment with use the following:
     23
     24>>> import TorCtl
     25>>> conn = TorCtl.connect()
     26>>> conn.get_info("version")["version"]
     27'0.2.1.24'
    2228"""
    2329
    24 __all__ = ["EVENT_TYPE", "TorCtlError", "TorCtlClosed", "ProtocolError",
    25            "ErrorReply", "NetworkStatus", "ExitPolicyLine", "Router",
    26            "RouterVersion", "Connection", "parse_ns_body",
    27            "EventHandler", "DebugEventHandler", "NetworkStatusEvent",
    28            "NewDescEvent", "CircuitEvent", "StreamEvent", "ORConnEvent",
    29            "StreamBwEvent", "LogEvent", "AddrMapEvent", "BWEvent",
    30            "BuildTimeoutSetEvent", "UnknownEvent", "ConsensusTracker",
    31            "EventListener", "EVENT_STATE" ]
     30__all__ = ["EVENT_TYPE", "make_ctl_conn", "init_ctl_conn", "connect",
     31           "TorCtlError", "TorCtlClosed", "ProtocolError", "ErrorReply",
     32           "NetworkStatus", "ExitPolicyLine", "Router", "RouterVersion",
     33           "Connection", "parse_ns_body", "EventHandler", "DebugEventHandler",
     34           "NetworkStatusEvent", "NewDescEvent", "CircuitEvent", "StreamEvent",
     35           "ORConnEvent", "StreamBwEvent", "LogEvent", "AddrMapEvent",
     36           "BWEvent", "BuildTimeoutSetEvent", "UnknownEvent",
     37           "ConsensusTracker", "EventListener", "EVENT_STATE" ]
    3238
    3339import os
    3440import re
    import Queue 
    3945import datetime
    4046import traceback
    4147import socket
     48import getpass
    4249import binascii
    4350import types
    4451import time
    EVENT_STATE = Enum2( 
    7986          POSTLISTEN="POSTLISTEN",
    8087          DONE="DONE")
    8188
     89def make_ctl_conn(controlAddr="127.0.0.1", controlPort=9051):
     90  """
     91  Opens a socket to the tor controller and queries its authentication type,
     92  raising an IOError if problems occur. The result of this function is a tuple
     93  of the TorCtl connection and the authentication type, where the later is one
     94  of the following:
     95  "NONE"          - no authentication required
     96  "PASSWORD"      - requires authentication via a hashed password
     97  "COOKIE=<FILE>" - requires the specified authentication cookie
     98 
     99  Arguments:
     100    controlAddr - ip address belonging to the controller
     101    controlPort - port belonging to the controller
     102  """
     103 
     104  try:
     105    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     106    s.connect((controlAddr, controlPort))
     107    conn = Connection(s)
     108  except socket.error, exc:
     109    if "Connection refused" in exc.args:
     110      # most common case - tor control port isn't available
     111      raise IOError("Connection refused. Is the ControlPort enabled?")
     112    else: raise IOError("Failed to establish socket: %s" % exc)
     113 
     114  # check PROTOCOLINFO for authentication type
     115  try:
     116    authInfo = conn.sendAndRecv("PROTOCOLINFO\r\n")[1][1]
     117  except ErrorReply, exc:
     118    raise IOError("Unable to query PROTOCOLINFO for authentication type: %s" % exc)
     119 
     120  if authInfo.startswith("AUTH METHODS=NULL"):
     121    # no authentication required
     122    return (conn, "NONE")
     123  elif authInfo.startswith("AUTH METHODS=HASHEDPASSWORD"):
     124    # password authentication
     125    return (conn, "PASSWORD")
     126  elif authInfo.startswith("AUTH METHODS=COOKIE"):
     127    # cookie authentication, parses authentication cookie path
     128    start = authInfo.find("COOKIEFILE=\"") + 12
     129    end = authInfo.find("\"", start)
     130    return (conn, "COOKIE=%s" % authInfo[start:end])
     131
     132def init_ctl_conn(conn, authType="NONE", authVal=None):
     133  """
     134  Authenticates to a tor connection. The authentication type can be any of the
     135  following strings:
     136  NONE, PASSWORD, COOKIE
     137 
     138  if the authentication type is anything other than NONE then either a
     139  passphrase or path to an authentication cookie is expected. If an issue
     140  arises this raises either of the following:
     141    - IOError for failures in reading an authentication cookie
     142    - TorCtl.ErrorReply for authentication failures
     143 
     144  Argument:
     145    conn     - unauthenticated TorCtl connection
     146    authType - type of authentication method to use
     147    authVal  - passphrase or path to authentication cookie
     148  """
     149 
     150  # validates input
     151  if authType not in ("NONE", "PASSWORD", "COOKIE"):
     152    # authentication type unrecognized (possibly a new addition to the controlSpec?)
     153    raise ErrorReply("Unrecognized authentication type: %s" % authType)
     154  elif authType != "NONE" and authVal == None:
     155    typeLabel = "passphrase" if authType == "PASSWORD" else "cookie"
     156    raise ErrorReply("Unable to authenticate: no %s provided" % typeLabel)
     157 
     158  authCookie = None
     159  try:
     160    if authType == "NONE": conn.authenticate("")
     161    elif authType == "PASSWORD": conn.authenticate(authVal)
     162    else:
     163      authCookie = open(authVal, "r")
     164      conn.authenticate_cookie(authCookie)
     165      authCookie.close()
     166  except ErrorReply, exc:
     167    if authCookie: authCookie.close()
     168    issue = str(exc)
     169   
     170    # simplifies message if the wrong credentials were provided (common mistake)
     171    if issue.startswith("515 Authentication failed: "):
     172      if issue[27:].startswith("Password did not match"):
     173        issue = "password incorrect"
     174      elif issue[27:] == "Wrong length on authentication cookie.":
     175        issue = "cookie value incorrect"
     176   
     177    raise ErrorReply("Unable to authenticate: %s" % issue)
     178  except IOError, exc:
     179    if authCookie: authCookie.close()
     180    issue = None
     181   
     182    # cleaner message for common errors
     183    if str(exc).startswith("[Errno 13] Permission denied"): issue = "permission denied"
     184    elif str(exc).startswith("[Errno 2] No such file or directory"): issue = "file doesn't exist"
     185   
     186    # if problem's recognized give concise message, otherwise print exception string
     187    if issue: raise IOError("Failed to read authentication cookie (%s): %s" % (issue, authVal))
     188    else: raise IOError("Failed to read authentication cookie: %s" % exc)
     189
     190def connect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None):
     191  """
     192  Convenience function for quickly getting a TorCtl connection. This is very
     193  handy for debugging or CLI setup, handling setup and prompting for a password
     194  if necessary (if either none is provided as input or it fails). If any issues
     195  arise this prints a description of the problem and returns None.
     196 
     197  Arguments:
     198    controlAddr - ip address belonging to the controller
     199    controlPort - port belonging to the controller
     200    passphrase  - authentication passphrase (if defined this is used rather
     201                  than prompting the user)
     202  """
     203 
     204  try:
     205    conn, authType = make_ctl_conn(controlAddr, controlPort)
     206    authValue = None
     207   
     208    if authType == "PASSWORD":
     209      # password authentication, promting for the password if it wasn't provided
     210      if passphrase: authValue = passphrase
     211      else:
     212        try: authValue = getpass.getpass()
     213        except KeyboardInterrupt: return None
     214    elif authType.startswith("COOKIE"):
     215      authType, authValue = authType.split("=", 1)
     216   
     217    init_ctl_conn(conn, authType, authValue)
     218    return conn
     219  except Exception, exc:
     220    if passphrase and str(exc) == "Unable to authenticate: password incorrect":
     221      # provide a warning that the provided password didn't work, then try
     222      # again prompting for the user to enter it
     223      print INCORRECT_PASSWORD_MSG
     224      return connect(controlAddr, controlPort)
     225    else:
     226      print exc
     227      return None
     228
    82229class TorCtlError(Exception):
    83230  "Generic error raised by TorControl code."
    84231  pass