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
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
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
170
171
172
173 ret[key] = value[:]
174 elif key == 'identityfile':
175 ret[key].extend(value)
176 ret = self._expand_variables(ret, hostname)
177 return ret
178
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
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