diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java index beb051a..8377ea2 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java @@ -1,30 +1,106 @@ package com.icedberries.UBFunkeysServer.ArkOne; +import com.icedberries.UBFunkeysServer.ArkOne.Plugins.BasePlugin; +import com.icedberries.UBFunkeysServer.ArkOne.Plugins.GalaxyPlugin; +import com.icedberries.UBFunkeysServer.ArkOne.Plugins.UserPlugin; import javagrinko.spring.tcp.Connection; import javagrinko.spring.tcp.TcpController; import javagrinko.spring.tcp.TcpHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.w3c.dom.Element; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @TcpController public class ArkOneController implements TcpHandler { + public static final String IP_ADDRESS = "127.0.0.1"; + + // Plugins + @Autowired + BasePlugin basePlugin; + + @Autowired + UserPlugin userPlugin; + + @Autowired + GalaxyPlugin galaxyPlugin; + @Override public void receiveData(Connection connection, byte[] data) { - //TODO: IMPLEMENT PLUGIN CHECKING AND FORWARDING HERE - //Currently just echos back the received data until implemented - // Log the received request String xmlData = new String(data); System.out.println("[ArkOne] New Request: " + xmlData); - //TODO: USE THAT FOR PASSWORD ENCRYPTION FOR STORING IN THE DB - //TODO: SEE THIS ARTICLE FOR ENCODING: https://www.baeldung.com/spring-security-registration-password-encoding-bcrypt + // Create a list of responses to send back + ArrayList responses = new ArrayList<>(); - try { - connection.send(xmlData.toUpperCase().getBytes()); - } catch (IOException e) { - e.printStackTrace(); + // Parse the incoming data into individual commands + List commands = ArkOneParser.ParseReceivedMessage(xmlData); + + // Handle each command + for (String command : commands) { + try { + Element commandInfo = (Element)ArkOneParser.ParseCommand(command); + switch(commandInfo.getNodeName()) { + // Plugin 0 - Core + case "a_lgu": + responses.add(basePlugin.LoginGuestUser()); + break; + case "a_gpd": + responses.add(basePlugin.GetPluginDetails(commandInfo.getAttribute("p"))); + break; + case "a_gsd": + responses.add(basePlugin.GetServiceDetails(commandInfo.getAttribute("s"))); + break; + case "a_lru": + responses.add(basePlugin.LoginRegisteredUser(commandInfo)); + break; + + // Plugin 1 (User) + case "u_reg": + responses.add(userPlugin.RegisterUser(commandInfo)); + break; + + // Plugin 7 (Galaxy) + case "lpv": + responses.add(galaxyPlugin.LoadProfileVersion()); + break; + + // Plugin 10 (Trunk) + + // Catch Unhandled Commands + default: + responses.add(""); + System.out.println("[ArkOne][ERROR] Unhandled command: " + commandInfo.getNodeName()); + break; + } + } catch (Exception e) { + System.out.println("[ArkOne][ERROR] Unknown error occurred: "); + e.printStackTrace(); + responses.add(""); + } + } + + // Send the response + for(String response : responses) { + try { + // Append a 0x00 to the end of the response + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + outputStream.write(response.getBytes()); + outputStream.write((byte)0x00); + byte[] combinedResponse = outputStream.toByteArray(); + + connection.send(combinedResponse); + + System.out.println("[ArkOne] Response: " + response); + } catch (IOException e) { + System.out.println("[ArkOne][ERROR] Unknown error occurred: "); + e.printStackTrace(); + } } } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneParser.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneParser.java new file mode 100644 index 0000000..8ec0d0f --- /dev/null +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneParser.java @@ -0,0 +1,71 @@ +package com.icedberries.UBFunkeysServer.ArkOne; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class ArkOneParser { + + public static ArrayList ParseReceivedMessage(String xmlCommand) { + List rawCommandsList = Arrays.stream(xmlCommand.split("\0")) + .filter(str -> !isNullOrWhitespace(str)) + .collect(Collectors.toList()); + + return new ArrayList<>(rawCommandsList); + } + + public static Node ParseCommand(String command) throws Exception { + // Check to see if the command has a routing string at the end of it + if (command.endsWith("#")) { + command = command.substring(0, command.lastIndexOf(">") + 1); + } + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(new InputSource(new StringReader(command))); + doc.getDocumentElement().normalize(); + + return doc.getFirstChild(); + } + + public static String RemoveXMLTag(Document doc) throws TransformerException { + DOMSource domSource = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer = tf.newTransformer(); + transformer.transform(domSource, result); + return writer.toString().replaceAll("(<\\?xml.*?\\?>)",""); + } + + public static boolean isNullOrWhitespace(String s) { + return s == null || isWhitespace(s); + } + + private static boolean isWhitespace(String s) { + int length = s.length(); + if (length > 0) { + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + return false; + } +} diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/BasePlugin.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/BasePlugin.java new file mode 100644 index 0000000..ec85585 --- /dev/null +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/BasePlugin.java @@ -0,0 +1,206 @@ +package com.icedberries.UBFunkeysServer.ArkOne.Plugins; + +import com.icedberries.UBFunkeysServer.ArkOne.ArkOneController; +import com.icedberries.UBFunkeysServer.ArkOne.ArkOneParser; +import com.icedberries.UBFunkeysServer.domain.User; +import com.icedberries.UBFunkeysServer.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +@Service +public class BasePlugin { + + @Autowired + private UserService userService; + + @Autowired + private PasswordEncoder passwordEncoder; + + private static final String PORT = "20502"; + + public String LoginGuestUser() throws ParserConfigurationException, TransformerException { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + + // Create the root element + Document doc = dBuilder.newDocument(); + Element rootElement = doc.createElement("a_lgu"); + + // Set attributes + rootElement.setAttribute("r", "0"); + rootElement.setAttribute("u", "0"); + rootElement.setAttribute("n", "GUESTUSER"); + rootElement.setAttribute("p", ""); + rootElement.setAttribute("s", "1"); + + doc.appendChild(rootElement); + + return ArkOneParser.RemoveXMLTag(doc); + } + + public String GetServiceDetails(String s) throws ParserConfigurationException, TransformerException { + // Set some variables for the response + String serverID = "1"; + + String xIPAddress = ArkOneController.IP_ADDRESS; + String xPort = "80"; + + String bIPAddress = ArkOneController.IP_ADDRESS; + String bPort = "80"; + + // Update the port for each server + // ** This would be used if each routing plugin had their own server/port combo ** + switch (s){ + //User + case "1": + xPort = PORT; + bPort = PORT; + break; + + //Galaxy + case "7": + xPort = PORT; + bPort = PORT; + break; + + //Trunk + case "10": + xPort = PORT; + bPort = PORT; + break; + } + + // Build the response + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + + // Create the root element + Document doc = dBuilder.newDocument(); + Element rootElement = doc.createElement("a_gsd"); + + // Set attributes + rootElement.setAttribute("s", serverID); + rootElement.setAttribute("xi", xIPAddress); + rootElement.setAttribute("xp", xPort); + rootElement.setAttribute("bi", bIPAddress); + rootElement.setAttribute("bp", bPort); + + doc.appendChild(rootElement); + + return ArkOneParser.RemoveXMLTag(doc); + } + + public String GetPluginDetails(String p) throws ParserConfigurationException, TransformerException { + // Set some variables for the response + String serverID = "1"; + + String xIPAddress = ArkOneController.IP_ADDRESS; + String xPort = "80"; + + String bIPAddress = ArkOneController.IP_ADDRESS; + String bPort = "80"; + + // Update the port for each plugin + // ** This would be used if each routing plugin had their own server/port combo ** + switch (p){ + //User + case "1": + xPort = PORT; + bPort = PORT; + break; + + //Galaxy + case "7": + xPort = PORT; + bPort = PORT; + break; + + //Trunk + case "10": + xPort = PORT; + bPort = PORT; + break; + } + + // Build the response + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + + // Create the root element + Document doc = dBuilder.newDocument(); + Element rootElement = doc.createElement("a_gpd"); + + // Set attributes + rootElement.setAttribute("s", serverID); + rootElement.setAttribute("xi", xIPAddress); + rootElement.setAttribute("xp", xPort); + rootElement.setAttribute("bi", bIPAddress); + rootElement.setAttribute("bp", bPort); + rootElement.setAttribute("p", p); + + doc.appendChild(rootElement); + + return ArkOneParser.RemoveXMLTag(doc); + } + + public String LoginRegisteredUser(Element element) throws ParserConfigurationException, TransformerException { + // Response r codes: + // 0 - Accepted Login + // 1 - Already exist in Terrapinia + // 2 - Already exist in Terrapinia + // 3 - Problem with your account, please call phone number + // 4 - Password Incorrect + // 5 - Funkey Name Not Found + Integer responseCode = 0; + + String username = element.getAttribute("n"); + String password = element.getAttribute("p"); + + // Get a funkey with that username + User user = userService.findByUsername(username).orElse(null); + String uuid = ""; + + // Check if null (no funkey with that name) + if (user == null) { + responseCode = 5; + } else { + // Funkey name exists - Verify Password + if (!passwordEncoder.matches(password, user.getPassword())) { + // Passwords don't match + responseCode = 4; + } else { + // Login success + uuid = String.valueOf(user.getUUID()); + } + } + + // Build response + // Build the response + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + + // Create the root element + Document doc = dBuilder.newDocument(); + Element rootElement = doc.createElement("a_lru"); + + // Set attributes + rootElement.setAttribute("s", "1"); + rootElement.setAttribute("r", String.valueOf(responseCode)); + if (responseCode == 0) { + // Only set the user id field if successfully found the user + rootElement.setAttribute("u", uuid); + } + + doc.appendChild(rootElement); + + return ArkOneParser.RemoveXMLTag(doc); + } +} diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/GalaxyPlugin.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/GalaxyPlugin.java new file mode 100644 index 0000000..62be88e --- /dev/null +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/GalaxyPlugin.java @@ -0,0 +1,12 @@ +package com.icedberries.UBFunkeysServer.ArkOne.Plugins; + +import org.springframework.stereotype.Service; + +@Service +public class GalaxyPlugin { + + public String LoadProfileVersion() { + //TODO: IMPLEMENT THIS WITH PROFILE SAVING + return ""; + } +} diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/UserPlugin.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/UserPlugin.java new file mode 100644 index 0000000..32a7de2 --- /dev/null +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/Plugins/UserPlugin.java @@ -0,0 +1,73 @@ +package com.icedberries.UBFunkeysServer.ArkOne.Plugins; + +import com.icedberries.UBFunkeysServer.ArkOne.ArkOneParser; +import com.icedberries.UBFunkeysServer.domain.User; +import com.icedberries.UBFunkeysServer.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; + +@Service +public class UserPlugin { + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private UserService userService; + + public String RegisterUser(Element element) throws ParserConfigurationException, TransformerException { + String username = element.getAttribute("l"); + String password = element.getAttribute("p"); + String securityQuestion = element.getAttribute("sq"); + String securityAnswer = element.getAttribute("sa"); + + User newUser = User.builder() + .username(username) + .password(passwordEncoder.encode(password)) + .securityQuestion(securityQuestion) + .securityAnswer(securityAnswer) + .build(); + + // 0 - Successfully registered + // 1 - Name already exists + // 2 - Issues connecting to server + Integer responseCode = 0; + + String uniqueId = ""; + + // First check if username doesn't already exist in the DB + if (userService.existsByUsername(newUser.getUsername())) { + // Username already exists + responseCode = 1; + } else { + // Username doesn't exist - save it + User newUserInDB = userService.save(newUser); + + uniqueId = String.valueOf(newUserInDB.getUUID()); + } + + // Build response + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + + // Create the root element + Document doc = dBuilder.newDocument(); + Element rootElement = doc.createElement("u_reg"); + + // Set attributes + rootElement.setAttribute("r", String.valueOf(responseCode)); + rootElement.setAttribute("u", uniqueId); + + doc.appendChild(rootElement); + + return ArkOneParser.RemoveXMLTag(doc); + } +} diff --git a/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java b/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java index f139061..64f2367 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/Galaxy/GalaxyServer.java @@ -1,5 +1,6 @@ package com.icedberries.UBFunkeysServer.Galaxy; +import com.icedberries.UBFunkeysServer.ArkOne.ArkOneParser; import com.icedberries.UBFunkeysServer.domain.Crib; import com.icedberries.UBFunkeysServer.service.CribService; import com.icedberries.UBFunkeysServer.service.EmailService; @@ -245,13 +246,7 @@ public class GalaxyServer { Node importedNode = newDocument.importNode(profileNode, true); newDocument.appendChild(importedNode); - DOMSource domSource = new DOMSource(newDocument); - StringWriter writer = new StringWriter(); - StreamResult result = new StreamResult(writer); - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.newTransformer(); - transformer.transform(domSource, result); - profileData = writer.toString().replaceAll("(<\\?xml.*?\\?>)",""); + profileData = ArkOneParser.RemoveXMLTag(newDocument); } catch (ParserConfigurationException | TransformerException e) { System.out.println("[Galaxy][POST] Exception thrown when saving crib: "); e.printStackTrace(); diff --git a/src/main/java/com/icedberries/UBFunkeysServer/domain/Crib.java b/src/main/java/com/icedberries/UBFunkeysServer/domain/Crib.java index 9120a84..6fbb71a 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/domain/Crib.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/domain/Crib.java @@ -5,12 +5,9 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.Type; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import javax.persistence.*; @Getter @Setter @@ -29,5 +26,7 @@ public class Crib { private String username; + @Column(columnDefinition = "MEDIUMTEXT") + @Type(type = "org.hibernate.type.TextType") private String profileData; } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/repository/UserRepository.java b/src/main/java/com/icedberries/UBFunkeysServer/repository/UserRepository.java index 8bb8655..a138209 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/repository/UserRepository.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/repository/UserRepository.java @@ -15,4 +15,9 @@ public interface UserRepository extends CrudRepository { @Query("select user from User user where user.UUID = :uuid") Optional findByUUID(@Param("uuid") Integer uuid); + + Boolean existsByUsername(String username); + + @Query("select user from User user where user.username = :username") + Optional findByUsername(@Param("username") String username); } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/service/UserService.java b/src/main/java/com/icedberries/UBFunkeysServer/service/UserService.java index 10902c1..8e86333 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/service/UserService.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/service/UserService.java @@ -10,5 +10,9 @@ public interface UserService { Boolean existsByUUID(Integer uuid); - void save(User user); + User save(User user); + + Boolean existsByUsername(String username); + + Optional findByUsername(String username); } diff --git a/src/main/java/com/icedberries/UBFunkeysServer/service/impl/UserServiceImpl.java b/src/main/java/com/icedberries/UBFunkeysServer/service/impl/UserServiceImpl.java index 941524d..ec124e3 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/service/impl/UserServiceImpl.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/service/impl/UserServiceImpl.java @@ -26,7 +26,17 @@ public class UserServiceImpl implements UserService { } @Override - public void save(User user) { - userRepository.save(user); + public User save(User user) { + return userRepository.save(user); + } + + @Override + public Boolean existsByUsername(String username) { + return userRepository.existsByUsername(username); + } + + @Override + public Optional findByUsername(String username) { + return userRepository.findByUsername(username); } } diff --git a/src/main/java/javagrinko/spring/tcp/TcpConnection.java b/src/main/java/javagrinko/spring/tcp/TcpConnection.java index 3304b25..fa868c2 100644 --- a/src/main/java/javagrinko/spring/tcp/TcpConnection.java +++ b/src/main/java/javagrinko/spring/tcp/TcpConnection.java @@ -57,7 +57,7 @@ public class TcpConnection implements Connection { public void start() { new Thread(() -> { while (true) { - byte buf[] = new byte[64 * 1024]; + byte[] buf = new byte[51200]; try { int count = inputStream.read(buf); if (count > 0) {