1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
34 """
35 Returns the host's fqdn on request as string.
36 """
37
41
43 if self.fqdn is None:
44
45
46
47
48
49
50
51
52
53
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
71 if fqdn is None:
72 fqdn = socket.getfqdn()
73
74 self.fqdn = fqdn
75 return self.fqdn
76
77
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
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
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
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
130
131
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
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
169
170
171
172 ret[key] = value[:]
173 elif key == 'identityfile':
174 ret[key].extend(value)
175 ret = self._expand_variables(ret, hostname)
176 return ret
177
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
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