paramiko.config

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

# Source Code for Module paramiko.config

      1  # Copyright (C) 2006-2007  Robey Pointer <robeypointer@gmail.com>
      2  # Copyright (C) 2012  Olle Lundberg <geek@nerd.sh>
      3  #
      4  # This file is part of paramiko.
      5  #
      6  # Paramiko is free software; you can redistribute it and/or modify it under the
      7  # terms of the GNU Lesser General Public License as published by the Free
      8  # Software Foundation; either version 2.1 of the License, or (at your option)
      9  # any later version.
     10  #
     11  # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY
     12  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     13  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     14  # details.
     15  #
     16  # You should have received a copy of the GNU Lesser General Public License
     17  # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
     18  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
     19
     20  """
     21  L{SSHConfig}.
     22  """
     23
     24  import fnmatch
     25  import os
     26  import re
     27  import socket
     28
     29  SSH_PORT = 22
     30  proxy_re = re.compile(r"^(proxycommand)\s*=*\s*(.*)", re.I)
     31
     32
     33 -class LazyFqdn(object):
     34      """
     35      Returns the host's fqdn on request as string.
     36      """
     37
     38 -    def __init__(self, config, host=None):
     39          self.fqdn = None
     40          self.config = config
     41          self.host = host
     42
     43 -    def __str__(self):
     44          if self.fqdn is None:
     45              #
     46              # If the SSH config contains AddressFamily, use that when
     47              # determining  the local host's FQDN. Using socket.getfqdn() from
     48              # the standard library is the most general solution, but can
     49              # result in noticeable delays on some platforms when IPv6 is
     50              # misconfigured or not available, as it calls getaddrinfo with no
     51              # address family specified, so both IPv4 and IPv6 are checked.
     52              #
     53
     54              # Handle specific option
     55              fqdn = None
     56              address_family = self.config.get('addressfamily', 'any').lower()
     57              if address_family != 'any':
     58                  try:
     59                      family = socket.AF_INET if address_family == 'inet' \
     60                          else socket.AF_INET6
     61                      results = socket.getaddrinfo(
     62                          self.host,
     63                          None,
     64                          family,
     65                          socket.SOCK_DGRAM,
     66                          socket.IPPROTO_IP,
     67                          socket.AI_CANONNAME
     68                      )
     69                      for res in results:
     70                          af, socktype, proto, canonname, sa = res
     71                          if canonname and '.' in canonname:
     72                              fqdn = canonname
     73                              break
     74                  # giaerror -> socket.getaddrinfo() can't resolve self.host
     75                  # (which is from socket.gethostname()). Fall back to the
     76                  # getfqdn() call below.
     77                  except socket.gaierror:
     78                      pass
     79              # Handle 'any' / unspecified
     80              if fqdn is None:
     81                  fqdn = socket.getfqdn()
     82              # Cache
     83              self.fqdn = fqdn
     84          return self.fqdn
     85
     86
     87 -class SSHConfig (object):
     88      """
     89      Representation of config information as stored in the format used by
     90      OpenSSH. Queries can be made via L{lookup}. The format is described in
     91      OpenSSH's C{ssh_config} man page. This class is provided primarily as a
     92      convenience to posix users (since the OpenSSH format is a de-facto
     93      standard on posix) but should work fine on Windows too.
     94
     95      @since: 1.6
     96      """
     97
     98 -    def __init__(self):
     99          """
    100          Create a new OpenSSH config object.
    101          """
    102          self._config = []
    103
    104 -    def parse(self, file_obj):
    105          """
    106          Read an OpenSSH config from the given file object.
    107
    108          @param file_obj: a file-like object to read the config file from
    109          @type file_obj: file
    110          """
    111          host = {"host": ['*'], "config": {}}
    112          for line in file_obj:
    113              line = line.rstrip('\n').lstrip()
    114              if (line == '') or (line[0] == '#'):
    115                  continue
    116              if '=' in line:
    117                  # Ensure ProxyCommand gets properly split
    118                  if line.lower().strip().startswith('proxycommand'):
    119                      match = proxy_re.match(line)
    120                      key, value = match.group(1).lower(), match.group(2)
    121                  else:
    122                      key, value = line.split('=', 1)
    123                      key = key.strip().lower()
    124              else:
    125                  # find first whitespace, and split there
    126                  i = 0
    127                  while (i < len(line)) and not line[i].isspace():
    128                      i += 1
    129                  if i == len(line):
    130                      raise Exception('Unparsable line: %r' % line)
    131                  key = line[:i].lower()
    132                  value = line[i:].lstrip()
    133
    134              if key == 'host':
    135                  self._config.append(host)
    136                  value = value.split()
    137                  host = {key: value, 'config': {}}
    138              #identityfile, localforward, remoteforward keys are special cases, since they are allowed to be
    139              # specified multiple times and they should be tried in order
    140              # of specification.
    141
    142              elif key in ['identityfile', 'localforward', 'remoteforward']:
    143                  if key in host['config']:
    144                      host['config'][key].append(value)
    145                  else:
    146                      host['config'][key] = [value]
    147              elif key not in host['config']:
    148                  host['config'].update({key: value})
    149          self._config.append(host)
    150
    151 -    def lookup(self, hostname):
    152          """
    153          Return a dict of config options for a given hostname.
    154
    155          The host-matching rules of OpenSSH's C{ssh_config} man page are used,
    156          which means that all configuration options from matching host
    157          specifications are merged, with more specific hostmasks taking
    158          precedence. In other words, if C{"Port"} is set under C{"Host *"}
    159          and also C{"Host *.example.com"}, and the lookup is for
    160          C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"}
    161          will win out.
    162
    163          The keys in the returned dict are all normalized to lowercase (look for
    164          C{"port"}, not C{"Port"}. The values are processed according to the
    165          rules for substitution variable expansion in C{ssh_config}.
    166
    167          @param hostname: the hostname to lookup
    168          @type hostname: str
    169          """
    170
    171          matches = [config for config in self._config if
    172                     self._allowed(hostname, config['host'])]
    173
    174          ret = {}
    175          for match in matches:
    176              for key, value in match['config'].iteritems():
    177                  if key not in ret:
    178                      # Create a copy of the original value,
    179                      # else it will reference the original list
    180                      # in self._config and update that value too
    181                      # when the extend() is being called.
    182                      ret[key] = value[:]
    183                  elif key == 'identityfile':
    184                      ret[key].extend(value)
    185          ret = self._expand_variables(ret, hostname)
    186          return ret
    187
    188 -    def _allowed(self, hostname, hosts):
    189          match = False
    190          for host in hosts:
    191              if host.startswith('!') and fnmatch.fnmatch(hostname, host[1:]):
    192                  return False
    193              elif fnmatch.fnmatch(hostname, host):
    194                  match = True
    195          return match
    196
    197 -    def _expand_variables(self, config, hostname):
    198          """
    199          Return a dict of config options with expanded substitutions
    200          for a given hostname.
    201
    202          Please refer to man C{ssh_config} for the parameters that
    203          are replaced.
    204
    205          @param config: the config for the hostname
    206          @type hostname: dict
    207          @param hostname: the hostname that the config belongs to
    208          @type hostname: str
    209          """
    210
    211          if 'hostname' in config:
    212              config['hostname'] = config['hostname'].replace('%h', hostname)
    213          else:
    214              config['hostname'] = hostname
    215
    216          if 'port' in config:
    217              port = config['port']
    218          else:
    219              port = SSH_PORT
    220
    221          user = os.getenv('USER')
    222          if 'user' in config:
    223              remoteuser = config['user']
    224          else:
    225              remoteuser = user
    226
    227          host = socket.gethostname().split('.')[0]
    228          fqdn = LazyFqdn(config, host)
    229          homedir = os.path.expanduser('~')
    230          replacements = {'controlpath':
    231                          [
    232                              ('%h', config['hostname']),
    233                              ('%l', fqdn),
    234                              ('%L', host),
    235                              ('%n', hostname),
    236                              ('%p', port),
    237                              ('%r', remoteuser),
    238                              ('%u', user)
    239                          ],
    240                          'identityfile':
    241                          [
    242                              ('~', homedir),
    243                              ('%d', homedir),
    244                              ('%h', config['hostname']),
    245                              ('%l', fqdn),
    246                              ('%u', user),
    247                              ('%r', remoteuser)
    248                          ],
    249                          'proxycommand':
    250                          [
    251                              ('%h', config['hostname']),
    252                              ('%p', port),
    253                              ('%r', remoteuser)
    254                          ]
    255                          }
    256
    257          for k in config:
    258              if k in replacements:
    259                  for find, replace in replacements[k]:
    260                      if isinstance(config[k], list):
    261                          for item in range(len(config[k])):
    262                              config[k][item] = config[k][item].\
    263                                  replace(find, str(replace))
    264                      else:
    265                          config[k] = config[k].replace(find, str(replace))
    266          return config
    267