commit a4aec058a1e9cd10584ff9f228b14ef4ef420d31 Author: Ng Yat Yan Date: Sat Oct 12 01:46:29 2024 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8801278 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target/ +/.classpath +/.project +/.settings \ No newline at end of file diff --git a/conf/.gitignore b/conf/.gitignore new file mode 100644 index 0000000..b8bd887 --- /dev/null +++ b/conf/.gitignore @@ -0,0 +1 @@ +/private.key diff --git a/conf/log4j2.xml b/conf/log4j2.xml new file mode 100644 index 0000000..710e29a --- /dev/null +++ b/conf/log4j2.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/conf/springboot.yml b/conf/springboot.yml new file mode 100644 index 0000000..a4628de --- /dev/null +++ b/conf/springboot.yml @@ -0,0 +1,4 @@ +ssh-server: + port: 1022 + private-key: + location: "conf/private.key" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9b71dae --- /dev/null +++ b/pom.xml @@ -0,0 +1,99 @@ + + 4.0.0 + com.example.sshd + echo-sshd-server + 1.0.0 + ECHO SSH SERVER + Learning Apache Mina SSHD library + + org.springframework.boot + spring-boot-starter-parent + 3.3.2 + + + 2.0.25 + 0.14.0 + 1.78.1 + 2.13.0 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.apache.mina + mina-core + ${mina.version} + + + org.apache.sshd + sshd-core + ${sshd.version} + + + org.bouncycastle + bcprov-jdk18on + ${bouncycastle.version} + + + org.bouncycastle + bcpkix-jdk18on + ${bouncycastle.version} + + + commons-io + commons-io + ${commons-io.version} + + + org.apache.commons + commons-lang3 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + org.springframework.boot + spring-boot-maven-plugin + + com.example.sshd.Boot + + + + build-info + + build-info + + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/example/sshd/Boot.java b/src/main/java/com/example/sshd/Boot.java new file mode 100644 index 0000000..08b966b --- /dev/null +++ b/src/main/java/com/example/sshd/Boot.java @@ -0,0 +1,31 @@ +package com.example.sshd; + +import org.apache.sshd.SshServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Boot { + + private static final Logger logger = LoggerFactory.getLogger(Boot.class); + + @Autowired + protected SshServer sshd; + + public static void main(String[] args) throws Exception { + String configDirectory = "conf"; + if (args.length > 0) { + configDirectory = args[0]; + } + logger.info("config directory: {}", configDirectory); + + if (new java.io.File(configDirectory).exists() && new java.io.File(configDirectory).isDirectory()) { + System.setProperty("spring.config.location", configDirectory + "/springboot.yml"); + System.setProperty("logging.config", configDirectory + "/log4j2.xml"); + } + SpringApplication.run(Boot.class, args); + } +} diff --git a/src/main/java/com/example/sshd/config/EchoShellFactory.java b/src/main/java/com/example/sshd/config/EchoShellFactory.java new file mode 100644 index 0000000..b342793 --- /dev/null +++ b/src/main/java/com/example/sshd/config/EchoShellFactory.java @@ -0,0 +1,121 @@ +package com.example.sshd.config; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sshd.common.Factory; +import org.apache.sshd.server.Command; +import org.apache.sshd.server.Environment; +import org.apache.sshd.server.ExitCallback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EchoShellFactory implements Factory { + + private static final Logger logger = LoggerFactory.getLogger(EchoShellFactory.class); + + @Override + public Command create() { + return new EchoShell(); + } + + public static class EchoShell implements Command, Runnable { + + private InputStream in; + private OutputStream out; + private OutputStream err; + private ExitCallback callback; + private Environment environment; + private Thread thread; + + public InputStream getIn() { + return in; + } + + public OutputStream getOut() { + return out; + } + + public OutputStream getErr() { + return err; + } + + public Environment getEnvironment() { + return environment; + } + + @Override + public void setInputStream(InputStream in) { + this.in = in; + } + + @Override + public void setOutputStream(OutputStream out) { + this.out = out; + } + + @Override + public void setErrorStream(OutputStream err) { + this.err = err; + } + + @Override + public void setExitCallback(ExitCallback callback) { + this.callback = callback; + } + + @Override + public void start(Environment env) throws IOException { + environment = env; + thread = new Thread(this, UUID.randomUUID().toString()); + thread.start(); + } + + @Override + public void destroy() { + thread.interrupt(); + } + + @Override + public void run() { + try { + out.write("$ ".getBytes()); + out.flush(); + + BufferedReader r = new BufferedReader(new InputStreamReader(in)); + String command = ""; + + while (!Thread.currentThread().isInterrupted()) { + int s = r.read(); + if (s == 13 || s == 10) { + + if (StringUtils.isBlank(command)) { + logger.info("Blank command detected: {}", command); + out.write(("\r\n$ ").getBytes()); + } else if (StringUtils.equals(command, "exit")) { + logger.info("Exiting command detected: {}", command); + return; + } else { + logger.info("Command not found: {}", command); + out.write(("\r\nCommand '" + command + "' not found. Try 'exit'.\r\n$ ").getBytes()); + } + command = ""; + } else { + out.write(s); + command += (char) s; + } + out.flush(); + } + } catch (Exception e) { + logger.error("run error!", e); + } finally { + callback.onExit(0); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/sshd/config/SshConfig.java b/src/main/java/com/example/sshd/config/SshConfig.java new file mode 100644 index 0000000..f2ff6b0 --- /dev/null +++ b/src/main/java/com/example/sshd/config/SshConfig.java @@ -0,0 +1,44 @@ +package com.example.sshd.config; + +import java.io.File; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sshd.SshServer; +import org.apache.sshd.server.PasswordAuthenticator; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.apache.sshd.server.session.ServerSession; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SshConfig { + + @Value("${ssh-server.port}") + private int port; + + @Value("${ssh-server.private-key.location}") + private String pkLocation; + + @Value("${ssh-server.root.username:root}") + private String rootUsername; + + @Bean + public SshServer sshd() throws IOException, NoSuchAlgorithmException { + SshServer sshd = SshServer.setUpDefaultServer(); + sshd.setPort(port); + sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File(pkLocation).getPath(), "RSA", 2048)); + + sshd.setPasswordAuthenticator(new PasswordAuthenticator() { + @Override + public boolean authenticate(final String username, final String password, final ServerSession session) { + return StringUtils.equals(username, rootUsername); + } + }); + sshd.setShellFactory(new EchoShellFactory()); + sshd.start(); + return sshd; + } +}