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
    127 -    def load_system_host_keys(self, filename=None):
    128          """
    129          Load host keys from a system (read-only) file.  Host keys read with
    130          this method will not be saved back by L{save_host_keys}.
    131
    132          This method can be called multiple times.  Each new set of host keys
    133          will be merged with the existing set (new replacing old if there are
    134          conflicts).
    135
    136          If C{filename} is left as C{None}, an attempt will be made to read
    137          keys from the user's local "known hosts" file, as used by OpenSSH,
    138          and no exception will be raised if the file can't be read.  This is
    139          probably only useful on posix.
    140
    141          @param filename: the filename to read, or C{None}
    142          @type filename: str
    143
    144          @raise IOError: if a filename was provided and the file could not be
    145              read
    146          """
    147          if filename is None:
    148              # try the user's .ssh key file, and mask exceptions
    149              filename = os.path.expanduser('~/.ssh/known_hosts')
    150              try:
    151                  self._system_host_keys.load(filename)
    152              except IOError:
    153                  pass
    154              return
    155          self._system_host_keys.load(filename)
    156
    157 -    def load_host_keys(self, filename):
    158          """
    159          Load host keys from a local host-key file.  Host keys read with this
    160          method will be checked I{after} keys loaded via L{load_system_host_keys},
    161          but will be saved back by L{save_host_keys} (so they can be modified).
    162          The missing host key policy L{AutoAddPolicy} adds keys to this set and
    163          saves them, when connecting to a previously-unknown server.
    164
    165          This method can be called multiple times.  Each new set of host keys
    166          will be merged with the existing set (new replacing old if there are
    167          conflicts).  When automatically saving, the last hostname is used.
    168
    169          @param filename: the filename to read
    170          @type filename: str
    171
    172          @raise IOError: if the filename could not be read
    173          """
    174          self._host_keys_filename = filename
    175          self._host_keys.load(filename)
    176
    177 -    def save_host_keys(self, filename):
    178          """
    179          Save the host keys back to a file.  Only the host keys loaded with
    180          L{load_host_keys} (plus any added directly) will be saved -- not any
    181          host keys loaded with L{load_system_host_keys}.
    182
    183          @param filename: the filename to save to
    184          @type filename: str
    185
    186          @raise IOError: if the file could not be written
    187          """
    188          f = open(filename, 'w')
    189          f.write('# SSH host keys collected by paramiko\n')
    190          for hostname, keys in self._host_keys.iteritems():
    191              for keytype, key in keys.iteritems():
    192                  f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
    193          f.close()
    194
    195 -    def get_host_keys(self):
    196          """
    197          Get the local L{HostKeys} object.  This can be used to examine the
    198          local host keys or change them.
    199
    200          @return: the local host keys
    201          @rtype: L{HostKeys}
    202          """
    203          return self._host_keys
    204
    205 -    def set_log_channel(self, name):
    206          """
    207          Set the channel for logging.  The default is C{"paramiko.transport"}
    208          but it can be set to anything you want.
    209
    210          @param name: new channel name for logging
    211          @type name: str
    212          """
    213          self._log_channel = name
    214
    215 -    def set_missing_host_key_policy(self, policy):
    216          """
    217          Set the policy to use when connecting to a server that doesn't have a
    218          host key in either the system or local L{HostKeys} objects.  The
    219          default policy is to reject all unknown servers (using L{RejectPolicy}).
    220          You may substitute L{AutoAddPolicy} or write your own policy class.
    221
    222          @param policy: the policy to use when receiving a host key from a
    223              previously-unknown server
    224          @type policy: L{MissingHostKeyPolicy}
    225          """
    226          self._policy = policy
    227
    228 -    def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
    229                  key_filename=None, timeout=None, allow_agent=True, look_for_keys=True):
    230          """
    231          Connect to an SSH server and authenticate to it.  The server's host key
    232          is checked against the system host keys (see L{load_system_host_keys})
    233          and any local host keys (L{load_host_keys}).  If the server's hostname
    234          is not found in either set of host keys, the missing host key policy
    235          is used (see L{set_missing_host_key_policy}).  The default policy is
    236          to reject the key and raise an L{SSHException}.
    237
    238          Authentication is attempted in the following order of priority:
    239
    240              - The C{pkey} or C{key_filename} passed in (if any)
    241              - Any key we can find through an SSH agent
    242              - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
    243              - Plain username/password auth, if a password was given
    244
    245          If a private key requires a password to unlock it, and a password is
    246          passed in, that password will be used to attempt to unlock the key.
    247
    248          @param hostname: the server to connect to
    249          @type hostname: str
    250          @param port: the server port to connect to
    251          @type port: int
    252          @param username: the username to authenticate as (defaults to the
    253              current local username)
    254          @type username: str
    255          @param password: a password to use for authentication or for unlocking
    256              a private key
    257          @type password: str
    258          @param pkey: an optional private key to use for authentication
    259          @type pkey: L{PKey}
    260          @param key_filename: the filename, or list of filenames, of optional
    261              private key(s) to try for authentication
    262          @type key_filename: str or list(str)
    263          @param timeout: an optional timeout (in seconds) for the TCP connect
    264          @type timeout: float
    265          @param allow_agent: set to False to disable connecting to the SSH agent
    266          @type allow_agent: bool
    267          @param look_for_keys: set to False to disable searching for discoverable
    268              private key files in C{~/.ssh/}
    269          @type look_for_keys: bool
    270
    271          @raise BadHostKeyException: if the server's host key could not be
    272              verified
    273          @raise AuthenticationException: if authentication failed
    274          @raise SSHException: if there was any other error connecting or
    275              establishing an SSH session
    276          @raise socket.error: if a socket error occurred while connecting
    277          """
    278          for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
    279              if socktype == socket.SOCK_STREAM:
    280                  af = family
    281                  addr = sockaddr
    282                  break
    283          else:
    284              raise SSHException('No suitable address family for %s' % hostname)
    285          sock = socket.socket(af, socket.SOCK_STREAM)
    286          if timeout is not None:
    287              try:
    288                  sock.settimeout(timeout)
    289              except:
    290                  pass
    291          sock.connect(addr)
    292          t = self._transport = Transport(sock)
    293
    294          if self._log_channel is not None:
    295              t.set_log_channel(self._log_channel)
    296          t.start_client()
    297          ResourceManager.register(self, t)
    298
    299          server_key = t.get_remote_server_key()
    300          keytype = server_key.get_name()
    301
    302          if port == SSH_PORT:
    303              server_hostkey_name = hostname
    304          else:
    305              server_hostkey_name = "[%s]:%d" % (hostname, port)
    306          our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None)
    307          if our_server_key is None:
    308              our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None)
    309          if our_server_key is None:
    310              # will raise exception if the key is rejected; let that fall out
    311              self._policy.missing_host_key(self, server_hostkey_name, server_key)
    312              # if the callback returns, assume the key is ok
    313              our_server_key = server_key
    314
    315          if server_key != our_server_key:
    316              raise BadHostKeyException(hostname, server_key, our_server_key)
    317
    318          if username is None:
    319              username = getpass.getuser()
    320
    321          if key_filename is None:
    322              key_filenames = []
    323          elif isinstance(key_filename, (str, unicode)):
    324              key_filenames = [ key_filename ]
    325          else:
    326              key_filenames = key_filename
    327          self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
    328
    329 -    def close(self):
    330          """
    331          Close this SSHClient and its underlying L{Transport}.
    332          """
    333          if self._transport is None:
    334              return
    335          self._transport.close()
    336          self._transport = None
    337
    338 -    def exec_command(self, command, bufsize=-1):
    339          """
    340          Execute a command on the SSH server.  A new L{Channel} is opened and
    341          the requested command is executed.  The command's input and output
    342          streams are returned as python C{file}-like objects representing
    343          stdin, stdout, and stderr.
    344
    345          @param command: the command to execute
    346          @type command: str
    347          @param bufsize: interpreted the same way as by the built-in C{file()} function in python
    348          @type bufsize: int
    349          @return: the stdin, stdout, and stderr of the executing command
    350          @rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
    351
    352          @raise SSHException: if the server fails to execute the command
    353          """
    354          chan = self._transport.open_session()
    355          chan.exec_command(command)
    356          stdin = chan.makefile('wb', bufsize)
    357          stdout = chan.makefile('rb', bufsize)
    358          stderr = chan.makefile_stderr('rb', bufsize)
    359          return stdin, stdout, stderr
    360
    361 -    def invoke_shell(self, term='vt100', width=80, height=24):
    362          """
    363          Start an interactive shell session on the SSH server.  A new L{Channel}
    364          is opened and connected to a pseudo-terminal using the requested
    365          terminal type and size.
    366
    367          @param term: the terminal type to emulate (for example, C{"vt100"})
    368          @type term: str
    369          @param width: the width (in characters) of the terminal window
    370          @type width: int
    371          @param height: the height (in characters) of the terminal window
    372          @type height: int
    373          @return: a new channel connected to the remote shell
    374          @rtype: L{Channel}
    375
    376          @raise SSHException: if the server fails to invoke a shell
    377          """
    378          chan = self._transport.open_session()
    379          chan.get_pty(term, width, height)
    380          chan.invoke_shell()
    381          return chan
    382
    383 -    def open_sftp(self):
    384          """
    385          Open an SFTP session on the SSH server.
    386
    387          @return: a new SFTP session object
    388          @rtype: L{SFTPClient}
    389          """
    390          return self._transport.open_sftp_client()
    391
    392 -    def get_transport(self):
    393          """
    394          Return the underlying L{Transport} object for this SSH connection.
    395          This can be used to perform lower-level tasks, like opening specific
    396          kinds of channels.
    397
    398          @return: the Transport for this connection
    399          @rtype: L{Transport}
    400          """
    401          return self._transport
    402
    403 -    def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
    404          """
    405          Try, in order:
    406
    407              - The key passed in, if one was passed in.
    408              - Any key we can find through an SSH agent (if allowed).
    409              - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed).
    410              - Plain username/password auth, if a password was given.
    411
    412          (The password might be needed to unlock a private key.)
    413          """
    414          saved_exception = None
    415
    416          if pkey is not None:
    417              try:
    418                  self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
    419                  self._transport.auth_publickey(username, pkey)
    420                  return
    421              except SSHException, e:
    422                  saved_exception = e
    423
    424          for key_filename in key_filenames:
    425              for pkey_class in (RSAKey, DSSKey):
    426                  try:
    427                      key = pkey_class.from_private_key_file(key_filename, password)
    428                      self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename))
    429                      self._transport.auth_publickey(username, key)
    430                      return
    431                  except SSHException, e:
    432                      saved_exception = e
    433
    434          if allow_agent:
    435              for key in Agent().get_keys():
    436                  try:
    437                      self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
    438                      self._transport.auth_publickey(username, key)
    439                      return
    440                  except SSHException, e:
    441                      saved_exception = e
    442
    443          keyfiles = []
    444          rsa_key = os.path.expanduser('~/.ssh/id_rsa')
    445          dsa_key = os.path.expanduser('~/.ssh/id_dsa')
    446          if os.path.isfile(rsa_key):
    447              keyfiles.append((RSAKey, rsa_key))
    448          if os.path.isfile(dsa_key):
    449              keyfiles.append((DSSKey, dsa_key))
    450          # look in ~/ssh/ for windows users:
    451          rsa_key = os.path.expanduser('~/ssh/id_rsa')
    452          dsa_key = os.path.expanduser('~/ssh/id_dsa')
    453          if os.path.isfile(rsa_key):
    454              keyfiles.append((RSAKey, rsa_key))
    455          if os.path.isfile(dsa_key):
    456              keyfiles.append((DSSKey, dsa_key))
    457
    458          if not look_for_keys:
    459              keyfiles = []
    460
    461          for pkey_class, filename in keyfiles:
    462              try:
    463                  key = pkey_class.from_private_key_file(filename, password)
    464                  self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
    465                  self._transport.auth_publickey(username, key)
    466                  return
    467              except SSHException, e:
    468                  saved_exception = e
    469              except IOError, e:
    470                  saved_exception = e
    471
    472          if password is not None:
    473              try:
    474                  self._transport.auth_password(username, password)
    475                  return
    476              except SSHException, e:
    477                  saved_exception = e
    478
    479          # if we got an auth-failed exception earlier, re-raise it
    480          if saved_exception is not None:
    481              raise saved_exception
    482          raise SSHException('No authentication methods available')
    483
    484 -    def _log(self, level, msg):
    485          self._transport._log(level, msg)
    486