1from contextlib import contextmanager
2from threading import Event
3
4try:
5    from invoke.vendor.six import StringIO
6    from invoke.vendor.decorator import decorator
7    from invoke.vendor.six import string_types
8except ImportError:
9    from six import StringIO
10    from decorator import decorator
11    from six import string_types
12import socket
13
14from invoke import Context
15from invoke.exceptions import ThreadException
16from paramiko.agent import AgentRequestHandler
17from paramiko.client import SSHClient, AutoAddPolicy
18from paramiko.config import SSHConfig
19from paramiko.proxy import ProxyCommand
20
21from .config import Config
22from .exceptions import InvalidV1Env
23from .transfer import Transfer
24from .tunnels import TunnelManager, Tunnel
25
26
27@decorator
28def opens(method, self, *args, **kwargs):
29    self.open()
30    return method(self, *args, **kwargs)
31
32
33def derive_shorthand(host_string):
34    user_hostport = host_string.rsplit("@", 1)
35    hostport = user_hostport.pop()
36    user = user_hostport[0] if user_hostport and user_hostport[0] else None
37
38    # IPv6: can't reliably tell where addr ends and port begins, so don't
39    # try (and don't bother adding special syntax either, user should avoid
40    # this situation by using port=).
41    if hostport.count(":") > 1:
42        host = hostport
43        port = None
44    # IPv4: can split on ':' reliably.
45    else:
46        host_port = hostport.rsplit(":", 1)
47        host = host_port.pop(0) or None
48        port = host_port[0] if host_port and host_port[0] else None
49
50    if port is not None:
51        port = int(port)
52
53    return {"user": user, "host": host, "port": port}
54
55
56class Connection(Context):
57    """
58    A connection to an SSH daemon, with methods for commands and file transfer.
59
60    **Basics**
61
62    This class inherits from Invoke's `~invoke.context.Context`, as it is a
63    context within which commands, tasks etc can operate. It also encapsulates
64    a Paramiko `~paramiko.client.SSHClient` instance, performing useful high
65    level operations with that `~paramiko.client.SSHClient` and
66    `~paramiko.channel.Channel` instances generated from it.
67
68    .. _connect_kwargs:
69
70    .. note::
71        Many SSH specific options -- such as specifying private keys and
72        passphrases, timeouts, disabling SSH agents, etc -- are handled
73        directly by Paramiko and should be specified via the
74        :ref:`connect_kwargs argument <connect_kwargs-arg>` of the constructor.
75
76    **Lifecycle**
77
78    `.Connection` has a basic "`create <__init__>`, `connect/open <open>`, `do
79    work <run>`, `disconnect/close <close>`" lifecycle:
80
81    - `Instantiation <__init__>` imprints the object with its connection
82      parameters (but does **not** actually initiate the network connection).
83
84        - An alternate constructor exists for users :ref:`upgrading piecemeal
85          from Fabric 1 <from-v1>`: `from_v1`
86
87    - Methods like `run`, `get` etc automatically trigger a call to
88      `open` if the connection is not active; users may of course call `open`
89      manually if desired.
90    - Connections do not always need to be explicitly closed; much of the
91      time, Paramiko's garbage collection hooks or Python's own shutdown
92      sequence will take care of things. **However**, should you encounter edge
93      cases (for example, sessions hanging on exit) it's helpful to explicitly
94      close connections when you're done with them.
95
96      This can be accomplished by manually calling `close`, or by using the
97      object as a contextmanager::
98
99        with Connection('host') as c:
100            c.run('command')
101            c.put('file')
102
103    .. note::
104        This class rebinds `invoke.context.Context.run` to `.local` so both
105        remote and local command execution can coexist.
106
107    **Configuration**
108
109    Most `.Connection` parameters honor :doc:`Invoke-style configuration
110    </concepts/configuration>` as well as any applicable :ref:`SSH config file
111    directives <connection-ssh-config>`. For example, to end up with a
112    connection to ``admin@myhost``, one could:
113
114    - Use any built-in config mechanism, such as ``/etc/fabric.yml``,
115      ``~/.fabric.json``, collection-driven configuration, env vars, etc,
116      stating ``user: admin`` (or ``{"user": "admin"}``, depending on config
117      format.) Then ``Connection('myhost')`` would implicitly have a ``user``
118      of ``admin``.
119    - Use an SSH config file containing ``User admin`` within any applicable
120      ``Host`` header (``Host myhost``, ``Host *``, etc.) Again,
121      ``Connection('myhost')`` will default to an ``admin`` user.
122    - Leverage host-parameter shorthand (described in `.Config.__init__`), i.e.
123      ``Connection('admin@myhost')``.
124    - Give the parameter directly: ``Connection('myhost', user='admin')``.
125
126    The same applies to agent forwarding, gateways, and so forth.
127
128    .. versionadded:: 2.0
129    """
130
131    # NOTE: these are initialized here to hint to invoke.Config.__setattr__
132    # that they should be treated as real attributes instead of config proxies.
133    # (Additionally, we're doing this instead of using invoke.Config._set() so
134    # we can take advantage of Sphinx's attribute-doc-comment static analysis.)
135    # Once an instance is created, these values will usually be non-None
136    # because they default to the default config values.
137    host = None
138    original_host = None
139    user = None
140    port = None
141    ssh_config = None
142    gateway = None
143    forward_agent = None
144    connect_timeout = None
145    connect_kwargs = None
146    client = None
147    transport = None
148    _sftp = None
149    _agent_handler = None
150
151    @classmethod
152    def from_v1(cls, env, **kwargs):
153        """
154        Alternate constructor which uses Fabric 1's ``env`` dict for settings.
155
156        All keyword arguments besides ``env`` are passed unmolested into the
157        primary constructor.
158
159        .. warning::
160            Because your own config overrides will win over data from ``env``,
161            make sure you only set values you *intend* to change from your v1
162            environment!
163
164        For details on exactly which ``env`` vars are imported and what they
165        become in the new API, please see :ref:`v1-env-var-imports`.
166
167        :param env:
168            An explicit Fabric 1 ``env`` dict (technically, any
169            ``fabric.utils._AttributeDict`` instance should work) to pull
170            configuration from.
171
172        .. versionadded:: 2.4
173        """
174        # TODO: import fabric.state.env (need good way to test it first...)
175        # TODO: how to handle somebody accidentally calling this in a process
176        # where 'fabric' is fabric 2, and there's no fabric 1? Probably just a
177        # re-raise of ImportError??
178        # Our only requirement is a non-empty host_string
179        if not env.host_string:
180            raise InvalidV1Env(
181                "Supplied v1 env has an empty `host_string` value! Please make sure you're calling Connection.from_v1 within a connected Fabric 1 session."  # noqa
182            )
183        # TODO: detect collisions with kwargs & except instead of overwriting?
184        # (More Zen of Python compliant, but also, effort, and also, makes it
185        # harder for users to intentionally overwrite!)
186        connect_kwargs = kwargs.setdefault("connect_kwargs", {})
187        kwargs.setdefault("host", env.host_string)
188        shorthand = derive_shorthand(env.host_string)
189        # TODO: don't we need to do the below skipping for user too?
190        kwargs.setdefault("user", env.user)
191        # Skip port if host string seemed to have it; otherwise we hit our own
192        # ambiguity clause in __init__. v1 would also have been doing this
193        # anyways (host string wins over other settings).
194        if not shorthand["port"]:
195            # Run port through int(); v1 inexplicably has a string default...
196            kwargs.setdefault("port", int(env.port))
197        # key_filename defaults to None in v1, but in v2, we expect it to be
198        # either unset, or set to a list. Thus, we only pull it over if it is
199        # not None.
200        if env.key_filename is not None:
201            connect_kwargs.setdefault("key_filename", env.key_filename)
202        # Obtain config values, if not given, from its own from_v1
203        # NOTE: not using setdefault as we truly only want to call
204        # Config.from_v1 when necessary.
205        if "config" not in kwargs:
206            kwargs["config"] = Config.from_v1(env)
207        return cls(**kwargs)
208
209    # TODO: should "reopening" an existing Connection object that has been
210    # closed, be allowed? (See e.g. how v1 detects closed/semi-closed
211    # connections & nukes them before creating a new client to the same host.)
212    # TODO: push some of this into paramiko.client.Client? e.g. expand what
213    # Client.exec_command does, it already allows configuring a subset of what
214    # we do / will eventually do / did in 1.x. It's silly to have to do
215    # .get_transport().open_session().
216    def __init__(
217        self,
218        host,
219        user=None,
220        port=None,
221        config=None,
222        gateway=None,
223        forward_agent=None,
224        connect_timeout=None,
225        connect_kwargs=None,
226        inline_ssh_env=None,
227    ):
228        """
229        Set up a new object representing a server connection.
230
231        :param str host:
232            the hostname (or IP address) of this connection.
233
234            May include shorthand for the ``user`` and/or ``port`` parameters,
235            of the form ``user@host``, ``host:port``, or ``user@host:port``.
236
237            .. note::
238                Due to ambiguity, IPv6 host addresses are incompatible with the
239                ``host:port`` shorthand (though ``user@host`` will still work
240                OK). In other words, the presence of >1 ``:`` character will
241                prevent any attempt to derive a shorthand port number; use the
242                explicit ``port`` parameter instead.
243
244            .. note::
245                If ``host`` matches a ``Host`` clause in loaded SSH config
246                data, and that ``Host`` clause contains a ``Hostname``
247                directive, the resulting `.Connection` object will behave as if
248                ``host`` is equal to that ``Hostname`` value.
249
250                In all cases, the original value of ``host`` is preserved as
251                the ``original_host`` attribute.
252
253                Thus, given SSH config like so::
254
255                    Host myalias
256                        Hostname realhostname
257
258                a call like ``Connection(host='myalias')`` will result in an
259                object whose ``host`` attribute is ``realhostname``, and whose
260                ``original_host`` attribute is ``myalias``.
261
262        :param str user:
263            the login user for the remote connection. Defaults to
264            ``config.user``.
265
266        :param int port:
267            the remote port. Defaults to ``config.port``.
268
269        :param config:
270            configuration settings to use when executing methods on this
271            `.Connection` (e.g. default SSH port and so forth).
272
273            Should be a `.Config` or an `invoke.config.Config`
274            (which will be turned into a `.Config`).
275
276            Default is an anonymous `.Config` object.
277
278        :param gateway:
279            An object to use as a proxy or gateway for this connection.
280
281            This parameter accepts one of the following:
282
283            - another `.Connection` (for a ``ProxyJump`` style gateway);
284            - a shell command string (for a ``ProxyCommand`` style style
285              gateway).
286
287            Default: ``None``, meaning no gatewaying will occur (unless
288            otherwise configured; if one wants to override a configured gateway
289            at runtime, specify ``gateway=False``.)
290
291            .. seealso:: :ref:`ssh-gateways`
292
293        :param bool forward_agent:
294            Whether to enable SSH agent forwarding.
295
296            Default: ``config.forward_agent``.
297
298        :param int connect_timeout:
299            Connection timeout, in seconds.
300
301            Default: ``config.timeouts.connect``.
302
303        .. _connect_kwargs-arg:
304
305        :param dict connect_kwargs:
306            Keyword arguments handed verbatim to
307            `SSHClient.connect <paramiko.client.SSHClient.connect>` (when
308            `.open` is called).
309
310            `.Connection` tries not to grow additional settings/kwargs of its
311            own unless it is adding value of some kind; thus,
312            ``connect_kwargs`` is currently the right place to hand in paramiko
313            connection parameters such as ``pkey`` or ``key_filename``. For
314            example::
315
316                c = Connection(
317                    host="hostname",
318                    user="admin",
319                    connect_kwargs={
320                        "key_filename": "/home/myuser/.ssh/private.key",
321                    },
322                )
323
324            Default: ``config.connect_kwargs``.
325
326        :param bool inline_ssh_env:
327            Whether to send environment variables "inline" as prefixes in front
328            of command strings (``export VARNAME=value && mycommand here``),
329            instead of trying to submit them through the SSH protocol itself
330            (which is the default behavior). This is necessary if the remote
331            server has a restricted ``AcceptEnv`` setting (which is the common
332            default).
333
334            The default value is the value of the ``inline_ssh_env``
335            :ref:`configuration value <default-values>` (which itself defaults
336            to ``False``).
337
338            .. warning::
339                This functionality does **not** currently perform any shell
340                escaping on your behalf! Be careful when using nontrivial
341                values, and note that you can put in your own quoting,
342                backslashing etc if desired.
343
344                Consider using a different approach (such as actual
345                remote shell scripts) if you run into too many issues here.
346
347            .. note::
348                When serializing into prefixed ``FOO=bar`` format, we apply the
349                builtin `sorted` function to the env dictionary's keys, to
350                remove what would otherwise be ambiguous/arbitrary ordering.
351
352            .. note::
353                This setting has no bearing on *local* shell commands; it only
354                affects remote commands, and thus, methods like `.run` and
355                `.sudo`.
356
357        :raises ValueError:
358            if user or port values are given via both ``host`` shorthand *and*
359            their own arguments. (We `refuse the temptation to guess`_).
360
361        .. _refuse the temptation to guess:
362            http://zen-of-python.info/
363            in-the-face-of-ambiguity-refuse-the-temptation-to-guess.html#12
364
365        .. versionchanged:: 2.3
366            Added the ``inline_ssh_env`` parameter.
367        """
368        # NOTE: parent __init__ sets self._config; for now we simply overwrite
369        # that below. If it's somehow problematic we would want to break parent
370        # __init__ up in a manner that is more cleanly overrideable.
371        super(Connection, self).__init__(config=config)
372
373        #: The .Config object referenced when handling default values (for e.g.
374        #: user or port, when not explicitly given) or deciding how to behave.
375        if config is None:
376            config = Config()
377        # Handle 'vanilla' Invoke config objects, which need cloning 'into' one
378        # of our own Configs (which grants the new defaults, etc, while not
379        # squashing them if the Invoke-level config already accounted for them)
380        elif not isinstance(config, Config):
381            config = config.clone(into=Config)
382        self._set(_config=config)
383        # TODO: when/how to run load_files, merge, load_shell_env, etc?
384        # TODO: i.e. what is the lib use case here (and honestly in invoke too)
385
386        shorthand = self.derive_shorthand(host)
387        host = shorthand["host"]
388        err = "You supplied the {} via both shorthand and kwarg! Please pick one."  # noqa
389        if shorthand["user"] is not None:
390            if user is not None:
391                raise ValueError(err.format("user"))
392            user = shorthand["user"]
393        if shorthand["port"] is not None:
394            if port is not None:
395                raise ValueError(err.format("port"))
396            port = shorthand["port"]
397
398        # NOTE: we load SSH config data as early as possible as it has
399        # potential to affect nearly every other attribute.
400        #: The per-host SSH config data, if any. (See :ref:`ssh-config`.)
401        self.ssh_config = self.config.base_ssh_config.lookup(host)
402
403        self.original_host = host
404        #: The hostname of the target server.
405        self.host = host
406        if "hostname" in self.ssh_config:
407            # TODO: log that this occurred?
408            self.host = self.ssh_config["hostname"]
409
410        #: The username this connection will use to connect to the remote end.
411        self.user = user or self.ssh_config.get("user", self.config.user)
412        # TODO: is it _ever_ possible to give an empty user value (e.g.
413        # user='')? E.g. do some SSH server specs allow for that?
414
415        #: The network port to connect on.
416        self.port = port or int(self.ssh_config.get("port", self.config.port))
417
418        # Gateway/proxy/bastion/jump setting: non-None values - string,
419        # Connection, even eg False - get set directly; None triggers seek in
420        # config/ssh_config
421        #: The gateway `.Connection` or ``ProxyCommand`` string to be used,
422        #: if any.
423        self.gateway = gateway if gateway is not None else self.get_gateway()
424        # NOTE: we use string above, vs ProxyCommand obj, to avoid spinning up
425        # the ProxyCommand subprocess at init time, vs open() time.
426        # TODO: make paramiko.proxy.ProxyCommand lazy instead?
427
428        if forward_agent is None:
429            # Default to config...
430            forward_agent = self.config.forward_agent
431            # But if ssh_config is present, it wins
432            if "forwardagent" in self.ssh_config:
433                # TODO: SSHConfig really, seriously needs some love here, god
434                map_ = {"yes": True, "no": False}
435                forward_agent = map_[self.ssh_config["forwardagent"]]
436        #: Whether agent forwarding is enabled.
437        self.forward_agent = forward_agent
438
439        if connect_timeout is None:
440            connect_timeout = self.ssh_config.get(
441                "connecttimeout", self.config.timeouts.connect
442            )
443        if connect_timeout is not None:
444            connect_timeout = int(connect_timeout)
445        #: Connection timeout
446        self.connect_timeout = connect_timeout
447
448        #: Keyword arguments given to `paramiko.client.SSHClient.connect` when
449        #: `open` is called.
450        self.connect_kwargs = self.resolve_connect_kwargs(connect_kwargs)
451
452        #: The `paramiko.client.SSHClient` instance this connection wraps.
453        client = SSHClient()
454        client.set_missing_host_key_policy(AutoAddPolicy())
455        self.client = client
456
457        #: A convenience handle onto the return value of
458        #: ``self.client.get_transport()``.
459        self.transport = None
460
461        if inline_ssh_env is None:
462            inline_ssh_env = self.config.inline_ssh_env
463        #: Whether to construct remote command lines with env vars prefixed
464        #: inline.
465        self.inline_ssh_env = inline_ssh_env
466
467    def resolve_connect_kwargs(self, connect_kwargs):
468        # Grab connect_kwargs from config if not explicitly given.
469        if connect_kwargs is None:
470            # TODO: is it better to pre-empt conflicts w/ manually-handled
471            # connect() kwargs (hostname, username, etc) here or in open()?
472            # We're doing open() for now in case e.g. someone manually modifies
473            # .connect_kwargs attributewise, but otherwise it feels better to
474            # do it early instead of late.
475            connect_kwargs = self.config.connect_kwargs
476        # Special case: key_filename gets merged instead of overridden.
477        # TODO: probably want some sorta smart merging generally, special cases
478        # are bad.
479        elif "key_filename" in self.config.connect_kwargs:
480            kwarg_val = connect_kwargs.get("key_filename", [])
481            conf_val = self.config.connect_kwargs["key_filename"]
482            # Config value comes before kwarg value (because it may contain
483            # CLI flag value.)
484            connect_kwargs["key_filename"] = conf_val + kwarg_val
485
486        # SSH config identityfile values come last in the key_filename
487        # 'hierarchy'.
488        if "identityfile" in self.ssh_config:
489            connect_kwargs.setdefault("key_filename", [])
490            connect_kwargs["key_filename"].extend(
491                self.ssh_config["identityfile"]
492            )
493
494        return connect_kwargs
495
496    def get_gateway(self):
497        # SSH config wins over Invoke-style config
498        if "proxyjump" in self.ssh_config:
499            # Reverse hop1,hop2,hop3 style ProxyJump directive so we start
500            # with the final (itself non-gatewayed) hop and work up to
501            # the front (actual, supplied as our own gateway) hop
502            hops = reversed(self.ssh_config["proxyjump"].split(","))
503            prev_gw = None
504            for hop in hops:
505                # Short-circuit if we appear to be our own proxy, which would
506                # be a RecursionError. Implies SSH config wildcards.
507                # TODO: in an ideal world we'd check user/port too in case they
508                # differ, but...seriously? They can file a PR with those extra
509                # half dozen test cases in play, E_NOTIME
510                if self.derive_shorthand(hop)["host"] == self.host:
511                    return None
512                # Happily, ProxyJump uses identical format to our host
513                # shorthand...
514                kwargs = dict(config=self.config.clone())
515                if prev_gw is not None:
516                    kwargs["gateway"] = prev_gw
517                cxn = Connection(hop, **kwargs)
518                prev_gw = cxn
519            return prev_gw
520        elif "proxycommand" in self.ssh_config:
521            # Just a string, which we interpret as a proxy command..
522            return self.ssh_config["proxycommand"]
523        # Fallback: config value (may be None).
524        return self.config.gateway
525
526    def __repr__(self):
527        # Host comes first as it's the most common differentiator by far
528        bits = [("host", self.host)]
529        # TODO: maybe always show user regardless? Explicit is good...
530        if self.user != self.config.user:
531            bits.append(("user", self.user))
532        # TODO: harder to make case for 'always show port'; maybe if it's
533        # non-22 (even if config has overridden the local default)?
534        if self.port != self.config.port:
535            bits.append(("port", self.port))
536        # NOTE: sometimes self.gateway may be eg False if someone wants to
537        # explicitly override a configured non-None value (as otherwise it's
538        # impossible for __init__ to tell if a None means "nothing given" or
539        # "seriously please no gatewaying". So, this must always be a vanilla
540        # truth test and not eg "is not None".
541        if self.gateway:
542            # Displaying type because gw params would probs be too verbose
543            val = "proxyjump"
544            if isinstance(self.gateway, string_types):
545                val = "proxycommand"
546            bits.append(("gw", val))
547        return "<Connection {}>".format(
548            " ".join("{}={}".format(*x) for x in bits)
549        )
550
551    def _identity(self):
552        # TODO: consider including gateway and maybe even other init kwargs?
553        # Whether two cxns w/ same user/host/port but different
554        # gateway/keys/etc, should be considered "the same", is unclear.
555        return (self.host, self.user, self.port)
556
557    def __eq__(self, other):
558        if not isinstance(other, Connection):
559            return False
560        return self._identity() == other._identity()
561
562    def __lt__(self, other):
563        return self._identity() < other._identity()
564
565    def __hash__(self):
566        # NOTE: this departs from Context/DataProxy, which is not usefully
567        # hashable.
568        return hash(self._identity())
569
570    def derive_shorthand(self, host_string):
571        # NOTE: used to be defined inline; preserving API call for both
572        # backwards compatibility and because it seems plausible we may want to
573        # modify behavior later, using eg config or other attributes.
574        return derive_shorthand(host_string)
575
576    @property
577    def is_connected(self):
578        """
579        Whether or not this connection is actually open.
580
581        .. versionadded:: 2.0
582        """
583        return self.transport.active if self.transport else False
584
585    def open(self):
586        """
587        Initiate an SSH connection to the host/port this object is bound to.
588
589        This may include activating the configured gateway connection, if one
590        is set.
591
592        Also saves a handle to the now-set Transport object for easier access.
593
594        Various connect-time settings (and/or their corresponding :ref:`SSH
595        config options <ssh-config>`) are utilized here in the call to
596        `SSHClient.connect <paramiko.client.SSHClient.connect>`. (For details,
597        see :doc:`the configuration docs </concepts/configuration>`.)
598
599        .. versionadded:: 2.0
600        """
601        # Short-circuit
602        if self.is_connected:
603            return
604        err = "Refusing to be ambiguous: connect() kwarg '{}' was given both via regular arg and via connect_kwargs!"  # noqa
605        # These may not be given, period
606        for key in """
607            hostname
608            port
609            username
610        """.split():
611            if key in self.connect_kwargs:
612                raise ValueError(err.format(key))
613        # These may be given one way or the other, but not both
614        if (
615            "timeout" in self.connect_kwargs
616            and self.connect_timeout is not None
617        ):
618            raise ValueError(err.format("timeout"))
619        # No conflicts -> merge 'em together
620        kwargs = dict(
621            self.connect_kwargs,
622            username=self.user,
623            hostname=self.host,
624            port=self.port,
625        )
626        if self.gateway:
627            kwargs["sock"] = self.open_gateway()
628        if self.connect_timeout:
629            kwargs["timeout"] = self.connect_timeout
630        # Strip out empty defaults for less noisy debugging
631        if "key_filename" in kwargs and not kwargs["key_filename"]:
632            del kwargs["key_filename"]
633        # Actually connect!
634        self.client.connect(**kwargs)
635        self.transport = self.client.get_transport()
636
637    def open_gateway(self):
638        """
639        Obtain a socket-like object from `gateway`.
640
641        :returns:
642            A ``direct-tcpip`` `paramiko.channel.Channel`, if `gateway` was a
643            `.Connection`; or a `~paramiko.proxy.ProxyCommand`, if `gateway`
644            was a string.
645
646        .. versionadded:: 2.0
647        """
648        # ProxyCommand is faster to set up, so do it first.
649        if isinstance(self.gateway, string_types):
650            # Leverage a dummy SSHConfig to ensure %h/%p/etc are parsed.
651            # TODO: use real SSH config once loading one properly is
652            # implemented.
653            ssh_conf = SSHConfig()
654            dummy = "Host {}\n    ProxyCommand {}"
655            ssh_conf.parse(StringIO(dummy.format(self.host, self.gateway)))
656            return ProxyCommand(ssh_conf.lookup(self.host)["proxycommand"])
657        # Handle inner-Connection gateway type here.
658        # TODO: logging
659        self.gateway.open()
660        # TODO: expose the opened channel itself as an attribute? (another
661        # possible argument for separating the two gateway types...) e.g. if
662        # someone wanted to piggyback on it for other same-interpreter socket
663        # needs...
664        # TODO: and the inverse? allow users to supply their own socket/like
665        # object they got via $WHEREEVER?
666        # TODO: how best to expose timeout param? reuse general connection
667        # timeout from config?
668        return self.gateway.transport.open_channel(
669            kind="direct-tcpip",
670            dest_addr=(self.host, int(self.port)),
671            # NOTE: src_addr needs to be 'empty but not None' values to
672            # correctly encode into a network message. Theoretically Paramiko
673            # could auto-interpret None sometime & save us the trouble.
674            src_addr=("", 0),
675        )
676
677    def close(self):
678        """
679        Terminate the network connection to the remote end, if open.
680
681        If no connection is open, this method does nothing.
682
683        .. versionadded:: 2.0
684        """
685        if self.is_connected:
686            self.client.close()
687            if self.forward_agent and self._agent_handler is not None:
688                self._agent_handler.close()
689
690    def __enter__(self):
691        return self
692
693    def __exit__(self, *exc):
694        self.close()
695
696    @opens
697    def create_session(self):
698        channel = self.transport.open_session()
699        if self.forward_agent:
700            self._agent_handler = AgentRequestHandler(channel)
701        return channel
702
703    def _remote_runner(self):
704        return self.config.runners.remote(self, inline_env=self.inline_ssh_env)
705
706    @opens
707    def run(self, command, **kwargs):
708        """
709        Execute a shell command on the remote end of this connection.
710
711        This method wraps an SSH-capable implementation of
712        `invoke.runners.Runner.run`; see its documentation for details.
713
714        .. warning::
715            There are a few spots where Fabric departs from Invoke's default
716            settings/behaviors; they are documented under
717            `.Config.global_defaults`.
718
719        .. versionadded:: 2.0
720        """
721        return self._run(self._remote_runner(), command, **kwargs)
722
723    @opens
724    def sudo(self, command, **kwargs):
725        """
726        Execute a shell command, via ``sudo``, on the remote end.
727
728        This method is identical to `invoke.context.Context.sudo` in every way,
729        except in that -- like `run` -- it honors per-host/per-connection
730        configuration overrides in addition to the generic/global ones. Thus,
731        for example, per-host sudo passwords may be configured.
732
733        .. versionadded:: 2.0
734        """
735        return self._sudo(self._remote_runner(), command, **kwargs)
736
737    def local(self, *args, **kwargs):
738        """
739        Execute a shell command on the local system.
740
741        This method is effectively a wrapper of `invoke.run`; see its docs for
742        details and call signature.
743
744        .. versionadded:: 2.0
745        """
746        # Superclass run() uses runners.local, so we can literally just call it
747        # straight.
748        return super(Connection, self).run(*args, **kwargs)
749
750    @opens
751    def sftp(self):
752        """
753        Return a `~paramiko.sftp_client.SFTPClient` object.
754
755        If called more than one time, memoizes the first result; thus, any
756        given `.Connection` instance will only ever have a single SFTP client,
757        and state (such as that managed by
758        `~paramiko.sftp_client.SFTPClient.chdir`) will be preserved.
759
760        .. versionadded:: 2.0
761        """
762        if self._sftp is None:
763            self._sftp = self.client.open_sftp()
764        return self._sftp
765
766    def get(self, *args, **kwargs):
767        """
768        Get a remote file to the local filesystem or file-like object.
769
770        Simply a wrapper for `.Transfer.get`. Please see its documentation for
771        all details.
772
773        .. versionadded:: 2.0
774        """
775        return Transfer(self).get(*args, **kwargs)
776
777    def put(self, *args, **kwargs):
778        """
779        Put a remote file (or file-like object) to the remote filesystem.
780
781        Simply a wrapper for `.Transfer.put`. Please see its documentation for
782        all details.
783
784        .. versionadded:: 2.0
785        """
786        return Transfer(self).put(*args, **kwargs)
787
788    # TODO: yield the socket for advanced users? Other advanced use cases
789    # (perhaps factor out socket creation itself)?
790    # TODO: probably push some of this down into Paramiko
791    @contextmanager
792    @opens
793    def forward_local(
794        self,
795        local_port,
796        remote_port=None,
797        remote_host="localhost",
798        local_host="localhost",
799    ):
800        """
801        Open a tunnel connecting ``local_port`` to the server's environment.
802
803        For example, say you want to connect to a remote PostgreSQL database
804        which is locked down and only accessible via the system it's running
805        on. You have SSH access to this server, so you can temporarily make
806        port 5432 on your local system act like port 5432 on the server::
807
808            import psycopg2
809            from fabric import Connection
810
811            with Connection('my-db-server').forward_local(5432):
812                db = psycopg2.connect(
813                    host='localhost', port=5432, database='mydb'
814                )
815                # Do things with 'db' here
816
817        This method is analogous to using the ``-L`` option of OpenSSH's
818        ``ssh`` program.
819
820        :param int local_port: The local port number on which to listen.
821
822        :param int remote_port:
823            The remote port number. Defaults to the same value as
824            ``local_port``.
825
826        :param str local_host:
827            The local hostname/interface on which to listen. Default:
828            ``localhost``.
829
830        :param str remote_host:
831            The remote hostname serving the forwarded remote port. Default:
832            ``localhost`` (i.e., the host this `.Connection` is connected to.)
833
834        :returns:
835            Nothing; this method is only useful as a context manager affecting
836            local operating system state.
837
838        .. versionadded:: 2.0
839        """
840        if not remote_port:
841            remote_port = local_port
842
843        # TunnelManager does all of the work, sitting in the background (so we
844        # can yield) and spawning threads every time somebody connects to our
845        # local port.
846        finished = Event()
847        manager = TunnelManager(
848            local_port=local_port,
849            local_host=local_host,
850            remote_port=remote_port,
851            remote_host=remote_host,
852            # TODO: not a huge fan of handing in our transport, but...?
853            transport=self.transport,
854            finished=finished,
855        )
856        manager.start()
857
858        # Return control to caller now that things ought to be operational
859        try:
860            yield
861        # Teardown once user exits block
862        finally:
863            # Signal to manager that it should close all open tunnels
864            finished.set()
865            # Then wait for it to do so
866            manager.join()
867            # Raise threading errors from within the manager, which would be
868            # one of:
869            # - an inner ThreadException, which was created by the manager on
870            # behalf of its Tunnels; this gets directly raised.
871            # - some other exception, which would thus have occurred in the
872            # manager itself; we wrap this in a new ThreadException.
873            # NOTE: in these cases, some of the metadata tracking in
874            # ExceptionHandlingThread/ExceptionWrapper/ThreadException (which
875            # is useful when dealing with multiple nearly-identical sibling IO
876            # threads) is superfluous, but it doesn't feel worth breaking
877            # things up further; we just ignore it for now.
878            wrapper = manager.exception()
879            if wrapper is not None:
880                if wrapper.type is ThreadException:
881                    raise wrapper.value
882                else:
883                    raise ThreadException([wrapper])
884
885            # TODO: cancel port forward on transport? Does that even make sense
886            # here (where we used direct-tcpip) vs the opposite method (which
887            # is what uses forward-tcpip)?
888
889    # TODO: probably push some of this down into Paramiko
890    @contextmanager
891    @opens
892    def forward_remote(
893        self,
894        remote_port,
895        local_port=None,
896        remote_host="127.0.0.1",
897        local_host="localhost",
898    ):
899        """
900        Open a tunnel connecting ``remote_port`` to the local environment.
901
902        For example, say you're running a daemon in development mode on your
903        workstation at port 8080, and want to funnel traffic to it from a
904        production or staging environment.
905
906        In most situations this isn't possible as your office/home network
907        probably blocks inbound traffic. But you have SSH access to this
908        server, so you can temporarily make port 8080 on that server act like
909        port 8080 on your workstation::
910
911            from fabric import Connection
912
913            c = Connection('my-remote-server')
914            with c.forward_remote(8080):
915                c.run("remote-data-writer --port 8080")
916                # Assuming remote-data-writer runs until interrupted, this will
917                # stay open until you Ctrl-C...
918
919        This method is analogous to using the ``-R`` option of OpenSSH's
920        ``ssh`` program.
921
922        :param int remote_port: The remote port number on which to listen.
923
924        :param int local_port:
925            The local port number. Defaults to the same value as
926            ``remote_port``.
927
928        :param str local_host:
929            The local hostname/interface the forwarded connection talks to.
930            Default: ``localhost``.
931
932        :param str remote_host:
933            The remote interface address to listen on when forwarding
934            connections. Default: ``127.0.0.1`` (i.e. only listen on the remote
935            localhost).
936
937        :returns:
938            Nothing; this method is only useful as a context manager affecting
939            local operating system state.
940
941        .. versionadded:: 2.0
942        """
943        if not local_port:
944            local_port = remote_port
945        # Callback executes on each connection to the remote port and is given
946        # a Channel hooked up to said port. (We don't actually care about the
947        # source/dest host/port pairs at all; only whether the channel has data
948        # to read and suchlike.)
949        # We then pair that channel with a new 'outbound' socket connection to
950        # the local host/port being forwarded, in a new Tunnel.
951        # That Tunnel is then added to a shared data structure so we can track
952        # & close them during shutdown.
953        #
954        # TODO: this approach is less than ideal because we have to share state
955        # between ourselves & the callback handed into the transport's own
956        # thread handling (which is roughly analogous to our self-controlled
957        # TunnelManager for local forwarding). See if we can use more of
958        # Paramiko's API (or improve it and then do so) so that isn't
959        # necessary.
960        tunnels = []
961
962        def callback(channel, src_addr_tup, dst_addr_tup):
963            sock = socket.socket()
964            # TODO: handle connection failure such that channel, etc get closed
965            sock.connect((local_host, local_port))
966            # TODO: we don't actually need to generate the Events at our level,
967            # do we? Just let Tunnel.__init__ do it; all we do is "press its
968            # button" on shutdown...
969            tunnel = Tunnel(channel=channel, sock=sock, finished=Event())
970            tunnel.start()
971            # Communication between ourselves & the Paramiko handling subthread
972            tunnels.append(tunnel)
973
974        # Ask Paramiko (really, the remote sshd) to call our callback whenever
975        # connections are established on the remote iface/port.
976        # transport.request_port_forward(remote_host, remote_port, callback)
977        try:
978            self.transport.request_port_forward(
979                address=remote_host, port=remote_port, handler=callback
980            )
981            yield
982        finally:
983            # TODO: see above re: lack of a TunnelManager
984            # TODO: and/or also refactor with TunnelManager re: shutdown logic.
985            # E.g. maybe have a non-thread TunnelManager-alike with a method
986            # that acts as the callback? At least then there's a tiny bit more
987            # encapsulation...meh.
988            for tunnel in tunnels:
989                tunnel.finished.set()
990                tunnel.join()
991            self.transport.cancel_port_forward(
992                address=remote_host, port=remote_port
993            )
994