001/* 002 * Copyright (C) 2012 eXo Platform SAS. 003 * 004 * This is free software; you can redistribute it and/or modify it 005 * under the terms of the GNU Lesser General Public License as 006 * published by the Free Software Foundation; either version 2.1 of 007 * the License, or (at your option) any later version. 008 * 009 * This software is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this software; if not, write to the Free 016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 018 */ 019package org.crsh.ssh.term; 020 021import org.apache.sshd.SshServer; 022import org.apache.sshd.common.KeyPairProvider; 023import org.apache.sshd.common.NamedFactory; 024import org.apache.sshd.common.Session; 025import org.apache.sshd.server.Command; 026import org.apache.sshd.server.PasswordAuthenticator; 027import org.apache.sshd.server.PublickeyAuthenticator; 028import org.apache.sshd.server.ServerFactoryManager; 029import org.apache.sshd.server.session.ServerSession; 030import org.crsh.plugin.PluginContext; 031import org.crsh.auth.AuthenticationPlugin; 032import org.crsh.shell.ShellFactory; 033import org.crsh.ssh.term.scp.SCPCommandFactory; 034import org.crsh.ssh.term.subsystem.SubsystemFactoryPlugin; 035 036import java.security.PublicKey; 037import java.util.ArrayList; 038import java.util.logging.Level; 039import java.util.logging.Logger; 040 041/** 042 * Interesting stuff here : http://gerrit.googlecode.com/git-history/4b9e5e7fb9380cfadd28d7ffe3dc496dc06f5892/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java 043 */ 044public class SSHLifeCycle { 045 046 /** . */ 047 public static final Session.AttributeKey<String> USERNAME = new Session.AttributeKey<java.lang.String>(); 048 049 /** . */ 050 public static final Session.AttributeKey<String> PASSWORD = new Session.AttributeKey<java.lang.String>(); 051 052 /** . */ 053 private final Logger log = Logger.getLogger(SSHLifeCycle.class.getName()); 054 055 /** . */ 056 private SshServer server; 057 058 /** . */ 059 private int port; 060 061 /** . */ 062 private int idleTimeout; 063 064 /** . */ 065 private int authTimeout; 066 067 068 /** . */ 069 private KeyPairProvider keyPairProvider; 070 071 /** . */ 072 private final ArrayList<AuthenticationPlugin> authenticationPlugins; 073 074 /** . */ 075 private Integer localPort; 076 077 /** . */ 078 private final PluginContext context; 079 080 public SSHLifeCycle(PluginContext context, ArrayList<AuthenticationPlugin> authenticationPlugins) { 081 this.authenticationPlugins = authenticationPlugins; 082 this.context = context; 083 } 084 085 public int getPort() { 086 return port; 087 } 088 089 public void setPort(int port) { 090 this.port = port; 091 } 092 093 094 public int getIdleTimeout() { 095 return idleTimeout; 096 } 097 098 public void setIdleTimeout(int idleTimeout) { 099 this.idleTimeout = idleTimeout; 100 } 101 102 public int getAuthTimeout() { 103 return authTimeout; 104 } 105 106 public void setAuthTimeout(int authTimeout) { 107 this.authTimeout = authTimeout; 108 } 109 110 111 /** 112 * Returns the local part after the ssh server has been succesfully bound or null. This is useful when 113 * the port is chosen at random by the system. 114 * 115 * @return the local port 116 */ 117 public Integer getLocalPort() { 118 return localPort; 119 } 120 121 public KeyPairProvider getKeyPairProvider() { 122 return keyPairProvider; 123 } 124 125 public void setKeyPairProvider(KeyPairProvider keyPairProvider) { 126 this.keyPairProvider = keyPairProvider; 127 } 128 129 public void init() { 130 try { 131 132 // 133 ShellFactory factory = context.getPlugin(ShellFactory.class); 134 135 // 136 SshServer server = SshServer.setUpDefaultServer(); 137 server.setPort(port); 138 139 if (this.idleTimeout > 0) { 140 server.getProperties().put(ServerFactoryManager.IDLE_TIMEOUT, String.valueOf(this.idleTimeout)); 141 } 142 if (this.authTimeout > 0) { 143 server.getProperties().put(ServerFactoryManager.AUTH_TIMEOUT, String.valueOf(this.authTimeout)); 144 } 145 146 server.setShellFactory(new CRaSHCommandFactory(factory)); 147 server.setCommandFactory(new SCPCommandFactory(context)); 148 server.setKeyPairProvider(keyPairProvider); 149 150 // 151 ArrayList<NamedFactory<Command>> namedFactoryList = new ArrayList<NamedFactory<Command>>(0); 152 for (SubsystemFactoryPlugin plugin : context.getPlugins(SubsystemFactoryPlugin.class)) { 153 namedFactoryList.add(plugin.getFactory()); 154 } 155 server.setSubsystemFactories(namedFactoryList); 156 157 // 158 for (AuthenticationPlugin authenticationPlugin : authenticationPlugins) { 159 if (server.getPasswordAuthenticator() == null && authenticationPlugin.getCredentialType().equals(String.class)) { 160 server.setPasswordAuthenticator(new PasswordAuthenticator() { 161 public boolean authenticate(String _username, String _password, ServerSession session) { 162 if (genericAuthenticate(String.class, _username, _password)) { 163 // We store username and password in session for later reuse 164 session.setAttribute(USERNAME, _username); 165 session.setAttribute(PASSWORD, _password); 166 return true; 167 } else { 168 return false; 169 } 170 } 171 }); 172 } 173 174 if (server.getPublickeyAuthenticator() == null && authenticationPlugin.getCredentialType().equals(PublicKey.class)) { 175 server.setPublickeyAuthenticator(new PublickeyAuthenticator() { 176 public boolean authenticate(String username, PublicKey key, ServerSession session) { 177 return genericAuthenticate(PublicKey.class, username, key); 178 } 179 }); 180 } 181 } 182 183 // 184 log.log(Level.INFO, "About to start CRaSSHD"); 185 server.start(); 186 localPort = server.getPort(); 187 log.log(Level.INFO, "CRaSSHD started on port " + localPort); 188 189 // 190 this.server = server; 191 } 192 catch (Throwable e) { 193 log.log(Level.SEVERE, "Could not start CRaSSHD", e); 194 } 195 } 196 197 public void destroy() { 198 if (server != null) { 199 try { 200 server.stop(); 201 } 202 catch (InterruptedException e) { 203 log.log(Level.FINE, "Got an interruption when stopping server", e); 204 } 205 } 206 } 207 208 private <T> boolean genericAuthenticate(Class<T> type, String username, T credential) { 209 for (AuthenticationPlugin authenticationPlugin : authenticationPlugins) { 210 if (authenticationPlugin.getCredentialType().equals(type)) { 211 try { 212 log.log(Level.FINE, "Using authentication plugin " + authenticationPlugin + " to authenticate user " + username); 213 @SuppressWarnings("unchecked") 214 AuthenticationPlugin<T> authPlugin = (AuthenticationPlugin<T>) authenticationPlugin; 215 if (authPlugin.authenticate(username, credential)) { 216 return true; 217 } 218 } catch (Exception e) { 219 log.log(Level.SEVERE, "Exception authenticating user " + username + " in authentication plugin: " + authenticationPlugin, e); 220 } 221 } 222 } 223 224 return false; 225 } 226}