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