View Javadoc
1   /*
2    * Copyright (C) 2008, 2017 Google Inc. and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  //TODO(ms): move to org.eclipse.jgit.ssh.jsch in 6.0
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 		// Tilde expansion occurs within the parser
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 		// Host does tilde replacement
350 		assertEquals(new File(home, "foo/ba z"), f);
351 		final ConfigRepository.Config c = h.getConfig();
352 		// Config does tilde replacement, too
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 		// Host does tilde replacement
366 		assertEquals(new File(home, "foo/ba z"), f);
367 		final ConfigRepository.Config c = h.getConfig();
368 		// Config does tilde replacement, too
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 }