2018-12-15 00:08:54 +00:00
# Copyright 2012-2018, Damian Johnson and The Tor Project
2015-11-23 21:13:53 +00:00
# See LICENSE for licensing information
2018-12-15 00:08:54 +00:00
import sys
import stem . prereq
2015-11-23 21:13:53 +00:00
import stem . response
import stem . socket
import stem . version
2018-12-15 00:08:54 +00:00
import stem . util . str_tools
2015-11-23 21:13:53 +00:00
from stem . connection import AuthMethod
from stem . util import log
class ProtocolInfoResponse ( stem . response . ControlMessage ) :
"""
Version one PROTOCOLINFO query response .
The protocol_version is the only mandatory data for a valid PROTOCOLINFO
response , so all other values are None if undefined or empty if a collection .
: var int protocol_version : protocol version of the response
: var stem . version . Version tor_version : version of the tor process
: var tuple auth_methods : : data : ` stem . connection . AuthMethod ` types that tor will accept
: var tuple unknown_auth_methods : strings of unrecognized auth methods
: var str cookie_path : path of tor ' s authentication cookie
"""
def _parse_message ( self ) :
# Example:
# 250-PROTOCOLINFO 1
# 250-AUTH METHODS=COOKIE COOKIEFILE="/home/atagar/.tor/control_auth_cookie"
# 250-VERSION Tor="0.2.1.30"
# 250 OK
self . protocol_version = None
self . tor_version = None
self . auth_methods = ( )
self . unknown_auth_methods = ( )
self . cookie_path = None
auth_methods , unknown_auth_methods = [ ] , [ ]
remaining_lines = list ( self )
if not self . is_ok ( ) or not remaining_lines . pop ( ) == ' OK ' :
raise stem . ProtocolError ( " PROTOCOLINFO response didn ' t have an OK status: \n %s " % self )
# sanity check that we're a PROTOCOLINFO response
if not remaining_lines [ 0 ] . startswith ( ' PROTOCOLINFO ' ) :
raise stem . ProtocolError ( ' Message is not a PROTOCOLINFO response: \n %s ' % self )
while remaining_lines :
line = remaining_lines . pop ( 0 )
line_type = line . pop ( )
if line_type == ' PROTOCOLINFO ' :
# Line format:
# FirstLine = "PROTOCOLINFO" SP PIVERSION CRLF
# PIVERSION = 1*DIGIT
if line . is_empty ( ) :
raise stem . ProtocolError ( " PROTOCOLINFO response ' s initial line is missing the protocol version: %s " % line )
try :
self . protocol_version = int ( line . pop ( ) )
except ValueError :
raise stem . ProtocolError ( ' PROTOCOLINFO response version is non-numeric: %s ' % line )
# The piversion really should be '1' but, according to the spec, tor
# does not necessarily need to provide the PROTOCOLINFO version that we
# requested. Log if it's something we aren't expecting but still make
# an effort to parse like a v1 response.
if self . protocol_version != 1 :
log . info ( " We made a PROTOCOLINFO version 1 query but got a version %i response instead. We ' ll still try to use it, but this may cause problems. " % self . protocol_version )
elif line_type == ' AUTH ' :
# Line format:
# AuthLine = "250-AUTH" SP "METHODS=" AuthMethod *("," AuthMethod)
# *(SP "COOKIEFILE=" AuthCookieFile) CRLF
# AuthMethod = "NULL" / "HASHEDPASSWORD" / "COOKIE"
# AuthCookieFile = QuotedString
# parse AuthMethod mapping
if not line . is_next_mapping ( ' METHODS ' ) :
raise stem . ProtocolError ( " PROTOCOLINFO response ' s AUTH line is missing its mandatory ' METHODS ' mapping: %s " % line )
for method in line . pop_mapping ( ) [ 1 ] . split ( ' , ' ) :
if method == ' NULL ' :
auth_methods . append ( AuthMethod . NONE )
elif method == ' HASHEDPASSWORD ' :
auth_methods . append ( AuthMethod . PASSWORD )
elif method == ' COOKIE ' :
auth_methods . append ( AuthMethod . COOKIE )
elif method == ' SAFECOOKIE ' :
auth_methods . append ( AuthMethod . SAFECOOKIE )
else :
unknown_auth_methods . append ( method )
message_id = ' stem.response.protocolinfo.unknown_auth_ %s ' % method
log . log_once ( message_id , log . INFO , " PROTOCOLINFO response included a type of authentication that we don ' t recognize: %s " % method )
# our auth_methods should have a single AuthMethod.UNKNOWN entry if
# any unknown authentication methods exist
if AuthMethod . UNKNOWN not in auth_methods :
auth_methods . append ( AuthMethod . UNKNOWN )
# parse optional COOKIEFILE mapping (quoted and can have escapes)
2018-12-15 00:08:54 +00:00
2015-11-23 21:13:53 +00:00
if line . is_next_mapping ( ' COOKIEFILE ' , True , True ) :
2018-12-15 00:08:54 +00:00
self . cookie_path = line . pop_mapping ( True , True , get_bytes = True ) [ 1 ] . decode ( sys . getfilesystemencoding ( ) )
if stem . prereq . is_python_3 ( ) :
self . cookie_path = stem . util . str_tools . _to_unicode ( self . cookie_path ) # normalize back to str
2015-11-23 21:13:53 +00:00
elif line_type == ' VERSION ' :
# Line format:
# VersionLine = "250-VERSION" SP "Tor=" TorVersion OptArguments CRLF
# TorVersion = QuotedString
if not line . is_next_mapping ( ' Tor ' , True ) :
raise stem . ProtocolError ( " PROTOCOLINFO response ' s VERSION line is missing its mandatory tor version mapping: %s " % line )
try :
self . tor_version = stem . version . Version ( line . pop_mapping ( True ) [ 1 ] )
except ValueError as exc :
raise stem . ProtocolError ( exc )
else :
log . debug ( " Unrecognized PROTOCOLINFO line type ' %s ' , ignoring it: %s " % ( line_type , line ) )
self . auth_methods = tuple ( auth_methods )
self . unknown_auth_methods = tuple ( unknown_auth_methods )