Exemple #1
0
 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)
Exemple #3
0
 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]
Exemple #4
0
 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)
Exemple #5
0
 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)
Exemple #6
0
        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))
Exemple #7
0
        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")
                )
Exemple #8
0
 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)
Exemple #9
0
 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
Exemple #10
0
 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
     )
Exemple #11
0
 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
     )
Exemple #12
0
 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)
Exemple #13
0
    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 _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)
Exemple #16
0
    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))
Exemple #17
0
    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]
Exemple #18
0
    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]
Exemple #19
0
    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]
Exemple #20
0
 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)
Exemple #21
0
    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)
Exemple #22
0
    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]
Exemple #23
0
    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)
Exemple #24
0
    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))
Exemple #25
0
    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')
Exemple #26
0
    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')
Exemple #27
0
 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)
Exemple #28
0
 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)
Exemple #29
0
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,
        )
    )
Exemple #30
0
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,
        )
    )
Exemple #31
0
 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 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."
        )
Exemple #34
0
    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)
Exemple #35
0
    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"?
Exemple #36
0
    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)
Exemple #37
0
    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.")
Exemple #38
0
    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)
Exemple #39
0
    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 _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'))
Exemple #41
0
    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)
Exemple #42
0
    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")
Exemple #43
0
    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")
Exemple #44
0
    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)
Exemple #45
0
    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)
Exemple #46
0
    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")
Exemple #47
0
    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)
Exemple #49
0
 def circuit_new(self, circuit):
     "ICircuitListener API"
     txtorlog.msg("circuit_new:", circuit.id)
     self.circuits[circuit.id] = circuit
Exemple #50
0
 def circuit_extend(self, circuit, router):
     "ICircuitListener API"
     txtorlog.msg("circuit_extend:", circuit.id, router)
Exemple #51
0
 def circuit_launched(self, circuit):
     "ICircuitListener API"
     txtorlog.msg("circuit_launched", circuit)
     self.circuits[circuit.id] = circuit
Exemple #52
0
 def circuit_extend(self, circuit, router):
     "ICircuitListener API"
     txtorlog.msg("circuit_extend:", circuit.id, router)
Exemple #53
0
 def stream_detach(self, stream, **kw):
     """
     IStreamListener
     """
     txtorlog.msg("stream_detach", stream.id)
Exemple #54
0
 def circuit_new(self, circuit):
     "ICircuitListener API"
     txtorlog.msg("circuit_new:", circuit.id)
     self.circuits[circuit.id] = circuit
Exemple #55
0
 def stream_succeeded(self, stream):
     "IStreamListener: stream has succeeded"
     txtorlog.msg("stream_succeeded", stream)
Exemple #56
0
 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."
        )
Exemple #58
0
 def circuit_closed(self, circuit, **kw):
     "ICircuitListener API"
     txtorlog.msg("circuit_closed", circuit)
     self.circuit_destroy(circuit)
Exemple #59
0
 def circuit_destroy(self, circuit):
     "Used by circuit_closed and circuit_failed (below)"
     txtorlog.msg("circuit_destroy:", circuit.id)
     del self.circuits[circuit.id]