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 configureJSch(jsch);
376 knownHosts(jsch, fs);
377 identities(jsch, fs);
378 return jsch;
379 }
380
381 private static void knownHosts(JSch sch, FS fs) throws JSchException {
382 final File home = fs.userHome();
383 if (home == null)
384 return;
385 final File known_hosts = new File(new File(home, ".ssh"), "known_hosts");
386 try (FileInputStream in = new FileInputStream(known_hosts)) {
387 sch.setKnownHosts(in);
388 } catch (FileNotFoundException none) {
389
390 } catch (IOException err) {
391
392 }
393 }
394
395 private static void identities(JSch sch, FS fs) {
396 final File home = fs.userHome();
397 if (home == null)
398 return;
399 final File sshdir = new File(home, ".ssh");
400 if (sshdir.isDirectory()) {
401 loadIdentity(sch, new File(sshdir, "identity"));
402 loadIdentity(sch, new File(sshdir, "id_rsa"));
403 loadIdentity(sch, new File(sshdir, "id_dsa"));
404 }
405 }
406
407 private static void loadIdentity(JSch sch, File priv) {
408 if (priv.isFile()) {
409 try {
410 sch.addIdentity(priv.getAbsolutePath());
411 } catch (JSchException e) {
412
413 }
414 }
415 }
416
417 private static class JschBugFixingConfigRepository
418 implements ConfigRepository {
419
420 private final ConfigRepository base;
421
422 public JschBugFixingConfigRepository(ConfigRepository base) {
423 this.base = base;
424 }
425
426 @Override
427 public Config getConfig(String host) {
428 return new JschBugFixingConfig(base.getConfig(host));
429 }
430
431
432
433
434
435
436
437
438
439
440
441
442 private static class JschBugFixingConfig implements Config {
443
444 private static final String[] NO_IDENTITIES = {};
445
446 private final Config real;
447
448 public JschBugFixingConfig(Config delegate) {
449 real = delegate;
450 }
451
452 @Override
453 public String getHostname() {
454 return real.getHostname();
455 }
456
457 @Override
458 public String getUser() {
459 return real.getUser();
460 }
461
462 @Override
463 public int getPort() {
464 return real.getPort();
465 }
466
467 @Override
468 public String getValue(String key) {
469 String k = key.toUpperCase(Locale.ROOT);
470 if ("IDENTITYFILE".equals(k)) {
471 return null;
472 }
473 String result = real.getValue(key);
474 if (result != null) {
475 if ("SERVERALIVEINTERVAL".equals(k)
476 || "CONNECTTIMEOUT".equals(k)) {
477
478
479
480
481 try {
482 int timeout = Integer.parseInt(result);
483 result = Long.toString(
484 TimeUnit.SECONDS.toMillis(timeout));
485 } catch (NumberFormatException e) {
486
487 }
488 }
489 }
490 return result;
491 }
492
493 @Override
494 public String[] getValues(String key) {
495 String k = key.toUpperCase(Locale.ROOT);
496 if ("IDENTITYFILE".equals(k)) {
497 return NO_IDENTITIES;
498 }
499 return real.getValues(key);
500 }
501 }
502 }
503
504
505
506
507
508
509
510 void setConfig(OpenSshConfig config) {
511 this.config = config;
512 }
513 }