View Javadoc
1   /*
2    * Copyright (C) 2008, 2017 Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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 		// Tilde expansion occurs within the parser
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 		// Host does tilde replacement
382 		assertEquals(new File(home, "foo/ba z"), f);
383 		final ConfigRepository.Config c = h.getConfig();
384 		// Config does tilde replacement, too
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 		// Host does tilde replacement
398 		assertEquals(new File(home, "foo/ba z"), f);
399 		final ConfigRepository.Config c = h.getConfig();
400 		// Config does tilde replacement, too
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 }