blob: 8900d2dd0871d7f10723467eee1da0f4969e1f03 [file] [log] [blame]
/*
* 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.server;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.sshd.common.Closeable;
import org.apache.sshd.common.Factory;
import org.apache.sshd.common.PropertyResolverUtils;
import org.apache.sshd.common.ServiceFactory;
import org.apache.sshd.common.helpers.AbstractFactoryManager;
import org.apache.sshd.common.io.IoAcceptor;
import org.apache.sshd.common.io.IoServiceFactory;
import org.apache.sshd.common.io.IoSession;
import org.apache.sshd.common.keyprovider.HostKeyCertificateProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.session.helpers.AbstractSession;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.core.CoreModuleProperties;
import org.apache.sshd.server.auth.UserAuthFactory;
import org.apache.sshd.server.auth.gss.GSSAuthenticator;
import org.apache.sshd.server.auth.hostbased.HostBasedAuthenticator;
import org.apache.sshd.server.auth.keyboard.KeyboardInteractiveAuthenticator;
import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.command.CommandFactory;
import org.apache.sshd.server.session.ServerConnectionServiceFactory;
import org.apache.sshd.server.session.ServerProxyAcceptor;
import org.apache.sshd.server.session.ServerUserAuthServiceFactory;
import org.apache.sshd.server.session.SessionFactory;
import org.apache.sshd.server.shell.ShellFactory;
import org.apache.sshd.server.subsystem.SubsystemFactory;
/**
* <p>
* The SshServer class is the main entry point for the server side of the SSH protocol.
* </p>
*
* <p>
* 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 Factory}, the
* {@link org.apache.sshd.common.keyprovider.KeyPairProvider} and the {@link PasswordAuthenticator}.
* </p>
*
* <p>
* Some properties can also be configured using the {@link PropertyResolverUtils} {@code updateProperty} methods.
* </p>
*
* Once the SshServer instance has been configured, it can be started using the {@link #start()} method and stopped
* using the {@link #stop()} method.
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
* @see ServerFactoryManager
* @see org.apache.sshd.common.FactoryManager
*/
public class SshServer extends AbstractFactoryManager implements ServerFactoryManager, Closeable {
public static final Factory<SshServer> DEFAULT_SSH_SERVER_FACTORY = SshServer::new;
public static final List<ServiceFactory> DEFAULT_SERVICE_FACTORIES = Collections.unmodifiableList(
Arrays.asList(
ServerUserAuthServiceFactory.INSTANCE,
ServerConnectionServiceFactory.INSTANCE));
protected IoAcceptor acceptor;
protected String host;
protected int port;
private ServerProxyAcceptor proxyAcceptor;
private ShellFactory shellFactory;
private SessionFactory sessionFactory;
private CommandFactory commandFactory;
private List<? extends SubsystemFactory> subsystemFactories;
private List<UserAuthFactory> userAuthFactories;
private KeyPairProvider keyPairProvider;
private HostKeyCertificateProvider hostKeyCertificateProvider;
private PasswordAuthenticator passwordAuthenticator;
private PublickeyAuthenticator publickeyAuthenticator;
private KeyboardInteractiveAuthenticator interactiveAuthenticator;
private HostBasedAuthenticator hostBasedAuthenticator;
private GSSAuthenticator gssAuthenticator;
private final AtomicBoolean started = new AtomicBoolean(false);
public SshServer() {
super();
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
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;
}
/**
* @return The currently bound addresses - valid only after server {@link #start() started} and while not
* {@link #stop() stopped}
*/
public Set<SocketAddress> getBoundAddresses() {
return (acceptor == null) ? Collections.emptySet() : acceptor.getBoundAddresses();
}
@Override
public List<UserAuthFactory> getUserAuthFactories() {
return userAuthFactories;
}
@Override
public void setUserAuthFactories(List<UserAuthFactory> userAuthFactories) {
this.userAuthFactories = userAuthFactories;
}
@Override
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;
}
@Override
public ServerProxyAcceptor getServerProxyAcceptor() {
return proxyAcceptor;
}
@Override
public void setServerProxyAcceptor(ServerProxyAcceptor proxyAcceptor) {
this.proxyAcceptor = proxyAcceptor;
}
@Override
public CommandFactory getCommandFactory() {
return commandFactory;
}
public void setCommandFactory(CommandFactory commandFactory) {
this.commandFactory = commandFactory;
}
@Override
public List<? extends SubsystemFactory> getSubsystemFactories() {
return subsystemFactories;
}
public void setSubsystemFactories(List<? extends SubsystemFactory> subsystemFactories) {
this.subsystemFactories = subsystemFactories;
}
@Override
public PasswordAuthenticator getPasswordAuthenticator() {
return passwordAuthenticator;
}
@Override
public void setPasswordAuthenticator(PasswordAuthenticator passwordAuthenticator) {
this.passwordAuthenticator = passwordAuthenticator;
}
@Override
public PublickeyAuthenticator getPublickeyAuthenticator() {
return publickeyAuthenticator;
}
@Override
public void setPublickeyAuthenticator(PublickeyAuthenticator publickeyAuthenticator) {
this.publickeyAuthenticator = publickeyAuthenticator;
}
@Override
public KeyboardInteractiveAuthenticator getKeyboardInteractiveAuthenticator() {
return interactiveAuthenticator;
}
@Override
public void setKeyboardInteractiveAuthenticator(KeyboardInteractiveAuthenticator interactiveAuthenticator) {
this.interactiveAuthenticator = interactiveAuthenticator;
}
@Override
public GSSAuthenticator getGSSAuthenticator() {
return gssAuthenticator;
}
@Override
public void setGSSAuthenticator(GSSAuthenticator gssAuthenticator) {
this.gssAuthenticator = gssAuthenticator;
}
@Override
public HostBasedAuthenticator getHostBasedAuthenticator() {
return hostBasedAuthenticator;
}
@Override
public void setHostBasedAuthenticator(HostBasedAuthenticator hostBasedAuthenticator) {
this.hostBasedAuthenticator = hostBasedAuthenticator;
}
@Override
public KeyPairProvider getKeyPairProvider() {
return keyPairProvider;
}
@Override
public void setKeyPairProvider(KeyPairProvider keyPairProvider) {
this.keyPairProvider = keyPairProvider;
}
@Override
public HostKeyCertificateProvider getHostKeyCertificateProvider() {
return hostKeyCertificateProvider;
}
@Override
public void setHostKeyCertificateProvider(HostKeyCertificateProvider hostKeyCertificateProvider) {
this.hostKeyCertificateProvider = hostKeyCertificateProvider;
}
@Override
protected void checkConfig() {
super.checkConfig();
ValidateUtils.checkTrue(getPort() >= 0 /* zero means not set yet */, "Bad port number: %d", Integer.valueOf(getPort()));
List<UserAuthFactory> authFactories = ServerAuthenticationManager.resolveUserAuthFactories(this);
setUserAuthFactories(
ValidateUtils.checkNotNullAndNotEmpty(authFactories, "UserAuthFactories not set"));
ValidateUtils.checkNotNullAndNotEmpty(getChannelFactories(), "ChannelFactories not set");
Objects.requireNonNull(getKeyPairProvider(), "HostKeyProvider not set");
Objects.requireNonNull(getFileSystemFactory(), "FileSystemFactory not set");
if (GenericUtils.isEmpty(getServiceFactories())) {
setServiceFactories(DEFAULT_SERVICE_FACTORIES);
}
}
public boolean isStarted() {
return started.get();
}
/**
* Start the SSH server and accept incoming exceptions on the configured port. Ignored if already
* {@link #isStarted() started}
*
* @throws IOException If failed to start
*/
public void start() throws IOException {
if (isClosed()) {
throw new IllegalStateException("Can not start the server again");
}
if (isStarted()) {
return;
}
checkConfig();
if (sessionFactory == null) {
sessionFactory = createSessionFactory();
}
acceptor = createAcceptor();
setupSessionTimeout(sessionFactory);
String hostsList = getHost();
if (!GenericUtils.isEmpty(hostsList)) {
String[] hosts = GenericUtils.split(hostsList, ',');
for (String host : hosts) {
if (log.isDebugEnabled()) {
log.debug("start() - resolve bind host={}", host);
}
InetAddress[] inetAddresses = InetAddress.getAllByName(host);
for (InetAddress inetAddress : inetAddresses) {
if (log.isTraceEnabled()) {
log.trace("start() - bind host={} / {}", host, inetAddress);
}
acceptor.bind(new InetSocketAddress(inetAddress, port));
if (port == 0) {
SocketAddress selectedAddress = GenericUtils.head(acceptor.getBoundAddresses());
port = ((InetSocketAddress) selectedAddress).getPort();
log.info("start() listen on auto-allocated port={}", port);
}
}
}
} else {
acceptor.bind(new InetSocketAddress(port));
if (port == 0) {
SocketAddress selectedAddress = GenericUtils.head(acceptor.getBoundAddresses());
port = ((InetSocketAddress) selectedAddress).getPort();
log.info("start() listen on auto-allocated port={}", port);
}
}
started.set(true);
}
/**
* Stop the SSH server. This method will block until all resources are actually disposed.
*
* @throws IOException if stopping failed somehow
*/
public void stop() throws IOException {
stop(false);
}
public void stop(boolean immediately) throws IOException {
if (!started.getAndSet(false)) {
return;
}
try {
Duration maxWait
= immediately ? CoreModuleProperties.STOP_WAIT_TIME.getRequired(this) : Duration.ofMillis(Long.MAX_VALUE);
boolean successful = close(immediately).await(maxWait);
if (!successful) {
throw new SocketTimeoutException("Failed to receive closure confirmation within " + maxWait + " millis");
}
} finally {
// clear the attributes since we close stop the server
clearAttributes();
}
}
public void open() throws IOException {
start();
}
@Override
protected Closeable getInnerCloseable() {
Object closeId = toString();
return builder()
.run(closeId, () -> removeSessionTimeout(sessionFactory))
.sequential(acceptor, ioServiceFactory)
.run(closeId, () -> {
acceptor = null;
ioServiceFactory = null;
if (shutdownExecutor && (executor != null) && (!executor.isShutdown())) {
try {
executor.shutdownNow();
} finally {
executor = null;
}
}
})
.build();
}
/**
* Obtain the list of active sessions.
*
* @return A {@link List} of the currently active session
*/
public List<AbstractSession> getActiveSessions() {
List<AbstractSession> sessions = new ArrayList<>();
for (IoSession ioSession : acceptor.getManagedSessions().values()) {
AbstractSession session = AbstractSession.getSession(ioSession, true);
if (session != null) {
sessions.add(session);
}
}
return sessions;
}
protected IoAcceptor createAcceptor() {
IoServiceFactory ioFactory = getIoServiceFactory();
SessionFactory sessFactory = getSessionFactory();
return ioFactory.createAcceptor(sessFactory);
}
protected SessionFactory createSessionFactory() {
return new SessionFactory(this);
}
@Override
public String toString() {
return getClass().getSimpleName()
+ "[" + Integer.toHexString(hashCode()) + "]"
+ "(port=" + getPort() + ")";
}
/**
* Setup a default server
*
* @return a newly create {@link SshServer} with default configurations
*/
public static SshServer setUpDefaultServer() {
ServerBuilder builder = ServerBuilder.builder();
return builder.build();
}
}