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