1 # Copyright 2012-2020, Damian Johnson and The Tor Project
2 # See LICENSE for licensing information
9 import stem
.util
.str_tools
11 from stem
.util
import log
12 from typing
import Tuple
15 class ProtocolInfoResponse(stem
.response
.ControlMessage
):
17 Version one PROTOCOLINFO query response.
19 The protocol_version is the only mandatory data for a valid PROTOCOLINFO
20 response, so all other values are None if undefined or empty if a collection.
22 :var int protocol_version: protocol version of the response
23 :var stem.version.Version tor_version: version of the tor process
24 :var tuple auth_methods: :data:`stem.connection.AuthMethod` types that tor will accept
25 :var tuple unknown_auth_methods: strings of unrecognized auth methods
26 :var str cookie_path: path of tor's authentication cookie
29 def _parse_message(self
) -> None:
32 # 250-AUTH METHODS=COOKIE COOKIEFILE="/home/atagar/.tor/control_auth_cookie"
33 # 250-VERSION Tor="0.2.1.30"
36 from stem
.connection
import AuthMethod
38 self
.protocol_version
= None
39 self
.tor_version
= None
40 self
.auth_methods
= () # type: Tuple[stem.connection.AuthMethod, ...]
41 self
.unknown_auth_methods
= () # type: Tuple[str, ...]
42 self
.cookie_path
= None
44 auth_methods
, unknown_auth_methods
= [], []
45 remaining_lines
= list(self
)
47 if not self
.is_ok() or not remaining_lines
.pop() == 'OK':
48 raise stem
.ProtocolError("PROTOCOLINFO response didn't have an OK status:\n%s" % self
)
50 # sanity check that we're a PROTOCOLINFO response
51 if not remaining_lines
[0].startswith('PROTOCOLINFO'):
52 raise stem
.ProtocolError('Message is not a PROTOCOLINFO response:\n%s' % self
)
54 while remaining_lines
:
55 line
= remaining_lines
.pop(0)
56 line_type
= line
.pop()
58 if line_type
== 'PROTOCOLINFO':
60 # FirstLine = "PROTOCOLINFO" SP PIVERSION CRLF
64 raise stem
.ProtocolError("PROTOCOLINFO response's initial line is missing the protocol version: %s" % line
)
67 self
.protocol_version
= int(line
.pop())
69 raise stem
.ProtocolError('PROTOCOLINFO response version is non-numeric: %s' % line
)
71 # The piversion really should be '1' but, according to the spec, tor
72 # does not necessarily need to provide the PROTOCOLINFO version that we
73 # requested. Log if it's something we aren't expecting but still make
74 # an effort to parse like a v1 response.
76 if self
.protocol_version
!= 1:
77 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
)
78 elif line_type
== 'AUTH':
80 # AuthLine = "250-AUTH" SP "METHODS=" AuthMethod *("," AuthMethod)
81 # *(SP "COOKIEFILE=" AuthCookieFile) CRLF
82 # AuthMethod = "NULL" / "HASHEDPASSWORD" / "COOKIE"
83 # AuthCookieFile = QuotedString
85 # parse AuthMethod mapping
86 if not line
.is_next_mapping('METHODS'):
87 raise stem
.ProtocolError("PROTOCOLINFO response's AUTH line is missing its mandatory 'METHODS' mapping: %s" % line
)
89 for method
in line
.pop_mapping()[1].split(','):
91 auth_methods
.append(AuthMethod
.NONE
)
92 elif method
== 'HASHEDPASSWORD':
93 auth_methods
.append(AuthMethod
.PASSWORD
)
94 elif method
== 'COOKIE':
95 auth_methods
.append(AuthMethod
.COOKIE
)
96 elif method
== 'SAFECOOKIE':
97 auth_methods
.append(AuthMethod
.SAFECOOKIE
)
99 unknown_auth_methods
.append(method
)
100 message_id
= 'stem.response.protocolinfo.unknown_auth_%s' % method
101 log
.log_once(message_id
, log
.INFO
, "PROTOCOLINFO response included a type of authentication that we don't recognize: %s" % method
)
103 # our auth_methods should have a single AuthMethod.UNKNOWN entry if
104 # any unknown authentication methods exist
105 if AuthMethod
.UNKNOWN
not in auth_methods
:
106 auth_methods
.append(AuthMethod
.UNKNOWN
)
108 # parse optional COOKIEFILE mapping (quoted and can have escapes)
110 if line
.is_next_mapping('COOKIEFILE', True, True):
111 path
= line
._pop
_mapping
_bytes
(True, True)[1]
113 # fall back if our filesystem encoding doesn't work
115 for encoding
in [sys
.getfilesystemencoding(), 'utf-8', 'latin-1']:
117 self
.cookie_path
= path
.decode(encoding
)
122 if self
.cookie_path
is None:
123 raise stem
.ProtocolError("Cookie path '%s' mismatches our filesystem encoding (%s)" % (repr(path
), sys
.getfilesystemencoding()))
124 elif line_type
== 'VERSION':
126 # VersionLine = "250-VERSION" SP "Tor=" TorVersion OptArguments CRLF
127 # TorVersion = QuotedString
129 if not line
.is_next_mapping('Tor', True):
130 raise stem
.ProtocolError("PROTOCOLINFO response's VERSION line is missing its mandatory tor version mapping: %s" % line
)
133 self
.tor_version
= stem
.version
.Version(line
.pop_mapping(True)[1])
134 except ValueError as exc
:
135 raise stem
.ProtocolError(exc
)
137 log
.debug("Unrecognized PROTOCOLINFO line type '%s', ignoring it: %s" % (line_type
, line
))
139 self
.auth_methods
= tuple(auth_methods
)
140 self
.unknown_auth_methods
= tuple(unknown_auth_methods
)