1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 """
20 L{SSHConfig}.
21 """
22
23 import fnmatch
24 import os
25 import socket
26
27 SSH_PORT=22
28
30 """
31 Representation of config information as stored in the format used by
32 OpenSSH. Queries can be made via L{lookup}. The format is described in
33 OpenSSH's C{ssh_config} man page. This class is provided primarily as a
34 convenience to posix users (since the OpenSSH format is a de-facto
35 standard on posix) but should work fine on Windows too.
36
37 @since: 1.6
38 """
39
41 """
42 Create a new OpenSSH config object.
43 """
44 self._config = [ { 'host': '*' } ]
45
46 - def parse(self, file_obj):
47 """
48 Read an OpenSSH config from the given file object.
49
50 @param file_obj: a file-like object to read the config file from
51 @type file_obj: file
52 """
53 configs = [self._config[0]]
54 for line in file_obj:
55 line = line.rstrip('\n').lstrip()
56 if (line == '') or (line[0] == '#'):
57 continue
58 if '=' in line:
59 key, value = line.split('=', 1)
60 key = key.strip().lower()
61 else:
62
63 i = 0
64 while (i < len(line)) and not line[i].isspace():
65 i += 1
66 if i == len(line):
67 raise Exception('Unparsable line: %r' % line)
68 key = line[:i].lower()
69 value = line[i:].lstrip()
70
71 if key == 'host':
72 del configs[:]
73
74 for host in value.split():
75
76 matches = [c for c in self._config if c['host'] == host]
77 if len(matches) > 0:
78 configs.append(matches[0])
79 else:
80 config = { 'host': host }
81 self._config.append(config)
82 configs.append(config)
83 else:
84 for config in configs:
85 config[key] = value
86
88 """
89 Return a dict of config options for a given hostname.
90
91 The host-matching rules of OpenSSH's C{ssh_config} man page are used,
92 which means that all configuration options from matching host
93 specifications are merged, with more specific hostmasks taking
94 precedence. In other words, if C{"Port"} is set under C{"Host *"}
95 and also C{"Host *.example.com"}, and the lookup is for
96 C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"}
97 will win out.
98
99 The keys in the returned dict are all normalized to lowercase (look for
100 C{"port"}, not C{"Port"}. No other processing is done to the keys or
101 values.
102
103 @param hostname: the hostname to lookup
104 @type hostname: str
105 """
106 matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
107
108 matches.sort(lambda x,y: cmp(len(x['host']), len(y['host'])))
109 ret = {}
110 for m in matches:
111 ret.update(m)
112 ret = self._expand_variables(ret, hostname)
113 del ret['host']
114 return ret
115
117 """
118 Return a dict of config options with expanded substitutions
119 for a given hostname.
120
121 Please refer to man ssh_config(5) for the parameters that
122 are replaced.
123
124 @param config: the config for the hostname
125 @type hostname: dict
126 @param hostname: the hostname that the config belongs to
127 @type hostname: str
128 """
129
130 if 'hostname' in config:
131 config['hostname'] = config['hostname'].replace('%h',hostname)
132 else:
133 config['hostname'] = hostname
134
135 if 'port' in config:
136 port = config['port']
137 else:
138 port = SSH_PORT
139
140 user = os.getenv('USER')
141 if 'user' in config:
142 remoteuser = config['user']
143 else:
144 remoteuser = user
145
146 host = socket.gethostname().split('.')[0]
147 fqdn = socket.getfqdn()
148 homedir = os.path.expanduser('~')
149 replacements = {'controlpath' :
150 [
151 ('%h', config['hostname']),
152 ('%l', fqdn),
153 ('%L', host),
154 ('%n', hostname),
155 ('%p', port),
156 ('%r', remoteuser),
157 ('%u', user)
158 ],
159 'identityfile' :
160 [
161 ('~', homedir),
162 ('%d', homedir),
163 ('%h', config['hostname']),
164 ('%l', fqdn),
165 ('%u', user),
166 ('%r', remoteuser)
167 ]
168 }
169 for k in config:
170 if k in replacements:
171 for find, replace in replacements[k]:
172 config[k] = config[k].replace(find, str(replace))
173 return config
174