1
2
3
4
5
6
7
8
9
10 package org.eclipse.jgit.transport.sshd;
11
12 import static org.apache.sshd.core.CoreModuleProperties.MAX_CONCURRENT_SESSIONS;
13 import static org.junit.Assert.assertEquals;
14 import static org.junit.Assert.assertFalse;
15 import static org.junit.Assert.assertNotNull;
16 import static org.junit.Assert.assertThrows;
17 import static org.junit.Assert.assertTrue;
18
19 import java.io.BufferedWriter;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.UncheckedIOException;
23 import java.net.URISyntaxException;
24 import java.nio.charset.StandardCharsets;
25 import java.nio.file.Files;
26 import java.nio.file.StandardOpenOption;
27 import java.security.KeyPair;
28 import java.security.KeyPairGenerator;
29 import java.security.PublicKey;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.stream.Collectors;
34
35 import org.apache.sshd.client.config.hosts.KnownHostEntry;
36 import org.apache.sshd.client.config.hosts.KnownHostHashValue;
37 import org.apache.sshd.common.NamedFactory;
38 import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
39 import org.apache.sshd.common.config.keys.KeyUtils;
40 import org.apache.sshd.common.config.keys.PublicKeyEntry;
41 import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
42 import org.apache.sshd.common.kex.BuiltinDHFactories;
43 import org.apache.sshd.common.kex.DHFactory;
44 import org.apache.sshd.common.kex.KeyExchangeFactory;
45 import org.apache.sshd.common.session.Session;
46 import org.apache.sshd.common.util.net.SshdSocketAddress;
47 import org.apache.sshd.server.ServerAuthenticationManager;
48 import org.apache.sshd.server.ServerBuilder;
49 import org.apache.sshd.server.SshServer;
50 import org.apache.sshd.server.forward.StaticDecisionForwardingFilter;
51 import org.eclipse.jgit.api.Git;
52 import org.eclipse.jgit.api.errors.TransportException;
53 import org.eclipse.jgit.junit.ssh.SshTestBase;
54 import org.eclipse.jgit.lib.Constants;
55 import org.eclipse.jgit.transport.RemoteSession;
56 import org.eclipse.jgit.transport.SshSessionFactory;
57 import org.eclipse.jgit.transport.URIish;
58 import org.eclipse.jgit.util.FS;
59 import org.junit.Test;
60 import org.junit.experimental.theories.Theories;
61 import org.junit.runner.RunWith;
62
63 @RunWith(Theories.class)
64 public class ApacheSshTest extends SshTestBase {
65
66 @Override
67 protected SshSessionFactory createSessionFactory() {
68 SshdSessionFactory result = new SshdSessionFactory(new JGitKeyCache(),
69 null);
70
71 result.setHomeDirectory(FS.DETECTED.userHome());
72 result.setSshDirectory(sshDir);
73 return result;
74 }
75
76 @Override
77 protected void installConfig(String... config) {
78 File configFile = new File(sshDir, Constants.CONFIG);
79 if (config != null) {
80 try {
81 Files.write(configFile.toPath(), Arrays.asList(config));
82 } catch (IOException e) {
83 throw new UncheckedIOException(e);
84 }
85 }
86 }
87
88 @Test
89 public void testEd25519HostKey() throws Exception {
90
91
92 File newHostKey = new File(getTemporaryDirectory(), "newhostkey");
93 copyTestResource("id_ed25519", newHostKey);
94 server.addHostKey(newHostKey.toPath(), true);
95 File newHostKeyPub = new File(getTemporaryDirectory(),
96 "newhostkey.pub");
97 copyTestResource("id_ed25519.pub", newHostKeyPub);
98 createKnownHostsFile(knownHosts, "localhost", testPort, newHostKeyPub);
99 cloneWith("ssh://git/doesntmatter", defaultCloneDir, null, //
100 "Host git",
101 "HostName localhost",
102 "Port " + testPort,
103 "User " + TEST_USER,
104 "IdentityFile " + privateKey1.getAbsolutePath());
105 }
106
107 @Test
108 public void testHashedKnownHosts() throws Exception {
109 assertTrue("Failed to delete known_hosts", knownHosts.delete());
110
111
112 TestCredentialsProvider provider = new TestCredentialsProvider();
113 cloneWith("ssh://localhost/doesntmatter", defaultCloneDir, provider, //
114 "HashKnownHosts yes",
115 "Host localhost",
116 "HostName localhost",
117 "Port " + testPort,
118 "User " + TEST_USER,
119 "IdentityFile " + privateKey1.getAbsolutePath());
120 List<LogEntry> messages = provider.getLog();
121 assertFalse("Expected user interaction", messages.isEmpty());
122 assertEquals(
123 "Expected to be asked about the key, and the file creation", 2,
124 messages.size());
125 assertTrue("~/.ssh/known_hosts should exist now", knownHosts.exists());
126
127
128 File clonedAgain = new File(getTemporaryDirectory(), "cloned2");
129 cloneWith("ssh://localhost/doesntmatter", clonedAgain, null, //
130 "Host localhost",
131 "HostName localhost",
132 "Port " + testPort,
133 "User " + TEST_USER,
134 "IdentityFile " + privateKey1.getAbsolutePath());
135
136
137 List<String> lines = Files.readAllLines(knownHosts.toPath()).stream()
138 .filter(s -> s != null && s.length() >= 1 && s.charAt(0) != '#'
139 && !s.trim().isEmpty())
140 .collect(Collectors.toList());
141 assertEquals("Unexpected number of known_hosts lines", 1, lines.size());
142 String line = lines.get(0);
143 assertFalse("Found host in line", line.contains("localhost"));
144 assertFalse("Found IP in line", line.contains("127.0.0.1"));
145 assertTrue("Hash not found", line.contains("|"));
146 KnownHostEntry entry = KnownHostEntry.parseKnownHostEntry(line);
147 assertTrue("Hash doesn't match localhost",
148 entry.isHostMatch("localhost", testPort)
149 || entry.isHostMatch("127.0.0.1", testPort));
150 }
151
152 @Test
153 public void testPreamble() throws Exception {
154
155
156 StringBuilder b = new StringBuilder();
157 for (int i = 0; i < 257; i++) {
158 b.append('a');
159 }
160 server.setPreamble("A line with a \000 NUL",
161 "A long line: " + b.toString());
162 cloneWith(
163 "ssh://" + TEST_USER + "@localhost:" + testPort
164 + "/doesntmatter",
165 defaultCloneDir, null,
166 "IdentityFile " + privateKey1.getAbsolutePath());
167 }
168
169 @Test
170 public void testLongPreamble() throws Exception {
171
172 StringBuilder b = new StringBuilder();
173 for (int i = 0; i < 1024; i++) {
174 b.append('a');
175 }
176 String line = b.toString();
177 String[] lines = new String[60];
178 for (int i = 0; i < lines.length; i++) {
179 lines[i] = line;
180 }
181 server.setPreamble(lines);
182 cloneWith(
183 "ssh://" + TEST_USER + "@localhost:" + testPort
184 + "/doesntmatter",
185 defaultCloneDir, null,
186 "IdentityFile " + privateKey1.getAbsolutePath());
187 }
188
189 @Test
190 public void testHugePreamble() throws Exception {
191
192 StringBuilder b = new StringBuilder();
193 for (int i = 0; i < 1024; i++) {
194 b.append('a');
195 }
196 String line = b.toString();
197 String[] lines = new String[70];
198 for (int i = 0; i < lines.length; i++) {
199 lines[i] = line;
200 }
201 server.setPreamble(lines);
202 TransportException e = assertThrows(TransportException.class,
203 () -> cloneWith(
204 "ssh://" + TEST_USER + "@localhost:" + testPort
205 + "/doesntmatter",
206 defaultCloneDir, null,
207 "IdentityFile " + privateKey1.getAbsolutePath()));
208
209 assertFalse(e.getMessage().contains("timeout"));
210 assertTrue(e.getMessage().contains("65536")
211 || e.getMessage().contains("closed"));
212 }
213
214
215
216
217
218
219
220
221
222
223 @Test
224 public void testCloneAndFetchWithSessionLimit() throws Exception {
225 MAX_CONCURRENT_SESSIONS
226 .set(server.getPropertyResolver(), Integer.valueOf(2));
227 File localClone = cloneWith("ssh://localhost/doesntmatter",
228 defaultCloneDir, null,
229 "Host localhost",
230 "HostName localhost",
231 "Port " + testPort,
232 "User " + TEST_USER,
233 "IdentityFile " + privateKey1.getAbsolutePath());
234
235 try (Git git = Git.open(localClone)) {
236 git.fetch().call();
237 git.fetch().call();
238 }
239 }
240
241
242
243
244
245
246
247
248
249
250
251 private SshServer createServer(String user, File userKey) throws Exception {
252 SshServer srv = SshServer.setUpDefaultServer();
253
254 KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
255 generator.initialize(2048);
256 KeyPair proxyHostKey = generator.generateKeyPair();
257 srv.setKeyPairProvider(
258 session -> Collections.singletonList(proxyHostKey));
259
260 srv.setUserAuthFactories(Collections.singletonList(
261 ServerAuthenticationManager.DEFAULT_USER_AUTH_PUBLIC_KEY_FACTORY));
262
263 PublicKey userProxyKey = AuthorizedKeyEntry
264 .readAuthorizedKeys(userKey.toPath()).get(0)
265 .resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
266 srv.setPublickeyAuthenticator(
267 (userName, publicKey, session) -> user.equals(userName)
268 && KeyUtils.compareKeys(userProxyKey, publicKey));
269 return srv;
270 }
271
272
273
274
275
276
277
278 private void registerServer(SshServer srv) throws Exception {
279
280 try (BufferedWriter writer = Files.newBufferedWriter(
281 knownHosts.toPath(), StandardCharsets.US_ASCII,
282 StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
283 writer.append('\n');
284 KnownHostHashValue.appendHostPattern(writer, "localhost",
285 srv.getPort());
286 writer.append(',');
287 KnownHostHashValue.appendHostPattern(writer, "127.0.0.1",
288 srv.getPort());
289 writer.append(' ');
290 PublicKeyEntry.appendPublicKeyEntry(writer,
291 srv.getKeyPairProvider().loadKeys(null).iterator().next().getPublic());
292 writer.append('\n');
293 }
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 private SshServer createProxy(String user, File userKey,
311 SshdSocketAddress[] report) throws Exception {
312 SshServer proxy = createServer(user, userKey);
313
314 proxy.setForwardingFilter(new StaticDecisionForwardingFilter(true) {
315
316 @Override
317 protected boolean checkAcceptance(String request, Session session,
318 SshdSocketAddress target) {
319 report[0] = target;
320 return super.checkAcceptance(request, session, target);
321 }
322 });
323 proxy.start();
324 registerServer(proxy);
325 return proxy;
326 }
327
328 @Test
329 public void testJumpHost() throws Exception {
330 SshdSocketAddress[] forwarded = { null };
331 try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
332 forwarded)) {
333 try {
334
335 cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
336 "Host server",
337 "HostName localhost",
338 "Port " + testPort,
339 "User " + TEST_USER,
340 "IdentityFile " + privateKey1.getAbsolutePath(),
341 "ProxyJump " + TEST_USER + "X@proxy:" + proxy.getPort(),
342 "",
343 "Host proxy",
344 "Hostname localhost",
345 "IdentityFile " + privateKey2.getAbsolutePath());
346 assertNotNull(forwarded[0]);
347 assertEquals(testPort, forwarded[0].getPort());
348 } finally {
349 proxy.stop();
350 }
351 }
352 }
353
354 @Test
355 public void testJumpHostWrongKeyAtProxy() throws Exception {
356
357 SshdSocketAddress[] forwarded = { null };
358 try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
359 forwarded)) {
360 try {
361
362 TransportException e = assertThrows(TransportException.class,
363 () -> cloneWith("ssh://server/doesntmatter",
364 defaultCloneDir, null,
365 "Host server",
366 "HostName localhost",
367 "Port " + testPort,
368 "User " + TEST_USER,
369 "IdentityFile " + privateKey1.getAbsolutePath(),
370 "ProxyJump " + TEST_USER + "X@proxy:"
371 + proxy.getPort(),
372 "",
373 "Host proxy",
374 "Hostname localhost",
375 "IdentityFile "
376 + privateKey1.getAbsolutePath()));
377 String message = e.getMessage();
378 assertTrue(message.contains("localhost:" + proxy.getPort()));
379 assertTrue(message.contains("proxy:" + proxy.getPort()));
380 } finally {
381 proxy.stop();
382 }
383 }
384 }
385
386 @Test
387 public void testJumpHostWrongKeyAtServer() throws Exception {
388
389 SshdSocketAddress[] forwarded = { null };
390 try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
391 forwarded)) {
392 try {
393
394 TransportException e = assertThrows(TransportException.class,
395 () -> cloneWith("ssh://server/doesntmatter",
396 defaultCloneDir, null,
397 "Host server",
398 "HostName localhost",
399 "Port " + testPort,
400 "User " + TEST_USER,
401 "IdentityFile " + privateKey2.getAbsolutePath(),
402 "ProxyJump " + TEST_USER + "X@proxy:"
403 + proxy.getPort(),
404 "",
405 "Host proxy",
406 "Hostname localhost",
407 "IdentityFile "
408 + privateKey2.getAbsolutePath()));
409 String message = e.getMessage();
410 assertTrue(message.contains("localhost:" + testPort));
411 assertTrue(message.contains("ssh://server"));
412 } finally {
413 proxy.stop();
414 }
415 }
416 }
417
418 @Test
419 public void testJumpHostNonSsh() throws Exception {
420 SshdSocketAddress[] forwarded = { null };
421 try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
422 forwarded)) {
423 try {
424 TransportException e = assertThrows(TransportException.class,
425 () -> cloneWith("ssh://server/doesntmatter",
426 defaultCloneDir, null,
427 "Host server",
428 "HostName localhost",
429 "Port " + testPort,
430 "User " + TEST_USER,
431 "IdentityFile " + privateKey1.getAbsolutePath(),
432 "ProxyJump http://" + TEST_USER + "X@proxy:"
433 + proxy.getPort(),
434 "",
435 "Host proxy",
436 "Hostname localhost",
437 "IdentityFile "
438 + privateKey2.getAbsolutePath()));
439
440 Throwable t = e;
441 while (t != null) {
442 if (t instanceof URISyntaxException) {
443 break;
444 }
445 t = t.getCause();
446 }
447 assertNotNull(t);
448 assertTrue(t.getMessage().contains("Non-ssh"));
449 } finally {
450 proxy.stop();
451 }
452 }
453 }
454
455 @Test
456 public void testJumpHostWithPath() throws Exception {
457 SshdSocketAddress[] forwarded = { null };
458 try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
459 forwarded)) {
460 try {
461 TransportException e = assertThrows(TransportException.class,
462 () -> cloneWith("ssh://server/doesntmatter",
463 defaultCloneDir, null,
464 "Host server",
465 "HostName localhost",
466 "Port " + testPort,
467 "User " + TEST_USER,
468 "IdentityFile " + privateKey1.getAbsolutePath(),
469 "ProxyJump ssh://" + TEST_USER + "X@proxy:"
470 + proxy.getPort() + "/wrongPath",
471 "",
472 "Host proxy",
473 "Hostname localhost",
474 "IdentityFile "
475 + privateKey2.getAbsolutePath()));
476
477 Throwable t = e;
478 while (t != null) {
479 if (t instanceof URISyntaxException) {
480 break;
481 }
482 t = t.getCause();
483 }
484 assertNotNull(t);
485 assertTrue(t.getMessage().contains("wrongPath"));
486 } finally {
487 proxy.stop();
488 }
489 }
490 }
491
492 @Test
493 public void testJumpHostWithPathShort() throws Exception {
494 SshdSocketAddress[] forwarded = { null };
495 try (SshServer proxy = createProxy(TEST_USER + 'X', publicKey2,
496 forwarded)) {
497 try {
498 TransportException e = assertThrows(TransportException.class,
499 () -> cloneWith("ssh://server/doesntmatter",
500 defaultCloneDir, null,
501 "Host server",
502 "HostName localhost",
503 "Port " + testPort,
504 "User " + TEST_USER,
505 "IdentityFile " + privateKey1.getAbsolutePath(),
506 "ProxyJump " + TEST_USER + "X@proxy:wrongPath",
507 "",
508 "Host proxy",
509 "Hostname localhost",
510 "Port " + proxy.getPort(),
511 "IdentityFile "
512 + privateKey2.getAbsolutePath()));
513
514 Throwable t = e;
515 while (t != null) {
516 if (t instanceof URISyntaxException) {
517 break;
518 }
519 t = t.getCause();
520 }
521 assertNotNull(t);
522 assertTrue(t.getMessage().contains("wrongPath"));
523 } finally {
524 proxy.stop();
525 }
526 }
527 }
528
529 @Test
530 public void testJumpHostChain() throws Exception {
531 SshdSocketAddress[] forwarded1 = { null };
532 SshdSocketAddress[] forwarded2 = { null };
533 try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
534 forwarded1);
535 SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
536 try {
537
538 cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
539 "Host server",
540 "HostName localhost",
541 "Port " + testPort,
542 "User " + TEST_USER,
543 "IdentityFile " + privateKey1.getAbsolutePath(),
544 "ProxyJump proxy2," + TEST_USER + "X@proxy:"
545 + proxy1.getPort(),
546 "",
547 "Host proxy",
548 "Hostname localhost",
549 "IdentityFile " + privateKey2.getAbsolutePath(),
550 "",
551 "Host proxy2",
552 "Hostname localhost",
553 "User foo",
554 "Port " + proxy2.getPort(),
555 "IdentityFile " + privateKey1.getAbsolutePath());
556 assertNotNull(forwarded1[0]);
557 assertEquals(proxy2.getPort(), forwarded1[0].getPort());
558 assertNotNull(forwarded2[0]);
559 assertEquals(testPort, forwarded2[0].getPort());
560 } finally {
561 proxy1.stop();
562 proxy2.stop();
563 }
564 }
565 }
566
567 @Test
568 public void testJumpHostCascade() throws Exception {
569 SshdSocketAddress[] forwarded1 = { null };
570 SshdSocketAddress[] forwarded2 = { null };
571 try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
572 forwarded1);
573 SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
574 try {
575
576 cloneWith("ssh://server/doesntmatter", defaultCloneDir, null, //
577 "Host server",
578 "HostName localhost",
579 "Port " + testPort,
580 "User " + TEST_USER,
581 "IdentityFile " + privateKey1.getAbsolutePath(),
582 "ProxyJump " + TEST_USER + "X@proxy",
583 "",
584 "Host proxy",
585 "Hostname localhost",
586 "Port " + proxy1.getPort(),
587 "ProxyJump ssh://proxy2:" + proxy2.getPort(), //
588 "IdentityFile " + privateKey2.getAbsolutePath(),
589 "",
590 "Host proxy2",
591 "Hostname localhost",
592 "User foo",
593 "IdentityFile " + privateKey1.getAbsolutePath());
594 assertNotNull(forwarded1[0]);
595 assertEquals(testPort, forwarded1[0].getPort());
596 assertNotNull(forwarded2[0]);
597 assertEquals(proxy1.getPort(), forwarded2[0].getPort());
598 } finally {
599 proxy1.stop();
600 proxy2.stop();
601 }
602 }
603 }
604
605 @Test
606 public void testJumpHostRecursion() throws Exception {
607 SshdSocketAddress[] forwarded1 = { null };
608 SshdSocketAddress[] forwarded2 = { null };
609 try (SshServer proxy1 = createProxy(TEST_USER + 'X', publicKey2,
610 forwarded1);
611 SshServer proxy2 = createProxy("foo", publicKey1, forwarded2)) {
612 try {
613 TransportException e = assertThrows(TransportException.class,
614 () -> cloneWith(
615 "ssh://server/doesntmatter", defaultCloneDir, null, //
616 "Host server",
617 "HostName localhost",
618 "Port " + testPort,
619 "User " + TEST_USER,
620 "IdentityFile " + privateKey1.getAbsolutePath(),
621 "ProxyJump " + TEST_USER + "X@proxy",
622 "",
623 "Host proxy",
624 "Hostname localhost",
625 "Port " + proxy1.getPort(),
626 "ProxyJump ssh://proxy2:" + proxy2.getPort(), //
627 "IdentityFile " + privateKey2.getAbsolutePath(),
628 "",
629 "Host proxy2",
630 "Hostname localhost",
631 "User foo",
632 "ProxyJump " + TEST_USER + "X@proxy",
633 "IdentityFile " + privateKey1.getAbsolutePath()));
634 assertTrue(e.getMessage().contains("proxy"));
635 } finally {
636 proxy1.stop();
637 proxy2.stop();
638 }
639 }
640 }
641
642
643
644
645
646
647
648
649
650
651
652 @Test
653 public void testConnectAuthSshRsaPubkeyAcceptedAlgorithms()
654 throws Exception {
655 try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
656 oldServer.setSignatureFactoriesNames("ssh-rsa");
657 oldServer.start();
658 registerServer(oldServer);
659 installConfig("Host server",
660 "HostName localhost",
661 "Port " + oldServer.getPort(),
662 "User " + TEST_USER,
663 "IdentityFile " + privateKey1.getAbsolutePath(),
664 "PubkeyAcceptedAlgorithms ^ssh-rsa");
665 RemoteSession session = getSessionFactory().getSession(
666 new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
667 10000);
668 assertNotNull(session);
669 session.disconnect();
670 }
671 }
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692 @Test
693 public void testConnectAuthSshRsa() throws Exception {
694 try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
695 oldServer.setSignatureFactoriesNames("ssh-rsa");
696 oldServer.start();
697 registerServer(oldServer);
698 installConfig("Host server",
699 "HostName localhost",
700 "Port " + oldServer.getPort(),
701 "User " + TEST_USER,
702 "IdentityFile " + privateKey1.getAbsolutePath());
703 RemoteSession session = getSessionFactory().getSession(
704 new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
705 10000);
706 assertNotNull(session);
707 session.disconnect();
708 }
709 }
710
711
712
713
714
715
716
717
718
719
720 @Test
721 public void testConnectOnlyRsaSha1() throws Exception {
722 try (SshServer oldServer = createServer(TEST_USER, publicKey1)) {
723 oldServer.setSignatureFactoriesNames("ssh-rsa");
724 List<DHFactory> sha1Factories = BuiltinDHFactories
725 .parseDHFactoriesList(
726 "diffie-hellman-group1-sha1,diffie-hellman-group14-sha1")
727 .getParsedFactories();
728 assertEquals(2, sha1Factories.size());
729 List<KeyExchangeFactory> kexFactories = NamedFactory
730 .setUpTransformedFactories(true, sha1Factories,
731 ServerBuilder.DH2KEX);
732 oldServer.setKeyExchangeFactories(kexFactories);
733 oldServer.start();
734 registerServer(oldServer);
735 installConfig("Host server",
736 "HostName localhost",
737 "Port " + oldServer.getPort(),
738 "User " + TEST_USER,
739 "IdentityFile " + privateKey1.getAbsolutePath(),
740 "KexAlgorithms +diffie-hellman-group1-sha1");
741 RemoteSession session = getSessionFactory().getSession(
742 new URIish("ssh://server/doesntmatter"), null, FS.DETECTED,
743 10000);
744 assertNotNull(session);
745 session.disconnect();
746 }
747 }
748 }