def circuit_destroy(self, circuit): "Used by circuit_closed and circuit_failed (below)" txtorlog.msg("circuit_destroy:", circuit.id) circuit._when_built.fire( Failure(Exception("Destroying circuit; will never hit BUILT")) ) del self.circuits[circuit.id]
def _bootstrap(self, *args): """ The inlineCallbacks decorator allows us to make this method look synchronous; see the Twisted docs. Each yeild is for a Deferred after which the method continues. When this method finally exits, we're set up and do the post_bootstrap callback. """ try: self.valid_signals = yield self.get_info('signal/names') self.valid_signals = self.valid_signals['signal/names'] except TorProtocolError: self.valid_signals = ["RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE"] self.version = yield self.get_info('version') self.version = self.version['version'] txtorlog.msg("Connected to a Tor with VERSION", self.version) eventnames = yield self.get_info('events/names') eventnames = eventnames['events/names'] self._set_valid_events(eventnames) yield self.queue_command('USEFEATURE EXTENDED_EVENTS') self.post_bootstrap.callback(self) defer.returnValue(self)
def circuit_failed(self, circuit, **kw): "ICircuitListener API" txtorlog.msg("circuit_failed", circuit, str(kw)) circuit._when_built.fire( Failure(Exception("Circuit failed ('{}')".format(_extract_reason(kw)))) ) self.circuit_destroy(circuit)
def issue_stream_attach(circ): txtorlog.msg("circuit:", circ) if circ is None: # tell Tor to do what it likes return self.protocol.queue_command("ATTACHSTREAM %d 0" % stream.id) elif circ is self.DO_NOT_ATTACH: # do nothing; don't attach the stream return else: # should get a Circuit instance; check it for suitability if not isinstance(circ, Circuit): raise RuntimeError( "IStreamAttacher.attach() must return a Circuit instance " "(or None or DO_NOT_ATTACH): %s") if circ.id not in self.circuits: raise RuntimeError( "Attacher returned a circuit unknown to me.") if circ.state != 'BUILT': raise RuntimeError( "Can only attach to BUILT circuits; %d is in %s." % (circ.id, circ.state)) # we've got a valid Circuit instance; issue the command return self.protocol.queue_command("ATTACHSTREAM %d %d" % (stream.id, circ.id))
def issue_stream_attach(circ): txtorlog.msg("circuit:", circ) if circ is None or circ is TorState.DO_NOT_ATTACH: # tell Tor to do what it likes return self.protocol.queue_command( u"ATTACHSTREAM {} 0".format(stream.id).encode("ascii") ) else: # should get a Circuit instance; check it for suitability if not isinstance(circ, Circuit): raise RuntimeError( "IStreamAttacher.attach() must return a Circuit instance " "(or None or DO_NOT_ATTACH): %s" ) if circ.id not in self.circuits: raise RuntimeError( "Attacher returned a circuit unknown to me." ) if circ.state != 'BUILT': raise RuntimeError( "Can only attach to BUILT circuits; %d is in %s." % (circ.id, circ.state) ) # we've got a valid Circuit instance; issue the command return self.protocol.queue_command( u"ATTACHSTREAM {} {}".format(stream.id, circ.id).encode("ascii") )
def connectionMade(self): "Protocol API" txtorlog.msg('got connection, authenticating') # XXX this Deferred is just being dropped on the floor d = self.protocolinfo() d.addCallback(self._do_authenticate) d.addErrback(self._auth_failed)
def connectionLost(self, reason): "Protocol API" txtorlog.msg('connection terminated: ' + str(reason)) # ...and this is why we don't do on_disconnect = Deferred() :( # and instead should have had on_disconnect() method that # returned a new Deferred to each caller..(we're checking if # this Deferred has any callbacks because if it doesn't we'll # generate an "Unhandled error in Deferred") if not self.on_disconnect.called and self.on_disconnect.callbacks: if reason.check(ConnectionDone): self.on_disconnect.callback(self) else: self.on_disconnect.errback(reason) self.on_disconnect = None outstanding = [self.command ] + self.commands if self.command else self.commands for d, cmd, cmd_arg in outstanding: if not d.called: d.errback( Failure( TorDisconnectError( text=("Tor unexpectedly disconnected while " "running: {}".format(cmd.decode('ascii'))), error=reason, ))) return None
def circuit_built(self, circuit): "ICircuitListener API" txtorlog.msg( "circuit_built:", circuit.id, "->".join("%s.%s" % (x.name, x.location.countrycode) for x in circuit.path), circuit.streams )
def stream_attach(self, stream, circuit): """ IStreamListener: the stream has been attached to a circuit. It seems you get an attach to None followed by an attach to real circuit fairly frequently. Perhaps related to __LeaveStreamsUnattached? """ txtorlog.msg("stream_attach", stream.id, stream.target_host, " -> ", circuit)
def _bootstrap(self, *args): """ The inlineCallbacks decorator allows us to make this method look synchronous; see the Twisted docs. Each yeild is for a Deferred after which the method continues. When this method finally exits, we're set up and do the post_bootstrap callback. """ ## unfortunately I don't see a way to get this from the runing ## tor like the events...so this was taken from some version ## of the control-spec and must be kept up-to-date (or accpet ## any signal name and just wait for the reply? self.valid_signals = [ "RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE" ] self.version = yield self.get_info('version') self.version = self.version['version'] txtorlog.msg("Connected to a Tor with VERSION", self.version) eventnames = yield self.get_info('events/names') eventnames = eventnames['events/names'] self._set_valid_events(eventnames) yield self.queue_command('USEFEATURE EXTENDED_EVENTS') self.post_bootstrap.callback(self) defer.returnValue(self)
def _bootstrap(self, *args): """ The inlineCallbacks decorator allows us to make this method look synchronous; see the Twisted docs. Each yeild is for a Deferred after which the method continues. When this method finally exits, we're set up and do the post_bootstrap callback. """ ## unfortunately I don't see a way to get this from the runing ## tor like the events...so this was taken from some version ## of the control-spec and must be kept up-to-date (or accpet ## any signal name and just wait for the reply? self.valid_signals = ["RELOAD", "DUMP", "DEBUG", "NEWNYM", "CLEARDNSCACHE"] self.version = yield self.get_info('version') self.version = self.version['version'] txtorlog.msg("Connected to a Tor with VERSION", self.version) eventnames = yield self.get_info('events/names') eventnames = eventnames['events/names'] self._set_valid_events(eventnames) yield self.queue_command('USEFEATURE EXTENDED_EVENTS') self.post_bootstrap.callback(self) self.post_bootstrap = None defer.returnValue(self)
def _maybe_attach(self, stream): """ If we've got a custom stream-attachment instance (see set_attacher) this will ask it for the appropriate circuit. Note that we ignore .exit URIs and let Tor deal with those (by passing circuit ID 0). The stream attacher is allowed to return a Deferred which will callback with the desired circuit. You may return the special object DO_NOT_ATTACH which will cause the circuit attacher to simply ignore the stream (neither attaching it, nor telling Tor to attach it). """ if self.attacher: if stream.target_host is not None and '.exit' in stream.target_host: ## we want to totally ignore .exit URIs as these are ## used to specify a particular exit node, and trying ## to do STREAMATTACH on them will fail with an error ## from Tor anyway. txtorlog.msg("ignore attacher:", stream) return circ = IStreamAttacher(self.attacher).attach_stream( stream, self.circuits) if circ is self.DO_NOT_ATTACH: return if circ is None: self.protocol.queue_command("ATTACHSTREAM %d 0" % stream.id) else: if isinstance(circ, defer.Deferred): class IssueStreamAttach: def __init__(self, state, streamid): self.stream_id = streamid self.state = state def __call__(self, arg): circid = arg.id self.state.protocol.queue_command( "ATTACHSTREAM %d %d" % (self.stream_id, circid)) circ.addCallback(IssueStreamAttach( self, stream.id)).addErrback(log.err) else: if circ.id not in self.circuits: raise RuntimeError( "Attacher returned a circuit unknown to me.") if circ.state != 'BUILT': raise RuntimeError( "Can only attach to BUILT circuits; %d is in %s." % (circ.id, circ.state)) self.protocol.queue_command("ATTACHSTREAM %d %d" % (stream.id, circ.id))
def stream_closed(self, stream, **kw): """ IStreamListener: stream has been closed (won't be in controller's list anymore) """ txtorlog.msg("stream_closed", stream.id) del self.streams[stream.id]
def stream_failed(self, stream, **kw): """ IStreamListener: stream failed for some reason (won't be in controller's list anymore) """ txtorlog.msg("stream_failed", stream.id) del self.streams[stream.id]
def tor_connected(self, proto): txtorlog.msg("tor_connected %s" % proto) self.tor_protocol = proto if self.config is not None: self.config._update_proto(proto) self.tor_protocol.is_owned = self.transport.pid self.tor_protocol.post_bootstrap.addCallback(self.protocol_bootstrapped).addErrback(self.tor_connection_failed)
def _maybe_attach(self, stream): """ If we've got a custom stream-attachment instance (see set_attacher) this will ask it for the appropriate circuit. Note that we ignore .exit URIs and let Tor deal with those (by passing circuit ID 0). The stream attacher is allowed to return a Deferred which will callback with the desired circuit. You may return the special object DO_NOT_ATTACH which will cause the circuit attacher to simply ignore the stream (neither attaching it, nor telling Tor to attach it). """ if self.attacher: if stream.target_host is not None and ".exit" in stream.target_host: # we want to totally ignore .exit URIs as these are # used to specify a particular exit node, and trying # to do STREAMATTACH on them will fail with an error # from Tor anyway. txtorlog.msg("ignore attacher:", stream) return circ = self.attacher.attach_stream(stream, self.circuits) if circ is self.DO_NOT_ATTACH: return if circ is None: self.protocol.queue_command("ATTACHSTREAM %d 0" % stream.id) else: if isinstance(circ, defer.Deferred): class IssueStreamAttach: def __init__(self, state, streamid): self.stream_id = streamid self.state = state def __call__(self, arg): if arg is None: return self.state.protocol.queue_command("ATTACHSTREAM %d 0" % stream.id) else: circid = arg.id return self.state.protocol.queue_command( "ATTACHSTREAM %d %d" % (self.stream_id, circid) ) circ.addCallback(IssueStreamAttach(self, stream.id)) circ.addErrback(log.err) else: if circ.id not in self.circuits: raise RuntimeError("Attacher returned a circuit unknown to me.") if circ.state != "BUILT": raise RuntimeError("Can only attach to BUILT circuits; %d is in %s." % (circ.id, circ.state)) self.protocol.queue_command("ATTACHSTREAM %d %d" % (stream.id, circ.id))
def protocol_bootstrapped(self, proto): txtorlog.msg("Protocol is bootstrapped") self.tor_protocol.add_event_listener('STATUS_CLIENT', self.status_client) ## FIXME: should really listen for these to complete as well ## as bootstrap etc. For now, we'll be optimistic. self.tor_protocol.queue_command('TAKEOWNERSHIP') self.tor_protocol.queue_command('RESETCONF __OwningControllerProcess')
def circuit_failed(self, circuit, **kw): "ICircuitListener API" txtorlog.msg("circuit_failed", circuit, str(kw)) circuit._when_built.fire( Failure( CircuitBuildFailedError(_extract_reason(kw)) ) ) self.circuit_destroy(circuit)
def agent_for_socks_port(reactor, torconfig, socks_config, pool=None): """ This returns a Deferred that fires with an object that implements :class:`twisted.web.iweb.IAgent` and is thus suitable for passing to ``treq`` as the ``agent=`` kwarg. Of course can be used directly; see `using Twisted web cliet <http://twistedmatrix.com/documents/current/web/howto/client.html>`_. If you have a :class:`txtorcon.Tor` instance already, the preferred API is to call :meth:`txtorcon.Tor.web_agent` on it. :param torconfig: a :class:`txtorcon.TorConfig` instance. :param socks_config: anything valid for Tor's ``SocksPort`` option. This is generally just a TCP port (e.g. ``9050``), but can also be a unix path like so ``unix:/path/to/socket`` (Tor has restrictions on the ownership/permissions of the directory containing ``socket``). If the given SOCKS option is not already available in the underlying Tor instance, it is re-configured to add the SOCKS option. """ # :param tls: True (the default) will use Twisted's default options # with the hostname in the URI -- that is, TLS verification # similar to a Browser. Otherwise, you can pass whatever Twisted # returns for `optionsForClientTLS # <https://twistedmatrix.com/documents/current/api/twisted.internet.ssl.optionsForClientTLS.html>`_ socks_config = str(socks_config) # sadly, all lists are lists-of-strings to Tor :/ if socks_config not in torconfig.SocksPort: txtorlog.msg("Adding SOCKS port '{}' to Tor".format(socks_config)) torconfig.SocksPort.append(socks_config) try: yield torconfig.save() except Exception as e: raise RuntimeError( "Failed to reconfigure Tor with SOCKS port '{}': {}".format( socks_config, str(e) ) ) if socks_config.startswith('unix:'): socks_ep = UNIXClientEndpoint(reactor, socks_config[5:]) else: if ':' in socks_config: host, port = socks_config.split(':', 1) else: host = '127.0.0.1' port = int(socks_config) socks_ep = TCP4ClientEndpoint(reactor, host, port) returnValue( Agent.usingEndpointFactory( reactor, _AgentEndpointFactoryUsingTor(reactor, socks_ep), pool=pool, ) )
def connectionLost(self, reason): "Protocol API" txtorlog.msg('connection terminated: ' + str(reason)) if self.on_disconnect.callbacks: if reason.check(ConnectionDone): self.on_disconnect.callback(self) else: self.on_disconnect.errback(reason) self.on_disconnect = None return None
def _do_authenticate(self, protoinfo): """ Callback on PROTOCOLINFO to actually authenticate once we know what's supported. """ methods = None for line in protoinfo.split('\n'): if line[:5] == 'AUTH ': kw = parse_keywords(line[5:].replace(' ', '\n')) methods = kw['METHODS'].split(',') if not methods: raise RuntimeError( "Didn't find AUTH line in PROTOCOLINFO response.") if 'SAFECOOKIE' in methods: cookie = re.search('COOKIEFILE="(.*)"', protoinfo).group(1) self.cookie_data = open(cookie, 'r').read() if len(self.cookie_data) != 32: raise RuntimeError( "Expected authentication cookie to be 32 bytes, got %d" % len(self.cookie_data)) txtorlog.msg("Using SAFECOOKIE authentication", cookie, len(self.cookie_data), "bytes") self.client_nonce = os.urandom(32) d = self.queue_command('AUTHCHALLENGE SAFECOOKIE %s' % base64.b16encode(self.client_nonce)) d.addCallback(self._safecookie_authchallenge).addCallback( self._bootstrap).addErrback(self._auth_failed) return elif 'COOKIE' in methods: cookie = re.search('COOKIEFILE="(.*)"', protoinfo).group(1) with open(cookie, 'r') as cookiefile: data = cookiefile.read() if len(data) != 32: raise RuntimeError( "Expected authentication cookie to be 32 bytes, got %d" % len(data)) txtorlog.msg("Using COOKIE authentication", cookie, len(data), "bytes") self.authenticate(data).addCallback(self._bootstrap).addErrback( self._auth_failed) return if self.password_function: passwd = defer.maybeDeferred(self.password_function) passwd.addCallback(self._do_password_authentication).addErrback( self._auth_failed) return raise RuntimeError( "The Tor I connected to doesn't support SAFECOOKIE nor COOKIE authentication and I have no password_function specified." )
def _newdesc_update(self, args): """ Callback used internall for ORCONN and NEWDESC events to update Router information. FIXME: need to look at state for NEWDESC; if it's CLOSED we probably want to remove it from dicts... """ hsh = args[:41] if not self.routers.has_key(hsh): txtorlog.msg("haven't seen", hsh, "yet!") self.protocol.get_info_raw('ns/id/%s' % hsh[1:]).addCallback(self._update_network_status).addErrback(log.err) txtorlog.msg("NEWDESC", args)
def _tor_connected(self, proto): txtorlog.msg("tor_connected %s" % proto) self.tor_protocol = proto self.tor_protocol.is_owned = self.transport.pid yield self.tor_protocol.post_bootstrap txtorlog.msg("Protocol is bootstrapped") yield self.tor_protocol.add_event_listener('STATUS_CLIENT', self._status_client) yield self.tor_protocol.queue_command('TAKEOWNERSHIP') yield self.tor_protocol.queue_command('RESETCONF __OwningControllerProcess') if self.config is not None and self.config.protocol is None: yield self.config.attach_protocol(proto) returnValue(self) # XXX or "proto"?
def _newdesc_update(self, args): """ Callback used internall for ORCONN and NEWDESC events to update Router information. FIXME: need to look at state for NEWDESC; if it's CLOSED we probably want to remove it from dicts... """ hsh = args[:41] if hsh not in self.routers: txtorlog.msg("haven't seen", hsh, "yet!") self.protocol.get_info_raw('ns/id/%s' % hsh[1:]).addCallback( self._update_network_status).addErrback(log.err) txtorlog.msg("NEWDESC", args)
def _do_authenticate(self, protoinfo): """ Callback on PROTOCOLINFO to actually authenticate once we know what's supported. """ methods = None for line in protoinfo.split('\n'): if line[:5] == 'AUTH ': kw = parse_keywords(line[5:].replace(' ', '\n')) methods = kw['METHODS'].split(',') if not methods: raise RuntimeError("Didn't find AUTH line in PROTOCOLINFO response.") if 'SAFECOOKIE' in methods: cookie = re.search('COOKIEFILE="(.*)"', protoinfo).group(1) self.cookie_data = open(cookie, 'r').read() if len(self.cookie_data) != 32: raise RuntimeError("Expected authentication cookie to be 32 bytes, got %d" % len(self.cookie_data)) txtorlog.msg("Using SAFECOOKIE authentication", cookie, len(self.cookie_data), "bytes") self.client_nonce = os.urandom(32) d = self.queue_command('AUTHCHALLENGE SAFECOOKIE %s' % base64.b16encode(self.client_nonce)) d.addCallback(self._safecookie_authchallenge).addCallback(self._bootstrap).addErrback(self._auth_failed) return elif 'COOKIE' in methods: cookie = re.search('COOKIEFILE="(.*)"', protoinfo).group(1) with open(cookie, 'r') as cookiefile: data = cookiefile.read() if len(data) != 32: raise RuntimeError("Expected authentication cookie to be 32 bytes, got %d" % len(data)) txtorlog.msg("Using COOKIE authentication", cookie, len(data), "bytes") self.authenticate(data).addCallback(self._bootstrap).addErrback(self._auth_failed) return if self.password_function: passwd = defer.maybeDeferred(self.password_function) passwd.addCallback(self._do_password_authentication).addErrback(self._auth_failed) return raise RuntimeError("The Tor I connected to doesn't support SAFECOOKIE nor COOKIE authentication and I have no password_function specified.")
def outReceived(self, data): """ :api:`twisted.internet.protocol.ProcessProtocol <ProcessProtocol>` API """ self.stdout.append(data) ## minor hack: we can't try this in connectionMade because ## that's when the process first starts up so Tor hasn't ## opened any ports properly yet. So, we presume that after ## its first output we're good-to-go. If this fails, we'll ## reset and try again at the next output (see this class' ## tor_connection_failed) txtorlog.msg(data) if not self.attempted_connect and 'Bootstrap' in data: self.attempted_connect = True d = self.connection_creator() d.addCallback(self.tor_connected) d.addErrback(self.tor_connection_failed)
def _maybe_issue_command(self): """ If there's at least one command queued and we're not currently processing a command, this will issue the next one on the wire. """ if self.command: return if len(self.commands): self.command = self.commands.pop(0) (d, cmd, cmd_arg) = self.command self.defer = d self.debuglog.write(cmd + '\n') self.debuglog.flush() data = cmd + '\r\n' txtorlog.msg("cmd: {}".format(data.strip())) self.transport.write(data.encode('utf8'))
def _update_network_status(self, data): """ Used internally as a callback for updating Router information from NEWCONSENSUS events. """ # XXX why are we ever getting this with 0 data? if len(data): self._old_routers = self.routers self.routers = dict() self.all_routers = set() self.routers_by_hash = dict() self.routers_by_name = dict() for line in data.split('\n'): self._network_status_parser.feed_line(line) self._network_status_parser.done() txtorlog.msg(len(self.routers_by_name), "named routers found.") # remove any names we added that turned out to have dups remove_keys = set() for (k, v) in self.routers.items(): if v is None: txtorlog.msg(len(self.routers_by_name[k]), "dups:", k) remove_keys.add(k) for k in remove_keys: del self.routers[k] txtorlog.msg(len(self.guards), "GUARDs")
def outReceived(self, data): """ :api:`twisted.internet.protocol.ProcessProtocol <ProcessProtocol>` API """ if self.stdout: self.stdout.write(data.decode('ascii')) # minor hack: we can't try this in connectionMade because # that's when the process first starts up so Tor hasn't # opened any ports properly yet. So, we presume that after # its first output we're good-to-go. If this fails, we'll # reset and try again at the next output (see this class' # tor_connection_failed) txtorlog.msg(data) if not self.attempted_connect and self.connection_creator \ and b'Bootstrap' in data: self.attempted_connect = True # hmmm, we don't "do" anything with this Deferred? # (should it be connected to the when_connected # Deferreds?) d = self.connection_creator() d.addCallback(self._tor_connected) d.addErrback(self._tor_connection_failed)
def _update_network_status(self, data): """ Used internally as a callback for updating Router information from NS and NEWCONSENSUS events. """ for line in data.split('\n'): self._network_status_parser.process(line) txtorlog.msg(len(self.routers_by_name), "named routers found.") ## remove any names we added that turned out to have dups for (k, v) in self.routers.items(): if v is None: txtorlog.msg(len(self.routers_by_name[k]), "dups:", k) del self.routers[k] txtorlog.msg(len(self.guards), "GUARDs")
def connectionMade(self): "Protocol API" txtorlog.msg('got connection, authenticating') d = self.protocolinfo() d.addCallback(self._do_authenticate) d.addErrback(self._auth_failed)
def circuit_new(self, circuit): "ICircuitListener API" txtorlog.msg("circuit_new:", circuit.id) self.circuits[circuit.id] = circuit
def circuit_extend(self, circuit, router): "ICircuitListener API" txtorlog.msg("circuit_extend:", circuit.id, router)
def circuit_launched(self, circuit): "ICircuitListener API" txtorlog.msg("circuit_launched", circuit) self.circuits[circuit.id] = circuit
def stream_detach(self, stream, **kw): """ IStreamListener """ txtorlog.msg("stream_detach", stream.id)
def stream_succeeded(self, stream): "IStreamListener: stream has succeeded" txtorlog.msg("stream_succeeded", stream)
def circuit_failed(self, circuit, **kw): "ICircuitListener API" txtorlog.msg("circuit_failed", circuit, str(kw)) self.circuit_destroy(circuit)
def _do_authenticate(self, protoinfo): """ Callback on PROTOCOLINFO to actually authenticate once we know what's supported. """ methods = None cookie_auth = False for line in protoinfo.split('\n'): if line[:5] == 'AUTH ': kw = parse_keywords(line[5:].replace(' ', '\n')) methods = kw['METHODS'].split(',') if not methods: raise RuntimeError( "Didn't find AUTH line in PROTOCOLINFO response." ) if 'SAFECOOKIE' in methods or 'COOKIE' in methods: cookiefile_match = re.search(r'COOKIEFILE=("(?:[^"\\]|\\.)*")', protoinfo) if cookiefile_match: cookiefile = cookiefile_match.group(1) cookiefile = unescape_quoted_string(cookiefile) try: self._read_cookie(cookiefile) except IOError as why: txtorlog.msg("Reading COOKIEFILE failed: " + str(why)) cookie_auth = False else: cookie_auth = True else: txtorlog.msg("Didn't get COOKIEFILE") if cookie_auth: if 'SAFECOOKIE' in methods: txtorlog.msg("Using SAFECOOKIE authentication", cookiefile, len(self._cookie_data), "bytes") self.client_nonce = os.urandom(32) cmd = 'AUTHCHALLENGE SAFECOOKIE ' + \ base64.b16encode(self.client_nonce) d = self.queue_command(cmd) d.addCallback(self._safecookie_authchallenge) d.addCallback(self._bootstrap) d.addErrback(self._auth_failed) return elif 'COOKIE' in methods: txtorlog.msg("Using COOKIE authentication", cookiefile, len(self._cookie_data), "bytes") d = self.authenticate(self._cookie_data) d.addCallback(self._bootstrap) d.addErrback(self._auth_failed) return if self.password_function and 'HASHEDPASSWORD' in methods: d = defer.maybeDeferred(self.password_function) d.addCallback(self._do_password_authentication) d.addErrback(self._auth_failed) return if 'NULL' in methods: d = self.queue_command('AUTHENTICATE') d.addCallback(self._bootstrap) d.addErrback(self._auth_failed) return raise RuntimeError( "The Tor I connected to doesn't support SAFECOOKIE nor COOKIE" " authentication and I have no password_function specified." )
def circuit_closed(self, circuit, **kw): "ICircuitListener API" txtorlog.msg("circuit_closed", circuit) self.circuit_destroy(circuit)
def circuit_destroy(self, circuit): "Used by circuit_closed and circuit_failed (below)" txtorlog.msg("circuit_destroy:", circuit.id) del self.circuits[circuit.id]