1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.transport;
45
46 import java.io.BufferedReader;
47 import java.io.File;
48 import java.io.FileInputStream;
49 import java.io.FileNotFoundException;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.InputStreamReader;
53 import java.security.AccessController;
54 import java.security.PrivilegedAction;
55 import java.util.ArrayList;
56 import java.util.Collections;
57 import java.util.LinkedHashMap;
58 import java.util.List;
59 import java.util.Map;
60
61 import org.eclipse.jgit.errors.InvalidPatternException;
62 import org.eclipse.jgit.fnmatch.FileNameMatcher;
63 import org.eclipse.jgit.lib.Constants;
64 import org.eclipse.jgit.util.FS;
65 import org.eclipse.jgit.util.StringUtils;
66
67
68
69
70
71
72
73
74 public class OpenSshConfig {
75
76 static final int SSH_PORT = 22;
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 public static OpenSshConfig get(FS fs) {
92 File home = fs.userHome();
93 if (home == null)
94 home = new File(".").getAbsoluteFile();
95
96 final File config = new File(new File(home, ".ssh"), Constants.CONFIG);
97 final OpenSshConfig osc = new OpenSshConfig(home, config);
98 osc.refresh();
99 return osc;
100 }
101
102
103 private final File home;
104
105
106 private final File configFile;
107
108
109 private long lastModified;
110
111
112 private Map<String, Host> hosts;
113
114 OpenSshConfig(final File h, final File cfg) {
115 home = h;
116 configFile = cfg;
117 hosts = Collections.emptyMap();
118 }
119
120
121
122
123
124
125
126
127
128
129 public Host lookup(final String hostName) {
130 final Map<String, Host> cache = refresh();
131 Host h = cache.get(hostName);
132 if (h == null)
133 h = new Host();
134 if (h.patternsApplied)
135 return h;
136
137 for (final Map.Entry<String, Host> e : cache.entrySet()) {
138 if (!isHostPattern(e.getKey()))
139 continue;
140 if (!isHostMatch(e.getKey(), hostName))
141 continue;
142 h.copyFrom(e.getValue());
143 }
144
145 if (h.hostName == null)
146 h.hostName = hostName;
147 if (h.user == null)
148 h.user = OpenSshConfig.userName();
149 if (h.port == 0)
150 h.port = OpenSshConfig.SSH_PORT;
151 if (h.connectionAttempts == 0)
152 h.connectionAttempts = 1;
153 h.patternsApplied = true;
154 return h;
155 }
156
157 private synchronized Map<String, Host> refresh() {
158 final long mtime = configFile.lastModified();
159 if (mtime != lastModified) {
160 try {
161 final FileInputStream in = new FileInputStream(configFile);
162 try {
163 hosts = parse(in);
164 } finally {
165 in.close();
166 }
167 } catch (FileNotFoundException none) {
168 hosts = Collections.emptyMap();
169 } catch (IOException err) {
170 hosts = Collections.emptyMap();
171 }
172 lastModified = mtime;
173 }
174 return hosts;
175 }
176
177 private Map<String, Host> parse(final InputStream in) throws IOException {
178 final Map<String, Host> m = new LinkedHashMap<String, Host>();
179 final BufferedReader br = new BufferedReader(new InputStreamReader(in));
180 final List<Host> current = new ArrayList<Host>(4);
181 String line;
182
183 while ((line = br.readLine()) != null) {
184 line = line.trim();
185 if (line.length() == 0 || line.startsWith("#"))
186 continue;
187
188 final String[] parts = line.split("[ \t]*[= \t]", 2);
189 final String keyword = parts[0].trim();
190 final String argValue = parts[1].trim();
191
192 if (StringUtils.equalsIgnoreCase("Host", keyword)) {
193 current.clear();
194 for (final String pattern : argValue.split("[ \t]")) {
195 final String name = dequote(pattern);
196 Host c = m.get(name);
197 if (c == null) {
198 c = new Host();
199 m.put(name, c);
200 }
201 current.add(c);
202 }
203 continue;
204 }
205
206 if (current.isEmpty()) {
207
208
209
210 continue;
211 }
212
213 if (StringUtils.equalsIgnoreCase("HostName", keyword)) {
214 for (final Host c : current)
215 if (c.hostName == null)
216 c.hostName = dequote(argValue);
217 } else if (StringUtils.equalsIgnoreCase("User", keyword)) {
218 for (final Host c : current)
219 if (c.user == null)
220 c.user = dequote(argValue);
221 } else if (StringUtils.equalsIgnoreCase("Port", keyword)) {
222 try {
223 final int port = Integer.parseInt(dequote(argValue));
224 for (final Host c : current)
225 if (c.port == 0)
226 c.port = port;
227 } catch (NumberFormatException nfe) {
228
229 }
230 } else if (StringUtils.equalsIgnoreCase("IdentityFile", keyword)) {
231 for (final Host c : current)
232 if (c.identityFile == null)
233 c.identityFile = toFile(dequote(argValue));
234 } else if (StringUtils.equalsIgnoreCase(
235 "PreferredAuthentications", keyword)) {
236 for (final Host c : current)
237 if (c.preferredAuthentications == null)
238 c.preferredAuthentications = nows(dequote(argValue));
239 } else if (StringUtils.equalsIgnoreCase("BatchMode", keyword)) {
240 for (final Host c : current)
241 if (c.batchMode == null)
242 c.batchMode = yesno(dequote(argValue));
243 } else if (StringUtils.equalsIgnoreCase(
244 "StrictHostKeyChecking", keyword)) {
245 String value = dequote(argValue);
246 for (final Host c : current)
247 if (c.strictHostKeyChecking == null)
248 c.strictHostKeyChecking = value;
249 } else if (StringUtils.equalsIgnoreCase(
250 "ConnectionAttempts", keyword)) {
251 try {
252 final int connectionAttempts = Integer.parseInt(dequote(argValue));
253 if (connectionAttempts > 0) {
254 for (final Host c : current)
255 if (c.connectionAttempts == 0)
256 c.connectionAttempts = connectionAttempts;
257 }
258 } catch (NumberFormatException nfe) {
259
260 }
261 }
262 }
263
264 return m;
265 }
266
267 private static boolean isHostPattern(final String s) {
268 return s.indexOf('*') >= 0 || s.indexOf('?') >= 0;
269 }
270
271 private static boolean isHostMatch(final String pattern, final String name) {
272 final FileNameMatcher fn;
273 try {
274 fn = new FileNameMatcher(pattern, null);
275 } catch (InvalidPatternException e) {
276 return false;
277 }
278 fn.append(name);
279 return fn.isMatch();
280 }
281
282 private static String dequote(final String value) {
283 if (value.startsWith("\"") && value.endsWith("\""))
284 return value.substring(1, value.length() - 1);
285 return value;
286 }
287
288 private static String nows(final String value) {
289 final StringBuilder b = new StringBuilder();
290 for (int i = 0; i < value.length(); i++) {
291 if (!Character.isSpaceChar(value.charAt(i)))
292 b.append(value.charAt(i));
293 }
294 return b.toString();
295 }
296
297 private static Boolean yesno(final String value) {
298 if (StringUtils.equalsIgnoreCase("yes", value))
299 return Boolean.TRUE;
300 return Boolean.FALSE;
301 }
302
303 private File toFile(final String path) {
304 if (path.startsWith("~/"))
305 return new File(home, path.substring(2));
306 File ret = new File(path);
307 if (ret.isAbsolute())
308 return ret;
309 return new File(home, path);
310 }
311
312 static String userName() {
313 return AccessController.doPrivileged(new PrivilegedAction<String>() {
314 public String run() {
315 return System.getProperty("user.name");
316 }
317 });
318 }
319
320
321
322
323
324
325
326
327
328
329
330
331 public static class Host {
332 boolean patternsApplied;
333
334 String hostName;
335
336 int port;
337
338 File identityFile;
339
340 String user;
341
342 String preferredAuthentications;
343
344 Boolean batchMode;
345
346 String strictHostKeyChecking;
347
348 int connectionAttempts;
349
350 void copyFrom(final Host src) {
351 if (hostName == null)
352 hostName = src.hostName;
353 if (port == 0)
354 port = src.port;
355 if (identityFile == null)
356 identityFile = src.identityFile;
357 if (user == null)
358 user = src.user;
359 if (preferredAuthentications == null)
360 preferredAuthentications = src.preferredAuthentications;
361 if (batchMode == null)
362 batchMode = src.batchMode;
363 if (strictHostKeyChecking == null)
364 strictHostKeyChecking = src.strictHostKeyChecking;
365 if (connectionAttempts == 0)
366 connectionAttempts = src.connectionAttempts;
367 }
368
369
370
371
372
373
374
375 public String getStrictHostKeyChecking() {
376 return strictHostKeyChecking;
377 }
378
379
380
381
382 public String getHostName() {
383 return hostName;
384 }
385
386
387
388
389 public int getPort() {
390 return port;
391 }
392
393
394
395
396
397 public File getIdentityFile() {
398 return identityFile;
399 }
400
401
402
403
404 public String getUser() {
405 return user;
406 }
407
408
409
410
411
412 public String getPreferredAuthentications() {
413 return preferredAuthentications;
414 }
415
416
417
418
419
420 public boolean isBatchMode() {
421 return batchMode != null && batchMode.booleanValue();
422 }
423
424
425
426
427
428
429
430
431 public int getConnectionAttempts() {
432 return connectionAttempts;
433 }
434 }
435 }