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
39 self.fqdn = None
40 self.config = config
41 self.host = host
42
44 if self.fqdn is None:
45
46
47
48
49
50
51
52
53
54
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
75
76
77 except socket.gaierror:
78 pass
79
80 if fqdn is None:
81 fqdn = socket.getfqdn()
82
83 self.fqdn = fqdn
84 return self.fqdn
85
86
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
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
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
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
139
140
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
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
179
180
181
182 ret[key] = value[:]
183 elif key == 'identityfile':
184 ret[key].extend(value)
185 ret = self._expand_variables(ret, hostname)
186 return ret
187
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
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