All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.jct.ssh.simple.SimpleConsoleSshServer Maven / Gradle / Ivy


/*
 * Copyright (C) 2023 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.jct.ssh.simple;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.file.Path;
import java.security.GeneralSecurityException;

import org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
import org.apache.sshd.common.keyprovider.ClassLoadableResourceKeyPairProvider;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
import org.apache.sshd.server.config.keys.AuthorizedKeysAuthenticator;
import org.dellroad.jct.core.Exec;
import org.dellroad.jct.core.Shell;
import org.dellroad.jct.ssh.JctExecFactory;
import org.dellroad.jct.ssh.JctShellFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A simple SSH server exposing a {@link Shell} and/or and {@link Exec}.
 *
 * 

* The only supported authentication type is public key authentication. * *

* Instances must be {@link #start start()}'ed before use and should be {@link #stop stop()}'ed when no longer needed. */ public class SimpleConsoleSshServer implements Closeable { private static final String LOOPBACK_HOST_ADDRESS = "127.0.0.1"; protected final Logger log = LoggerFactory.getLogger(this.getClass()); protected final Exec exec; protected final Shell shell; protected final int listenPort; protected final boolean loopbackOnly; protected final PublickeyAuthenticator authenticator; protected final KeyPairProvider hostKeyProvider; protected SshServer sshd; /** * Constructor. * * @param builder configuration */ protected SimpleConsoleSshServer(Builder builder) { this.exec = builder.exec; this.shell = builder.shell; this.listenPort = builder.listenPort; this.loopbackOnly = builder.loopbackOnly; this.authenticator = builder.authenticator; this.hostKeyProvider = builder.hostKeyProvider; if (this.exec == null && this.shell == null) throw new IllegalArgumentException("no exec or shell configured"); if (this.authenticator == null) throw new IllegalArgumentException("no authenticator configured"); if (this.hostKeyProvider == null) throw new IllegalArgumentException("no host key provider configured"); } // Public methods /** * Create an instance builder. * * @return new instance builder */ public static Builder builder() { return new Builder(); } // SelectorSupport /** * Start this instance. * *

* Does nothing if already started. * * @throws IOException if an I/O error occurs setting up the listen socket */ public synchronized void start() throws IOException { // Already running? if (this.sshd != null) return; // Configure and start server boolean success = false; try { // Create server this.sshd = SshServer.setUpDefaultServer(); // Configure listen port this.sshd.setPort(this.listenPort); if (this.loopbackOnly) this.sshd.setHost(LOOPBACK_HOST_ADDRESS); // Configure security stuff this.sshd.setPublickeyAuthenticator(this.authenticator); this.sshd.setKeyPairProvider(this.hostKeyProvider); // Connect to console if (this.shell != null) this.sshd.setShellFactory(new JctShellFactory(this.shell)); if (this.exec != null) this.sshd.setCommandFactory(new JctExecFactory(this.exec)); // Start server this.sshd.start(); // Done success = true; } finally { if (!success) this.stop(); } } /** * Stop this instance. */ public synchronized void stop() { if (this.sshd == null) return; try { this.sshd.stop(); } catch (IOException e) { // ignore } finally { this.sshd = null; } } // Closeable /** * Close this instance. * *

* Delegates to {@link #stop}. */ @Override public void close() { this.stop(); } // Builder /** * Builder for new {@link SimpleConsoleSshServer} instances. * *

* The following properties are required: console, authenticator, and host key. */ public static final class Builder { private Exec exec; private Shell shell; private int listenPort = SshConstants.DEFAULT_PORT; private boolean loopbackOnly = true; private PublickeyAuthenticator authenticator; private KeyPairProvider hostKeyProvider; private Builder() { } /** * Configure the {@link Exec} that successful incoming execute connections should connect to. * *

* This property or {@code #shell shell()} is required. * * @param exec target exec * @return this instance */ public Builder exec(Exec exec) { this.exec = exec; return this; } /** * Configure the {@link Shell} that successful incoming shell connections should connect to. * *

* This property or {@code #shell shell()} is required. * * @param shell target shell * @return this instance */ public Builder shell(Shell shell) { this.shell = shell; return this; } /** * Configure whether to listen for new connections only on the loopback interface, or on all interfaces. * *

* Default is to listen only on the loopback interface. * * @param loopbackOnly true to listen for new connections only on the loopback interface * @return this instance */ public Builder loopbackOnly(boolean loopbackOnly) { this.loopbackOnly = loopbackOnly; return this; } /** * Configure the TCP port on which to listen for new connections. * *

* Default is {@link SshConstants#DEFAULT_PORT}. * * @param port TCP port to listen on * @return this instance * @throws IllegalArgumentException if {@code port} is invalid */ public Builder listenPort(int port) { if (port < 1 || port > 65535) throw new IllegalArgumentException("invalid port"); this.listenPort = port; return this; } // PublickeyAuthenticator /** * Configure the public key authenticator that authenticates new incoming connections. * * @param authenticator public key authenticator * @return this instance */ public Builder authenticator(PublickeyAuthenticator authenticator) { this.authenticator = authenticator; return this; } /** * Read authorized users' public keys from a {@link String}. * * @param authorizedKeys the content of an openssh {@code authorized_keys} file * @return this instance * @throws IOException if {@code authorizedKeys} contains invalid content * @throws GeneralSecurityException if public key data could not be parsed * @throws IllegalArgumentException if {@code authorizedKeys} is null */ public Builder authorizedKeys(String authorizedKeys) throws IOException, GeneralSecurityException { if (authorizedKeys == null) throw new IllegalArgumentException("null authorizedKeys"); this.authenticator = PublickeyAuthenticator.fromAuthorizedEntries("id", null, AuthorizedKeyEntry.readAuthorizedKeys(new StringReader(authorizedKeys), true), null); return this; } /** * Read authorized users' public keys from an {@link InputStream}. * *

* The given stream will not be closed by this method. * * @param authorizedKeys input from an openssh {@code authorized_keys} file * @return this instance * @throws IOException if {@code authorizedKeys} contains invalid content * @throws GeneralSecurityException if public key data could not be parsed * @throws IllegalArgumentException if {@code authorizedKeys} is null */ public Builder authorizedKeys(InputStream authorizedKeys) throws IOException, GeneralSecurityException { if (authorizedKeys == null) throw new IllegalArgumentException("null authorizedKeys"); this.authenticator = PublickeyAuthenticator.fromAuthorizedEntries("id", null, AuthorizedKeyEntry.readAuthorizedKeys(authorizedKeys, false), null); return this; } /** * Configurea authorized users' public keys to be read from a file at connection time. * * @param authorizedKeys openssh {@code authorized_keys} file * @return this instance * @throws IllegalArgumentException if {@code authorizedKeys} is null */ public Builder authorizedKeys(Path authorizedKeys) { if (authorizedKeys == null) throw new IllegalArgumentException("null authorizedKeys"); this.authenticator = new AuthorizedKeysAuthenticator(authorizedKeys); return this; } // KeyPairProvider /** * Configure the host key provider. * * @param hostKeyProvider public key provider for host keys * @return this instance */ public Builder hostKeyProvider(KeyPairProvider hostKeyProvider) { this.hostKeyProvider = hostKeyProvider; return this; } /** * Configure the host key to be read from a file at connection time. * * @param hostKey openssh host key file * @return this instance * @throws IllegalArgumentException if {@code hostKey} is null */ public Builder hostKey(Path hostKey) { if (hostKey == null) throw new IllegalArgumentException("null hostKey"); this.hostKeyProvider = new FileKeyPairProvider(hostKey); return this; } /** * Configure the host key to be read from a classpath resource at connection time. * * @param loader class loader * @param resource classpath resource * @return this instance * @throws IllegalArgumentException if either parameter is null */ public Builder hostKey(ClassLoader loader, String resource) { if (loader == null) throw new IllegalArgumentException("null loader"); if (resource == null) throw new IllegalArgumentException("null resource"); this.hostKeyProvider = new ClassLoadableResourceKeyPairProvider(loader, resource); return this; } // Build /** * Build a new {@link SimpleConsoleSshServer} based on this instance. * * @return new server configured by this instance * @throws IllegalArgumentException if this builder is incompletely configured */ public SimpleConsoleSshServer build() { return new SimpleConsoleSshServer(this); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy