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 */ 019 package org.crsh.ssh.term; 020 021 import org.apache.sshd.SshServer; 022 import org.apache.sshd.common.KeyPairProvider; 023 import org.apache.sshd.common.NamedFactory; 024 import org.apache.sshd.common.Session; 025 import org.apache.sshd.server.Command; 026 import org.apache.sshd.server.PasswordAuthenticator; 027 import org.apache.sshd.server.PublickeyAuthenticator; 028 import org.apache.sshd.server.ServerFactoryManager; 029 import org.apache.sshd.server.session.ServerSession; 030 import org.crsh.plugin.PluginContext; 031 import org.crsh.auth.AuthenticationPlugin; 032 import org.crsh.shell.ShellFactory; 033 import org.crsh.ssh.term.scp.SCPCommandFactory; 034 import org.crsh.ssh.term.subsystem.SubsystemFactoryPlugin; 035 036 import java.nio.charset.Charset; 037 import java.security.PublicKey; 038 import java.util.ArrayList; 039 import java.util.logging.Level; 040 import java.util.logging.Logger; 041 042 /** 043 * Interesting stuff here : http://gerrit.googlecode.com/git-history/4b9e5e7fb9380cfadd28d7ffe3dc496dc06f5892/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java 044 */ 045 public class SSHLifeCycle { 046 047 /** . */ 048 public static final Session.AttributeKey<String> USERNAME = new Session.AttributeKey<java.lang.String>(); 049 050 /** . */ 051 public static final Session.AttributeKey<String> PASSWORD = new Session.AttributeKey<java.lang.String>(); 052 053 /** . */ 054 private final Logger log = Logger.getLogger(SSHLifeCycle.class.getName()); 055 056 /** . */ 057 private final PluginContext context; 058 059 /** . */ 060 private final int port; 061 062 /** . */ 063 private final int idleTimeout; 064 065 /** . */ 066 private final int authTimeout; 067 068 /** . */ 069 private final Charset encoding; 070 071 /** . */ 072 private final KeyPairProvider keyPairProvider; 073 074 /** . */ 075 private final ArrayList<AuthenticationPlugin> authenticationPlugins; 076 077 /** . */ 078 private SshServer server; 079 080 /** . */ 081 private Integer localPort; 082 083 public SSHLifeCycle( 084 PluginContext context, 085 Charset encoding, 086 int port, 087 int idleTimeout, 088 int authTimeout, 089 KeyPairProvider keyPairProvider, 090 ArrayList<AuthenticationPlugin> authenticationPlugins) { 091 this.authenticationPlugins = authenticationPlugins; 092 this.context = context; 093 this.encoding = encoding; 094 this.port = port; 095 this.idleTimeout = idleTimeout; 096 this.authTimeout = authTimeout; 097 this.keyPairProvider = keyPairProvider; 098 } 099 100 public Charset getEncoding() { 101 return encoding; 102 } 103 104 public int getPort() { 105 return port; 106 } 107 108 public int getIdleTimeout() { 109 return idleTimeout; 110 } 111 112 public int getAuthTimeout() { 113 return authTimeout; 114 } 115 116 117 /** 118 * Returns the local part after the ssh server has been succesfully bound or null. This is useful when 119 * the port is chosen at random by the system. 120 * 121 * @return the local port 122 */ 123 public Integer getLocalPort() { 124 return localPort; 125 } 126 127 public KeyPairProvider getKeyPairProvider() { 128 return keyPairProvider; 129 } 130 131 public void init() { 132 try { 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, encoding)); 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 }