Correctly honor IdentityFile from ~/.ssh/config
[jgit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / SshConfigSessionFactory.java
blob4d29829d2e015c32e02381dc488a44b008b554bb
1 /*
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2009, JetBrains s.r.o.
5 * Copyright (C) 2009, Google, Inc.
7 * All rights reserved.
9 * Redistribution and use in source and binary forms, with or
10 * without modification, are permitted provided that the following
11 * conditions are met:
13 * - Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
16 * - Redistributions in binary form must reproduce the above
17 * copyright notice, this list of conditions and the following
18 * disclaimer in the documentation and/or other materials provided
19 * with the distribution.
21 * - Neither the name of the Git Development Community nor the
22 * names of its contributors may be used to endorse or promote
23 * products derived from this software without specific prior
24 * written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
27 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
28 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
31 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
33 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 package org.spearce.jgit.transport;
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.io.IOException;
46 import java.io.OutputStream;
47 import java.util.HashMap;
48 import java.util.Map;
50 import org.spearce.jgit.util.FS;
52 import com.jcraft.jsch.JSch;
53 import com.jcraft.jsch.JSchException;
54 import com.jcraft.jsch.Session;
55 import com.jcraft.jsch.UserInfo;
57 /**
58 * The base session factory that loads known hosts and private keys from
59 * <code>$HOME/.ssh</code>.
60 * <p>
61 * This is the default implementation used by JGit and provides most of the
62 * compatibility necessary to match OpenSSH, a popular implementation of SSH
63 * used by C Git.
64 * <p>
65 * The factory does not provide UI behavior. Override the method
66 * {@link #configure(org.spearce.jgit.transport.OpenSshConfig.Host, Session)}
67 * to supply appropriate {@link UserInfo} to the session.
69 public abstract class SshConfigSessionFactory extends SshSessionFactory {
70 private final Map<String, JSch> byIdentityFile = new HashMap<String, JSch>();
72 private JSch defaultJSch;
74 private OpenSshConfig config;
76 @Override
77 public synchronized Session getSession(String user, String pass,
78 String host, int port) throws JSchException {
79 final OpenSshConfig.Host hc = getConfig().lookup(host);
80 host = hc.getHostName();
81 if (port <= 0)
82 port = hc.getPort();
83 if (user == null)
84 user = hc.getUser();
86 final Session session = createSession(hc, user, host, port);
87 if (pass != null)
88 session.setPassword(pass);
89 final String strictHostKeyCheckingPolicy = hc
90 .getStrictHostKeyChecking();
91 if (strictHostKeyCheckingPolicy != null)
92 session.setConfig("StrictHostKeyChecking",
93 strictHostKeyCheckingPolicy);
94 final String pauth = hc.getPreferredAuthentications();
95 if (pauth != null)
96 session.setConfig("PreferredAuthentications", pauth);
97 configure(hc, session);
98 return session;
102 * Create a new JSch session for the requested address.
104 * @param hc
105 * host configuration
106 * @param user
107 * login to authenticate as.
108 * @param host
109 * server name to connect to.
110 * @param port
111 * port number of the SSH daemon (typically 22).
112 * @return new session instance, but otherwise unconfigured.
113 * @throws JSchException
114 * the session could not be created.
116 protected Session createSession(final OpenSshConfig.Host hc,
117 final String user, final String host, final int port)
118 throws JSchException {
119 return getJSch(hc).getSession(user, host, port);
123 * Provide additional configuration for the session based on the host
124 * information. This method could be used to supply {@link UserInfo}.
126 * @param hc
127 * host configuration
128 * @param session
129 * session to configure
131 protected abstract void configure(OpenSshConfig.Host hc, Session session);
134 * Obtain the JSch used to create new sessions.
136 * @param hc
137 * host configuration
138 * @return the JSch instance to use.
139 * @throws JSchException
140 * the user configuration could not be created.
142 protected JSch getJSch(final OpenSshConfig.Host hc) throws JSchException {
143 final JSch def = getDefaultJSch();
144 final File identityFile = hc.getIdentityFile();
145 if (identityFile == null) {
146 return def;
149 final String identityKey = identityFile.getAbsolutePath();
150 JSch jsch = byIdentityFile.get(identityKey);
151 if (jsch == null) {
152 jsch = new JSch();
153 jsch.setHostKeyRepository(def.getHostKeyRepository());
154 jsch.addIdentity(identityKey);
155 byIdentityFile.put(identityKey, jsch);
157 return jsch;
160 private JSch getDefaultJSch() throws JSchException {
161 if (defaultJSch == null) {
162 defaultJSch = createDefaultJSch();
163 for (Object name : defaultJSch.getIdentityNames()) {
164 byIdentityFile.put((String) name, defaultJSch);
167 return defaultJSch;
171 * @return the new default JSch implementation.
172 * @throws JSchException
173 * known host keys cannot be loaded.
175 protected JSch createDefaultJSch() throws JSchException {
176 final JSch jsch = new JSch();
177 knownHosts(jsch);
178 identities(jsch);
179 return jsch;
182 private OpenSshConfig getConfig() {
183 if (config == null)
184 config = OpenSshConfig.get();
185 return config;
188 private static void knownHosts(final JSch sch) throws JSchException {
189 final File home = FS.userHome();
190 if (home == null)
191 return;
192 final File known_hosts = new File(new File(home, ".ssh"), "known_hosts");
193 try {
194 final FileInputStream in = new FileInputStream(known_hosts);
195 try {
196 sch.setKnownHosts(in);
197 } finally {
198 in.close();
200 } catch (FileNotFoundException none) {
201 // Oh well. They don't have a known hosts in home.
202 } catch (IOException err) {
203 // Oh well. They don't have a known hosts in home.
207 private static void identities(final JSch sch) {
208 final File home = FS.userHome();
209 if (home == null)
210 return;
211 final File sshdir = new File(home, ".ssh");
212 if (sshdir.isDirectory()) {
213 loadIdentity(sch, new File(sshdir, "identity"));
214 loadIdentity(sch, new File(sshdir, "id_rsa"));
215 loadIdentity(sch, new File(sshdir, "id_dsa"));
219 private static void loadIdentity(final JSch sch, final File priv) {
220 if (priv.isFile()) {
221 try {
222 sch.addIdentity(priv.getAbsolutePath());
223 } catch (JSchException e) {
224 // Instead, pretend the key doesn't exist.
229 @Override
230 public OutputStream getErrorStream() {
231 return new OutputStream() {
232 private StringBuilder all = new StringBuilder();
234 private StringBuilder sb = new StringBuilder();
236 public String toString() {
237 String r = all.toString();
238 while (r.endsWith("\n"))
239 r = r.substring(0, r.length() - 1);
240 return r;
243 @Override
244 public void write(final int b) throws IOException {
245 if (b == '\r') {
246 System.err.print('\r');
247 return;
250 sb.append((char) b);
252 if (b == '\n') {
253 final String line = sb.toString();
254 System.err.print(line);
255 all.append(line);
256 sb = new StringBuilder();