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