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.assertTrue;
54
55 import java.io.File;
56 import java.io.FileOutputStream;
57 import java.io.IOException;
58 import java.io.OutputStreamWriter;
59 import java.time.Instant;
60 import java.util.concurrent.TimeUnit;
61
62 import org.eclipse.jgit.junit.RepositoryTestCase;
63 import org.eclipse.jgit.lib.Constants;
64 import org.eclipse.jgit.transport.OpenSshConfig.Host;
65 import org.eclipse.jgit.util.FS;
66 import org.eclipse.jgit.util.FileUtils;
67 import org.eclipse.jgit.util.SystemReader;
68 import org.junit.Before;
69 import org.junit.Test;
70
71 import com.jcraft.jsch.ConfigRepository;
72 import com.jcraft.jsch.ConfigRepository.Config;
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 testCaseInsensitiveKeyLookup() throws Exception {
177 config("Host orcz\n" + "Port 29418\n"
178 + "\tHostName repo.or.cz\nStrictHostKeyChecking yes\n");
179 final Host h = osc.lookup("orcz");
180 Config c = h.getConfig();
181 String exactCase = c.getValue("StrictHostKeyChecking");
182 assertEquals("yes", exactCase);
183 assertEquals(exactCase, c.getValue("stricthostkeychecking"));
184 assertEquals(exactCase, c.getValue("STRICTHOSTKEYCHECKING"));
185 assertEquals(exactCase, c.getValue("sTrIcThostKEYcheckING"));
186 assertNull(c.getValue("sTrIcThostKEYcheckIN"));
187 }
188
189 @Test
190 public void testAlias_DoesNotMatch() throws Exception {
191 config("Host orcz\n" + "Port 29418\n" + "\tHostName repo.or.cz\n");
192 final Host h = osc.lookup("repo.or.cz");
193 assertNotNull(h);
194 assertEquals("repo.or.cz", h.getHostName());
195 assertEquals("jex_junit", h.getUser());
196 assertEquals(22, h.getPort());
197 assertNull(h.getIdentityFile());
198 final Host h2 = osc.lookup("orcz");
199 assertEquals("repo.or.cz", h.getHostName());
200 assertEquals("jex_junit", h.getUser());
201 assertEquals(29418, h2.getPort());
202 assertNull(h.getIdentityFile());
203 }
204
205 @Test
206 public void testAlias_OptionsSet() throws Exception {
207 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\tPort 2222\n"
208 + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n"
209 + "\tForwardX11 no\n");
210 final Host h = osc.lookup("orcz");
211 assertNotNull(h);
212 assertEquals("repo.or.cz", h.getHostName());
213 assertEquals("jex", h.getUser());
214 assertEquals(2222, h.getPort());
215 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
216 }
217
218 @Test
219 public void testAlias_OptionsKeywordCaseInsensitive() throws Exception {
220 config("hOsT orcz\n" + "\thOsTnAmE repo.or.cz\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_OptionsInherit() throws Exception {
233 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
234 + "\tHostName not.a.host.example.com\n" + "\tPort 2222\n"
235 + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n"
236 + "\tForwardX11 no\n");
237 final Host h = osc.lookup("orcz");
238 assertNotNull(h);
239 assertEquals("repo.or.cz", h.getHostName());
240 assertEquals("jex", h.getUser());
241 assertEquals(2222, h.getPort());
242 assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile());
243 }
244
245 @Test
246 public void testAlias_PreferredAuthenticationsDefault() throws Exception {
247 final Host h = osc.lookup("orcz");
248 assertNotNull(h);
249 assertNull(h.getPreferredAuthentications());
250 }
251
252 @Test
253 public void testAlias_PreferredAuthentications() throws Exception {
254 config("Host orcz\n" + "\tPreferredAuthentications publickey\n");
255 final Host h = osc.lookup("orcz");
256 assertNotNull(h);
257 assertEquals("publickey", h.getPreferredAuthentications());
258 }
259
260 @Test
261 public void testAlias_InheritPreferredAuthentications() throws Exception {
262 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
263 + "\tPreferredAuthentications publickey, hostbased\n");
264 final Host h = osc.lookup("orcz");
265 assertNotNull(h);
266 assertEquals("publickey,hostbased", h.getPreferredAuthentications());
267 }
268
269 @Test
270 public void testAlias_BatchModeDefault() throws Exception {
271 final Host h = osc.lookup("orcz");
272 assertNotNull(h);
273 assertFalse(h.isBatchMode());
274 }
275
276 @Test
277 public void testAlias_BatchModeYes() throws Exception {
278 config("Host orcz\n" + "\tBatchMode yes\n");
279 final Host h = osc.lookup("orcz");
280 assertNotNull(h);
281 assertTrue(h.isBatchMode());
282 }
283
284 @Test
285 public void testAlias_InheritBatchMode() throws Exception {
286 config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n"
287 + "\tBatchMode yes\n");
288 final Host h = osc.lookup("orcz");
289 assertNotNull(h);
290 assertTrue(h.isBatchMode());
291 }
292
293 @Test
294 public void testAlias_ConnectionAttemptsDefault() throws Exception {
295 final Host h = osc.lookup("orcz");
296 assertNotNull(h);
297 assertEquals(1, h.getConnectionAttempts());
298 }
299
300 @Test
301 public void testAlias_ConnectionAttempts() throws Exception {
302 config("Host orcz\n" + "\tConnectionAttempts 5\n");
303 final Host h = osc.lookup("orcz");
304 assertNotNull(h);
305 assertEquals(5, h.getConnectionAttempts());
306 }
307
308 @Test
309 public void testAlias_invalidConnectionAttempts() throws Exception {
310 config("Host orcz\n" + "\tConnectionAttempts -1\n");
311 final Host h = osc.lookup("orcz");
312 assertNotNull(h);
313 assertEquals(1, h.getConnectionAttempts());
314 }
315
316 @Test
317 public void testAlias_badConnectionAttempts() throws Exception {
318 config("Host orcz\n" + "\tConnectionAttempts xxx\n");
319 final Host h = osc.lookup("orcz");
320 assertNotNull(h);
321 assertEquals(1, h.getConnectionAttempts());
322 }
323
324 @Test
325 public void testDefaultBlock() throws Exception {
326 config("ConnectionAttempts 5\n\nHost orcz\nConnectionAttempts 3\n");
327 final Host h = osc.lookup("orcz");
328 assertNotNull(h);
329 assertEquals(5, h.getConnectionAttempts());
330 }
331
332 @Test
333 public void testHostCaseInsensitive() throws Exception {
334 config("hOsT orcz\nConnectionAttempts 3\n");
335 final Host h = osc.lookup("orcz");
336 assertNotNull(h);
337 assertEquals(3, h.getConnectionAttempts());
338 }
339
340 @Test
341 public void testListValueSingle() throws Exception {
342 config("Host orcz\nUserKnownHostsFile /foo/bar\n");
343 final ConfigRepository.Config c = osc.getConfig("orcz");
344 assertNotNull(c);
345 assertEquals("/foo/bar", c.getValue("UserKnownHostsFile"));
346 }
347
348 @Test
349 public void testListValueMultiple() throws Exception {
350
351 config("Host orcz\nUserKnownHostsFile \"~/foo/ba z\" /foo/bar \n");
352 final ConfigRepository.Config c = osc.getConfig("orcz");
353 assertNotNull(c);
354 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
355 "/foo/bar" },
356 c.getValues("UserKnownHostsFile"));
357 }
358
359 @Test
360 public void testRepeatedLookupsWithModification() throws Exception {
361 config("Host orcz\n" + "\tConnectionAttempts -1\n");
362 final Host h1 = osc.lookup("orcz");
363 assertNotNull(h1);
364 assertEquals(1, h1.getConnectionAttempts());
365 config("Host orcz\n" + "\tConnectionAttempts 5\n");
366 final Host h2 = osc.lookup("orcz");
367 assertNotNull(h2);
368 assertNotSame(h1, h2);
369 assertEquals(5, h2.getConnectionAttempts());
370 assertEquals(1, h1.getConnectionAttempts());
371 assertNotSame(h1.getConfig(), h2.getConfig());
372 }
373
374 @Test
375 public void testIdentityFile() throws Exception {
376 config("Host orcz\nIdentityFile \"~/foo/ba z\"\nIdentityFile /foo/bar");
377 final Host h = osc.lookup("orcz");
378 assertNotNull(h);
379 File f = h.getIdentityFile();
380 assertNotNull(f);
381
382 assertEquals(new File(home, "foo/ba z"), f);
383 final ConfigRepository.Config c = h.getConfig();
384
385 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
386 "/foo/bar" },
387 c.getValues("IdentityFile"));
388 }
389
390 @Test
391 public void testMultiIdentityFile() throws Exception {
392 config("IdentityFile \"~/foo/ba z\"\nHost orcz\nIdentityFile /foo/bar\nHOST *\nIdentityFile /foo/baz");
393 final Host h = osc.lookup("orcz");
394 assertNotNull(h);
395 File f = h.getIdentityFile();
396 assertNotNull(f);
397
398 assertEquals(new File(home, "foo/ba z"), f);
399 final ConfigRepository.Config c = h.getConfig();
400
401 assertArrayEquals(new Object[] { new File(home, "foo/ba z").getPath(),
402 "/foo/bar", "/foo/baz" },
403 c.getValues("IdentityFile"));
404 }
405
406 @Test
407 public void testNegatedPattern() throws Exception {
408 config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST !*.or.cz\nIdentityFile /foo/baz");
409 final Host h = osc.lookup("repo.or.cz");
410 assertNotNull(h);
411 assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
412 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
413 h.getConfig().getValues("IdentityFile"));
414 }
415
416 @Test
417 public void testPattern() throws Exception {
418 config("Host repo.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
419 final Host h = osc.lookup("repo.or.cz");
420 assertNotNull(h);
421 assertEquals(new File(home, "foo/bar"), h.getIdentityFile());
422 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
423 "/foo/baz" },
424 h.getConfig().getValues("IdentityFile"));
425 }
426
427 @Test
428 public void testMultiHost() throws Exception {
429 config("Host orcz *.or.cz\nIdentityFile ~/foo/bar\nHOST *.or.cz\nIdentityFile /foo/baz");
430 final Host h1 = osc.lookup("repo.or.cz");
431 assertNotNull(h1);
432 assertEquals(new File(home, "foo/bar"), h1.getIdentityFile());
433 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath(),
434 "/foo/baz" },
435 h1.getConfig().getValues("IdentityFile"));
436 final Host h2 = osc.lookup("orcz");
437 assertNotNull(h2);
438 assertEquals(new File(home, "foo/bar"), h2.getIdentityFile());
439 assertArrayEquals(new Object[] { new File(home, "foo/bar").getPath() },
440 h2.getConfig().getValues("IdentityFile"));
441 }
442
443 @Test
444 public void testEqualsSign() throws Exception {
445 config("Host=orcz\n\tConnectionAttempts = 5\n\tUser=\t foobar\t\n");
446 final Host h = osc.lookup("orcz");
447 assertNotNull(h);
448 assertEquals(5, h.getConnectionAttempts());
449 assertEquals("foobar", h.getUser());
450 }
451
452 @Test
453 public void testMissingArgument() throws Exception {
454 config("Host=orcz\n\tSendEnv\nIdentityFile\t\nForwardX11\n\tUser=\t foobar\t\n");
455 final Host h = osc.lookup("orcz");
456 assertNotNull(h);
457 assertEquals("foobar", h.getUser());
458 assertArrayEquals(new String[0], h.getConfig().getValues("SendEnv"));
459 assertNull(h.getIdentityFile());
460 assertNull(h.getConfig().getValue("ForwardX11"));
461 }
462
463 @Test
464 public void testHomeDirUserReplacement() throws Exception {
465 config("Host=orcz\n\tIdentityFile %d/.ssh/%u_id_dsa");
466 final Host h = osc.lookup("orcz");
467 assertNotNull(h);
468 assertEquals(new File(new File(home, ".ssh"), "jex_junit_id_dsa"),
469 h.getIdentityFile());
470 }
471
472 @Test
473 public void testHostnameReplacement() throws Exception {
474 config("Host=orcz\nHost *.*\n\tHostname %h\nHost *\n\tHostname %h.example.org");
475 final Host h = osc.lookup("orcz");
476 assertNotNull(h);
477 assertEquals("orcz.example.org", h.getHostName());
478 }
479
480 @Test
481 public void testRemoteUserReplacement() throws Exception {
482 config("Host=orcz\n\tUser foo\n" + "Host *.*\n\tHostname %h\n"
483 + "Host *\n\tHostname %h.ex%%20ample.org\n\tIdentityFile ~/.ssh/%h_%r_id_dsa");
484 final Host h = osc.lookup("orcz");
485 assertNotNull(h);
486 assertEquals(
487 new File(new File(home, ".ssh"),
488 "orcz.ex%20ample.org_foo_id_dsa"),
489 h.getIdentityFile());
490 }
491
492 @Test
493 public void testLocalhostFQDNReplacement() throws Exception {
494 String localhost = SystemReader.getInstance().getHostname();
495 config("Host=orcz\n\tIdentityFile ~/.ssh/%l_id_dsa");
496 final Host h = osc.lookup("orcz");
497 assertNotNull(h);
498 assertEquals(
499 new File(new File(home, ".ssh"), localhost + "_id_dsa"),
500 h.getIdentityFile());
501 }
502 }