Add support for ~/.ssh/config BatchMode
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / DefaultSshSessionFactory.java
blob89beab7eb6349de9fe5fd2c6433d299eae1c77af
1 /*
2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * All rights reserved.
7 * Redistribution and use in source and binary forms, with or
8 * without modification, are permitted provided that the following
9 * conditions are met:
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * - Neither the name of the Git Development Community nor the
20 * names of its contributors may be used to endorse or promote
21 * products derived from this software without specific prior
22 * written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
25 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
26 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 package org.spearce.jgit.transport;
41 import java.awt.Container;
42 import java.awt.GridBagConstraints;
43 import java.awt.GridBagLayout;
44 import java.awt.Insets;
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.FileNotFoundException;
48 import java.io.IOException;
49 import java.io.OutputStream;
50 import java.security.AccessController;
51 import java.security.PrivilegedAction;
52 import java.util.HashSet;
53 import java.util.Set;
55 import javax.swing.JLabel;
56 import javax.swing.JOptionPane;
57 import javax.swing.JPanel;
58 import javax.swing.JPasswordField;
59 import javax.swing.JTextField;
61 import org.spearce.jgit.util.FS;
63 import com.jcraft.jsch.JSch;
64 import com.jcraft.jsch.JSchException;
65 import com.jcraft.jsch.Session;
66 import com.jcraft.jsch.UIKeyboardInteractive;
67 import com.jcraft.jsch.UserInfo;
69 /**
70 * Loads known hosts and private keys from <code>$HOME/.ssh</code>.
71 * <p>
72 * This is the default implementation used by JGit and provides most of the
73 * compatibility necessary to match OpenSSH, a popular implementation of SSH
74 * used by C Git.
75 * <p>
76 * If user interactivity is required by SSH (e.g. to obtain a password) AWT is
77 * used to display a password input field to the end-user.
79 class DefaultSshSessionFactory extends SshSessionFactory {
80 /** IANA assigned port number for SSH. */
81 static final int SSH_PORT = 22;
83 private Set<String> loadedIdentities;
85 private JSch userJSch;
87 private OpenSshConfig config;
89 @Override
90 public synchronized Session getSession(String user, String pass,
91 String host, int port) throws JSchException {
92 final OpenSshConfig.Host hc = getConfig().lookup(host);
93 host = hc.getHostName();
94 if (port <= 0)
95 port = hc.getPort();
96 if (user == null)
97 user = hc.getUser();
99 final Session session = getUserJSch().getSession(user, host, port);
100 if (hc.getIdentityFile() != null)
101 addIdentity(hc.getIdentityFile());
102 if (pass != null)
103 session.setPassword(pass);
104 else if (!hc.isBatchMode())
105 session.setUserInfo(new AWT_UserInfo());
107 final String pauth = hc.getPreferredAuthentications();
108 if (pauth != null)
109 session.setConfig("PreferredAuthentications", pauth);
110 return session;
113 static String userName() {
114 return AccessController.doPrivileged(new PrivilegedAction<String>() {
115 public String run() {
116 return System.getProperty("user.name");
121 private JSch getUserJSch() throws JSchException {
122 if (userJSch == null) {
123 loadedIdentities = new HashSet<String>();
124 userJSch = new JSch();
125 knownHosts(userJSch);
126 identities();
128 return userJSch;
131 private OpenSshConfig getConfig() {
132 if (config == null)
133 config = OpenSshConfig.get();
134 return config;
137 private void knownHosts(final JSch sch) throws JSchException {
138 final File home = FS.userHome();
139 if (home == null)
140 return;
141 final File known_hosts = new File(new File(home, ".ssh"), "known_hosts");
142 try {
143 final FileInputStream in = new FileInputStream(known_hosts);
144 try {
145 sch.setKnownHosts(in);
146 } finally {
147 in.close();
149 } catch (FileNotFoundException none) {
150 // Oh well. They don't have a known hosts in home.
151 } catch (IOException err) {
152 // Oh well. They don't have a known hosts in home.
156 private void identities() throws JSchException {
157 final File home = FS.userHome();
158 if (home == null)
159 return;
160 final File sshdir = new File(home, ".ssh");
161 final File[] keys = sshdir.listFiles();
162 if (keys == null)
163 return;
164 for (int i = 0; i < keys.length; i++) {
165 final File pk = keys[i];
166 final String n = pk.getName();
167 if (!n.endsWith(".pub"))
168 continue;
169 final File k = new File(sshdir, n.substring(0, n.length() - 4));
170 if (!k.isFile())
171 continue;
173 try {
174 addIdentity(k);
175 } catch (JSchException e) {
176 continue;
181 private void addIdentity(final File identityFile) throws JSchException {
182 final String path = identityFile.getAbsolutePath();
183 if (!loadedIdentities.contains(path)) {
184 userJSch.addIdentity(path);
185 loadedIdentities.add(path);
189 private static class AWT_UserInfo implements UserInfo,
190 UIKeyboardInteractive {
191 private String passwd;
193 private String passphrase;
195 public void showMessage(final String msg) {
196 JOptionPane.showMessageDialog(null, msg);
199 public boolean promptYesNo(final String msg) {
200 return JOptionPane.showConfirmDialog(null, msg, "Warning",
201 JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
204 public boolean promptPassword(final String msg) {
205 passwd = null;
206 final JPasswordField passwordField = new JPasswordField(20);
207 final int result = JOptionPane.showConfirmDialog(null,
208 new Object[] { passwordField }, msg,
209 JOptionPane.OK_CANCEL_OPTION);
210 if (result == JOptionPane.OK_OPTION) {
211 passwd = new String(passwordField.getPassword());
212 return true;
214 return false;
217 public boolean promptPassphrase(final String msg) {
218 passphrase = null;
219 final JPasswordField passwordField = new JPasswordField(20);
220 final int result = JOptionPane.showConfirmDialog(null,
221 new Object[] { passwordField }, msg,
222 JOptionPane.OK_CANCEL_OPTION);
223 if (result == JOptionPane.OK_OPTION) {
224 passphrase = new String(passwordField.getPassword());
225 return true;
227 return false;
230 public String getPassword() {
231 return passwd;
234 public String getPassphrase() {
235 return passphrase;
238 public String[] promptKeyboardInteractive(final String destination,
239 final String name, final String instruction,
240 final String[] prompt, final boolean[] echo) {
241 final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1,
242 1, 1, GridBagConstraints.NORTHWEST,
243 GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);
244 final Container panel = new JPanel();
245 panel.setLayout(new GridBagLayout());
247 gbc.weightx = 1.0;
248 gbc.gridwidth = GridBagConstraints.REMAINDER;
249 gbc.gridx = 0;
250 panel.add(new JLabel(instruction), gbc);
251 gbc.gridy++;
253 gbc.gridwidth = GridBagConstraints.RELATIVE;
255 final JTextField[] texts = new JTextField[prompt.length];
256 for (int i = 0; i < prompt.length; i++) {
257 gbc.fill = GridBagConstraints.NONE;
258 gbc.gridx = 0;
259 gbc.weightx = 1;
260 panel.add(new JLabel(prompt[i]), gbc);
262 gbc.gridx = 1;
263 gbc.fill = GridBagConstraints.HORIZONTAL;
264 gbc.weighty = 1;
265 if (echo[i]) {
266 texts[i] = new JTextField(20);
267 } else {
268 texts[i] = new JPasswordField(20);
270 panel.add(texts[i], gbc);
271 gbc.gridy++;
274 if (JOptionPane.showConfirmDialog(null, panel, destination + ": "
275 + name, JOptionPane.OK_CANCEL_OPTION,
276 JOptionPane.QUESTION_MESSAGE) == JOptionPane.OK_OPTION) {
277 String[] response = new String[prompt.length];
278 for (int i = 0; i < prompt.length; i++) {
279 response[i] = texts[i].getText();
281 return response;
283 return null; // cancel
287 @Override
288 public OutputStream getErrorStream() {
289 return new OutputStream() {
290 private StringBuilder all = new StringBuilder();
292 private StringBuilder sb = new StringBuilder();
294 public String toString() {
295 String r = all.toString();
296 while (r.endsWith("\n"))
297 r = r.substring(0, r.length() - 1);
298 return r;
301 @Override
302 public void write(final int b) throws IOException {
303 if (b == '\r') {
304 System.err.print('\r');
305 return;
308 sb.append((char) b);
310 if (b == '\n') {
311 final String line = sb.toString();
312 System.err.print(line);
313 all.append(line);
314 sb = new StringBuilder();