1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.eclipse.jgit.transport;
19
20 import static java.util.stream.Collectors.joining;
21 import static java.util.stream.Collectors.toList;
22
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 import java.net.ConnectException;
30 import java.net.UnknownHostException;
31 import java.text.MessageFormat;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.concurrent.TimeUnit;
37 import java.util.stream.Stream;
38
39 import org.eclipse.jgit.errors.TransportException;
40 import org.eclipse.jgit.internal.JGitText;
41 import org.eclipse.jgit.util.FS;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 import com.jcraft.jsch.ConfigRepository;
46 import com.jcraft.jsch.ConfigRepository.Config;
47 import com.jcraft.jsch.HostKey;
48 import com.jcraft.jsch.HostKeyRepository;
49 import com.jcraft.jsch.JSch;
50 import com.jcraft.jsch.JSchException;
51 import com.jcraft.jsch.Session;
52
53
54
55
56
57
58
59
60
61
62
63
64
65 public abstract class JschConfigSessionFactory extends SshSessionFactory {
66
67 private static final Logger LOG = LoggerFactory
68 .getLogger(JschConfigSessionFactory.class);
69
70
71
72
73
74
75
76
77
78 private final Map<String, JSch> byIdentityFile = new HashMap<>();
79
80 private JSch defaultJSch;
81
82 private OpenSshConfig config;
83
84
85 @Override
86 public synchronized RemoteSession getSession(URIish uri,
87 CredentialsProvider credentialsProvider, FS fs, int tms)
88 throws TransportException {
89
90 String user = uri.getUser();
91 final String pass = uri.getPass();
92 String host = uri.getHost();
93 int port = uri.getPort();
94
95 try {
96 if (config == null)
97 config = OpenSshConfig.get(fs);
98
99 final OpenSshConfig.Host hc = config.lookup(host);
100 if (port <= 0)
101 port = hc.getPort();
102 if (user == null)
103 user = hc.getUser();
104
105 Session session = createSession(credentialsProvider, fs, user,
106 pass, host, port, hc);
107
108 int retries = 0;
109 while (!session.isConnected()) {
110 try {
111 retries++;
112 session.connect(tms);
113 } catch (JSchException e) {
114 session.disconnect();
115 session = null;
116
117 knownHosts(getJSch(hc, fs), fs);
118
119 if (isAuthenticationCanceled(e)) {
120 throw e;
121 } else if (isAuthenticationFailed(e)
122 && credentialsProvider != null) {
123
124
125 if (retries < 3) {
126 credentialsProvider.reset(uri);
127 session = createSession(credentialsProvider, fs,
128 user, pass, host, port, hc);
129 } else
130 throw e;
131 } else if (retries >= hc.getConnectionAttempts()) {
132 throw e;
133 } else {
134 try {
135 Thread.sleep(1000);
136 session = createSession(credentialsProvider, fs,
137 user, pass, host, port, hc);
138 } catch (InterruptedException e1) {
139 throw new TransportException(
140 JGitText.get().transportSSHRetryInterrupt,
141 e1);
142 }
143 }
144 }
145 }
146
147 return new JschSession(session, uri);
148
149 } catch (JSchException je) {
150 final Throwable c = je.getCause();
151 if (c instanceof UnknownHostException) {
152 throw new TransportException(uri, JGitText.get().unknownHost,
153 je);
154 }
155 if (c instanceof ConnectException) {
156 throw new TransportException(uri, c.getMessage(), je);
157 }
158 throw new TransportException(uri, je.getMessage(), je);
159 }
160
161 }
162
163 private static boolean isAuthenticationFailed(JSchException e) {
164 return e.getCause() == null && e.getMessage().equals("Auth fail");
165 }
166
167 private static boolean isAuthenticationCanceled(JSchException e) {
168 return e.getCause() == null && e.getMessage().equals("Auth cancel");
169 }
170
171
172 Session createSession(CredentialsProvider credentialsProvider,
173 FS fs, String user, final String pass, String host, int port,
174 final OpenSshConfig.Host hc) throws JSchException {
175 final Session session = createSession(hc, user, host, port, fs);
176
177
178 setUserName(session, user);
179
180 if (port > 0 && port != session.getPort()) {
181 session.setPort(port);
182 }
183
184
185 session.setConfig("MaxAuthTries", "1");
186 if (pass != null)
187 session.setPassword(pass);
188 final String strictHostKeyCheckingPolicy = hc
189 .getStrictHostKeyChecking();
190 if (strictHostKeyCheckingPolicy != null)
191 session.setConfig("StrictHostKeyChecking",
192 strictHostKeyCheckingPolicy);
193 final String pauth = hc.getPreferredAuthentications();
194 if (pauth != null)
195 session.setConfig("PreferredAuthentications", pauth);
196 if (credentialsProvider != null
197 && (!hc.isBatchMode() || !credentialsProvider.isInteractive())) {
198 session.setUserInfo(new CredentialsProviderUserInfo(session,
199 credentialsProvider));
200 }
201 safeConfig(session, hc.getConfig());
202 if (hc.getConfig().getValue("HostKeyAlgorithms") == null) {
203 setPreferredKeyTypesOrder(session);
204 }
205 configure(hc, session);
206 return session;
207 }
208
209 private void safeConfig(Session session, Config cfg) {
210
211
212
213
214 copyConfigValueToSession(session, cfg, "Ciphers", "CheckCiphers");
215 copyConfigValueToSession(session, cfg, "KexAlgorithms", "CheckKexes");
216 copyConfigValueToSession(session, cfg, "HostKeyAlgorithms",
217 "CheckSignatures");
218 }
219
220 private static void setPreferredKeyTypesOrder(Session session) {
221 HostKeyRepository hkr = session.getHostKeyRepository();
222 HostKey[] hostKeys = hkr.getHostKey(hostName(session), null);
223
224 if (hostKeys == null) {
225 return;
226 }
227
228 List<String> known = Stream.of(hostKeys)
229 .map(HostKey::getType)
230 .collect(toList());
231
232 if (!known.isEmpty()) {
233 String serverHostKey = "server_host_key";
234 String current = session.getConfig(serverHostKey);
235 if (current == null) {
236 session.setConfig(serverHostKey, String.join(",", known));
237 return;
238 }
239
240 String knownFirst = Stream.concat(
241 known.stream(),
242 Stream.of(current.split(","))
243 .filter(s -> !known.contains(s)))
244 .collect(joining(","));
245 session.setConfig(serverHostKey, knownFirst);
246 }
247 }
248
249 private static String hostName(Session s) {
250 if (s.getPort() == SshConstants.SSH_DEFAULT_PORT) {
251 return s.getHost();
252 }
253 return String.format("[%s]:%d", s.getHost(),
254 Integer.valueOf(s.getPort()));
255 }
256
257 private void copyConfigValueToSession(Session session, Config cfg,
258 String from, String to) {
259 String value = cfg.getValue(from);
260 if (value != null) {
261 session.setConfig(to, value);
262 }
263 }
264
265 private void setUserName(Session session, String userName) {
266
267
268
269 if (userName == null || userName.isEmpty()
270 || userName.equals(session.getUserName())) {
271 return;
272 }
273 try {
274 Class<?>[] parameterTypes = { String.class };
275 Method method = Session.class.getDeclaredMethod("setUserName",
276 parameterTypes);
277 method.setAccessible(true);
278 method.invoke(session, userName);
279 } catch (NullPointerException | IllegalAccessException
280 | IllegalArgumentException | InvocationTargetException
281 | NoSuchMethodException | SecurityException e) {
282 LOG.error(MessageFormat.format(JGitText.get().sshUserNameError,
283 userName, session.getUserName()), e);
284 }
285 }
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305 protected Session createSession(final OpenSshConfig.Host hc,
306 final String user, final String host, final int port, FS fs)
307 throws JSchException {
308 return getJSch(hc, fs).getSession(user, host, port);
309 }
310
311
312
313
314
315
316
317
318
319
320 protected void configureJSch(JSch jsch) {
321
322 }
323
324
325
326
327
328
329
330
331
332
333
334 protected abstract void configure(OpenSshConfig.Host hc, Session session);
335
336
337
338
339
340
341
342
343
344
345
346
347
348 protected JSch getJSch(OpenSshConfig.Host hc, FS fs) throws JSchException {
349 if (defaultJSch == null) {
350 defaultJSch = createDefaultJSch(fs);
351 if (defaultJSch.getConfigRepository() == null) {
352 defaultJSch.setConfigRepository(
353 new JschBugFixingConfigRepository(config));
354 }
355 for (Object name : defaultJSch.getIdentityNames())
356 byIdentityFile.put((String) name, defaultJSch);
357 }
358
359 final File identityFile = hc.getIdentityFile();
360 if (identityFile == null)
361 return defaultJSch;
362
363 final String identityKey = identityFile.getAbsolutePath();
364 JSch jsch = byIdentityFile.get(identityKey);
365 if (jsch == null) {
366 jsch = new JSch();
367 configureJSch(jsch);
368 if (jsch.getConfigRepository() == null) {
369 jsch.setConfigRepository(defaultJSch.getConfigRepository());
370 }
371 jsch.setHostKeyRepository(defaultJSch.getHostKeyRepository());
372 jsch.addIdentity(identityKey);
373 byIdentityFile.put(identityKey, jsch);
374 }
375 return jsch;
376 }
377
378
379
380
381
382
383
384
385
386
387
388 protected JSch createDefaultJSch(FS fs) throws JSchException {
389 final JSch jsch = new JSch();
390 JSch.setConfig("ssh-rsa", JSch.getConfig("signature.rsa"));
391 JSch.setConfig("ssh-dss", JSch.getConfig("signature.dss"));
392 configureJSch(jsch);
393 knownHosts(jsch, fs);
394 identities(jsch, fs);
395 return jsch;
396 }
397
398 private static void knownHosts(JSch sch, FS fs) throws JSchException {
399 final File home = fs.userHome();
400 if (home == null)
401 return;
402 final File known_hosts = new File(new File(home, ".ssh"), "known_hosts");
403 try (FileInputStream in = new FileInputStream(known_hosts)) {
404 sch.setKnownHosts(in);
405 } catch (FileNotFoundException none) {
406
407 } catch (IOException err) {
408
409 }
410 }
411
412 private static void identities(JSch sch, FS fs) {
413 final File home = fs.userHome();
414 if (home == null)
415 return;
416 final File sshdir = new File(home, ".ssh");
417 if (sshdir.isDirectory()) {
418 loadIdentity(sch, new File(sshdir, "identity"));
419 loadIdentity(sch, new File(sshdir, "id_rsa"));
420 loadIdentity(sch, new File(sshdir, "id_dsa"));
421 }
422 }
423
424 private static void loadIdentity(JSch sch, File priv) {
425 if (priv.isFile()) {
426 try {
427 sch.addIdentity(priv.getAbsolutePath());
428 } catch (JSchException e) {
429
430 }
431 }
432 }
433
434 private static class JschBugFixingConfigRepository
435 implements ConfigRepository {
436
437 private final ConfigRepository base;
438
439 public JschBugFixingConfigRepository(ConfigRepository base) {
440 this.base = base;
441 }
442
443 @Override
444 public Config getConfig(String host) {
445 return new JschBugFixingConfig(base.getConfig(host));
446 }
447
448
449
450
451
452
453
454
455
456
457
458
459 private static class JschBugFixingConfig implements Config {
460
461 private static final String[] NO_IDENTITIES = {};
462
463 private final Config real;
464
465 public JschBugFixingConfig(Config delegate) {
466 real = delegate;
467 }
468
469 @Override
470 public String getHostname() {
471 return real.getHostname();
472 }
473
474 @Override
475 public String getUser() {
476 return real.getUser();
477 }
478
479 @Override
480 public int getPort() {
481 return real.getPort();
482 }
483
484 @Override
485 public String getValue(String key) {
486 String k = key.toUpperCase(Locale.ROOT);
487 if ("IDENTITYFILE".equals(k)) {
488 return null;
489 }
490 String result = real.getValue(key);
491 if (result != null) {
492 if ("SERVERALIVEINTERVAL".equals(k)
493 || "CONNECTTIMEOUT".equals(k)) {
494
495
496
497
498 try {
499 int timeout = Integer.parseInt(result);
500 result = Long.toString(
501 TimeUnit.SECONDS.toMillis(timeout));
502 } catch (NumberFormatException e) {
503
504 }
505 }
506 }
507 return result;
508 }
509
510 @Override
511 public String[] getValues(String key) {
512 String k = key.toUpperCase(Locale.ROOT);
513 if ("IDENTITYFILE".equals(k)) {
514 return NO_IDENTITIES;
515 }
516 return real.getValues(key);
517 }
518 }
519 }
520
521
522
523
524
525
526
527 synchronized void setConfig(OpenSshConfig config) {
528 this.config = config;
529 }
530 }