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 is a special case, since it is allowed to be 130 # specified multiple times and they should be tried in order 131 # of specification. 132 elif key == 'identityfile': 133 if key in host['config']: 134 host['config']['identityfile'].append(value) 135 else: 136 host['config']['identityfile'] = [value] 137 elif key not in host['config']: 138 host['config'].update({key: value}) 139 self._config.append(host)
140
141 - def lookup(self, hostname):
142 """ 143 Return a dict of config options for a given hostname. 144 145 The host-matching rules of OpenSSH's C{ssh_config} man page are used, 146 which means that all configuration options from matching host 147 specifications are merged, with more specific hostmasks taking 148 precedence. In other words, if C{"Port"} is set under C{"Host *"} 149 and also C{"Host *.example.com"}, and the lookup is for 150 C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"} 151 will win out. 152 153 The keys in the returned dict are all normalized to lowercase (look for 154 C{"port"}, not C{"Port"}. The values are processed according to the 155 rules for substitution variable expansion in C{ssh_config}. 156 157 @param hostname: the hostname to lookup 158 @type hostname: str 159 """ 160 161 matches = [config for config in self._config if 162 self._allowed(hostname, config['host'])] 163 164 ret = {} 165 for match in matches: 166 for key, value in match['config'].iteritems(): 167 if key not in ret: 168 # Create a copy of the original value, 169 # else it will reference the original list 170 # in self._config and update that value too 171 # when the extend() is being called. 172 ret[key] = value[:] 173 elif key == 'identityfile': 174 ret[key].extend(value) 175 ret = self._expand_variables(ret, hostname) 176 return ret
177
178 - def _allowed(self, hostname, hosts):
179 match = False 180 for host in hosts: 181 if host.startswith('!') and fnmatch.fnmatch(hostname, host[1:]): 182 return False 183 elif fnmatch.fnmatch(hostname, host): 184 match = True 185 return match
186
187 - def _expand_variables(self, config, hostname):
188 """ 189 Return a dict of config options with expanded substitutions 190 for a given hostname. 191 192 Please refer to man C{ssh_config} for the parameters that 193 are replaced. 194 195 @param config: the config for the hostname 196 @type hostname: dict 197 @param hostname: the hostname that the config belongs to 198 @type hostname: str 199 """ 200 201 if 'hostname' in config: 202 config['hostname'] = config['hostname'].replace('%h', hostname) 203 else: 204 config['hostname'] = hostname 205 206 if 'port' in config: 207 port = config['port'] 208 else: 209 port = SSH_PORT 210 211 user = os.getenv('USER') 212 if 'user' in config: 213 remoteuser = config['user'] 214 else: 215 remoteuser = user 216 217 host = socket.gethostname().split('.')[0] 218 fqdn = LazyFqdn(config) 219 homedir = os.path.expanduser('~') 220 replacements = {'controlpath': 221 [ 222 ('%h', config['hostname']), 223 ('%l', fqdn), 224 ('%L', host), 225 ('%n', hostname), 226 ('%p', port), 227 ('%r', remoteuser), 228 ('%u', user) 229 ], 230 'identityfile': 231 [ 232 ('~', homedir), 233 ('%d', homedir), 234 ('%h', config['hostname']), 235 ('%l', fqdn), 236 ('%u', user), 237 ('%r', remoteuser) 238 ], 239 'proxycommand': 240 [ 241 ('%h', config['hostname']), 242 ('%p', port), 243 ('%r', remoteuser) 244 ] 245 } 246 247 for k in config: 248 if k in replacements: 249 for find, replace in replacements[k]: 250 if isinstance(config[k], list): 251 for item in range(len(config[k])): 252 config[k][item] = config[k][item].\ 253 replace(find, str(replace)) 254 else: 255 config[k] = config[k].replace(find, str(replace)) 256 return config
257