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 static java.nio.charset.StandardCharsets.UTF_8;
47 import static org.junit.Assert.assertArrayEquals;
48 import static org.junit.Assert.assertEquals;
49 import static org.junit.Assert.assertFalse;
50 import static org.junit.Assert.assertNotNull;
51 import static org.junit.Assert.assertNotSame;
52 import static org.junit.Assert.assertNull;
53 import static org.junit.Assert.assertSame;
54 import static org.junit.Assert.assertTrue;
55
56 import java.io.File;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.OutputStreamWriter;
60 import java.time.Instant;
61 import java.util.concurrent.TimeUnit;
62
63 import org.eclipse.jgit.junit.RepositoryTestCase;
64 import org.eclipse.jgit.lib.Constants;
65 import org.eclipse.jgit.transport.OpenSshConfig.Host;
66 import org.eclipse.jgit.util.FS;
67 import org.eclipse.jgit.util.FileUtils;
68 import org.eclipse.jgit.util.SystemReader;
69 import org.junit.Before;
70 import org.junit.Test;
71
72 import com.jcraft.jsch.ConfigRepository;
73
74 public class OpenSshConfigTest extends RepositoryTestCase {
75 private File home;
76
77 private File configFile;
78
79 private OpenSshConfig osc;
80
81 @Override
82 @Before
83 public void setUp() throws Exception {
84 super.setUp();
85
86 home = new File(trash, "home");
87 FileUtils.mkdir(home);
88
89 configFile = new File(new File(home, ".ssh"), Constants.CONFIG);
90 FileUtils.mkdir(configFile.getParentFile());
91
92 mockSystemReader.setProperty(Constants.OS_USER_NAME_KEY, "jex_junit");
93 osc = new OpenSshConfig(home, configFile);
94 }
95
96 private void config(String data) throws IOException {
97 FS fs = FS.DETECTED;
98 long resolution = FS.getFileStoreAttributes(configFile.toPath())
99 .getFsTimestampResolution().toNanos();
100 Instant lastMtime = fs.lastModifiedInstant(configFile);
101 do {
102 try (final OutputStreamWriter fw = new OutputStreamWriter(
103 new FileOutputStream(configFile), UTF_8)) {
104 fw.write(data);
105 TimeUnit.NANOSECONDS.sleep(resolution);
106 } catch (InterruptedException e) {
107 Thread.interrupted();
108 }
109 } while (lastMtime.equals(fs.lastModifiedInstant(configFile)));
110 }
111
112 @Test
113 public void testNoConfig() {
114 final Host h = osc.lookup("repo.or.cz");
115 assertNotNull(h);
116 assertEquals("repo.or.cz", h.getHostName());
117 assertEquals("jex_junit", h.getUser());
118 assertEquals(22, h.getPort());
119 assertEquals(1, h.getConnectionAttempts());
120 assertNull(h.getIdentityFile());
121 }
122
123 @Test
124 public void testSeparatorParsing() throws Exception {
125 config("Host\tfirst\n" +
126 "\tHostName\tfirst.tld\n" +
127 "\n" +
128 "Host second\n" +
129 " HostName\tsecond.tld\n" +
130 "Host=third\n" +
131 "HostName=third.tld\n\n\n" +
132 "\t Host = fourth\n\n\n" +
133 " \t HostName\t=fourth.tld\n" +
134 "Host\t = last\n" +
135 "HostName \t last.tld");
136 assertNotNull(osc.lookup("first"));
137 assertEquals("first.tld", osc.lookup("first").getHostName());
138 assertNotNull(osc.lookup("second"));
139 assertEquals("second.tld", osc.lookup("second").getHostName());
140 assertNotNull(osc.lookup("third"));
141 assertEquals("third.tld", osc.lookup("third").getHostName());
142 assertNotNull(osc.lookup("fourth"));
143 assertEquals("fourth.tld", osc.lookup("fourth").getHostName());
144 assertNotNull(osc.lookup("last"));
145 assertEquals("last.tld", osc.lookup("last").getHostName());
146 }
147
148 @Test
149 public void testQuoteParsing() throws Exception {
150 config("Host \"good\"\n" +
151 " HostName=\"good.tld\"\n" +
152 " Port=\"6007\"\n" +
153 " User=\"gooduser\"\n" +
154 "Host multiple unquoted and \"quoted\" \"hosts\"\n" +
155 " Port=\"2222\"\n" +
156 "Host \"spaced\"\n" +
157 "# Bad host name, but testing preservation of spaces\n" +
158 " HostName=\" spaced\ttld \"\n" +
159 "# Misbalanced quotes\n" +
160 "Host \"bad\"\n" +
161 "# OpenSSH doesn't allow this but ...\n" +
162 " HostName=bad.tld\"\n");
163 assertEquals("good.tld", osc.lookup("good").getHostName());
164 assertEquals("gooduser", osc.lookup("good").getUser());
165 assertEquals(6007, osc.lookup("good").getPort());
166 assertEquals(2222, osc.lookup("multiple").getPort());
167 assertEquals(2222, osc.lookup("quoted").getPort());
168 assertEquals(2222, osc.lookup("and").getPort());
169 assertEquals(2222, osc.lookup("unquoted").getPort());
170 assertEquals(2222, osc.lookup("hosts").getPort());
171 assertEquals(" spaced\ttld ", osc.lookup("spaced").getHostName());
172 assertEquals("bad.tld\"", osc.lookup("bad").getHostName());
173 }
174
175 @Test
176 public void testAlias_DoesNotMatch() throws Exception {
177 config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n");
178 final Host h = osc.lookup("repo.or.cz");
179 assertNotNull(h);
180 assertEquals("repo.or.cz", h.getHostName());
181 assertEquals("jex_junit", h.getUser());
182 assertEquals(22, h.getPort());
183 assertNull(h.getIdentityFile());
184 final Host h2 = osc.lookup("orcz");
185 assertEquals("repo.or.cz", h.getHostName());
186 assertEquals("jex_junit", h.getUser());
187 assertEquals(29418, h2.getPort());
188 assertNull(h.getIdentityFile());
189 }
190
191 @Test
192 public void testAlias_OptionsSet() throws Exception {
193 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\tPort 2222\n"
194 + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n"
195 + "\tForwardX11 no\n");
196 final Host h = osc.lookup("orcz");
197 assertNotNull(h);
198 assertEquals("repo.or.cz", h.getHostName());
199 assertEquals("jex", h.getUser());
200 assertEquals(2222, h.getPort());
201 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
202 }
203
204 @Test
205 public void testAlias_OptionsKeywordCaseInsensitive() throws Exception {
206 config("hOsT orcz\n" + "\thOsTnAmE repo.or.cz\n" + "\tPORT 2222\n"
207 + "\tuser jex\n" + "\tidentityfile .ssh/id_jex\n"
208 + "\tForwardX11 no\n");
209 final Host h = osc.lookup("orcz");
210 assertNotNull(h);
211 assertEquals("repo.or.cz", h.getHostName());
212 assertEquals("jex", h.getUser());
213 assertEquals(2222, h.getPort());
214 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
215 }
216
217 @Test
218 public void testAlias_OptionsInherit() throws Exception {
219 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
220 + "\tHostName not.a.host.example.com\n" + "\tPort 2222\n"
221 + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n"
222 + "\tForwardX11 no\n");
223 final Host h = osc.lookup("orcz");
224 assertNotNull(h);
225 assertEquals("repo.or.cz", h.getHostName());
226 assertEquals("jex", h.getUser());
227 assertEquals(2222, h.getPort());
228 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
229 }
230
231 @Test
232 public void testAlias_PreferredAuthenticationsDefault() throws Exception {
233 final Host h = osc.lookup("orcz");
234 assertNotNull(h);
235 assertNull(h.getPreferredAuthentications());
236 }
237
238 @Test
239 public void testAlias_PreferredAuthentications() throws Exception {
240 config("Host orcz\n" + "\tPreferredAuthentications publickey\n");
241 final Host h = osc.lookup("orcz");
242 assertNotNull(h);
243 assertEquals("publickey", h.getPreferredAuthentications());
244 }
245
246 @Test
247 public void testAlias_InheritPreferredAuthentications() throws Exception {
248 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
249 + "\tPreferredAuthentications publickey, hostbased\n");
250 final Host h = osc.lookup("orcz");
251 assertNotNull(h);
252 assertEquals("publickey,hostbased", h.getPreferredAuthentications());
253 }
254
255 @Test
256 public void testAlias_BatchModeDefault() throws Exception {
257 final Host h = osc.lookup("orcz");
258 assertNotNull(h);
259 assertFalse(h.isBatchMode());
260 }
261
262 @Test
263 public void testAlias_BatchModeYes() throws Exception {
264 config("Host orcz\n" + "\tBatchMode yes\n");
265 final Host h = osc.lookup("orcz");
266 assertNotNull(h);
267 assertTrue(h.isBatchMode());
268 }
269
270 @Test
271 public void testAlias_InheritBatchMode() throws Exception {
272 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
273 + "\tBatchMode yes\n");
274 final Host h = osc.lookup("orcz");
275 assertNotNull(h);
276 assertTrue(h.isBatchMode());
277 }
278
279 @Test
280 public void testAlias_ConnectionAttemptsDefault() throws Exception {
281 final Host h = osc.lookup("orcz");
282 assertNotNull(h);
283 assertEquals(1, h.getConnectionAttempts());
284 }
285
286 @Test
287 public void testAlias_ConnectionAttempts() throws Exception {
288 config("Host orcz\n" + "\tConnectionAttempts 5\n");
289 final Host h = osc.lookup("orcz");
290 assertNotNull(h);
291 assertEquals(5, h.getConnectionAttempts());
292 }
293
294 @Test
295 public void testAlias_invalidConnectionAttempts() throws Exception {
296 config("Host orcz\n" + "\tConnectionAttempts -1\n");
297 final Host h = osc.lookup("orcz");
298 assertNotNull(h);
299 assertEquals(1, h.getConnectionAttempts());
300 }
301
302 @Test
303 public void testAlias_badConnectionAttempts() throws Exception {
304 config("Host orcz\n" + "\tConnectionAttempts xxx\n");
305 final Host h = osc.lookup("orcz");
306 assertNotNull(h);
307 assertEquals(1, h.getConnectionAttempts());
308 }
309
310 @Test
311 public void testDefaultBlock() throws Exception {
312 config("ConnectionAttempts 5\n\nHost orcz\nConnectionAttempts 3\n");
313 final Host h = osc.lookup("orcz");
314 assertNotNull(h);
315 assertEquals(5, h.getConnectionAttempts());
316 }
317
318 @Test
319 public void testHostCaseInsensitive() throws Exception {
320 config("hOsT orcz\nConnectionAttempts 3\n");
321 final Host h = osc.lookup("orcz");
322 assertNotNull(h);
323 assertEquals(3, h.getConnectionAttempts());
324 }
325
326 @Test
327 public void testListValueSingle() throws Exception {
328 config("Host orcz\nUserKnownHostsFile /foo/bar\n");
329 final ConfigRepository.Config c = osc.getConfig("orcz");
330 assertNotNull(c);
331 assertEquals("/foo/bar", c.getValue("UserKnownHostsFile"));
332 }
333
334 @Test
335 public void testListValueMultiple() throws Exception {
336
337 config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n");
338 final ConfigRepository.Config c = osc.getConfig("orcz");
339 assertNotNull(c);
340 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
341 "/foo/bar" },
342 c.getValues("UserKnownHostsFile"));
343 }
344
345 @Test
346 public void testRepeatedLookups() throws Exception {
347 config("Host orcz\n" + "\tConnectionAttempts 5\n");
348 final Host h1 = osc.lookup("orcz");
349 final Host h2 = osc.lookup("orcz");
350 assertNotNull(h1);
351 assertSame(h1, h2);
352 assertEquals(5, h1.getConnectionAttempts());
353 assertEquals(h1.getConnectionAttempts(), h2.getConnectionAttempts());
354 final ConfigRepository.Config c = osc.getConfig("orcz");
355 assertNotNull(c);
356 assertSame(c, h1.getConfig());
357 assertSame(c, h2.getConfig());
358 }
359
360 @Test
361 public void testRepeatedLookupsWithModification() throws Exception {
362 config("Host orcz\n" + "\tConnectionAttempts -1\n");
363 final Host h1 = osc.lookup("orcz");
364 assertNotNull(h1);
365 assertEquals(1, h1.getConnectionAttempts());
366 config("Host orcz\n" + "\tConnectionAttempts 5\n");
367 final Host h2 = osc.lookup("orcz");
368 assertNotNull(h2);
369 assertNotSame(h1, h2);
370 assertEquals(5, h2.getConnectionAttempts());
371 assertEquals(1, h1.getConnectionAttempts());
372 assertNotSame(h1.getConfig(), h2.getConfig());
373 }
374
375 @Test
376 public void testIdentityFile() throws Exception {
377 config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar");
378 final Host h = osc.lookup("orcz");
379 assertNotNull(h);
380 File f = h.getIdentityFile();
381 assertNotNull(f);
382
383 assertEquals(new File(home, "foo/ba z"), f);
384 final ConfigRepository.Config c = h.getConfig();
385
386 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
387 "/foo/bar" },
388 c.getValues("IdentityFile"));
389 }
390
391 @Test
392 public void testMultiIdentityFile() throws Exception {
393 config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz");
394 final Host h = osc.lookup("orcz");
395 assertNotNull(h);
396 File f = h.getIdentityFile();
397 assertNotNull(f);
398
399 assertEquals(new File(home, "foo/ba z"), f);
400 final ConfigRepository.Config c = h.getConfig();
401
402 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
403 "/foo/bar", "/foo/baz" },
404 c.getValues("IdentityFile"));
405 }
406
407 @Test
408 public void testNegatedPattern() throws Exception {
409 config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST !*.or.cz\nIdentityFile /foo/baz");
410 final Host h = osc.lookup("repo.or.cz");
411 assertNotNull(h);
412 assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
413 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
414 h.getConfig().getValues("IdentityFile"));
415 }
416
417 @Test
418 public void testPattern() throws Exception {
419 config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
420 final Host h = osc.lookup("repo.or.cz");
421 assertNotNull(h);
422 assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
423 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
424 "/foo/baz" },
425 h.getConfig().getValues("IdentityFile"));
426 }
427
428 @Test
429 public void testMultiHost() throws Exception {
430 config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
431 final Host h1 = osc.lookup("repo.or.cz");
432 assertNotNull(h1);
433 assertEquals(new File(home, "foo/bar"), h1.getIdentityFile());
434 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
435 "/foo/baz" },
436 h1.getConfig().getValues("IdentityFile"));
437 final Host h2 = osc.lookup("orcz");
438 assertNotNull(h2);
439 assertEquals(new File(home, "foo/bar"), h2.getIdentityFile());
440 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
441 h2.getConfig().getValues("IdentityFile"));
442 }
443
444 @Test
445 public void testEqualsSign() throws Exception {
446 config("Host=orcz\n\tConnectionAttempts = 5\n\tUser=\t foobar\t\n");
447 final Host h = osc.lookup("orcz");
448 assertNotNull(h);
449 assertEquals(5, h.getConnectionAttempts());
450 assertEquals("foobar", h.getUser());
451 }
452
453 @Test
454 public void testMissingArgument() throws Exception {
455 config("Host=orcz\n\tSendEnv\nIdentityFile\t\nForwardX11\n\tUser=\t foobar\t\n");
456 final Host h = osc.lookup("orcz");
457 assertNotNull(h);
458 assertEquals("foobar", h.getUser());
459 assertArrayEquals(new String[0], h.getConfig().getValues("SendEnv"));
460 assertNull(h.getIdentityFile());
461 assertNull(h.getConfig().getValue("ForwardX11"));
462 }
463
464 @Test
465 public void testHomeDirUserReplacement() throws Exception {
466 config("Host=orcz\n\tIdentityFile %d/.ssh/%u_id_dsa");
467 final Host h = osc.lookup("orcz");
468 assertNotNull(h);
469 assertEquals(new File(new File(home, ".ssh"), "jex_junit_id_dsa"),
470 h.getIdentityFile());
471 }
472
473 @Test
474 public void testHostnameReplacement() throws Exception {
475 config("Host=orcz\nHost *.*\n\tHostname %h\nHost *\n\tHostname %h.example.org");
476 final Host h = osc.lookup("orcz");
477 assertNotNull(h);
478 assertEquals("orcz.example.org", h.getHostName());
479 }
480
481 @Test
482 public void testRemoteUserReplacement() throws Exception {
483 config("Host=orcz\n\tUser foo\n" + "Host *.*\n\tHostname %h\n"
484 + "Host *\n\tHostname %h.ex%%20ample.org\n\tIdentityFile ~/.ssh/%h_%r_id_dsa");
485 final Host h = osc.lookup("orcz");
486 assertNotNull(h);
487 assertEquals(
488 new File(new File(home, ".ssh"),
489 "orcz.ex%20ample.org_foo_id_dsa"),
490 h.getIdentityFile());
491 }
492
493 @Test
494 public void testLocalhostFQDNReplacement() throws Exception {
495 String localhost = SystemReader.getInstance().getHostname();
496 config("Host=orcz\n\tIdentityFile ~/.ssh/%l_id_dsa");
497 final Host h = osc.lookup("orcz");
498 assertNotNull(h);
499 assertEquals(
500 new File(new File(home, ".ssh"), localhost + "_id_dsa"),
501 h.getIdentityFile());
502 }
503 }