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