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 _star = matches.pop(0)
109 matches.append(_star)
110 ret = {}
111 for m in matches:
112 for k,v in m.iteritems():
113 if not k in ret:
114 ret[k] = v
115 ret = self._expand_variables(ret, hostname)
116 del ret['host']
117 return ret
118
120 """
121 Return a dict of config options with expanded substitutions
122 for a given hostname.
123
124 Please refer to man ssh_config(5) for the parameters that
125 are replaced.
126
127 @param config: the config for the hostname
128 @type hostname: dict
129 @param hostname: the hostname that the config belongs to
130 @type hostname: str
131 """
132
133 if 'hostname' in config:
134 config['hostname'] = config['hostname'].replace('%h',hostname)
135 else:
136 config['hostname'] = hostname
137
138 if 'port' in config:
139 port = config['port']
140 else:
141 port = SSH_PORT
142
143 user = os.getenv('USER')
144 if 'user' in config:
145 remoteuser = config['user']
146 else:
147 remoteuser = user
148
149 host = socket.gethostname().split('.')[0]
150 fqdn = socket.getfqdn()
151 homedir = os.path.expanduser('~')
152 replacements = {'controlpath' :
153 [
154 ('%h', config['hostname']),
155 ('%l', fqdn),
156 ('%L', host),
157 ('%n', hostname),
158 ('%p', port),
159 ('%r', remoteuser),
160 ('%u', user)
161 ],
162 'identityfile' :
163 [
164 ('~', homedir),
165 ('%d', homedir),
166 ('%h', config['hostname']),
167 ('%l', fqdn),
168 ('%u', user),
169 ('%r', remoteuser)
170 ]
171 }
172 for k in config:
173 if k in replacements:
174 for find, replace in replacements[k]:
175 config[k] = config[k].replace(find, str(replace))
176 return config
177