--- /dev/null
+package org.argeo.cms.ssh;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+
+import org.apache.sshd.common.forward.PortForwardingEventListener;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
+import org.apache.sshd.scp.server.ScpCommandFactory;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.config.keys.DefaultAuthorizedKeysAuthenticator;
+import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
+import org.apache.sshd.server.jaas.JaasPasswordAuthenticator;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+import org.apache.sshd.server.shell.InteractiveProcessShellFactory;
+import org.apache.sshd.sftp.server.SftpSubsystemFactory;
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsState;
+import org.argeo.cms.CmsDeployProperty;
+
+public class CmsSshServer {
+ private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
+ private static final String DEFAULT_SSH_HOST_KEY_PATH = CmsConstants.NODE + '/' + CmsConstants.NODE + ".ser";
+
+ private CmsState cmsState;
+ private SshServer sshd = null;
+
+ private int port;
+ private String host;
+
+ public void start() {
+ String portStr = cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
+ if (portStr == null)
+ return; // ignore
+ port = Integer.parseInt(portStr);
+
+ host = cmsState.getDeployProperty(CmsDeployProperty.HOST.getProperty());
+
+ Path hostKeyPath = cmsState.getDataPath(DEFAULT_SSH_HOST_KEY_PATH);
+
+ try {
+ sshd = SshServer.setUpDefaultServer();
+ sshd.setPort(port);
+ if (host != null)
+ sshd.setHost(host);
+ if (hostKeyPath == null)
+ throw new IllegalStateException("An SSH server key must be set");
+ sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKeyPath));
+ // sshd.setShellFactory(new ProcessShellFactory(new String[] { "/bin/sh", "-i",
+ // "-l" }));
+// String[] shellCommand = OS.LOCAL.getDefaultShellCommand();
+ // FIXME transfer args
+// sshd.setShellFactory(new ProcessShellFactory(shellCommand));
+ sshd.setShellFactory(InteractiveProcessShellFactory.INSTANCE);
+ sshd.setCommandFactory(new ScpCommandFactory());
+
+ // tunnels
+ sshd.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
+ // sshd.setForwardingFilter(ForwardingFilter.asForwardingFilter(null, null,
+ // TcpForwardingFilter.DEFAULT));
+ // sshd.setForwarderFactory(DefaultForwarderFactory.INSTANCE);
+// TcpForwardingFilter tcpForwardingFilter = sshd.getTcpForwardingFilter();
+ sshd.addPortForwardingEventListener(new PortForwardingEventListener() {
+
+ @Override
+ public void establishingExplicitTunnel(Session session, SshdSocketAddress local,
+ SshdSocketAddress remote, boolean localForwarding) throws IOException {
+ log.debug("Establishing tunnel " + local + ", " + remote);
+ }
+
+ @Override
+ public void establishedExplicitTunnel(Session session, SshdSocketAddress local,
+ SshdSocketAddress remote, boolean localForwarding, SshdSocketAddress boundAddress,
+ Throwable reason) throws IOException {
+ log.debug("Established tunnel " + local + ", " + remote + ", " + boundAddress);
+ }
+
+ @Override
+ public void establishingDynamicTunnel(Session session, SshdSocketAddress local) throws IOException {
+ log.debug("Establishing dynamic tunnel " + local);
+ }
+
+ @Override
+ public void establishedDynamicTunnel(Session session, SshdSocketAddress local,
+ SshdSocketAddress boundAddress, Throwable reason) throws IOException {
+ log.debug("Established dynamic tunnel " + local);
+ }
+
+ });
+
+ // Authentication
+ //sshd.setPublickeyAuthenticator(new DefaultAuthorizedKeysAuthenticator(true));
+ sshd.setPublickeyAuthenticator(null);
+ // sshd.setKeyboardInteractiveAuthenticator(null);
+ JaasPasswordAuthenticator jaasPasswordAuthenticator = new JaasPasswordAuthenticator();
+ jaasPasswordAuthenticator.setDomain(CmsAuth.NODE.getLoginContextName());
+ sshd.setPasswordAuthenticator(jaasPasswordAuthenticator);
+
+ Path krb5keyTab = cmsState.getDataPath("node/krb5.keytab");
+ if (Files.exists(krb5keyTab)) {
+ // FIXME experimental
+ GSSAuthenticator gssAuthenticator = new GSSAuthenticator();
+ gssAuthenticator.setKeytabFile(cmsState.getDataPath("node/krb5.keytab").toString());
+ gssAuthenticator.setServicePrincipalName("HTTP@" + host);
+ sshd.setGSSAuthenticator(gssAuthenticator);
+ }
+
+ // SFTP
+ sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
+
+ // start
+ sshd.start();
+
+ log.debug(() -> "CMS SSH server started on port " + port + (host != null ? " of host " + host : ""));
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot start SSH server on port " + port, e);
+ }
+
+ }
+
+ public void stop() {
+ if (sshd == null)
+ return;
+ try {
+ sshd.stop();
+ log.debug(() -> "CMS SSH server stopped on port " + port + (host != null ? " of host " + host : ""));
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot stop SSH server", e);
+ }
+
+ }
+
+ public void setCmsState(CmsState cmsState) {
+ this.cmsState = cmsState;
+ }
+
+}