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