/* * Copyright (C) 2016, Mark Ingram <markdingram@gmail.com> * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com> * Copyright (C) 2008-2009, Google Inc. * Copyright (C) 2009, Google, Inc. * Copyright (C) 2009, JetBrains s.r.o. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available * under the terms of the Eclipse Distribution License v1.0 which * accompanies this distribution, is reproduced below, and is * available at http://www.eclipse.org/org/documents/edl-v10.php * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Eclipse Foundation, Inc. nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.eclipse.jgit.transport; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.ConnectException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.FS; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.UserInfo; /** * The base session factory that loads known hosts and private keys from * <code>$HOME/.ssh</code>. * <p> * This is the default implementation used by JGit and provides most of the * compatibility necessary to match OpenSSH, a popular implementation of SSH * used by C Git. * <p> * The factory does not provide UI behavior. Override the method * {@link #configure(org.eclipse.jgit.transport.OpenSshConfig.Host, Session)} * to supply appropriate {@link UserInfo} to the session. */ public abstract class JschConfigSessionFactory extends SshSessionFactory { private final Map<String, JSch> byIdentityFile = new HashMap<String, JSch>(); private JSch defaultJSch; private OpenSshConfig config; @Override public synchronized RemoteSession getSession(URIish uri, CredentialsProvider credentialsProvider, FS fs, int tms) throws TransportException { String user = uri.getUser(); final String pass = uri.getPass(); String host = uri.getHost(); int port = uri.getPort(); try { if (config == null) config = OpenSshConfig.get(fs); final OpenSshConfig.Host hc = config.lookup(host); host = hc.getHostName(); if (port <= 0) port = hc.getPort(); if (user == null) user = hc.getUser(); Session session = createSession(credentialsProvider, fs, user, pass, host, port, hc); int retries = 0; while (!session.isConnected()) { try { retries++; session.connect(tms); } catch (JSchException e) { session.disconnect(); session = null; // Make sure our known_hosts is not outdated knownHosts(getJSch(hc, fs), fs); if (isAuthenticationCanceled(e)) { throw e; } else if (isAuthenticationFailed(e) && credentialsProvider != null) { // if authentication failed maybe credentials changed at // the remote end therefore reset credentials and retry if (retries < 3) { credentialsProvider.reset(uri); session = createSession(credentialsProvider, fs, user, pass, host, port, hc); } else throw e; } else if (retries >= hc.getConnectionAttempts()) { throw e; } else { try { Thread.sleep(1000); session = createSession(credentialsProvider, fs, user, pass, host, port, hc); } catch (InterruptedException e1) { throw new TransportException( JGitText.get().transportSSHRetryInterrupt, e1); } } } } return new JschSession(session, uri); } catch (JSchException je) { final Throwable c = je.getCause(); if (c instanceof UnknownHostException) throw new TransportException(uri, JGitText.get().unknownHost); if (c instanceof ConnectException) throw new TransportException(uri, c.getMessage()); throw new TransportException(uri, je.getMessage(), je); } } private static boolean isAuthenticationFailed(JSchException e) { return e.getCause() == null && e.getMessage().equals("Auth fail"); //$NON-NLS-1$ } private static boolean isAuthenticationCanceled(JSchException e) { return e.getCause() == null && e.getMessage().equals("Auth cancel"); //$NON-NLS-1$ } private Session createSession(CredentialsProvider credentialsProvider, FS fs, String user, final String pass, String host, int port, final OpenSshConfig.Host hc) throws JSchException { final Session session = createSession(hc, user, host, port, fs); // We retry already in getSession() method. JSch must not retry // on its own. session.setConfig("MaxAuthTries", "1"); //$NON-NLS-1$ //$NON-NLS-2$ if (pass != null) session.setPassword(pass); final String strictHostKeyCheckingPolicy = hc .getStrictHostKeyChecking(); if (strictHostKeyCheckingPolicy != null) session.setConfig("StrictHostKeyChecking", //$NON-NLS-1$ strictHostKeyCheckingPolicy); final String pauth = hc.getPreferredAuthentications(); if (pauth != null) session.setConfig("PreferredAuthentications", pauth); //$NON-NLS-1$ if (credentialsProvider != null && (!hc.isBatchMode() || !credentialsProvider.isInteractive())) { session.setUserInfo(new CredentialsProviderUserInfo(session, credentialsProvider)); } configure(hc, session); return session; } /** * Create a new remote session for the requested address. * * @param hc * host configuration * @param user * login to authenticate as. * @param host * server name to connect to. * @param port * port number of the SSH daemon (typically 22). * @param fs * the file system abstraction which will be necessary to * perform certain file system operations. * @return new session instance, but otherwise unconfigured. * @throws JSchException * the session could not be created. */ protected Session createSession(final OpenSshConfig.Host hc, final String user, final String host, final int port, FS fs) throws JSchException { return getJSch(hc, fs).getSession(user, host, port); } /** * Provide additional configuration for the JSch instance. This method could * be overridden to supply a preferred * {@link com.jcraft.jsch.IdentityRepository}. * * @param jsch * jsch instance * @since 4.5 */ protected void configureJSch(JSch jsch) { // No additional configuration required. } /** * Provide additional configuration for the session based on the host * information. This method could be used to supply {@link UserInfo}. * * @param hc * host configuration * @param session * session to configure */ protected abstract void configure(OpenSshConfig.Host hc, Session session); /** * Obtain the JSch used to create new sessions. * * @param hc * host configuration * @param fs * the file system abstraction which will be necessary to * perform certain file system operations. * @return the JSch instance to use. * @throws JSchException * the user configuration could not be created. */ protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException { if (defaultJSch == null) { defaultJSch = createDefaultJSch(fs); for (Object name : defaultJSch.getIdentityNames()) byIdentityFile.put((String) name, defaultJSch); } final File identityFile = hc.getIdentityFile(); if (identityFile == null) return defaultJSch; final String identityKey = identityFile.getAbsolutePath(); JSch jsch = byIdentityFile.get(identityKey); if (jsch == null) { jsch = new JSch(); configureJSch(jsch); jsch.setHostKeyRepository(defaultJSch.getHostKeyRepository()); jsch.addIdentity(identityKey); byIdentityFile.put(identityKey, jsch); } return jsch; } /** * @param fs * the file system abstraction which will be necessary to * perform certain file system operations. * @return the new default JSch implementation. * @throws JSchException * known host keys cannot be loaded. */ protected JSch createDefaultJSch(FS fs) throws JSchException { final JSch jsch = new JSch(); configureJSch(jsch); knownHosts(jsch, fs); identities(jsch, fs); return jsch; } private static void knownHosts(final JSch sch, FS fs) throws JSchException { final File home = fs.userHome(); if (home == null) return; final File known_hosts = new File(new File(home, ".ssh"), "known_hosts"); //$NON-NLS-1$ //$NON-NLS-2$ try { final FileInputStream in = new FileInputStream(known_hosts); try { sch.setKnownHosts(in); } finally { in.close(); } } catch (FileNotFoundException none) { // Oh well. They don't have a known hosts in home. } catch (IOException err) { // Oh well. They don't have a known hosts in home. } } private static void identities(final JSch sch, FS fs) { final File home = fs.userHome(); if (home == null) return; final File sshdir = new File(home, ".ssh"); //$NON-NLS-1$ if (sshdir.isDirectory()) { loadIdentity(sch, new File(sshdir, "identity")); //$NON-NLS-1$ loadIdentity(sch, new File(sshdir, "id_rsa")); //$NON-NLS-1$ loadIdentity(sch, new File(sshdir, "id_dsa")); //$NON-NLS-1$ } } private static void loadIdentity(final JSch sch, final File priv) { if (priv.isFile()) { try { sch.addIdentity(priv.getAbsolutePath()); } catch (JSchException e) { // Instead, pretend the key doesn't exist. } } } }