V2.1.0 Attempts to integrate XEP-0133

master
Ng Yat Yan 2 months ago
parent 10ff309803
commit 30bf0350b7

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.example.sshd</groupId> <groupId>com.example.sshd</groupId>
<artifactId>echo-sshd-server</artifactId> <artifactId>echo-sshd-server</artifactId>
<version>2.0.0</version> <version>2.1.0</version>
<name>ECHO SSH SERVER</name> <name>ECHO SSH SERVER</name>
<description>Learning Apache Mina SSHD library</description> <description>Learning Apache Mina SSHD library</description>
<parent> <parent>
@ -44,6 +44,10 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId> <artifactId>jackson-core</artifactId>

@ -1,6 +1,7 @@
package com.example.sshd.config; package com.example.sshd.config;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -15,10 +16,13 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.xmpp.packet.Message;
import com.fasterxml.jackson.core.exc.StreamReadException; import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
@Configuration @Configuration
public class AppConfig { public class AppConfig {
@ -32,6 +36,11 @@ public class AppConfig {
return new ObjectMapper().readValue(new java.io.File(xmppComponentConfigJson), XmppComponentConfig.class); return new ObjectMapper().readValue(new java.io.File(xmppComponentConfigJson), XmppComponentConfig.class);
} }
@Bean("userAdminCache")
public Cache<String, Message> userAdminCache() {
return Caffeine.newBuilder().expireAfterWrite(Duration.ofMinutes(1)).build();
}
@Bean @Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Map<String, Session> remoteSessionMapping() { public Map<String, Session> remoteSessionMapping() {

@ -1,16 +1,22 @@
package com.example.sshd.service; package com.example.sshd.service;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.jivesoftware.whack.ExternalComponentManager; import org.jivesoftware.whack.ExternalComponentManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.xmpp.component.AbstractComponent; import org.xmpp.component.AbstractComponent;
import org.xmpp.component.ComponentException; import org.xmpp.component.ComponentException;
import org.xmpp.packet.IQ;
import org.xmpp.packet.Message; import org.xmpp.packet.Message;
import org.xmpp.packet.IQ.Type;
import com.example.sshd.config.XmppComponentConfig; import com.example.sshd.config.XmppComponentConfig;
import com.github.benmanes.caffeine.cache.Cache;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@ -18,10 +24,17 @@ import jakarta.annotation.PostConstruct;
public class EchoComponent extends AbstractComponent { public class EchoComponent extends AbstractComponent {
private static final Logger logger = LoggerFactory.getLogger(EchoComponent.class); private static final Logger logger = LoggerFactory.getLogger(EchoComponent.class);
public static final String CONST_OPERATION_ADD_USER = "adduser";
public static final String CONST_OPERATION_CHANGE_USER_PASSWORD = "chgpasswd";
public static final String CONST_OPERATION_DELETE_USER = "deluser";
@Autowired @Autowired
XmppComponentConfig xmppComponentConfig; XmppComponentConfig xmppComponentConfig;
@Autowired
@Qualifier("userAdminCache")
private volatile Cache<String, Message> userAdminCache;
ExternalComponentManager externalComponentManager = null; ExternalComponentManager externalComponentManager = null;
@PostConstruct @PostConstruct
@ -35,6 +48,13 @@ public class EchoComponent extends AbstractComponent {
xmppComponentConfig.getSecretKey()); xmppComponentConfig.getSecretKey());
externalComponentManager.addComponent(xmppComponentConfig.getSubdomainPrefix(), this, externalComponentManager.addComponent(xmppComponentConfig.getSubdomainPrefix(), this,
xmppComponentConfig.getPort()); xmppComponentConfig.getPort());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
externalComponentManager.removeComponent(xmppComponentConfig.getSubdomainPrefix());
} catch (ComponentException e) {
e.printStackTrace();
}
}));
} }
@Override @Override
@ -47,8 +67,8 @@ public class EchoComponent extends AbstractComponent {
return this.getClass().getName(); return this.getClass().getName();
} }
protected void handleMessage(final Message inMsg) { private void doEcho(final Message inMsg, String body) {
logger.info("-- RECEIVED -- {}", inMsg); try {
Message outMsg = new Message(); Message outMsg = new Message();
outMsg.setType(inMsg.getType()); outMsg.setType(inMsg.getType());
outMsg.setFrom(inMsg.getTo()); outMsg.setFrom(inMsg.getTo());
@ -58,9 +78,211 @@ public class EchoComponent extends AbstractComponent {
outMsg.setTo(inMsg.getFrom()); outMsg.setTo(inMsg.getFrom());
} }
outMsg.setSubject(inMsg.getSubject()); outMsg.setSubject(inMsg.getSubject());
outMsg.setBody(inMsg.getBody()); outMsg.setBody(body == null ? inMsg.getBody() : body);
externalComponentManager.sendPacket(this, outMsg); externalComponentManager.sendPacket(this, outMsg);
logger.info("-- SENT -- {}", outMsg); logger.info("[doEcho] -- SENT -- {}", outMsg);
} catch (Exception err) {
logger.error("[doEcho] ", err);
}
}
protected void handleMessage(final Message inMsg) {
logger.info("[handleMessage] -- RECEIVED -- {}", inMsg);
try {
if (StringUtils.isNotBlank(inMsg.getBody())) {
String[] commandParts = StringUtils.split(inMsg.getBody(), ' ');
switch (commandParts[0]) {
case CONST_OPERATION_ADD_USER:
if (commandParts.length == 3)
requestAddUserForm(inMsg);
else
doEcho(inMsg, "adduser <username> <password>");
break;
case CONST_OPERATION_DELETE_USER:
if (commandParts.length == 2)
requestDeleteUserForm(inMsg);
else
doEcho(inMsg, "deluser <username>");
break;
case CONST_OPERATION_CHANGE_USER_PASSWORD:
if (commandParts.length == 3)
requestChangeUserPassword(inMsg);
else
doEcho(inMsg, "chgpasswd <username> <new_password>");
break;
default:
doEcho(inMsg, null);
break;
}
}
} catch (Exception err) {
logger.error("[handleMessage] ", err);
}
}
@Override
protected void handleIQResult(IQ iq) {
try {
logger.debug("[handleIQResult] {} has received iq-result: {}", getName(), iq);
if (iq.getChildElement() != null) {
logger.debug("[{}] {}'s child-namespace: {}", iq.getID(), getName(),
iq.getChildElement().getNamespace());
logger.debug("[{}] {}'s child-name: {}", iq.getID(), getName(), iq.getChildElement().getName());
if (iq.getChildElement().getNamespace().equals(Namespace.get("http://jabber.org/protocol/commands"))
&& iq.getChildElement().getName().equals("command")) {
Message inMsg = userAdminCache.getIfPresent(iq.getID());
handleCommands(iq.getID(), inMsg, iq.getChildElement());
}
}
} catch (Exception err) {
logger.error("[handleIQResult] ", err);
}
}
protected void handleCommands(String id, Message inMsg, Element command) throws InterruptedException {
String status = command.attributeValue("status");
String node = command.attributeValue("node");
String sessionid = command.attributeValue("sessionid");
logger.debug("[{}] sessionid: {}, status: {}, node: {}", id, sessionid, status, node);
if (status.equals("executing")) {
if (node.equals("http://jabber.org/protocol/admin#add-user")) {
sendAddUserForm(sessionid, inMsg);
} else if (node.equals("http://jabber.org/protocol/admin#delete-user")) {
sendDeleteUserForm(sessionid, inMsg);
} else if (node.equals("http://jabber.org/protocol/admin#change-user-password")) {
sendChangeUserPasswordForm(sessionid, inMsg);
}
} else if (status.equals("completed")) {
doEcho(inMsg, "OK");
}
}
public void requestAddUserForm(Message inMsg) {
try {
IQ addUserIq = new IQ(Type.set);
addUserIq.setFrom(xmppComponentConfig.getSubdomainPrefix() + "." + xmppComponentConfig.getDomain());
addUserIq.setTo(xmppComponentConfig.getDomain());
Element child = addUserIq.setChildElement("command", "http://jabber.org/protocol/commands");
child.addAttribute("action", "execute");
child.addAttribute("node", "http://jabber.org/protocol/admin#add-user");
userAdminCache.put(addUserIq.getID(), inMsg);
externalComponentManager.sendPacket(this, addUserIq);
logger.info("[requestAddUserForm] -- SENT -- {}", addUserIq);
} catch (Exception err) {
logger.error("[requestAddUserForm] ", err);
}
}
private void createFormTypeElement(Element x, String var, String type, String value) {
Element formType = x.addElement("field");
formType.addAttribute("var", var);
if (type != null) {
formType.addAttribute("type", type);
}
formType.addElement("value").setText(value);
}
public void sendAddUserForm(String sessionId, Message inMsg) {
try {
String[] commandParts = StringUtils.split(inMsg.getBody(), ' ');
IQ addUserIq = new IQ(Type.set);
addUserIq.setFrom(xmppComponentConfig.getSubdomainPrefix() + "." + xmppComponentConfig.getDomain());
addUserIq.setTo(xmppComponentConfig.getDomain());
Element child = addUserIq.setChildElement("command", "http://jabber.org/protocol/commands");
child.addAttribute("node", "http://jabber.org/protocol/admin#add-user");
child.addAttribute("sessionid", sessionId);
Element x = child.addElement("x", "jabber:x:data");
x.addAttribute("type", "submit");
createFormTypeElement(x, "FORM_TYPE", "hidden", "http://jabber.org/protocol/admin");
createFormTypeElement(x, "accountjid", "jid-single",
commandParts[1] + "@" + xmppComponentConfig.getDomain());
createFormTypeElement(x, "password", "text-private", commandParts[2]);
createFormTypeElement(x, "password-verify", "text-private", commandParts[2]);
userAdminCache.put(addUserIq.getID(), inMsg);
externalComponentManager.sendPacket(this, addUserIq);
logger.info("[sendAddUserForm] -- SENT -- {}", addUserIq);
} catch (Exception err) {
logger.error("[sendAddUserForm] ", err);
}
}
public void requestDeleteUserForm(Message inMsg) {
try {
IQ deleteUserIq = new IQ(Type.set);
deleteUserIq.setFrom(xmppComponentConfig.getSubdomainPrefix() + "." + xmppComponentConfig.getDomain());
deleteUserIq.setTo(xmppComponentConfig.getDomain());
Element child = deleteUserIq.setChildElement("command", "http://jabber.org/protocol/commands");
child.addAttribute("action", "execute");
child.addAttribute("node", "http://jabber.org/protocol/admin#delete-user");
userAdminCache.put(deleteUserIq.getID(), inMsg);
externalComponentManager.sendPacket(this, deleteUserIq);
logger.info("[requestDeleteUserForm] -- SENT -- {}", deleteUserIq);
} catch (Exception err) {
logger.error("[requestDeleteUserForm] ", err);
}
}
public void sendDeleteUserForm(String sessionId, Message inMsg) {
try {
String[] commandParts = StringUtils.split(inMsg.getBody(), ' ');
IQ deleteUserIq = new IQ(Type.set);
deleteUserIq.setFrom(xmppComponentConfig.getSubdomainPrefix() + "." + xmppComponentConfig.getDomain());
deleteUserIq.setTo(xmppComponentConfig.getDomain());
Element child = deleteUserIq.setChildElement("command", "http://jabber.org/protocol/commands");
child.addAttribute("node", "http://jabber.org/protocol/admin#delete-user");
child.addAttribute("sessionid", sessionId);
Element x = child.addElement("x", "jabber:x:data");
x.addAttribute("type", "submit");
createFormTypeElement(x, "FORM_TYPE", "hidden", "http://jabber.org/protocol/admin");
createFormTypeElement(x, "accountjids", "jid-single",
commandParts[1] + "@" + xmppComponentConfig.getDomain());
userAdminCache.put(deleteUserIq.getID(), inMsg);
externalComponentManager.sendPacket(this, deleteUserIq);
logger.info("[sendDeleteUserForm] -- SENT -- {}", deleteUserIq);
} catch (Exception err) {
logger.error("[sendDeleteUserForm] ", err);
}
}
public void requestChangeUserPassword(Message inMsg) {
try {
IQ changeUserPasswordIq = new IQ(Type.set);
changeUserPasswordIq
.setFrom(xmppComponentConfig.getSubdomainPrefix() + "." + xmppComponentConfig.getDomain());
changeUserPasswordIq.setTo(xmppComponentConfig.getDomain());
Element child = changeUserPasswordIq.setChildElement("command", "http://jabber.org/protocol/commands");
child.addAttribute("action", "execute");
child.addAttribute("node", "http://jabber.org/protocol/admin#change-user-password");
userAdminCache.put(changeUserPasswordIq.getID(), inMsg);
externalComponentManager.sendPacket(this, changeUserPasswordIq);
logger.info("[requestChangeUserPassword] -- SENT -- {}", changeUserPasswordIq);
} catch (Exception err) {
logger.error("[requestChangeUserPassword] ", err);
}
}
public void sendChangeUserPasswordForm(String sessionId, Message inMsg) {
try {
String[] commandParts = StringUtils.split(inMsg.getBody(), ' ');
IQ changeUserPasswordIq = new IQ(Type.set);
changeUserPasswordIq
.setFrom(xmppComponentConfig.getSubdomainPrefix() + "." + xmppComponentConfig.getDomain());
changeUserPasswordIq.setTo(xmppComponentConfig.getDomain());
Element child = changeUserPasswordIq.setChildElement("command", "http://jabber.org/protocol/commands");
child.addAttribute("node", "http://jabber.org/protocol/admin#change-user-password");
child.addAttribute("sessionid", sessionId);
Element x = child.addElement("x", "jabber:x:data");
x.addAttribute("type", "submit");
createFormTypeElement(x, "FORM_TYPE", "hidden", "http://jabber.org/protocol/admin");
createFormTypeElement(x, "accountjid", "jid-single",
commandParts[1] + "@" + xmppComponentConfig.getDomain());
createFormTypeElement(x, "password", "text-private", commandParts[2]);
userAdminCache.put(changeUserPasswordIq.getID(), inMsg);
externalComponentManager.sendPacket(this, changeUserPasswordIq);
logger.info("[sendChangeUserPasswordForm] -- SENT -- {}", changeUserPasswordIq);
} catch (Exception err) {
logger.error("[sendChangeUserPasswordForm] ", err);
}
} }
} }

Loading…
Cancel
Save