diff --git a/conf/log4j2.xml b/conf/log4j2.xml index f0fde93..d0cc96c 100644 --- a/conf/log4j2.xml +++ b/conf/log4j2.xml @@ -23,11 +23,23 @@ + + + + + + + + + diff --git a/pom.xml b/pom.xml index 2eed628..0f51583 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.example.sshd echo-sshd-server - 1.1.2 + 1.1.3 ECHO SSH SERVER Learning Apache Mina SSHD library @@ -66,6 +66,10 @@ org.apache.commons commons-lang3 + + org.apache.httpcomponents.client5 + httpclient5 + diff --git a/readme.md b/readme.md index 074fee0..b16a56a 100644 --- a/readme.md +++ b/readme.md @@ -8,4 +8,4 @@ I share this project so that others can learn this as well. mvn clean package ## How to run -java -jar echo-sshd-server-.jar +java -jar echo-sshd-server-{version}.jar diff --git a/src/main/java/com/example/sshd/config/AppConfig.java b/src/main/java/com/example/sshd/config/AppConfig.java index cbc418a..7aca54a 100644 --- a/src/main/java/com/example/sshd/config/AppConfig.java +++ b/src/main/java/com/example/sshd/config/AppConfig.java @@ -4,6 +4,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.core5.reactor.IOReactorConfig; import org.apache.sshd.common.Session; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; @@ -18,4 +21,18 @@ public class AppConfig { public Map remoteSessionMapping() { return Collections.synchronizedMap(new HashMap<>()); } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public Map ipInfoMapping() { + return Collections.synchronizedMap(new HashMap<>()); + } + + @Bean + public CloseableHttpAsyncClient asyncClient() { + final IOReactorConfig ioReactorConfig = IOReactorConfig.custom().build(); + final CloseableHttpAsyncClient client = HttpAsyncClients.custom().setIOReactorConfig(ioReactorConfig).build(); + client.start(); + return client; + } } diff --git a/src/main/java/com/example/sshd/core/EchoSessionListener.java b/src/main/java/com/example/sshd/core/EchoSessionListener.java index fc701af..28f5788 100644 --- a/src/main/java/com/example/sshd/core/EchoSessionListener.java +++ b/src/main/java/com/example/sshd/core/EchoSessionListener.java @@ -3,21 +3,39 @@ package com.example.sshd.core; import java.net.InetSocketAddress; import java.util.Map; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.sshd.common.Session; import org.apache.sshd.common.SessionListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class EchoSessionListener implements SessionListener { private static final Logger logger = LoggerFactory.getLogger(EchoSessionListener.class); + private static final Logger ipInfoLogger = LoggerFactory.getLogger("ip_info"); @Autowired Map remoteSessionMapping; + @Autowired + Map ipInfoMapping; + + @Autowired + CloseableHttpAsyncClient asyncClient; + + @Value("${ssh-server.ip-info-api.url:http://ip-api.com/json/%s}") + private String ipInfoApiUrl; + + @Value("${ssh-server.ip-info-api.method:GET}") + private String ipInfoApiMethod; + @Override public void sessionCreated(Session session) { logger.info("sessionCreated: {}", session); @@ -36,6 +54,36 @@ public class EchoSessionListener implements SessionListener { @Override public void sessionEvent(Session session, Event event) { logger.info("sessionEvent: {}, event: {}", session, event); + if (session.getIoSession().getRemoteAddress() instanceof InetSocketAddress && event == Event.KexCompleted) { + InetSocketAddress remoteAddress = (InetSocketAddress) session.getIoSession().getRemoteAddress(); + String remoteIpAddress = remoteAddress.getAddress().getHostAddress(); + if (!ipInfoMapping.containsKey(remoteIpAddress)) { + asyncClient.execute( + SimpleHttpRequest.create(ipInfoApiMethod, String.format(ipInfoApiUrl, remoteIpAddress)), + new FutureCallback() { + + @Override + public void completed(SimpleHttpResponse result) { + logger.info("[{}] asyncClient.execute completed, result: {}, content-type: {}, body: {}", + remoteIpAddress, result, result.getContentType(), result.getBodyText()); + ipInfoMapping.put(remoteIpAddress, result.getBodyText()); + ipInfoLogger.info("[{}] {}", remoteIpAddress, ipInfoMapping.get(remoteIpAddress)); + } + + @Override + public void failed(Exception exception) { + logger.info("[{}] asyncClient.execute failed, exception: {}", remoteIpAddress, exception); + } + + @Override + public void cancelled() { + logger.info("[{}] asyncClient.execute cancelled.", remoteIpAddress); + } + }); + } else { + ipInfoLogger.debug("[{}] {}", remoteIpAddress, ipInfoMapping.get(remoteIpAddress)); + } + } } @Override diff --git a/src/main/java/com/example/sshd/core/EchoShellFactory.java b/src/main/java/com/example/sshd/core/EchoShellFactory.java index 5e6eaf5..5b474e0 100644 --- a/src/main/java/com/example/sshd/core/EchoShellFactory.java +++ b/src/main/java/com/example/sshd/core/EchoShellFactory.java @@ -15,7 +15,6 @@ import org.apache.sshd.server.ExitCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import com.example.sshd.util.ReplyUtil; @@ -26,13 +25,10 @@ public class EchoShellFactory implements Factory { private static final Logger logger = LoggerFactory.getLogger(EchoShellFactory.class); @Autowired - Properties hashReplies; - - @Autowired - Properties regexMapping; + ReplyUtil replyUtil; @Autowired - ApplicationContext applicationContext; + Properties hashReplies; @Override public Command create() { @@ -41,28 +37,12 @@ public class EchoShellFactory implements Factory { public 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; - } + protected InputStream in; + protected OutputStream out; + protected OutputStream err; + protected ExitCallback callback; + protected Environment environment; + protected Thread thread; @Override public void setInputStream(InputStream in) { @@ -87,6 +67,7 @@ public class EchoShellFactory implements Factory { @Override public void start(Environment env) throws IOException { environment = env; + logger.info("environment: {}", environment.getEnv()); thread = new Thread(this, UUID.randomUUID().toString()); thread.start(); } @@ -109,7 +90,7 @@ public class EchoShellFactory implements Factory { while (!Thread.currentThread().isInterrupted()) { int s = r.read(); if (s == 13 || s == 10) { - if (!ReplyUtil.replyToCommand(command, out, prompt, hashReplies, regexMapping)) { + if (!replyUtil.replyToCommand(command, out, prompt)) { out.flush(); return; } diff --git a/src/main/java/com/example/sshd/core/OnetimeCommand.java b/src/main/java/com/example/sshd/core/OnetimeCommand.java index 966a4a4..4d16278 100644 --- a/src/main/java/com/example/sshd/core/OnetimeCommand.java +++ b/src/main/java/com/example/sshd/core/OnetimeCommand.java @@ -3,7 +3,6 @@ package com.example.sshd.core; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Properties; import org.apache.sshd.server.Command; import org.apache.sshd.server.Environment; @@ -18,12 +17,9 @@ import com.example.sshd.util.ReplyUtil; @Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class OnetimeCommand implements Command { - - @Autowired - Properties hashReplies; @Autowired - Properties regexMapping; + ReplyUtil replyUtil; private InputStream in; private OutputStream out; @@ -75,7 +71,7 @@ public class OnetimeCommand implements Command { @Override public void start(Environment env) throws IOException { environment = env; - ReplyUtil.replyToCommand(command, out, "", hashReplies, regexMapping); + replyUtil.replyToCommand(command, out, ""); out.flush(); callback.onExit(0); } diff --git a/src/main/java/com/example/sshd/util/ReplyUtil.java b/src/main/java/com/example/sshd/util/ReplyUtil.java index 238c14b..6051369 100644 --- a/src/main/java/com/example/sshd/util/ReplyUtil.java +++ b/src/main/java/com/example/sshd/util/ReplyUtil.java @@ -10,14 +10,22 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +@Component public class ReplyUtil { private static final Logger logger = LoggerFactory.getLogger(ReplyUtil.class); private static final Logger notFoundLogger = LoggerFactory.getLogger("not_found"); - public static boolean replyToCommand(String command, OutputStream out, String prompt, Properties hashReplies, - Properties regexMapping) throws IOException { + @Autowired + Properties hashReplies; + + @Autowired + Properties regexMapping; + + public boolean replyToCommand(String command, OutputStream out, String prompt) throws IOException { String cmdHash = DigestUtils.md5Hex(command.trim()).toUpperCase();