/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.InvalidKeyException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.sshd.common.AbstractFactoryManager;
import org.apache.sshd.common.Cipher;
import org.apache.sshd.common.Compression;
import org.apache.sshd.common.KeyExchange;
import org.apache.sshd.common.Mac;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Signature;
import org.apache.sshd.common.cipher.AES128CBC;
import org.apache.sshd.common.cipher.AES192CBC;
import org.apache.sshd.common.cipher.AES256CBC;
import org.apache.sshd.common.cipher.BlowfishCBC;
import org.apache.sshd.common.cipher.TripleDESCBC;
import org.apache.sshd.common.compression.CompressionNone;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.mac.HMACMD5;
import org.apache.sshd.common.mac.HMACMD596;
import org.apache.sshd.common.mac.HMACSHA1;
import org.apache.sshd.common.mac.HMACSHA196;
import org.apache.sshd.common.random.BouncyCastleRandom;
import org.apache.sshd.common.random.JceRandom;
import org.apache.sshd.common.random.SingletonRandomFactory;
import org.apache.sshd.common.signature.SignatureDSA;
import org.apache.sshd.common.signature.SignatureRSA;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.CommandFactory;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.ServerChannel;
import org.apache.sshd.server.ServerFactoryManager;
import org.apache.sshd.server.SessionFactory;
import org.apache.sshd.server.ShellFactory;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPassword;
import org.apache.sshd.server.auth.UserAuthPublicKey;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.shell.ProcessShellFactory;
/**
* The SshServer class is the main entry point for the server side of the SSH protocol.
*
* The SshServer has to be configured before being started. Such configuration can be
* done either using a dependency injection mechanism (such as the Spring framework)
* or programmatically. Basic setup is usually done using the {@link #setUpDefaultServer()}
* method, which will known ciphers, macs, channels, etc...
* Besides this basic setup, a few things have to be manually configured such as the
* port number, {@link ShellFactory}, the {@link org.apache.sshd.common.KeyPairProvider}
* and the {@link PasswordAuthenticator}.
*
* Some properties can also be configured using the {@link #setProperties(java.util.Map)}
* method.
*
* Once the SshServer instance has been configured, it can be started using the
* {@link #start()} method and stopped using the {@link #stop()} method.
*
* @see ServerFactoryManager
* @see org.apache.sshd.common.FactoryManager
*
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class SshServer extends AbstractFactoryManager implements ServerFactoryManager {
private IoAcceptor acceptor;
private int port;
private boolean reuseAddress = true;
private List<NamedFactory<UserAuth>> userAuthFactories;
private List<NamedFactory<ServerChannel>> channelFactories;
private ShellFactory shellFactory;
private SessionFactory sessionFactory;
private CommandFactory commandFactory;
private PasswordAuthenticator passwordAuthenticator;
private PublickeyAuthenticator publickeyAuthenticator;
public SshServer() {
}
public int getPort() {
return port;
}
/**
* Configure the port number to use for this SSH server.
*
* @param port the port number for this SSH server
*/
public void setPort(int port) {
this.port = port;
}
public boolean getReuseAddress() {
return reuseAddress;
}
public void setReuseAddress(boolean reuseAddress) {
this.reuseAddress = reuseAddress;
}
public List<NamedFactory<UserAuth>> getUserAuthFactories() {
return userAuthFactories;
}
public void setUserAuthFactories(List<NamedFactory<UserAuth>> userAuthFactories) {
this.userAuthFactories = userAuthFactories;
}
public List<NamedFactory<ServerChannel>> getChannelFactories() {
return channelFactories;
}
public void setChannelFactories(List<NamedFactory<ServerChannel>> channelFactories) {
this.channelFactories = channelFactories;
}
public ShellFactory getShellFactory() {
return shellFactory;
}
public void setShellFactory(ShellFactory shellFactory) {
this.shellFactory = shellFactory;
}
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public CommandFactory getCommandFactory() {
return commandFactory;
}
public void setCommandFactory(CommandFactory commandFactory) {
this.commandFactory = commandFactory;
}
public PasswordAuthenticator getPasswordAuthenticator() {
return passwordAuthenticator;
}
public void setPasswordAuthenticator(PasswordAuthenticator passwordAuthenticator) {
this.passwordAuthenticator = passwordAuthenticator;
}
public PublickeyAuthenticator getPublickeyAuthenticator() {
return publickeyAuthenticator;
}
public void setPublickeyAuthenticator(PublickeyAuthenticator publickeyAuthenticator) {
this.publickeyAuthenticator = publickeyAuthenticator;
}
protected void checkConfig() {
if (getPort() < 0) {
throw new IllegalArgumentException("Bad port number: " + port);
}
if (getKeyExchangeFactories() == null) {
throw new IllegalArgumentException("KeyExchangeFactories not set");
}
if (getUserAuthFactories() == null) {
throw new IllegalArgumentException("UserAuthFactories not set");
}
if (getCipherFactories() == null) {
throw new IllegalArgumentException("CipherFactories not set");
}
if (getCompressionFactories() == null) {
throw new IllegalArgumentException("CompressionFactories not set");
}
if (getMacFactories() == null) {
throw new IllegalArgumentException("MacFactories not set");
}
if (getChannelFactories() == null) {
throw new IllegalArgumentException("ChannelFactories not set");
}
if (getRandomFactory() == null) {
throw new IllegalArgumentException("RandomFactory not set");
}
if (getKeyPairProvider() == null) {
throw new IllegalArgumentException("HostKeyProvider not set");
}
}
/**
* Start the SSH server and accept incoming exceptions on the configured port.
*
* @throws IOException
*/
public void start() throws IOException {
checkConfig();
acceptor = new NioSocketAcceptor();
((NioSocketAcceptor) acceptor).setReuseAddress(reuseAddress);
SessionFactory handler = sessionFactory;
if (handler == null) {
handler = new SessionFactory();
}
handler.setServer(this);
acceptor.setHandler(handler);
acceptor.bind(new InetSocketAddress(port));
if (port == 0) {
port = ((InetSocketAddress) acceptor.getLocalAddress()).getPort();
}
}
/**
* Stop the SSH server. This method will block until all resources are actually disposed.
*/
public void stop() {
acceptor.dispose();
acceptor = null;
}
public static SshServer setUpDefaultServer() {
SshServer sshd = new SshServer();
// DHG14 uses 2048 bits key which are not supported by the default JCE provider
if (SecurityUtils.isBouncyCastleRegistered()) {
sshd.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
new DHG14.Factory(),
new DHG1.Factory()));
sshd.setRandomFactory(new SingletonRandomFactory(new BouncyCastleRandom.Factory()));
} else {
sshd.setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>>asList(
new DHG1.Factory()));
sshd.setRandomFactory(new SingletonRandomFactory(new JceRandom.Factory()));
}
sshd.setUserAuthFactories(Arrays.<NamedFactory<UserAuth>>asList(
new UserAuthPassword.Factory(),
new UserAuthPublicKey.Factory()));
setUpDefaultCiphers(sshd);
// Compression is not enabled by default
// sshd.setCompressionFactories(Arrays.<NamedFactory<Compression>>asList(
// new CompressionNone.Factory(),
// new CompressionZlib.Factory(),
// new CompressionDelayedZlib.Factory()));
sshd.setCompressionFactories(Arrays.<NamedFactory<Compression>>asList(
new CompressionNone.Factory()));
sshd.setMacFactories(Arrays.<NamedFactory<Mac>>asList(
new HMACMD5.Factory(),
new HMACSHA1.Factory(),
new HMACMD596.Factory(),
new HMACSHA196.Factory()));
sshd.setChannelFactories(Arrays.<NamedFactory<ServerChannel>>asList(
new ChannelSession.Factory()));
sshd.setSignatureFactories(Arrays.<NamedFactory<Signature>>asList(
new SignatureDSA.Factory(),
new SignatureRSA.Factory()));
return sshd;
}
private static void setUpDefaultCiphers(SshServer sshd) {
List<NamedFactory<Cipher>> avail = new LinkedList<NamedFactory<Cipher>>();
avail.add(new AES128CBC.Factory());
avail.add(new TripleDESCBC.Factory());
avail.add(new BlowfishCBC.Factory());
avail.add(new AES192CBC.Factory());
avail.add(new AES256CBC.Factory());
for (Iterator<NamedFactory<Cipher>> i = avail.iterator(); i.hasNext();) {
final NamedFactory<Cipher> f = i.next();
try {
final Cipher c = f.create();
final byte[] key = new byte[c.getBlockSize()];
final byte[] iv = new byte[c.getIVSize()];
c.init(Cipher.Mode.Encrypt, key, iv);
} catch (InvalidKeyException e) {
i.remove();
} catch (Exception e) {
i.remove();
}
}
sshd.setCipherFactories(avail);
}
/*=================================
Main class implementation
*=================================*/
public static void main(String[] args) throws Exception {
int port = 8000;
boolean error = false;
for (int i = 0; i < args.length; i++) {
if ("-p".equals(args[i])) {
if (i + 1 >= args.length) {
System.err.println("option requires an argument: " + args[i]);
break;
}
port = Integer.parseInt(args[++i]);
} else if (args[i].startsWith("-")) {
System.err.println("illegal option: " + args[i]);
error = true;
break;
} else {
System.err.println("extra argument: " + args[i]);
error = true;
break;
}
}
if (error) {
System.err.println("usage: sshd [-p port]");
System.exit(-1);
}
SshServer sshd = SshServer.setUpDefaultServer();
sshd.setPort(port);
sshd.setKeyPairProvider(new FileKeyPairProvider(new String[] { "/etc/ssh_host_rsa_key", "/etc/ssh_host_dsa_key" }));
//sshd.setShellFactory(new ProcessShellFactory(new String[] { "/usr/bin/login", "-f", "-h", "localhost", "$USER", "/bin/sh", "-i" }));
sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i", "-l" }));
//sshd.setPasswordAuthenticator(new PAMPasswordAuthenticator());
sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
public Object authenticate(String username, String password, ServerSession session) {
return (username != null && username.equals(password)) ? username : null;
}
});
sshd.start();
}
}