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.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 		// Tilde expansion occurs within the parser
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 		// Host does tilde replacement
383 		assertEquals(new File(home, "foo/ba z"), f);
384 		final ConfigRepository.Config c = h.getConfig();
385 		// Config does tilde replacement, too
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 		// Host does tilde replacement
399 		assertEquals(new File(home, "foo/ba z"), f);
400 		final ConfigRepository.Config c = h.getConfig();
401 		// Config does tilde replacement, too
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 }