paramiko.client

Package paramiko :: Module client |
[frames] | no frames]

# Source Code for Module paramiko.client

      1  # Copyright (C) 2006-2007  Robey Pointer <robeypointer@gmail.com>
      2  #
      3  # This file is part of paramiko.
      4  #
      5  # Paramiko is free software; you can redistribute it and/or modify it under the
      6  # terms of the GNU Lesser General Public License as published by the Free
      7  # Software Foundation; either version 2.1 of the License, or (at your option)
      8  # any later version.
      9  #
     10  # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
     11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     13  # details.
     14  #
     15  # You should have received a copy of the GNU Lesser General Public License
     16  # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
     17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
     18
     19  """
     20  L{SSHClient}.
     21  """
     22
     23  from binascii import hexlify
     24  import getpass
     25  import os
     26  import socket
     27  import warnings
     28
     29  from paramiko.agent import Agent
     30  from paramiko.common import *
     31  from paramiko.dsskey import DSSKey
     32  from paramiko.hostkeys import HostKeys
     33  from paramiko.resource import ResourceManager
     34  from paramiko.rsakey import RSAKey
     35  from paramiko.ssh_exception import SSHException, BadHostKeyException
     36  from paramiko.transport import Transport
     37
     38
     39  SSH_PORT = 22
     40
     41 -class MissingHostKeyPolicy (object):
     42      """
     43      Interface for defining the policy that L{SSHClient} should use when the
     44      SSH server's hostname is not in either the system host keys or the
     45      application's keys.  Pre-made classes implement policies for automatically
     46      adding the key to the application's L{HostKeys} object (L{AutoAddPolicy}),
     47      and for automatically rejecting the key (L{RejectPolicy}).
     48
     49      This function may be used to ask the user to verify the key, for example.
     50      """
     51
     52 -    def missing_host_key(self, client, hostname, key):
     53          """
     54          Called when an L{SSHClient} receives a server key for a server that
     55          isn't in either the system or local L{HostKeys} object.  To accept
     56          the key, simply return.  To reject, raised an exception (which will
     57          be passed to the calling application).
     58          """
     59          pass
     60
     61
     62 -class AutoAddPolicy (MissingHostKeyPolicy):
     63      """
     64      Policy for automatically adding the hostname and new host key to the
     65      local L{HostKeys} object, and saving it.  This is used by L{SSHClient}.
     66      """
     67
     68 -    def missing_host_key(self, client, hostname, key):
     69          client._host_keys.add(hostname, key.get_name(), key)
     70          if client._host_keys_filename is not None:
     71              client.save_host_keys(client._host_keys_filename)
     72          client._log(DEBUG, 'Adding %s host key for %s: %s' %
     73                      (key.get_name(), hostname, hexlify(key.get_fingerprint())))
     74
     75
     76 -class RejectPolicy (MissingHostKeyPolicy):
     77      """
     78      Policy for automatically rejecting the unknown hostname & key.  This is
     79      used by L{SSHClient}.
     80      """
     81
     82 -    def missing_host_key(self, client, hostname, key):
     83          client._log(DEBUG, 'Rejecting %s host key for %s: %s' %
     84                      (key.get_name(), hostname, hexlify(key.get_fingerprint())))
     85          raise SSHException('Unknown server %s' % hostname)
     86
     87
     88 -class WarningPolicy (MissingHostKeyPolicy):
     89      """
     90      Policy for logging a python-style warning for an unknown host key, but
     91      accepting it. This is used by L{SSHClient}.
     92      """
     93 -    def missing_host_key(self, client, hostname, key):
     94          warnings.warn('Unknown %s host key for %s: %s' %
     95                        (key.get_name(), hostname, hexlify(key.get_fingerprint())))
     96
     97
     98 -class SSHClient (object):
     99      """
    100      A high-level representation of a session with an SSH server.  This class
    101      wraps L{Transport}, L{Channel}, and L{SFTPClient} to take care of most
    102      aspects of authenticating and opening channels.  A typical use case is::
    103
    104          client = SSHClient()
    105          client.load_system_host_keys()
    106          client.connect('ssh.example.com')
    107          stdin, stdout, stderr = client.exec_command('ls -l')
    108
    109      You may pass in explicit overrides for authentication and server host key
    110      checking.  The default mechanism is to try to use local key files or an
    111      SSH agent (if one is running).
    112
    113      @since: 1.6
    114      """
    115
    116 -    def __init__(self):
    117          """
    118          Create a new SSHClient.
    119          """
    120          self._system_host_keys = HostKeys()
    121          self._host_keys = HostKeys()
    122          self._host_keys_filename = None
    123          self._log_channel = None
    124          self._policy = RejectPolicy()
    125          self._transport = None
    126          self._agent = None
    127
    128 -    def load_system_host_keys(self, filename=None):
    129          """
    130          Load host keys from a system (read-only) file.  Host keys read with
    131          this method will not be saved back by L{save_host_keys}.
    132
    133          This method can be called multiple times.  Each new set of host keys
    134          will be merged with the existing set (new replacing old if there are
    135          conflicts).
    136
    137          If C{filename} is left as C{None}, an attempt will be made to read
    138          keys from the user's local "known hosts" file, as used by OpenSSH,
    139          and no exception will be raised if the file can't be read.  This is
    140          probably only useful on posix.
    141
    142          @param filename: the filename to read, or C{None}
    143          @type filename: str
    144
    145          @raise IOError: if a filename was provided and the file could not be
    146              read
    147          """
    148          if filename is None:
    149              # try the user's .ssh key file, and mask exceptions
    150              filename = os.path.expanduser('~/.ssh/known_hosts')
    151              try:
    152                  self._system_host_keys.load(filename)
    153              except IOError:
    154                  pass
    155              return
    156          self._system_host_keys.load(filename)
    157
    158 -    def load_host_keys(self, filename):
    159          """
    160          Load host keys from a local host-key file.  Host keys read with this
    161          method will be checked I{after} keys loaded via L{load_system_host_keys},
    162          but will be saved back by L{save_host_keys} (so they can be modified).
    163          The missing host key policy L{AutoAddPolicy} adds keys to this set and
    164          saves them, when connecting to a previously-unknown server.
    165
    166          This method can be called multiple times.  Each new set of host keys
    167          will be merged with the existing set (new replacing old if there are
    168          conflicts).  When automatically saving, the last hostname is used.
    169
    170          @param filename: the filename to read
    171          @type filename: str
    172
    173          @raise IOError: if the filename could not be read
    174          """
    175          self._host_keys_filename = filename
    176          self._host_keys.load(filename)
    177
    178 -    def save_host_keys(self, filename):
    179          """
    180          Save the host keys back to a file.  Only the host keys loaded with
    181          L{load_host_keys} (plus any added directly) will be saved -- not any
    182          host keys loaded with L{load_system_host_keys}.
    183
    184          @param filename: the filename to save to
    185          @type filename: str
    186
    187          @raise IOError: if the file could not be written
    188          """
    189          f = open(filename, 'w')
    190          f.write('# SSH host keys collected by paramiko\n')
    191          for hostname, keys in self._host_keys.iteritems():
    192              for keytype, key in keys.iteritems():
    193                  f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
    194          f.close()
    195
    196 -    def get_host_keys(self):
    197          """
    198          Get the local L{HostKeys} object.  This can be used to examine the
    199          local host keys or change them.
    200
    201          @return: the local host keys
    202          @rtype: L{HostKeys}
    203          """
    204          return self._host_keys
    205
    206 -    def set_log_channel(self, name):
    207          """
    208          Set the channel for logging.  The default is C{"paramiko.transport"}
    209          but it can be set to anything you want.
    210
    211          @param name: new channel name for logging
    212          @type name: str
    213          """
    214          self._log_channel = name
    215
    216 -    def set_missing_host_key_policy(self, policy):
    217          """
    218          Set the policy to use when connecting to a server that doesn't have a
    219          host key in either the system or local L{HostKeys} objects.  The
    220          default policy is to reject all unknown servers (using L{RejectPolicy}).
    221          You may substitute L{AutoAddPolicy} or write your own policy class.
    222
    223          @param policy: the policy to use when receiving a host key from a
    224              previously-unknown server
    225          @type policy: L{MissingHostKeyPolicy}
    226          """
    227          self._policy = policy
    228
    229 -    def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
    230                  key_filename=None, timeout=None, allow_agent=True, look_for_keys=True,
    231                  compress=False):
    232          """
    233          Connect to an SSH server and authenticate to it.  The server's host key
    234          is checked against the system host keys (see L{load_system_host_keys})
    235          and any local host keys (L{load_host_keys}).  If the server's hostname
    236          is not found in either set of host keys, the missing host key policy
    237          is used (see L{set_missing_host_key_policy}).  The default policy is
    238          to reject the key and raise an L{SSHException}.
    239
    240          Authentication is attempted in the following order of priority:
    241
    242              - The C{pkey} or C{key_filename} passed in (if any)
    243              - Any key we can find through an SSH agent
    244              - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
    245              - Plain username/password auth, if a password was given
    246
    247          If a private key requires a password to unlock it, and a password is
    248          passed in, that password will be used to attempt to unlock the key.
    249
    250          @param hostname: the server to connect to
    251          @type hostname: str
    252          @param port: the server port to connect to
    253          @type port: int
    254          @param username: the username to authenticate as (defaults to the
    255              current local username)
    256          @type username: str
    257          @param password: a password to use for authentication or for unlocking
    258              a private key
    259          @type password: str
    260          @param pkey: an optional private key to use for authentication
    261          @type pkey: L{PKey}
    262          @param key_filename: the filename, or list of filenames, of optional
    263              private key(s) to try for authentication
    264          @type key_filename: str or list(str)
    265          @param timeout: an optional timeout (in seconds) for the TCP connect
    266          @type timeout: float
    267          @param allow_agent: set to False to disable connecting to the SSH agent
    268          @type allow_agent: bool
    269          @param look_for_keys: set to False to disable searching for discoverable
    270              private key files in C{~/.ssh/}
    271          @type look_for_keys: bool
    272          @param compress: set to True to turn on compression
    273          @type compress: bool
    274
    275          @raise BadHostKeyException: if the server's host key could not be
    276              verified
    277          @raise AuthenticationException: if authentication failed
    278          @raise SSHException: if there was any other error connecting or
    279              establishing an SSH session
    280          @raise socket.error: if a socket error occurred while connecting
    281          """
    282          for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
    283              if socktype == socket.SOCK_STREAM:
    284                  af = family
    285                  addr = sockaddr
    286                  break
    287          else:
    288              # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :(
    289              af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
    290          sock = socket.socket(af, socket.SOCK_STREAM)
    291          if timeout is not None:
    292              try:
    293                  sock.settimeout(timeout)
    294              except:
    295                  pass
    296          sock.connect(addr)
    297          t = self._transport = Transport(sock)
    298          t.use_compression(compress=compress)
    299          if self._log_channel is not None:
    300              t.set_log_channel(self._log_channel)
    301          t.start_client()
    302          ResourceManager.register(self, t)
    303
    304          server_key = t.get_remote_server_key()
    305          keytype = server_key.get_name()
    306
    307          if port == SSH_PORT:
    308              server_hostkey_name = hostname
    309          else:
    310              server_hostkey_name = "[%s]:%d" % (hostname, port)
    311          our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None)
    312          if our_server_key is None:
    313              our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None)
    314          if our_server_key is None:
    315              # will raise exception if the key is rejected; let that fall out
    316              self._policy.missing_host_key(self, server_hostkey_name, server_key)
    317              # if the callback returns, assume the key is ok
    318              our_server_key = server_key
    319
    320          if server_key != our_server_key:
    321              raise BadHostKeyException(hostname, server_key, our_server_key)
    322
    323          if username is None:
    324              username = getpass.getuser()
    325
    326          if key_filename is None:
    327              key_filenames = []
    328          elif isinstance(key_filename, (str, unicode)):
    329              key_filenames = [ key_filename ]
    330          else:
    331              key_filenames = key_filename
    332          self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
    333
    334 -    def close(self):
    335          """
    336          Close this SSHClient and its underlying L{Transport}.
    337          """
    338          if self._transport is None:
    339              return
    340          self._transport.close()
    341          self._transport = None
    342
    343          if self._agent != None:
    344              self._agent.close()
    345              self._agent = None
    346
    347 -    def exec_command(self, command, bufsize=-1):
    348          """
    349          Execute a command on the SSH server.  A new L{Channel} is opened and
    350          the requested command is executed.  The command's input and output
    351          streams are returned as python C{file}-like objects representing
    352          stdin, stdout, and stderr.
    353
    354          @param command: the command to execute
    355          @type command: str
    356          @param bufsize: interpreted the same way as by the built-in C{file()} function in python
    357          @type bufsize: int
    358          @return: the stdin, stdout, and stderr of the executing command
    359          @rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
    360
    361          @raise SSHException: if the server fails to execute the command
    362          """
    363          chan = self._transport.open_session()
    364          chan.exec_command(command)
    365          stdin = chan.makefile('wb', bufsize)
    366          stdout = chan.makefile('rb', bufsize)
    367          stderr = chan.makefile_stderr('rb', bufsize)
    368          return stdin, stdout, stderr
    369
    370 -    def invoke_shell(self, term='vt100', width=80, height=24):
    371          """
    372          Start an interactive shell session on the SSH server.  A new L{Channel}
    373          is opened and connected to a pseudo-terminal using the requested
    374          terminal type and size.
    375
    376          @param term: the terminal type to emulate (for example, C{"vt100"})
    377          @type term: str
    378          @param width: the width (in characters) of the terminal window
    379          @type width: int
    380          @param height: the height (in characters) of the terminal window
    381          @type height: int
    382          @return: a new channel connected to the remote shell
    383          @rtype: L{Channel}
    384
    385          @raise SSHException: if the server fails to invoke a shell
    386          """
    387          chan = self._transport.open_session()
    388          chan.get_pty(term, width, height)
    389          chan.invoke_shell()
    390          return chan
    391
    392 -    def open_sftp(self):
    393          """
    394          Open an SFTP session on the SSH server.
    395
    396          @return: a new SFTP session object
    397          @rtype: L{SFTPClient}
    398          """
    399          return self._transport.open_sftp_client()
    400
    401 -    def get_transport(self):
    402          """
    403          Return the underlying L{Transport} object for this SSH connection.
    404          This can be used to perform lower-level tasks, like opening specific
    405          kinds of channels.
    406
    407          @return: the Transport for this connection
    408          @rtype: L{Transport}
    409          """
    410          return self._transport
    411
    412 -    def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
    413          """
    414          Try, in order:
    415
    416              - The key passed in, if one was passed in.
    417              - Any key we can find through an SSH agent (if allowed).
    418              - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed).
    419              - Plain username/password auth, if a password was given.
    420
    421          (The password might be needed to unlock a private key.)
    422          """
    423          saved_exception = None
    424
    425          if pkey is not None:
    426              try:
    427                  self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
    428                  self._transport.auth_publickey(username, pkey)
    429                  return
    430              except SSHException, e:
    431                  saved_exception = e
    432
    433          for key_filename in key_filenames:
    434              for pkey_class in (RSAKey, DSSKey):
    435                  try:
    436                      key = pkey_class.from_private_key_file(key_filename, password)
    437                      self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename))
    438                      self._transport.auth_publickey(username, key)
    439                      return
    440                  except SSHException, e:
    441                      saved_exception = e
    442
    443          if allow_agent:
    444              if self._agent == None:
    445                  self._agent = Agent()
    446
    447              for key in self._agent.get_keys():
    448                  try:
    449                      self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
    450                      self._transport.auth_publickey(username, key)
    451                      return
    452                  except SSHException, e:
    453                      saved_exception = e
    454
    455          keyfiles = []
    456          rsa_key = os.path.expanduser('~/.ssh/id_rsa')
    457          dsa_key = os.path.expanduser('~/.ssh/id_dsa')
    458          if os.path.isfile(rsa_key):
    459              keyfiles.append((RSAKey, rsa_key))
    460          if os.path.isfile(dsa_key):
    461              keyfiles.append((DSSKey, dsa_key))
    462          # look in ~/ssh/ for windows users:
    463          rsa_key = os.path.expanduser('~/ssh/id_rsa')
    464          dsa_key = os.path.expanduser('~/ssh/id_dsa')
    465          if os.path.isfile(rsa_key):
    466              keyfiles.append((RSAKey, rsa_key))
    467          if os.path.isfile(dsa_key):
    468              keyfiles.append((DSSKey, dsa_key))
    469
    470          if not look_for_keys:
    471              keyfiles = []
    472
    473          for pkey_class, filename in keyfiles:
    474              try:
    475                  key = pkey_class.from_private_key_file(filename, password)
    476                  self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
    477                  self._transport.auth_publickey(username, key)
    478                  return
    479              except SSHException, e:
    480                  saved_exception = e
    481              except IOError, e:
    482                  saved_exception = e
    483
    484          if password is not None:
    485              try:
    486                  self._transport.auth_password(username, password)
    487                  return
    488              except SSHException, e:
    489                  saved_exception = e
    490
    491          # if we got an auth-failed exception earlier, re-raise it
    492          if saved_exception is not None:
    493              raise saved_exception
    494          raise SSHException('No authentication methods available')
    495
    496 -    def _log(self, level, msg):
    497          self._transport._log(level, msg)
    498