Add host negation support to paramiko config.

This is a rewrite of the SSHConfig class to
conform with the rules specified by the
manpage for ssh_config.
This change also adds support for negation
according to the rules introduced by
OpenSSH 5.9. Reference:
http://www.openssh.com/txt/release-5.9
This commit is contained in:
Olle Lundberg 2012-10-16 13:54:23 +02:00
parent 21689d9647
commit 57d776b318
1 changed files with 50 additions and 31 deletions

View File

@ -1,4 +1,5 @@
# Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com> # Copyright (C) 2006-2007 Robey Pointer <robeypointer@gmail.com>
# Copyright (C) 2012 Olle Lundberg <geek@nerd.sh>
# #
# This file is part of paramiko. # This file is part of paramiko.
# #
@ -82,7 +83,8 @@ class SSHConfig (object):
""" """
Create a new OpenSSH config object. Create a new OpenSSH config object.
""" """
self._config = [ { 'host': '*' } ] self._config = []
def parse(self, file_obj): def parse(self, file_obj):
""" """
@ -91,7 +93,7 @@ class SSHConfig (object):
@param file_obj: a file-like object to read the config file from @param file_obj: a file-like object to read the config file from
@type file_obj: file @type file_obj: file
""" """
configs = [self._config[0]] host = {"host":['*'],"config":{}}
for line in file_obj: for line in file_obj:
line = line.rstrip('\n').lstrip() line = line.rstrip('\n').lstrip()
if (line == '') or (line[0] == '#'): if (line == '') or (line[0] == '#'):
@ -115,20 +117,20 @@ class SSHConfig (object):
value = line[i:].lstrip() value = line[i:].lstrip()
if key == 'host': if key == 'host':
del configs[:] self._config.append(host)
# the value may be multiple hosts, space-delimited value = value.split()
for host in value.split(): host = {key:value,'config':{}}
# do we have a pre-existing host config to append to? #identitifile is a special case, since it is allowed to be
matches = [c for c in self._config if c['host'] == host] # specified multiple times and they should be tried in order
if len(matches) > 0: # of specification.
configs.append(matches[0]) elif key == 'identityfile':
else: if key in host['config']:
config = { 'host': host } host['config']['identityfile'].append(value)
self._config.append(config) else:
configs.append(config) host['config']['identityfile'] = [value]
else: elif key not in host['config']:
for config in configs: host['config'].update({key:value})
config[key] = value self._config.append(host)
def lookup(self, hostname): def lookup(self, hostname):
""" """
@ -143,31 +145,45 @@ class SSHConfig (object):
will win out. will win out.
The keys in the returned dict are all normalized to lowercase (look for The keys in the returned dict are all normalized to lowercase (look for
C{"port"}, not C{"Port"}. No other processing is done to the keys or C{"port"}, not C{"Port"}. The values are processed according to the
values. rules for substitution variable expansion in C{ssh_config}.
@param hostname: the hostname to lookup @param hostname: the hostname to lookup
@type hostname: str @type hostname: str
""" """
matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
# Move * to the end matches = [ config for config in self._config if
_star = matches.pop(0) self._allowed(hostname,config['host']) ]
matches.append(_star)
ret = {} ret = {}
for m in matches: for match in matches:
for k,v in m.iteritems(): for key in match['config']:
if not k in ret: value = match['config'][key]
ret[k] = v if key == 'identityfile':
if key in ret:
ret['identityfile'].extend(value)
else:
ret['identityfile'] = value
elif key not in ret:
ret[key] = value
ret = self._expand_variables(ret, hostname) ret = self._expand_variables(ret, hostname)
del ret['host']
return ret return ret
def _expand_variables(self, config, hostname ): def _allowed(self, hostname, hosts):
match = False
for host in hosts:
if host.startswith('!') and fnmatch.fnmatch(hostname, host[1:]):
return False
elif fnmatch.fnmatch(hostname, host):
match = True
return match
def _expand_variables(self, config, hostname):
""" """
Return a dict of config options with expanded substitutions Return a dict of config options with expanded substitutions
for a given hostname. for a given hostname.
Please refer to man ssh_config(5) for the parameters that Please refer to man C{ssh_config} for the parameters that
are replaced. are replaced.
@param config: the config for the hostname @param config: the config for the hostname
@ -222,6 +238,9 @@ class SSHConfig (object):
for k in config: for k in config:
if k in replacements: if k in replacements:
for find, replace in replacements[k]: for find, replace in replacements[k]:
if find in config[k]: if isinstance(config[k],list):
config[k] = config[k].replace(find, str(replace)) for item in range(len(config[k])):
config[k][item] = config[k][item].replace(find, str(replace))
else:
config[k] = config[k].replace(find, str(replace))
return config return config