1# -*- coding: utf-8 -*-
2
3from __future__ import absolute_import
4from __future__ import print_function
5from __future__ import with_statement
6
7import os
8import shutil
9import weakref
10import tempfile
11import functools
12from binascii import b2a_base64
13
14from txtorcon.util import available_tcp_port
15from txtorcon.socks import TorSocksEndpoint
16
17from twisted.internet.interfaces import IStreamClientEndpointStringParserWithReactor
18from twisted.internet import defer, error
19from twisted.python import log
20from twisted.python.deprecate import deprecated
21from twisted.python.failure import Failure
22from twisted.internet.interfaces import IStreamServerEndpointStringParser
23from twisted.internet.interfaces import IStreamServerEndpoint
24from twisted.internet.interfaces import IStreamClientEndpoint
25from twisted.internet.interfaces import IListeningPort
26from twisted.internet.interfaces import IAddress
27from twisted.internet.endpoints import serverFromString
28from twisted.internet.endpoints import clientFromString
29from twisted.internet.endpoints import TCP4ClientEndpoint
30# from twisted.internet.endpoints import UNIXClientEndpoint
31# from twisted.internet import error
32from twisted.plugin import IPlugin
33from twisted.python.util import FancyEqMixin
34
35from zope.interface import implementer
36from zope.interface import Interface, Attribute
37
38from .torconfig import TorConfig
39from .onion import IAuthenticatedOnionClients
40from .onion import FilesystemOnionService
41from .onion import IFilesystemOnionService
42from .onion import EphemeralOnionService
43from .onion import FilesystemAuthenticatedOnionService
44from .onion import EphemeralAuthenticatedOnionService
45from .onion import AuthStealth  # , AuthBasic
46from .torconfig import _endpoint_from_socksport_line
47from .util import SingleObserver, _Version
48
49
50_global_tor = None
51_global_tor_lock = defer.DeferredLock()
52# we need the lock because we (potentially) yield several times while
53# "creating" the TorConfig instance
54
55
56# in an ideal world, we'd have "get_global_tor()" and it would return
57# a Tor instance, and all would be well. HOWEVER, "get_global_tor" was
58# previously released to return a TorConfig instance. So it still
59# needs to do that, *and* it should be the very same TorConfig
60# instance attached to the "global Tor instance".
61# Anyway: get_global_tor_instance() is the newst API, and the one new
62# code should use (if at all -- ideally just use a .global_tor()
63# factory-function call instead)
64
65@defer.inlineCallbacks
66def get_global_tor_instance(reactor,
67                            control_port=None,
68                            progress_updates=None,
69                            _tor_launcher=None):
70    """
71    Normal users shouldn't need to call this; use
72    TCPHiddenServiceEndpoint::system_tor instead.
73
74    :return Tor: a 'global to this Python process' instance of
75        Tor. There isn't one of these until the first time this method
76        is called. All calls to this method return the same instance.
77    """
78    global _global_tor
79    global _global_tor_lock
80    yield _global_tor_lock.acquire()
81
82    if _tor_launcher is None:
83        # XXX :( mutual dependencies...really get_global_tor_instance
84        # should be in controller.py if it's going to return a Tor
85        # instance.
86        from .controller import launch
87        _tor_launcher = launch
88
89    try:
90        if _global_tor is None:
91            _global_tor = yield _tor_launcher(reactor, progress_updates=progress_updates)
92
93        else:
94            config = yield _global_tor.get_config()
95            already_port = config.ControlPort
96            if control_port is not None and control_port != already_port:
97                raise RuntimeError(
98                    "ControlPort is already '{}', but you wanted '{}'",
99                    already_port,
100                    control_port,
101                )
102
103        defer.returnValue(_global_tor)
104    finally:
105        _global_tor_lock.release()
106
107
108@deprecated(_Version("txtorcon", 18, 0, 0))
109@defer.inlineCallbacks
110def get_global_tor(reactor, control_port=None,
111                   progress_updates=None,
112                   _tor_launcher=None):
113    """
114    See description of :class:`txtorcon.TCPHiddenServiceEndpoint`'s
115    class-method ``global_tor``
116
117    :param control_port:
118        a TCP port upon which to run the launched Tor's
119        control-protocol (selected by the OS by default).
120
121    :param progress_updates:
122        A callable that takes 3 args: ``percent, tag, message`` which
123        is called when Tor announcing some progress setting itself up.
124
125    :returns:
126        a ``Deferred`` that fires a :class:`txtorcon.TorConfig` which is
127        bootstrapped.
128
129    The _tor_launcher keyword arg is internal-only.
130    """
131    tor = yield get_global_tor_instance(
132        reactor,
133        control_port=control_port,
134        progress_updates=progress_updates,
135        _tor_launcher=_tor_launcher,
136    )
137    cfg = yield tor.get_config()
138    defer.returnValue(cfg)
139
140
141class IProgressProvider(Interface):
142    """FIXME move elsewhere? think harder?"""
143    def add_progress_listener(listener):
144        """
145        Adds a progress listener. The listener is a callable that gets
146        called with 3 arguments corresponding to Tor's updates:
147        (percent, tag, message). percent is an integer from 0 to 100,
148        tag and message are both strings. (message is the
149        human-readable one)
150        """
151
152
153# XXX essentially, we either want an ephemeral vs. non-ephemeral etc
154# endpoint instance, *or* we just make this a "delayed" version of
155# create_onion_service -- i.e. holds all the same args as that and
156# listen() instantiates it and knows "which" tor it wants.
157@implementer(IStreamServerEndpoint)
158@implementer(IProgressProvider)
159class TCPHiddenServiceEndpoint(object):
160    """
161    This represents something listening on an arbitrary local port
162    that has a Tor configured with a Hidden Service pointing at
163    it. :api:`twisted.internet.endpoints.TCP4ServerEndpoint
164    <TCP4ServerEndpoint>` is used under the hood to do the local
165    listening.
166
167    There are three main ways to use this class, and you are
168    encouraged to use the @classmethod ways of creating instances:
169    `system_tor <#txtorcon.TCPHiddenServiceEndpoint.system_tor>`_,
170    `global_tor <#txtorcon.TCPHiddenServiceEndpoint.global_tor>`_,
171    and `private_tor <#txtorcon.TCPHiddenServiceEndpoint.private_tor>`_
172
173    1. system_tor(...) connects to an already-started tor on the
174       endpoint you specify; stricly speaking not a "system" tor since
175       you could have spawned it some other way. See `Tor bug 11291
176       <https://trac.torproject.org/projects/tor/ticket/11291>`_
177       however.
178
179    2. global_tor(...) refers to a single possible Tor instance
180       per python process. So the first call to this launches a new Tor, and
181       subsequent calls re-use the existing Tor (that is, add more hidden
182       services to it).
183
184    3. private_tor(...) launches a new Tor instance no matter what, so
185       it will have just the one hidden serivce on it.
186
187    If you need to set configuration options that are not reflected in
188    any of the method signatures above, you'll have to construct an
189    instance of this class yourself (i.e. with a TorConfig instance
190    you've created).
191
192    No matter how you came by this endpoint instance, you should call
193    `listen()` on it to trigger any work required to create the
194    service: Tor will be launched or connected-to; config for the
195    onion service will be added; the uploading of descriptors is
196    awaited.
197
198    The ``Deferred`` from ``listen()`` will fire with an
199    ``IListeningPort`` whose ``getHost()`` will return a
200    :class:`txtorcon.TorOnionAddress`. The port object also has a
201    `.onion_service` property which resolves to the
202    :class:`txtorcon.IOnionService` or
203    :class:`txtorcon.IAuthenticatedOnionClients` instance (and from
204    which you can recover private keys, the hostname, etc)
205
206    :ivar onion_uri: the public key, like ``timaq4ygg2iegci7.onion``
207        which came from the hidden_service_dir's ``hostname`` file
208
209    :ivar onion_private_key: the contents of ``hidden_service_dir/private_key``
210
211    :ivar hidden_service_dir: the data directory, either passed in or created
212        with ``tempfile.mkdtemp``
213
214    **NOTE** that if you do not specify a `version=` then you will get
215    a version 2 service (new onion APIs return version=3 services by
216    default). This is for backwards-compatiblity reasons, as version=
217    didn't exist before 18.0.0
218    """
219
220    @classmethod
221    def system_tor(cls, reactor, control_endpoint, public_port,
222                   hidden_service_dir=None,
223                   local_port=None,
224                   ephemeral=None,
225                   auth=None,
226                   private_key=None,
227                   version=None,
228                   single_hop=None):
229        """
230        This returns a TCPHiddenServiceEndpoint connected to the
231        endpoint you specify in `control_endpoint`. After connecting, a
232        single hidden service is added. The endpoint can be a Unix
233        socket if Tor's `ControlSocket` option was used (instead of
234        `ControlPort`).
235
236        .. note::
237
238            If Tor bug #11291 is not yet fixed, this won't work if you
239            only have Group access. XXX FIXME re-test
240        """
241
242        from txtorcon.controller import connect
243        tor = connect(reactor, control_endpoint)
244        tor.addCallback(lambda t: t.get_config())
245        # tor is a Deferred
246        return TCPHiddenServiceEndpoint(
247            reactor, tor, public_port,
248            hidden_service_dir=hidden_service_dir,
249            local_port=local_port,
250            ephemeral=ephemeral,
251            private_key=private_key,
252            auth=auth,
253            version=version,
254            single_hop=single_hop,
255        )
256
257    @classmethod
258    def global_tor(cls, reactor, public_port,
259                   hidden_service_dir=None,
260                   local_port=None,
261                   control_port=None,
262                   stealth_auth=None,  # backwards-compat; don't use
263                   auth=None,
264                   ephemeral=None,
265                   private_key=None,
266                   version=None,
267                   single_hop=None):
268        """
269        This returns a TCPHiddenServiceEndpoint connected to a
270        txtorcon global Tor instance. The first time you call this, a
271        new Tor will be launched. Subsequent calls will re-use the
272        same connection (in fact, the very same TorControlProtocol and
273        TorConfig instances). If the options you pass are incompatible
274        with an already-launched Tor, RuntimeError will be thrown.
275
276        It's probably best to not specify any option besides
277        `public_port`, `hidden_service_dir`, and maybe `local_port`
278        unless you have a specific need to.
279
280        You can also access this global txtorcon instance via
281        :meth:`txtorcon.get_global_tor_instance` (which is precisely what
282        this method uses to get it).
283
284        All keyword options have defaults (e.g. random ports, or
285        tempdirs).
286
287        :param stealth_auth: **Deprecated**
288            None, or a list of strings -- one for each stealth
289            authenticator you require. Use `auth=` now.
290
291        :param auth: None or an :class:`txtorcon.AuthBasic` or
292            :class:`txtorcon.AuthStealth` instance
293        """
294
295        def progress(*args):
296            progress.target(*args)
297        tor = get_global_tor_instance(
298            reactor,
299            control_port=control_port,
300            progress_updates=progress
301        )
302        # tor is a Deferred here, but endpoint resolves it in the
303        # listen() call. Also, we want it to resolve to a TorConfig,
304        # not a Tor
305        tor.addCallback(lambda tor: tor.get_config())
306        r = TCPHiddenServiceEndpoint(
307            reactor, tor, public_port,
308            hidden_service_dir=hidden_service_dir,
309            local_port=local_port,
310            auth=auth,
311            ephemeral=ephemeral,
312            private_key=private_key,
313            version=version,
314            single_hop=single_hop,
315        )
316        progress.target = r._tor_progress_update
317        return r
318
319    @classmethod
320    def private_tor(cls, reactor, public_port,
321                    hidden_service_dir=None,
322                    local_port=None,
323                    control_port=None,
324                    ephemeral=None,
325                    private_key=None,
326                    auth=None,
327                    version=None,
328                    single_hop=None):
329        """
330        This returns a TCPHiddenServiceEndpoint that's always
331        connected to its own freshly-launched Tor instance. All
332        keyword options have defaults (e.g. random ports, or
333        tempdirs).
334        """
335
336        def progress(*args):
337            progress.target(*args)
338
339        from .controller import launch
340        tor = launch(
341            reactor,
342            progress_updates=progress,
343            control_port=control_port,
344        )
345        tor.addCallback(lambda t: t.get_config())
346        r = TCPHiddenServiceEndpoint(
347            reactor, tor, public_port,
348            hidden_service_dir=hidden_service_dir,
349            local_port=local_port,
350            ephemeral=ephemeral,
351            private_key=private_key,
352            auth=auth,
353            version=version,
354            single_hop=single_hop,
355        )
356        progress.target = r._tor_progress_update
357        return r
358
359    def __init__(self, reactor, config, public_port,
360                 hidden_service_dir=None,
361                 local_port=None,
362                 auth=None,  # new way to pass authentication information
363                 stealth_auth=None,  # deprecated; use auth=
364                 ephemeral=None,  # will be set to True, unless hsdir spec'd
365                 private_key=None,
366                 group_readable=False,
367                 version=None,
368                 single_hop=None):
369        """
370        :param reactor:
371            :api:`twisted.internet.interfaces.IReactorTCP` provider
372
373        :param config:
374            :class:`txtorcon.TorConfig` instance or a Deferred yielding one
375
376        :param public_port:
377            The port number we will advertise in the hidden serivces
378            directory.
379
380        :param local_port:
381            The port number we will perform our local tcp listen on and
382            receive incoming connections from the tor process.
383
384        :param hidden_service_dir:
385            If not None, point to a HiddenServiceDir directory
386            (i.e. with "hostname" and "private_key" files in it). If
387            not provided, one is created with temp.mkdtemp() AND
388            DELETED when the reactor shuts down.
389
390        :param auth:
391            An AuthBasic or AuthStealth instance (or None)
392
393        :param stealth_auth:
394            **Deprecated; use ``auth=``**. This is for backwards-comapatibility only.
395
396        :param endpoint_generator:
397            A callable that generates a new instance of something that
398            implements IServerEndpoint (by default TCP4ServerEndpoint)
399
400        :param group_readable:
401            Only for filesystem services. Causes the directory to be
402            group-readable when Tor creates it.
403
404        :param version:
405            Either None, 2 or 3 to specify a version 2 service or
406            Proposition 224 (version 3) service.
407
408        :param single_hop: if True, pass the `NonAnonymous` flag. Note
409            that Tor options `HiddenServiceSingleHopMode`,
410            `HiddenServiceNonAnonymousMode` must be set to `1` and there
411            must be no `SOCKSPort` configured for this to actually work.
412        """
413
414        # this supports API backwards-compatibility -- if you didn't
415        # explicitly specify ephemeral=True, but *did* set
416        # hidden_service_dir
417        if ephemeral is None:
418            ephemeral = True
419            if hidden_service_dir is not None:
420                ephemeral = False
421
422        # backwards-compatibility for stealth_auth= kwarg
423        if stealth_auth is not None:
424            log.msg("'stealth_auth' is deprecated; use auth= instead")
425            if auth is not None:
426                raise ValueError(
427                    "Both stealth_auth= and auth= passed; use auth= only for new code"
428                )
429            auth = AuthStealth(stealth_auth)
430            stealth_auth = None
431
432        if ephemeral and isinstance(auth, AuthStealth):
433            raise ValueError(
434                "'ephemeral=True' onion services don't support 'stealth' auth"
435            )
436
437        if ephemeral and hidden_service_dir is not None:
438            raise ValueError(
439                "Specifying 'hidden_service_dir' is incompatible"
440                " with 'ephemeral=True'"
441            )
442
443        if private_key is not None and not ephemeral:
444            raise ValueError(
445                "'private_key' only understood for ephemeral services"
446            )
447
448        if single_hop and not ephemeral:
449            raise ValueError(
450                "'single_hop=' flag only makes sense for ephemeral onions"
451            )
452
453        self._reactor = reactor
454        self._config = defer.maybeDeferred(lambda: config)
455        self.public_port = public_port
456        self.local_port = local_port
457        self.auth = auth
458
459        self.ephemeral = ephemeral
460        self.private_key = private_key
461        # if we're an ephemeral service, hidden_service_dir is None
462        # and ephemeral is True
463        self.hidden_service_dir = hidden_service_dir
464        self.tcp_listening_port = None
465        self.hiddenservice = None
466        self.group_readable = group_readable
467        self.version = version
468        self.single_hop = single_hop
469        self.retries = 0
470
471        if self.version is None:
472            self.version = 2
473
474        '''for IProgressProvider to add_progress_listener'''
475        self.progress_listeners = []
476
477        if not self.ephemeral:
478            if self.hidden_service_dir is None:
479                self.hidden_service_dir = tempfile.mkdtemp(prefix='tortmp')
480                log.msg('Will delete "%s" at shutdown.' % self.hidden_service_dir)
481                delete = functools.partial(shutil.rmtree, self.hidden_service_dir)
482                self._reactor.addSystemEventTrigger('before', 'shutdown', delete)
483
484    @property
485    def onion_uri(self):
486        if self.hiddenservice is None:
487            return None
488        if IAuthenticatedOnionClients.providedBy(self.hiddenservice):
489            return _maybe_unique_host(self.hiddenservice)
490        return self.hiddenservice.hostname
491
492    @property
493    def onion_private_key(self):
494        if self.hiddenservice is None:
495            return None
496        return self.hiddenservice.private_key
497
498    def add_progress_listener(self, listener):
499        """IProgressProvider API"""
500        self.progress_listeners.append(listener)
501
502    def _descriptor_progress_update(self, prog, tag, summary):
503        # 'prog' here ranges from 0 -> 100.0 but we've only reserved
504        # "10.0" of the range for the "upload descriptors" portion,
505        # and we know that Tor has launched, so we're mapping "prog"
506        # to the "100 -> 110" part of the range
507        scaled_prog = (100.0 + (prog / 10.0)) * (100.0 / 110.0)
508        for p in self.progress_listeners:
509            try:
510                p(scaled_prog, tag, summary)
511            except Exception:
512                log.err()
513
514    def _tor_progress_update(self, prog, tag, summary):
515        # we re-adjust the percentage-scale, using 105% and 110% for
516        # the two parts of waiting for descriptor upload. That is, we
517        # want: 110 * constant == 100.0
518        scaled_prog = prog * (100.0 / 110.0)
519        log.msg('%d%% %s' % (scaled_prog, summary))
520        for p in self.progress_listeners:
521            try:
522                p(scaled_prog, tag, summary)
523            except Exception:
524                log.err()
525
526    @defer.inlineCallbacks
527    def listen(self, protocolfactory):
528        """
529        Implement :api:`twisted.internet.interfaces.IStreamServerEndpoint
530        <IStreamServerEndpoint>`.
531
532        Returns a Deferred that delivers an
533        :api:`twisted.internet.interfaces.IListeningPort` implementation.
534
535        This object will also have a `.onion_service` property which
536        resolve to an instance implementing
537        :class:`txtorcon.IOnionService` or
538        :class:`txtorcon.IAuthenticatedOnionClients` (depending on
539        whether the service is authenticated or not).
540
541        At this point, Tor will have fully started up and successfully
542        accepted the hidden service's config. The Onion Service's
543        descriptor will be uploaded to at least one directory (as
544        reported via the `HS_DESC` event).
545        """
546
547        self.protocolfactory = protocolfactory
548
549        # self._config is always a Deferred; see __init__
550        self._config = yield self._config
551        if not isinstance(self._config, TorConfig):
552            raise ValueError(
553                'Expected a TorConfig instance but '
554                'got "{}.{}" instead.'.format(
555                    self._config.__class__.__module__,
556                    self._config.__class__.__name__,
557                )
558            )
559        # just to be sure:
560        yield self._config.post_bootstrap
561
562        # XXX - perhaps allow the user to pass in an endpoint
563        # descriptor and make this one the default? Then would
564        # probably want to check for "is a local interface or not" and
565        # at *least* warn if it's not local...
566        self.tcp_endpoint = serverFromString(
567            self._reactor,
568            'tcp:0:interface=127.0.0.1',
569        )
570        d = self.tcp_endpoint.listen(self.protocolfactory)
571        self.tcp_listening_port = yield d
572        self.local_port = self.tcp_listening_port.getHost().port
573
574        # XXX can we detect if tor supports Unix sockets here? I guess
575        # we could try "unix:/tmp/blarg", and if it fails, try
576        # "tcp:0:interface=127.0.0.1" ...?
577
578        # specifically NOT creating the hidden-service dir; letting
579        # Tor do it will more-likely result in a usable situation...
580        if not self.ephemeral:
581            if not os.path.exists(self.hidden_service_dir):
582                log.msg(
583                    'Noting that "%s" does not exist; letting Tor create it.' %
584                    self.hidden_service_dir
585                )
586
587        # see note in _tor_progress_update; we extend the percent
588        # range to 110% for the descriptor upload
589        self._descriptor_progress_update(
590            0.0, 'wait_descriptor', 'uploading descriptor'
591        )
592
593        # see if the hidden-serivce instance we want is already in the
594        # config; for non-ephemeral services, the directory is unique;
595        # for ephemeral services, the key should exist and be unique.
596        already = False
597        if self.ephemeral:
598            already = self.hiddenservice is not None
599        else:
600            hs_dirs = [hs.dir for hs in self._config.HiddenServices if hasattr(hs, 'dir')]
601            already = os.path.abspath(self.hidden_service_dir) in hs_dirs
602
603        if not already:
604            if self.ephemeral:
605                if self.auth is not None:
606                    create_d = EphemeralAuthenticatedOnionService.create(
607                        self._reactor,
608                        self._config,
609                        ['%d 127.0.0.1:%d' % (self.public_port, self.local_port)],
610                        private_key=self.private_key,
611                        detach=False,
612                        progress=self._descriptor_progress_update,
613                        version=self.version,
614                        auth=self.auth,
615                        single_hop=self.single_hop,
616                    )
617
618                else:
619                    create_d = EphemeralOnionService.create(
620                        self._reactor,
621                        self._config,
622                        ['%d 127.0.0.1:%d' % (self.public_port, self.local_port)],
623                        private_key=self.private_key,
624                        detach=False,
625                        progress=self._descriptor_progress_update,
626                        version=self.version,
627                        single_hop=self.single_hop,
628                    )
629            else:
630                if self.auth is not None:
631                    create_d = FilesystemAuthenticatedOnionService.create(
632                        self._reactor,
633                        self._config,
634                        self.hidden_service_dir,
635                        ['%d 127.0.0.1:%d' % (self.public_port, self.local_port)],
636                        auth=self.auth,  # AuthBasic or AuthStealth
637                        progress=self._descriptor_progress_update,
638                        group_readable=self.group_readable,
639                        version=self.version,
640                    )
641                else:
642                    create_d = FilesystemOnionService.create(
643                        self._reactor,
644                        self._config,
645                        self.hidden_service_dir,
646                        ['%d 127.0.0.1:%d' % (self.public_port, self.local_port)],
647                        progress=self._descriptor_progress_update,
648                        group_readable=self.group_readable,
649                        version=self.version,
650                    )
651            self.hiddenservice = yield create_d
652
653        else:
654            if not self.ephemeral:
655                for hs in self._config.HiddenServices:
656                    if hs.dir == os.path.abspath(self.hidden_service_dir):
657                        self.hiddenservice = hs
658
659        assert self.hiddenservice is not None, "internal error"
660
661        if IAuthenticatedOnionClients.providedBy(self.hiddenservice):
662            log.msg('Started authenticated onion service on:')
663            for nm in self.hiddenservice.client_names():
664                log.msg('  {}: {}'.format(nm, self.hiddenservice.get_client(nm).hostname))
665        else:
666            log.msg('Started onion service on %s:%d' % (self.onion_uri, self.public_port))
667
668        # XXX should just return self.hiddenservice here??
669        # -> no, don't think so for a couple reasons:
670
671        # 1. it's "ports" in the services, so "TorOnionListeningPort"
672        # is the only thing that knows there's just one in this one
673        # (so can provide .local_port -> shoujld be local_endpoint I
674        # guess actually...)
675        # 2. anyway, can provide access to the "real" hs anyway if we want
676        defer.returnValue(
677            TorOnionListeningPort(
678                self.tcp_listening_port,
679                self.public_port,
680                self.hiddenservice,
681                self._config,
682            )
683        )
684
685
686@implementer(IAddress)
687class TorOnionAddress(FancyEqMixin, object):
688    """
689    A ``TorOnionAddress`` represents the public address of a Tor onion
690    service. Instances of these come from calling the Twisted method
691    `.getHost()` on :api:`twisted.internet.interfaces.IListeningPort`
692    which was returned from the :class:`txtorcon.TCPHiddenServiceEndpoint.listen`
693
694    :ivar type: A string describing the type of transport, 'onion'.
695
696    :ivar onion_port: The public port we're advertising
697
698    :ivar onion_key: the private key for the service
699    """
700    compareAttributes = ('type', 'onion_port', 'onion_key')
701    type = 'onion'
702
703    # for authenticated services, there is a private-key for "the
704    # service"; for stealth-auth'd services, there are *also*
705    # private-keys for each client
706
707    def __init__(self, port, hs):
708        self.onion_port = port
709
710        # this gets a bit weird .. partially for backwards-
711        # compatibility: .onion_uri is/was an existing property -- but
712        # doesn't always make sense. so, users should be encouraged to
713        # use .onion_service and access things directly (i.e. they
714        # know if they have an authenticated service or not, or can
715        # find out via .providedBy())
716
717        if IAuthenticatedOnionClients.providedBy(hs):
718            try:
719                self.onion_uri = _maybe_unique_host(hs)
720            except ValueError:
721                self.onion_uri = None
722        else:
723            self.onion_uri = hs.hostname
724        self._hiddenservice = hs
725
726    @property
727    def onion_service(self):
728        return self._hiddenservice
729
730    @property
731    def onion_key(self):
732        return self._hiddenservice.private_key
733
734    def __repr__(self):
735        return '%s(%s)' % (self.__class__.__name__, self.onion_uri)
736
737    def __hash__(self):
738        # should be "URIs", not URI
739        return hash((self.type, self.onion_uri, self.onion_port))
740
741
742class IHiddenService(Interface):
743    local_address = Attribute(
744        'The actual machine address we are listening on.')
745    hidden_service_dir = Attribute(
746        'The hidden service directory, where "hostname" and "private_key" '
747        'files live.')
748    tor_config = Attribute(
749        'The TorConfig object attached to the Tor hosting this hidden service '
750        '(in turn has .protocol for TorControlProtocol).')
751
752
753@implementer(IListeningPort)
754@implementer(IHiddenService)  # deprecated; use .onion_service
755class TorOnionListeningPort(object):
756    """
757    Our TCPHiddenServiceEndpoint's `listen` method will return a deferred
758    which fires an instance of this object.
759    The `getHost` method will return a TorOnionAddress instance... which
760    can be used to determine the onion address of a newly created Tor Hidden
761    Service.
762
763    `startListening` and `stopListening` methods proxy to the "TCP
764    ListeningPort" object...
765    which implements IListeningPort interface but has many more
766    responsibilities we needn't worry about here.
767    """
768
769    def __init__(self, listening_port, public_port, hiddenservice, tor_config):
770        # XXX can get these from the service
771        self._local_address = listening_port
772        self.public_port = public_port
773        # XXX should this be a weakref too? is there circ-ref here?
774        self._service = hiddenservice
775        # XXX why is this a weakref? circ-ref? (also, can get from the service anyway, no?)
776        self._config_ref = weakref.ref(tor_config)
777        self._address = TorOnionAddress(public_port, hiddenservice)
778
779    def startListening(self):
780        """IListeningPort API"""
781        self._local_address.startListening()
782
783    def stopListening(self):
784        """IListeningPort API"""
785        self._local_address.stopListening()
786
787    def getHost(self):
788        """IListeningPort API"""
789        return self._address
790
791    def __str__(self):
792        return '<TorOnionListeningPort %s:%d>' % (self._address.onion_uri, self._address.onion_port)
793
794    # preferred method of accessing everything
795    @property
796    def onion_service(self):
797        return self._service
798
799    # these are implemented by the now-deprecated IHiddenService API;
800    # do not use for new code -- get all information from .onion_service
801
802    @property
803    @deprecated(_Version("txtorcon", 18, 0, 0))
804    def tor_config(self):
805        return self._config_ref()  # None if ref dead
806
807    @property
808    @deprecated(_Version("txtorcon", 18, 0, 0))
809    def local_address(self):
810        return self._local_address
811
812    @property
813    @deprecated(_Version("txtorcon", 18, 0, 0))
814    def hidden_service_dir(self):
815        if not IFilesystemOnionService.providedBy(self._service):
816            raise ValueError(
817                "No 'hidden_service_dir' because our _service doesn't provide"
818                " IFilesystemOnionService"
819            )
820        return self._service.directory
821
822
823def _load_private_key_file(fname):
824    """
825    Loads an onion-service private-key from the given file. This can
826    be either a 'key blog' as returned from a previous ADD_ONION call,
827    or a v3 or v2 file as created by Tor when using the
828    HiddenServiceDir directive.
829
830    In any case, a key-blob suitable for ADD_ONION use is returned.
831    """
832    with open(fname, "rb") as f:
833        data = f.read()
834    if b"\x00\x00\x00" in data:  # v3 private key file
835        blob = data[data.find(b"\x00\x00\x00") + 3:]
836        return u"ED25519-V3:{}".format(b2a_base64(blob.strip()).decode('ascii').strip())
837    if b"-----BEGIN RSA PRIVATE KEY-----" in data:  # v2 RSA key
838        blob = "".join(data.decode('ascii').split('\n')[1:-2])
839        return u"RSA1024:{}".format(blob)
840    blob = data.decode('ascii').strip()
841    if ':' in blob:
842        kind, key = blob.split(':', 1)
843        if kind in ['ED25519-V3', 'RSA1024']:
844            return blob
845    raise ValueError(
846        "'{}' does not appear to contain v2 or v3 private key data".format(
847            fname,
848        )
849    )
850
851
852@implementer(IStreamServerEndpointStringParser, IPlugin)
853class TCPHiddenServiceEndpointParser(object):
854    """
855    This provides a twisted IPlugin and
856    IStreamServerEndpointsStringParser so you can call
857    :api:`twisted.internet.endpoints.serverFromString
858    <serverFromString>` with a string argument like:
859
860    ``onion:80:localPort=9876:controlPort=9052:hiddenServiceDir=/dev/shm/foo``
861
862    ...or simply:
863
864    ``onion:80``
865
866    If ``controlPort`` is specified, it means connect to an already-running Tor
867    on that port and add a hidden-serivce to it.
868
869    ``localPort`` is optional and if not specified, a port is selected by
870    the OS.
871
872    If ``hiddenServiceDir`` is not specified, one is created with
873    ``tempfile.mkdtemp()``. The IStreamServerEndpoint returned will be
874    an instance of :class:`txtorcon.TCPHiddenServiceEndpoint`
875
876    If ``privateKey`` or ``privateKeyFile`` is specified, the service
877    will be "ephemeral" and Tor will receive the private key via the
878    ADD_ONION control-port command.
879    """
880    prefix = "onion"
881
882    # note that these are all camelCase because Twisted uses them to
883    # do magic parsing stuff, and to conform to Twisted's conventions
884    # we should use camelCase in the endpoint definitions...
885
886    def parseStreamServer(self, reactor, public_port, localPort=None,
887                          controlPort=None, hiddenServiceDir=None,
888                          privateKey=None, privateKeyFile=None,
889                          version=None, singleHop=None):
890        """
891        :api:`twisted.internet.interfaces.IStreamServerEndpointStringParser`
892        """
893
894        if privateKeyFile is not None:
895            if privateKey is not None:
896                raise ValueError(
897                    "Can't specify both privateKey= and privateKeyFile="
898                )
899            privateKey = _load_private_key_file(privateKeyFile)
900            privateKeyFile = None
901
902        if hiddenServiceDir is not None and privateKey is not None:
903            raise ValueError(
904                "Only one of hiddenServiceDir and privateKey/privateKeyFile accepted"
905            )
906
907        if singleHop is not None:
908            if singleHop.strip().lower() in ['0', 'false']:
909                singleHop = False
910            elif singleHop.strip().lower() in ['1', 'true']:
911                singleHop = True
912            else:
913                raise ValueError(
914                    "singleHop= param must be 'true' or 'false'"
915                )
916        else:
917            singleHop = False
918
919        if version is not None:
920            try:
921                version = int(version)
922            except ValueError:
923                raise ValueError(
924                    "version must be an integer"
925                )
926        if version not in (None, 2, 3):
927            raise ValueError(
928                "Invalid version '{}'".format(version)
929            )
930
931        ephemeral = None
932
933        public_port = int(public_port)
934
935        if localPort is not None:
936            localPort = int(localPort)
937
938        hsd = hiddenServiceDir
939        if hsd:
940            orig = hsd
941            hsd = os.path.expanduser(hsd)
942            hsd = os.path.realpath(hsd)
943            if orig != hsd:
944                log.msg('Using "%s" for hsd' % hsd)
945
946        if controlPort:
947            try:
948                ep = clientFromString(
949                    reactor, "tcp:host=127.0.0.1:port=%d" % int(controlPort))
950            except ValueError:
951                ep = clientFromString(reactor, "unix:path=%s" % controlPort)
952            return TCPHiddenServiceEndpoint.system_tor(
953                reactor, ep, public_port,
954                hidden_service_dir=hsd,
955                local_port=localPort,
956                ephemeral=ephemeral,
957                version=version,
958                private_key=privateKey,
959                single_hop=singleHop,
960            )
961
962        return TCPHiddenServiceEndpoint.global_tor(
963            reactor, public_port,
964            hidden_service_dir=hsd,
965            local_port=localPort,
966            control_port=controlPort,
967            ephemeral=ephemeral,
968            version=version,
969            private_key=privateKey,
970            single_hop=singleHop,
971        )
972
973
974@defer.inlineCallbacks
975def _create_socks_endpoint(reactor, control_protocol, socks_config=None):
976    """
977    Internal helper.
978
979    This uses an already-configured SOCKS endpoint from the attached
980    Tor, or creates a new TCP one (and configures Tor with it). If
981    socks_config is non-None, it is a SOCKSPort line and will either
982    be used if it already exists or will be created.
983    """
984    socks_ports = yield control_protocol.get_conf('SOCKSPort')
985    if socks_ports:
986        socks_ports = list(socks_ports.values())[0]
987        if not isinstance(socks_ports, list):
988            socks_ports = [socks_ports]
989        # see TorConfig for more fun-times regarding *PortLines, including
990        # the __*Port things...
991        if socks_ports == ['DEFAULT']:
992            default = yield control_protocol.get_conf_single('__SocksPort')
993            socks_ports = [default]
994    else:
995        # return from get_conf was an empty dict; we want a list
996        socks_ports = []
997
998    # everything in the SocksPort list can include "options" after the
999    # initial value. We don't care about those, but do need to strip
1000    # them.
1001    socks_ports = [port.split()[0] for port in socks_ports]
1002
1003    # could check platform? but why would you have unix ports on a
1004    # platform that doesn't?
1005    unix_ports = set([p for p in socks_ports if p.startswith('unix:')])
1006    tcp_ports = set(socks_ports) - unix_ports
1007
1008    socks_endpoint = None
1009    for p in list(unix_ports) + list(tcp_ports):  # prefer unix-ports
1010        if socks_config and p != socks_config:
1011            continue
1012        try:
1013            socks_endpoint = _endpoint_from_socksport_line(reactor, p)
1014        except Exception as e:
1015            log.err(
1016                Failure(),
1017                "failed to process SOCKS port '{}': {}".format(p, e)
1018            )
1019
1020    # if we still don't have an endpoint, nothing worked (or there
1021    # were no SOCKSPort lines at all) so we add config to tor
1022    if socks_endpoint is None:
1023        if socks_config is None:
1024            # is a unix-socket in /tmp on a supported platform better than
1025            # this?
1026            port = yield available_tcp_port(reactor)
1027            socks_config = str(port)
1028        socks_ports.append(socks_config)
1029
1030        # NOTE! We must set all the ports in one command or we'll
1031        # destroy pre-existing config
1032        args = []
1033        for p in socks_ports:
1034            args.append('SOCKSPort')
1035            args.append(p)
1036        yield control_protocol.set_conf(*args)
1037        socks_endpoint = _endpoint_from_socksport_line(reactor, socks_config)
1038
1039    assert socks_endpoint is not None
1040    defer.returnValue(socks_endpoint)
1041
1042
1043@implementer(IStreamClientEndpoint)
1044class TorClientEndpoint(object):
1045    """
1046    An IStreamClientEndpoint which establishes a connection via Tor.
1047
1048    You should not instantiate these directly; use
1049    ``clientFromString()``, :meth:`txtorcon.Tor.stream_via` or
1050    :meth:`txtorcon.Circuit.stream_via`
1051
1052    :param host:
1053        The hostname to connect to. This of course can be a Tor Hidden
1054        Service onion address.
1055
1056    :param port: The tcp port or Tor Hidden Service port.
1057
1058    :param socks_endpoint: An IStreamClientEndpoint pointing at (one
1059        of) our Tor's SOCKS ports. These can be instantiated with
1060        :meth:`txtorcon.TorConfig.socks_endpoint`.
1061
1062    :param tls: Can be False or True (to get default Browser-like
1063        hostname verification) or the result of calling
1064        optionsForClientTLS() yourself. Default is True.
1065    """
1066
1067    socks_ports_to_try = [9050, 9150]
1068
1069    @classmethod
1070    def from_connection(cls, reactor, control_protocol, host, port,
1071                        tls=None,
1072                        socks_endpoint=None):
1073        if socks_endpoint is None:
1074            socks_endpoint = _create_socks_endpoint(reactor, control_protocol)
1075        return TorClientEndpoint(
1076            host, port,
1077            socks_endpoint=socks_endpoint,
1078            tls=tls,
1079            reactor=reactor,
1080        )
1081
1082    def __init__(self,
1083                 host, port,
1084                 socks_endpoint=None,  # can be Deferred
1085                 tls=False,
1086
1087                 # XXX our custom SOCKS stuff doesn't support auth (yet?)
1088                 socks_username=None, socks_password=None,
1089                 reactor=None, **kw):
1090        if host is None or port is None:
1091            raise ValueError('host and port must be specified')
1092
1093        self.host = host
1094        self.port = int(port)
1095        self._socks_endpoint = socks_endpoint
1096        self._socks_username = socks_username
1097        self._socks_password = socks_password
1098        self._tls = tls
1099        # XXX FIXME we 'should' probably include 'reactor' as the
1100        # first arg to this class, but technically that's a
1101        # breaking change :(
1102        self._reactor = reactor
1103        if reactor is None:
1104            from twisted.internet import reactor
1105            self._reactor = reactor
1106
1107        # backwards-compatibility: you used to specify a TCP SOCKS
1108        # endpoint via socks_host= and socks_port= kwargs
1109        if self._socks_endpoint is None:
1110            try:
1111                self._socks_endpoint = TCP4ClientEndpoint(
1112                    reactor,
1113                    kw['socks_hostname'],
1114                    kw['socks_port'],
1115                )
1116                # XXX should deprecation-warn here
1117            except KeyError:
1118                pass
1119
1120        # this is a separate "if" from above in case socks_endpoint
1121        # was None but the user specified the (old)
1122        # socks_hostname/socks_port (in which case we do NOT want
1123        # guessing_enabled
1124        if self._socks_endpoint is None:
1125            self._socks_guessing_enabled = True
1126        else:
1127            self._socks_guessing_enabled = False
1128
1129        # XXX think, do we want to expose these like this? Or some
1130        # other way (because they're for stream-isolation, not actual
1131        # auth)
1132        self._socks_username = socks_username
1133        self._socks_password = socks_password
1134        self._when_address = SingleObserver()
1135
1136    def _get_address(self):
1137        """
1138        internal helper.
1139
1140        *le sigh*. This is basically just to support
1141        TorCircuitEndpoint; see TorSocksEndpoint._get_address(). There
1142        shouldn't be any need for "actual users" to need this!
1143
1144        This returns a Deferred that fires once:
1145          - we have an underlying SOCKS5 endpoint
1146          - ...and it has received a local connection (and hence the address/port)
1147        """
1148        return self._when_address.when_fired()
1149
1150    @defer.inlineCallbacks
1151    def connect(self, protocolfactory):
1152        last_error = None
1153        # XXX fix in socks.py stuff for socks_username, socks_password
1154        if self._socks_username or self._socks_password:
1155            raise RuntimeError(
1156                "txtorcon socks support doesn't yet do username/password"
1157            )
1158        if self._socks_endpoint is not None:
1159            socks_ep = TorSocksEndpoint(
1160                self._socks_endpoint,
1161                self.host, self.port,
1162                self._tls,
1163            )
1164            # forward the address to any listeners we have
1165            socks_ep._get_address().addCallback(self._when_address.fire)
1166            proto = yield socks_ep.connect(protocolfactory)
1167            defer.returnValue(proto)
1168        else:
1169            for socks_port in self.socks_ports_to_try:
1170                tor_ep = TCP4ClientEndpoint(
1171                    self._reactor,
1172                    "127.0.0.1",  # XXX socks_hostname, no?
1173                    socks_port,
1174                )
1175                socks_ep = TorSocksEndpoint(tor_ep, self.host, self.port, self._tls)
1176                # forward the address to any listeners we have
1177                socks_ep._get_address().addCallback(self._when_address.fire)
1178                try:
1179                    proto = yield socks_ep.connect(protocolfactory)
1180                    defer.returnValue(proto)
1181
1182                except error.ConnectError as e0:
1183                    last_error = e0
1184            if last_error is not None:
1185                raise last_error
1186
1187
1188@implementer(IPlugin, IStreamClientEndpointStringParserWithReactor)
1189class TorClientEndpointStringParser(object):
1190    """
1191    This provides a twisted IPlugin and
1192    IStreamClientEndpointsStringParser so you can call
1193    :api:`twisted.internet.endpoints.clientFromString
1194    <clientFromString>` with a string argument like:
1195
1196    ``tor:host=timaq4ygg2iegci7.onion:port=80:socksPort=9050``
1197
1198    ...or simply:
1199
1200    ``tor:host=timaq4ygg2iegci7.onion:port=80``
1201
1202    You may also include a username + password. By default, Tor will
1203    not put two streams that provided different authentication on the
1204    same circuit.
1205
1206    ``tor:host=torproject.org:port=443:socksUsername=foo:socksPassword=bar``
1207
1208    If ``socksPort`` is specified, it means only use that port to
1209    attempt to proxy through Tor. If unspecified, we ... XXX?
1210
1211    NOTE that I'm using camelCase variable names in the endpoint
1212    string to be consistent with the rest of Twisted's naming (and
1213    their endpoint parsers).
1214
1215    XXX FIXME if there is no Tor instance found at socksPort, we
1216    should launch one. Perhaps a separate option? (Should be on by
1217    default, though, I think).
1218    """
1219    prefix = "tor"
1220
1221    def _parseClient(self, reactor,
1222                     host=None, port=None,
1223                     socksHostname=None, socksPort=None,
1224                     socksUsername=None, socksPassword=None):
1225        if port is not None:
1226            port = int(port)
1227
1228        ep = None
1229        if socksPort is not None:
1230            # Tor can speak SOCKS over unix, too, but this doesn't let
1231            # us pass one ...
1232            ep = TCP4ClientEndpoint(reactor, socksHostname, int(socksPort))
1233        return TorClientEndpoint(
1234            host, port,
1235            socks_endpoint=ep,
1236            socks_username=socksUsername,
1237            socks_password=socksPassword,
1238        )
1239
1240    def parseStreamClient(self, *args, **kwargs):
1241        # for Twisted 14 and 15 (and more) the first argument is
1242        # 'reactor', for older Twisteds it's not
1243        return self._parseClient(*args, **kwargs)
1244
1245
1246def _maybe_unique_host(onion):
1247    """
1248    :param onion: IAuthenticatedOnionClients provider
1249
1250    :returns: a .onion hostname if all clients have the same name or
1251        raises ValueError otherwise
1252    """
1253    hosts = [
1254        onion.get_client(nm).hostname
1255        for nm in onion.client_names()
1256    ]
1257    if not hosts:
1258        raise ValueError(
1259            "Can't access .onion_uri because there are no clients"
1260        )
1261    host = hosts[0]
1262    for h in hosts[1:]:
1263        if h != host:
1264            raise ValueError(
1265                "Cannot access .onion_uri for stealth-authenticated services "
1266                "because each client has a unique URI"
1267            )
1268    return host
1269