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 distributed 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.config import SSH_PORT
     32  from paramiko.dsskey import DSSKey
     33  from paramiko.hostkeys import HostKeys
     34  from paramiko.resource import ResourceManager
     35  from paramiko.rsakey import RSAKey
     36  from paramiko.ssh_exception import SSHException, BadHostKeyException
     37  from paramiko.transport import Transport
     38  from paramiko.util import retry_on_signal
     39
     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('Server %r not found in known_hosts' % 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
    190          # update local host keys from file (in case other SSH clients
    191          # have written to the known_hosts file meanwhile.
    192          if self._host_keys_filename is not None:
    193              self.load_host_keys(self._host_keys_filename)
    194
    195          f = open(filename, 'w')
    196          for hostname, keys in self._host_keys.iteritems():
    197              for keytype, key in keys.iteritems():
    198                  f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
    199          f.close()
    200
    201 -    def get_host_keys(self):
    202          """
    203          Get the local L{HostKeys} object.  This can be used to examine the
    204          local host keys or change them.
    205
    206          @return: the local host keys
    207          @rtype: L{HostKeys}
    208          """
    209          return self._host_keys
    210
    211 -    def set_log_channel(self, name):
    212          """
    213          Set the channel for logging.  The default is C{"paramiko.transport"}
    214          but it can be set to anything you want.
    215
    216          @param name: new channel name for logging
    217          @type name: str
    218          """
    219          self._log_channel = name
    220
    221 -    def set_missing_host_key_policy(self, policy):
    222          """
    223          Set the policy to use when connecting to a server that doesn't have a
    224          host key in either the system or local L{HostKeys} objects.  The
    225          default policy is to reject all unknown servers (using L{RejectPolicy}).
    226          You may substitute L{AutoAddPolicy} or write your own policy class.
    227
    228          @param policy: the policy to use when receiving a host key from a
    229              previously-unknown server
    230          @type policy: L{MissingHostKeyPolicy}
    231          """
    232          self._policy = policy
    233
    234 -    def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
    235                  key_filename=None, timeout=None, allow_agent=True, look_for_keys=True,
    236                  compress=False, sock=None):
    237          """
    238          Connect to an SSH server and authenticate to it.  The server's host key
    239          is checked against the system host keys (see L{load_system_host_keys})
    240          and any local host keys (L{load_host_keys}).  If the server's hostname
    241          is not found in either set of host keys, the missing host key policy
    242          is used (see L{set_missing_host_key_policy}).  The default policy is
    243          to reject the key and raise an L{SSHException}.
    244
    245          Authentication is attempted in the following order of priority:
    246
    247              - The C{pkey} or C{key_filename} passed in (if any)
    248              - Any key we can find through an SSH agent
    249              - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
    250              - Plain username/password auth, if a password was given
    251
    252          If a private key requires a password to unlock it, and a password is
    253          passed in, that password will be used to attempt to unlock the key.
    254
    255          @param hostname: the server to connect to
    256          @type hostname: str
    257          @param port: the server port to connect to
    258          @type port: int
    259          @param username: the username to authenticate as (defaults to the
    260              current local username)
    261          @type username: str
    262          @param password: a password to use for authentication or for unlocking
    263              a private key
    264          @type password: str
    265          @param pkey: an optional private key to use for authentication
    266          @type pkey: L{PKey}
    267          @param key_filename: the filename, or list of filenames, of optional
    268              private key(s) to try for authentication
    269          @type key_filename: str or list(str)
    270          @param timeout: an optional timeout (in seconds) for the TCP connect
    271          @type timeout: float
    272          @param allow_agent: set to False to disable connecting to the SSH agent
    273          @type allow_agent: bool
    274          @param look_for_keys: set to False to disable searching for discoverable
    275              private key files in C{~/.ssh/}
    276          @type look_for_keys: bool
    277          @param compress: set to True to turn on compression
    278          @type compress: bool
    279          @param sock: an open socket or socket-like object (such as a
    280              L{Channel}) to use for communication to the target host
    281          @type sock: socket
    282
    283          @raise BadHostKeyException: if the server's host key could not be
    284              verified
    285          @raise AuthenticationException: if authentication failed
    286          @raise SSHException: if there was any other error connecting or
    287              establishing an SSH session
    288          @raise socket.error: if a socket error occurred while connecting
    289          """
    290          if not sock:
    291              for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
    292                  if socktype == socket.SOCK_STREAM:
    293                      af = family
    294                      addr = sockaddr
    295                      break
    296              else:
    297                  # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :(
    298                  af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
    299              sock = socket.socket(af, socket.SOCK_STREAM)
    300              if timeout is not None:
    301                  try:
    302                      sock.settimeout(timeout)
    303                  except:
    304                      pass
    305              retry_on_signal(lambda: sock.connect(addr))
    306
    307          t = self._transport = Transport(sock)
    308          t.use_compression(compress=compress)
    309          if self._log_channel is not None:
    310              t.set_log_channel(self._log_channel)
    311          t.start_client()
    312          ResourceManager.register(self, t)
    313
    314          server_key = t.get_remote_server_key()
    315          keytype = server_key.get_name()
    316
    317          if port == SSH_PORT:
    318              server_hostkey_name = hostname
    319          else:
    320              server_hostkey_name = "[%s]:%d" % (hostname, port)
    321          our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None)
    322          if our_server_key is None:
    323              our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None)
    324          if our_server_key is None:
    325              # will raise exception if the key is rejected; let that fall out
    326              self._policy.missing_host_key(self, server_hostkey_name, server_key)
    327              # if the callback returns, assume the key is ok
    328              our_server_key = server_key
    329
    330          if server_key != our_server_key:
    331              raise BadHostKeyException(hostname, server_key, our_server_key)
    332
    333          if username is None:
    334              username = getpass.getuser()
    335
    336          if key_filename is None:
    337              key_filenames = []
    338          elif isinstance(key_filename, (str, unicode)):
    339              key_filenames = [ key_filename ]
    340          else:
    341              key_filenames = key_filename
    342          self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
    343
    344 -    def close(self):
    345          """
    346          Close this SSHClient and its underlying L{Transport}.
    347          """
    348          if self._transport is None:
    349              return
    350          self._transport.close()
    351          self._transport = None
    352
    353          if self._agent != None:
    354              self._agent.close()
    355              self._agent = None
    356
    357 -    def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False):
    358          """
    359          Execute a command on the SSH server.  A new L{Channel} is opened and
    360          the requested command is executed.  The command's input and output
    361          streams are returned as python C{file}-like objects representing
    362          stdin, stdout, and stderr.
    363
    364          @param command: the command to execute
    365          @type command: str
    366          @param bufsize: interpreted the same way as by the built-in C{file()} function in python
    367          @type bufsize: int
    368          @param timeout: set command's channel timeout. See L{Channel.settimeout}.settimeout
    369          @type timeout: int
    370          @return: the stdin, stdout, and stderr of the executing command
    371          @rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
    372
    373          @raise SSHException: if the server fails to execute the command
    374          """
    375          chan = self._transport.open_session()
    376          if(get_pty):
    377              chan.get_pty()
    378          chan.settimeout(timeout)
    379          chan.exec_command(command)
    380          stdin = chan.makefile('wb', bufsize)
    381          stdout = chan.makefile('rb', bufsize)
    382          stderr = chan.makefile_stderr('rb', bufsize)
    383          return stdin, stdout, stderr
    384
    385 -    def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0,
    386                  height_pixels=0):
    387          """
    388          Start an interactive shell session on the SSH server.  A new L{Channel}
    389          is opened and connected to a pseudo-terminal using the requested
    390          terminal type and size.
    391
    392          @param term: the terminal type to emulate (for example, C{"vt100"})
    393          @type term: str
    394          @param width: the width (in characters) of the terminal window
    395          @type width: int
    396          @param height: the height (in characters) of the terminal window
    397          @type height: int
    398          @param width_pixels: the width (in pixels) of the terminal window
    399          @type width_pixels: int
    400          @param height_pixels: the height (in pixels) of the terminal window
    401          @type height_pixels: int
    402          @return: a new channel connected to the remote shell
    403          @rtype: L{Channel}
    404
    405          @raise SSHException: if the server fails to invoke a shell
    406          """
    407          chan = self._transport.open_session()
    408          chan.get_pty(term, width, height, width_pixels, height_pixels)
    409          chan.invoke_shell()
    410          return chan
    411
    412 -    def open_sftp(self):
    413          """
    414          Open an SFTP session on the SSH server.
    415
    416          @return: a new SFTP session object
    417          @rtype: L{SFTPClient}
    418          """
    419          return self._transport.open_sftp_client()
    420
    421 -    def get_transport(self):
    422          """
    423          Return the underlying L{Transport} object for this SSH connection.
    424          This can be used to perform lower-level tasks, like opening specific
    425          kinds of channels.
    426
    427          @return: the Transport for this connection
    428          @rtype: L{Transport}
    429          """
    430          return self._transport
    431
    432 -    def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
    433          """
    434          Try, in order:
    435
    436              - The key passed in, if one was passed in.
    437              - Any key we can find through an SSH agent (if allowed).
    438              - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed).
    439              - Plain username/password auth, if a password was given.
    440
    441          (The password might be needed to unlock a private key, or for
    442          two-factor authentication [for which it is required].)
    443          """
    444          saved_exception = None
    445          two_factor = False
    446          allowed_types = []
    447
    448          if pkey is not None:
    449              try:
    450                  self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
    451                  allowed_types = self._transport.auth_publickey(username, pkey)
    452                  two_factor = (allowed_types == ['password'])
    453                  if not two_factor:
    454                      return
    455              except SSHException, e:
    456                  saved_exception = e
    457
    458          if not two_factor:
    459              for key_filename in key_filenames:
    460                  for pkey_class in (RSAKey, DSSKey):
    461                      try:
    462                          key = pkey_class.from_private_key_file(key_filename, password)
    463                          self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename))
    464                          self._transport.auth_publickey(username, key)
    465                          two_factor = (allowed_types == ['password'])
    466                          if not two_factor:
    467                              return
    468                          break
    469                      except SSHException, e:
    470                          saved_exception = e
    471
    472          if not two_factor and allow_agent:
    473              if self._agent == None:
    474                  self._agent = Agent()
    475
    476              for key in self._agent.get_keys():
    477                  try:
    478                      self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
    479                      # for 2-factor auth a successfully auth'd key will result in ['password']
    480                      allowed_types = self._transport.auth_publickey(username, key)
    481                      two_factor = (allowed_types == ['password'])
    482                      if not two_factor:
    483                          return
    484                      break
    485                  except SSHException, e:
    486                      saved_exception = e
    487
    488          if not two_factor:
    489              keyfiles = []
    490              rsa_key = os.path.expanduser('~/.ssh/id_rsa')
    491              dsa_key = os.path.expanduser('~/.ssh/id_dsa')
    492              if os.path.isfile(rsa_key):
    493                  keyfiles.append((RSAKey, rsa_key))
    494              if os.path.isfile(dsa_key):
    495                  keyfiles.append((DSSKey, dsa_key))
    496              # look in ~/ssh/ for windows users:
    497              rsa_key = os.path.expanduser('~/ssh/id_rsa')
    498              dsa_key = os.path.expanduser('~/ssh/id_dsa')
    499              if os.path.isfile(rsa_key):
    500                  keyfiles.append((RSAKey, rsa_key))
    501              if os.path.isfile(dsa_key):
    502                  keyfiles.append((DSSKey, dsa_key))
    503
    504              if not look_for_keys:
    505                  keyfiles = []
    506
    507              for pkey_class, filename in keyfiles:
    508                  try:
    509                      key = pkey_class.from_private_key_file(filename, password)
    510                      self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
    511                      # for 2-factor auth a successfully auth'd key will result in ['password']
    512                      allowed_types = self._transport.auth_publickey(username, key)
    513                      two_factor = (allowed_types == ['password'])
    514                      if not two_factor:
    515                          return
    516                      break
    517                  except SSHException, e:
    518                      saved_exception = e
    519                  except IOError, e:
    520                      saved_exception = e
    521
    522          if password is not None:
    523              try:
    524                  self._transport.auth_password(username, password)
    525                  return
    526              except SSHException, e:
    527                  saved_exception = e
    528          elif two_factor:
    529              raise SSHException('Two-factor authentication requires a password')
    530
    531          # if we got an auth-failed exception earlier, re-raise it
    532          if saved_exception is not None:
    533              raise saved_exception
    534          raise SSHException('No authentication methods available')
    535
    536 -    def _log(self, level, msg):
    537          self._transport._log(level, msg)
    538