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