commit 04d4a94b50da66f97b34787e12a26f2888642634 Author: ErfinderLabyrinth Date: Fri Dec 12 22:00:48 2025 +0100 Create diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13275f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.kotlin + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..668ff43 --- /dev/null +++ b/pom.xml @@ -0,0 +1,86 @@ + + + + erfinderlabyrinth + custompanel + 0.0.1 + + src + + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + erfinderlabyrinth.panel.Main + + + META-INF/services/java.sql.Driver + + + ${project.artifactId}-${project.version} + + + + + + + 4.0.0 + + + + mvnrepository + https://mvnrepository.com/ + + + + + + com.github.docker-java + docker-java + 3.7.0 + + + org.apache.commons + commons-text + 1.14.0 + + + org.json + json + 20250517 + + + org.xerial + sqlite-jdbc + 3.51.1.0 + + + \ No newline at end of file diff --git a/resources/default-config.json b/resources/default-config.json new file mode 100644 index 0000000..31ad322 --- /dev/null +++ b/resources/default-config.json @@ -0,0 +1,3 @@ +{ + "database_url": "Hier könnte ihre Database stehen" +} \ No newline at end of file diff --git a/resources/insert/header.html b/resources/insert/header.html new file mode 100644 index 0000000..aad7494 --- /dev/null +++ b/resources/insert/header.html @@ -0,0 +1 @@ +

Unser tolles Panel

\ No newline at end of file diff --git a/resources/website/js/login.js b/resources/website/js/login.js new file mode 100644 index 0000000..66d415e --- /dev/null +++ b/resources/website/js/login.js @@ -0,0 +1,28 @@ +var form = null + +async function submit(e) { + e.preventDefault() + + const formData = new FormData(form) + + try { + const response = await fetch("https://example.org/post", { + method: "POST", + body: formData, + }) + + const data = await response.json() + + window.sessionStorage.setItem("user", data.user) + window.sessionStorage.setItem("session", data.session) + + window.location.assign("/"); + } catch (e) { + console.error(e); + } +} + +window.onload = () => { + form = document.getElementById("login-form") + form.addEventListener("submit", submit) +} \ No newline at end of file diff --git a/resources/website/js/server-list-insert.js b/resources/website/js/server-list-insert.js new file mode 100644 index 0000000..8be3aa6 --- /dev/null +++ b/resources/website/js/server-list-insert.js @@ -0,0 +1,26 @@ +/** @type {HTMLElement} */ +var serverList + +window.onload = () => { + serverList = document.getElementById("server-list") + loadServerList() +} + +async function loadServerList() { + serversResponse = await fetch("/api/servers", { + method: "POST", + body: JSON.stringify({ + token: window.sessionStorage.getItem("session") + }) + }) + + +} + +function addServerElement(name) { + const container = new HTMLDivElement() + const nameHeader = new HTMLHeadingElement() + nameHeader.textContent = name + container.appendChild(nameHeader) + document.appendChild(container) +} \ No newline at end of file diff --git a/resources/website/login.html b/resources/website/login.html new file mode 100644 index 0000000..62aaa4e --- /dev/null +++ b/resources/website/login.html @@ -0,0 +1,27 @@ + + + + + Title + + + + +
+

Login

+
+ + + + + + + + + +
+ +
+
+ + \ No newline at end of file diff --git a/resources/website/server-list.html b/resources/website/server-list.html new file mode 100644 index 0000000..d072733 --- /dev/null +++ b/resources/website/server-list.html @@ -0,0 +1,15 @@ + + + + + Title + + + +!!!file header.html!!! + +
+ +
+ + \ No newline at end of file diff --git a/resources/website/style.css b/resources/website/style.css new file mode 100644 index 0000000..75e1c9c --- /dev/null +++ b/resources/website/style.css @@ -0,0 +1,15 @@ +body { + margin: 0; +} + +.center { + +} + +.fixed-center { + margin: 0; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} \ No newline at end of file diff --git a/src/erfinderlabyrinth/panel/Config.java b/src/erfinderlabyrinth/panel/Config.java new file mode 100644 index 0000000..1b4c521 --- /dev/null +++ b/src/erfinderlabyrinth/panel/Config.java @@ -0,0 +1,34 @@ +package erfinderlabyrinth.panel; + +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class Config { + public static String database_url; + + public static void load() { + if(!Files.exists(Path.of("config.json"))) { + try(FileOutputStream out = new FileOutputStream("config.json")) { + try(InputStream in = Config.class.getClassLoader().getResourceAsStream("/default-config.json")) { + if(in != null) { + in.transferTo(out); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + try(FileReader reader = new FileReader("config.json")) { + JSONObject object = new JSONObject(new JSONTokener(reader)); + + database_url = object.getString("database_url"); + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + } +} diff --git a/src/erfinderlabyrinth/panel/Main.java b/src/erfinderlabyrinth/panel/Main.java new file mode 100644 index 0000000..05329bb --- /dev/null +++ b/src/erfinderlabyrinth/panel/Main.java @@ -0,0 +1,30 @@ +package erfinderlabyrinth.panel; + +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.panel.PanelSettings; +import erfinderlabyrinth.panel.panel.server.Server; +import erfinderlabyrinth.panel.panel.server.type.ServerType; +import erfinderlabyrinth.panel.website.api.APIManager; +import erfinderlabyrinth.panel.website.api.interfaces.LoginInterface; +import erfinderlabyrinth.panel.website.page.FilePage; + +import java.io.IOException; +import java.io.InputStream; + +public class Main { + public static void main(String[] args) throws IOException { + Config.load(); + registerInterfaces(); + + Panel panel = new Panel(new PanelSettings()); + Server server = new Server(panel, new ServerType()); + server.exec("eclipse-temurin:25", new String[]{"java", "-jar", "test.jar"}).onFinished(() -> { + System.out.println("test.jar is finished"); + }); + + } + + private static void registerInterfaces() { + APIManager.registerInterface("login", new LoginInterface()); + } +} \ No newline at end of file diff --git a/src/erfinderlabyrinth/panel/database/DatabaseManager.java b/src/erfinderlabyrinth/panel/database/DatabaseManager.java new file mode 100644 index 0000000..afc7b11 --- /dev/null +++ b/src/erfinderlabyrinth/panel/database/DatabaseManager.java @@ -0,0 +1,26 @@ +package erfinderlabyrinth.panel.database; + +import erfinderlabyrinth.panel.Config; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class DatabaseManager { + static Connection connection; + public static void start() throws SQLException { + connection = DriverManager.getConnection(Config.database_url); + } + + public static Connection getConnection() { + return connection; + } + + public void stop() { + try { + connection.close(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/erfinderlabyrinth/panel/database/UserDatabase.java b/src/erfinderlabyrinth/panel/database/UserDatabase.java new file mode 100644 index 0000000..43a7ae6 --- /dev/null +++ b/src/erfinderlabyrinth/panel/database/UserDatabase.java @@ -0,0 +1,64 @@ +package erfinderlabyrinth.panel.database; + +import erfinderlabyrinth.panel.website.api.interfaces.LoginInterface; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.logging.Level; + +public class UserDatabase { + public static void init() throws SQLException { + String sql = "CREATE TABLE IF NOT EXISTS users (name varchar(64), password varchar(64));"; + Statement statement = DatabaseManager.getConnection().createStatement(); + statement.execute(sql); + statement.close(); + } + + public static LoginInterface.User getUser(String username, String password){ + try { + String sql = "SELECT * FROM users WHERE name = ? AND password = ?"; + PreparedStatement statement = DatabaseManager.getConnection().prepareStatement(sql); + statement.setString(1, username); + statement.setString(2, getSHA256(password)); + + ResultSet result = statement.executeQuery(); + + LoginInterface.User user = null; + if (result.next()) { + System.err.println("User exist with name " + result.getString("name")); + user = new LoginInterface.User(result.getString("name")); + } + statement.close(); + + return user; + } catch (Exception e) { + System.err.println("error while getting the user"); + e.printStackTrace(); + return null; + } + } + + public static String getSHA256(String original) { + MessageDigest digest = null; + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] hash = digest.digest( + original.getBytes(StandardCharsets.UTF_8)); + final StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + final String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) + hexString.append('0'); + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/src/erfinderlabyrinth/panel/docker/DockerConnection.java b/src/erfinderlabyrinth/panel/docker/DockerConnection.java new file mode 100644 index 0000000..363acde --- /dev/null +++ b/src/erfinderlabyrinth/panel/docker/DockerConnection.java @@ -0,0 +1,28 @@ +package erfinderlabyrinth.panel.docker; + +import javax.print.Doc; +import java.io.IOException; +import java.net.StandardProtocolFamily; +import java.net.UnixDomainSocketAddress; +import java.net.http.HttpClient; +import java.nio.channels.SocketChannel; +import java.nio.file.Path; + +public class DockerConnection { + public static Path DEFAULT_LINUX_DOCKER_PATH = Path.of("/unix/panel-connect"); + + public static DockerConnection open() throws IOException { + return open(DEFAULT_LINUX_DOCKER_PATH); + } + + public static DockerConnection open(Path unixPath) throws IOException { + UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(unixPath); + try (SocketChannel channel = SocketChannel + .open(StandardProtocolFamily.UNIX)) { + channel.connect(socketAddress); + + + } + return null; + } +} diff --git a/src/erfinderlabyrinth/panel/panel/Panel.java b/src/erfinderlabyrinth/panel/panel/Panel.java new file mode 100644 index 0000000..ffa3de5 --- /dev/null +++ b/src/erfinderlabyrinth/panel/panel/Panel.java @@ -0,0 +1,61 @@ +package erfinderlabyrinth.panel.panel; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Volume; +import com.github.dockerjava.core.DockerClientBuilder; +import erfinderlabyrinth.panel.panel.server.Server; +import erfinderlabyrinth.panel.website.Webserver; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class Panel { + public static final Map GROUP_LABEL = Map.of("group", "customPanel"); + Webserver webserver; + DockerClient dockerClient; + List servers; + + public Panel(PanelSettings settings) { + webserver = new Webserver(settings.getPort(), this); + + initDocker(); + + try { + webserver.start(); + } catch (IOException e) { + e.printStackTrace(); + stop(); + } + } + + public void initDocker() { + dockerClient = DockerClientBuilder.getInstance().build(); + + List containerList = dockerClient.listContainersCmd().withShowAll(true).withLabelFilter(GROUP_LABEL).exec(); + for (Container container : containerList) { + dockerClient.stopContainerCmd(container.getId()); + dockerClient.removeContainerCmd(container.getId()).withForce(true).exec(); + } + } + + public void stop() { + System.out.println("Stopping Panel"); + webserver.stop(); + + try { + dockerClient.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public DockerClient getDockerClient() { + return dockerClient; + } + + public List getServers() { + return servers; + } +} diff --git a/src/erfinderlabyrinth/panel/panel/PanelSettings.java b/src/erfinderlabyrinth/panel/panel/PanelSettings.java new file mode 100644 index 0000000..f122dc5 --- /dev/null +++ b/src/erfinderlabyrinth/panel/panel/PanelSettings.java @@ -0,0 +1,7 @@ +package erfinderlabyrinth.panel.panel; + +public class PanelSettings { + public int getPort() { + return 443; + } +} diff --git a/src/erfinderlabyrinth/panel/panel/server/ExecutedProcess.java b/src/erfinderlabyrinth/panel/panel/server/ExecutedProcess.java new file mode 100644 index 0000000..1421519 --- /dev/null +++ b/src/erfinderlabyrinth/panel/panel/server/ExecutedProcess.java @@ -0,0 +1,90 @@ +package erfinderlabyrinth.panel.panel.server; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.Bind; +import com.github.dockerjava.api.model.HostConfig; +import com.github.dockerjava.api.model.PullResponseItem; +import com.github.dockerjava.api.model.Volume; +import erfinderlabyrinth.panel.panel.Panel; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +public class ExecutedProcess { + CompletableFuture onFinish; + DockerClient client; + String dockerImage; + String[] command; + String containerId; + ExecutedProcess(DockerClient client, String dockerImage, String[] command) { + this.client = client; + this.dockerImage = dockerImage; + this.command = command; + + execute(); + } + + private void execute() { + try { + client.inspectImageCmd(dockerImage).exec(); + start(); + } catch (NotFoundException e) { + client.pullImageCmd(dockerImage).exec(new ResultCallback() { + @Override + public void onStart(Closeable closeable) { + } + + @Override + public void onNext(PullResponseItem object) { + if (object.getProgressDetail() != null) { + System.out.println(object.getProgressDetail().getStart() + " - " + object.getProgressDetail().getCurrent() + " - " + object.getProgressDetail().getTotal()); + }else { + System.out.println("ProgressDetail is null"); + } + } + + @Override + public void onError(Throwable throwable) { + throw new RuntimeException(throwable); + } + + @Override + public void onComplete() { + start(); + } + + @Override + public void close() throws IOException { + + } + }); + } + } + + private void start() { + String containerId = client.createContainerCmd(dockerImage) + .withHostConfig(HostConfig.newHostConfig() + .withBinds(new Bind("/unix", new Volume("/unix")), new Bind("/test/server1", new Volume("/server"))) + )//.withAutoRemove(false)) + .withLabels(Panel.GROUP_LABEL) + .withCmd(command) + .withWorkingDir("/server") + .exec().getId(); + client.startContainerCmd(containerId).exec(); + } + + public void onFinished(Runnable runnable) { + if (onFinish == null) { + onFinish = CompletableFuture.runAsync(() -> { + try { + client.waitContainerCmd(containerId).start().awaitCompletion(); + } catch (InterruptedException ignored) {} + }); + } + + onFinish.thenAccept(ignored -> runnable.run()); + } +} diff --git a/src/erfinderlabyrinth/panel/panel/server/Server.java b/src/erfinderlabyrinth/panel/panel/server/Server.java new file mode 100644 index 0000000..bf06a8a --- /dev/null +++ b/src/erfinderlabyrinth/panel/panel/server/Server.java @@ -0,0 +1,49 @@ +package erfinderlabyrinth.panel.panel.server; + +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.*; +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.panel.server.type.ServerType; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.function.Supplier; + +public class Server { + Panel panel; + String name; + ServerType type; + File directory; + + public Server(Panel panel, ServerType type) { + this.panel = panel; + this.type = type; + } + + public void install(boolean deleteAllFiles) { + if (deleteAllFiles) { + for (File file : directory.listFiles()) { + deleteFile(file); + } + } + + type.install(this); + } + + private void deleteFile(File file) { + if (file.isDirectory()) { + for (File file2 : file.listFiles()) { + deleteFile(file2); + } + } + file.delete(); + } + + public ExecutedProcess exec(String dockerImage, String[] command) { + return new ExecutedProcess(panel.getDockerClient(), dockerImage, command); + + } +} diff --git a/src/erfinderlabyrinth/panel/panel/server/type/ServerType.java b/src/erfinderlabyrinth/panel/panel/server/type/ServerType.java new file mode 100644 index 0000000..1330e8b --- /dev/null +++ b/src/erfinderlabyrinth/panel/panel/server/type/ServerType.java @@ -0,0 +1,13 @@ +package erfinderlabyrinth.panel.panel.server.type; + +import erfinderlabyrinth.panel.panel.server.Server; + +public class ServerType { + public String getDockerImage() { + return "eclipse-temurin:25"; + } + + public void install(Server server) { + + } +} diff --git a/src/erfinderlabyrinth/panel/panel/server/type/install/ExecutionStep.java b/src/erfinderlabyrinth/panel/panel/server/type/install/ExecutionStep.java new file mode 100644 index 0000000..5f4743a --- /dev/null +++ b/src/erfinderlabyrinth/panel/panel/server/type/install/ExecutionStep.java @@ -0,0 +1,13 @@ +package erfinderlabyrinth.panel.panel.server.type.install; + +import erfinderlabyrinth.panel.panel.server.Server; + +public class ExecutionStep implements InstallStep { + String dockerImage; + String[] command; + + @Override + public void execute(Server server) { + server.exec(dockerImage, command); + } +} diff --git a/src/erfinderlabyrinth/panel/panel/server/type/install/InstallStep.java b/src/erfinderlabyrinth/panel/panel/server/type/install/InstallStep.java new file mode 100644 index 0000000..b760fad --- /dev/null +++ b/src/erfinderlabyrinth/panel/panel/server/type/install/InstallStep.java @@ -0,0 +1,7 @@ +package erfinderlabyrinth.panel.panel.server.type.install; + +import erfinderlabyrinth.panel.panel.server.Server; + +public interface InstallStep { + void execute(Server server); +} diff --git a/src/erfinderlabyrinth/panel/website/Connection.java b/src/erfinderlabyrinth/panel/website/Connection.java new file mode 100644 index 0000000..bc920e1 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/Connection.java @@ -0,0 +1,93 @@ +package erfinderlabyrinth.panel.website; + +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.website.api.APIManager; +import erfinderlabyrinth.panel.website.page.FilePage; +import erfinderlabyrinth.panel.website.page.Page; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; + +public class Connection { + Socket s; + Panel panel; + public Connection(Socket s, Panel panel) { + this.s = s; + this.panel = panel; + } + + public void start() throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream())); + String line; + ConnectionType type = null; + String path = ""; + String host = ""; + int post_length = 0; + System.out.println("start reading"); + while (!(line = reader.readLine()).isEmpty()) { + System.out.println("Input Line: " + line); + if (line.startsWith("GET")) { + type = ConnectionType.GET; + path = line.split(" ")[1]; + }else if (line.startsWith("POST")) { + type = ConnectionType.POST; + path = line.split(" ")[1]; + }else if (line.startsWith("OPTIONS")) { + type = ConnectionType.OPTIONS; + path = line.split(" ")[1]; + } else if (line.startsWith("Host: ")) { + host = line.substring(6); + } else if (line.startsWith("Content-Length: ")) { + post_length = Integer.parseInt(line.substring(16)); + } + } + System.out.println("Finished reading"); + if (type == null) { + s.close(); + return; + } + + byte[] data = null; + + if (type == ConnectionType.POST) { + System.out.println("Read Data: " + post_length + " Bytes"); + data = new byte[post_length]; + for (int i = 0; i < post_length; i++) { + data[i] = (byte)reader.read(); + } + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + + OutputStream output = s.getOutputStream(); + + if (!path.startsWith("api/")) { + Page page; + + page = FilePage.load(path); + + if (page == null) { + output.write(("HTTP/1.0 404 UNKNOWN_PAGE\n\n").getBytes()); + }else { + output.write(("HTTP/1.0 200 OK\n\n").getBytes()); + output.write(page.getPageData()); + } + }else { + path = path.substring("api/".length()); + APIManager.call(path, type, data, output, panel); + } + + output.flush(); + s.close(); + + System.out.println("Written"); + } + + public enum ConnectionType { + GET, POST, OPTIONS + } +} diff --git a/src/erfinderlabyrinth/panel/website/Response.java b/src/erfinderlabyrinth/panel/website/Response.java new file mode 100644 index 0000000..1cea465 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/Response.java @@ -0,0 +1,44 @@ +package erfinderlabyrinth.panel.website; + +import org.json.JSONObject; + +public class Response { + int code; + JSONObject json; + public Response(int code, JSONObject json) { + this.code = code; + this.json = json; + } + + public static Response ok(JSONObject json) { + return new Response(200, json); + } + + public static Response missing(JSONObject json) { + return new Response(404, json); + } + + public static Response unauthorized(JSONObject json) { + return new Response(401, json); + } + + public static Response wrongConnectionType(JSONObject json) { + return new Response(405, json); + } + + public static Response wrongData(JSONObject json) { + return new Response(400, json); + } + + public static Response noContent(JSONObject json) { + return new Response(204, json); + } + + public int getResponseCode() { + return code; + } + + public JSONObject getJson() { + return json; + } +} diff --git a/src/erfinderlabyrinth/panel/website/Webserver.java b/src/erfinderlabyrinth/panel/website/Webserver.java new file mode 100644 index 0000000..06835dd --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/Webserver.java @@ -0,0 +1,100 @@ +package erfinderlabyrinth.panel.website; + +import erfinderlabyrinth.panel.panel.Panel; + +import javax.net.ssl.*; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.SecureRandom; + +public class Webserver { + int port; + Thread thread; + boolean running = true; + SSLServerSocket serverSocket; + Panel panel; + public Webserver(int port, Panel panel) { + this.port = port; + this.panel = panel; + } + + public void start() throws IOException { + byte[] passBytes; + try(FileInputStream in = new FileInputStream("pw.txt")) { + passBytes = in.readAllBytes(); + } catch (IOException | NullPointerException e) { + System.err.println("Missing Password. Couldn't find the server keystore pass"); + e.printStackTrace(); + return; + } + ByteBuffer byteBuf = ByteBuffer.wrap(passBytes); + CharBuffer charBuf = StandardCharsets.UTF_8.decode(byteBuf); + char[] pass = charBuf.array(); + + try { + KeyStore store = KeyStore.getInstance("jks"); + store.load(new FileInputStream("cert.jks"), pass); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + keyManagerFactory.init(store, pass); + KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); + SSLContext context = SSLContext.getInstance("SSL"); + context.init(keyManagers, null, new SecureRandom()); + SSLServerSocketFactory factory = context.getServerSocketFactory(); + serverSocket = (SSLServerSocket) factory.createServerSocket(port); + serverSocket.setNeedClientAuth(false); + serverSocket.setWantClientAuth(false); + } catch (IllegalArgumentException e) { + System.out.println("Port " + port + "is invalid."); + } catch (IOException e) { + System.out.println("An error occured while opening the ServerSocket:"); + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + + + + if (serverSocket != null) { + thread = new Thread(() -> { + while(running) { + System.out.println("Server is running"); + try { + Socket s = serverSocket.accept(); + System.out.println("accepted"); + Connection connection = new Connection(s, panel); + + new Thread(() -> { + try { + connection.start(); + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + thread.start(); + } + } + + public void stop() { + running = false; + try { + serverSocket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + thread.join(); + } catch (InterruptedException ignored) { + } + } +} diff --git a/src/erfinderlabyrinth/panel/website/api/APIInterface.java b/src/erfinderlabyrinth/panel/website/api/APIInterface.java new file mode 100644 index 0000000..f840200 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/APIInterface.java @@ -0,0 +1,9 @@ +package erfinderlabyrinth.panel.website.api; + +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.website.Connection; +import erfinderlabyrinth.panel.website.Response; + +public interface APIInterface { + Response onCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel); +} diff --git a/src/erfinderlabyrinth/panel/website/api/APIManager.java b/src/erfinderlabyrinth/panel/website/api/APIManager.java new file mode 100644 index 0000000..fee6ecf --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/APIManager.java @@ -0,0 +1,62 @@ +package erfinderlabyrinth.panel.website.api; + +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.website.Connection; +import erfinderlabyrinth.panel.website.Response; +import erfinderlabyrinth.panel.website.api.interfaces.LoginInterface; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.HashMap; + +public class APIManager { + public static HashMap sessions = new HashMap<>(); + static HashMap interfaces = new HashMap<>(); + public static void call(String path, Connection.ConnectionType connectionType, byte[] data, OutputStream output, Panel panel) throws IOException { + String mainCall = path.split("/")[0]; + Response response = null; + if (connectionType == Connection.ConnectionType.OPTIONS) { + response = Response.noContent(new JSONObject()); + }else { + APIInterface apiInterface = interfaces.get(mainCall); + if (apiInterface != null) { + response = apiInterface.onCall(path, connectionType, data, panel); + } + if (response == null) { + response = Response.missing(null); + } + } + output.write(("HTTP/1.0 " + response.getResponseCode() + " " + getDescription(response.getResponseCode()) + "\n").getBytes()); + output.write((""" + Access-Control-Allow-Origin: * + Access-Control-Allow-Methods: POST, GET, OPTIONS + Access-Control-Allow-Headers: * + + """).getBytes()); + JSONObject json = response.getJson(); + if (json == null) { + json = new JSONObject(); + } + json.put("success", response.getJson() != null); + output.write(json.toString().getBytes()); + output.flush(); + output.close(); + } + + public static void registerInterface(String path, APIInterface apiInterface) { + interfaces.put(path, apiInterface); + } + + private static String getDescription(int responseCode) { + return switch (responseCode) { + case 200 -> "OK"; + case 204 -> "NO CONTENT"; + case 404 -> "UNKNOWN_API_CALL"; + case 401 -> "UNAUTHORIZED"; + case 405 -> "WRONG_METHODE"; + case 400 -> "WRONG_DATA"; + default -> "MISSING_DESCRIPTION"; + }; + } +} diff --git a/src/erfinderlabyrinth/panel/website/api/LoginRequireAPIInterface.java b/src/erfinderlabyrinth/panel/website/api/LoginRequireAPIInterface.java new file mode 100644 index 0000000..e8fa1c5 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/LoginRequireAPIInterface.java @@ -0,0 +1,32 @@ +package erfinderlabyrinth.panel.website.api; + +import com.fasterxml.jackson.core.JsonParser; +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.website.Connection; +import erfinderlabyrinth.panel.website.Response; +import erfinderlabyrinth.panel.website.api.interfaces.LoginInterface; +import org.json.JSONObject; + +public interface LoginRequireAPIInterface extends APIInterface{ + @Override + default Response onCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) { + try { + JSONObject json = new JSONObject(new String(data)); + String token = json.getString("token"); + + LoginInterface.Session session = APIManager.sessions.get(token); + if (session != null) { + return onLoggedinCall(path, connectionType, data, session, panel); + } + return onNotLoggedinCall(path, connectionType, data, panel); + }catch (IllegalStateException|NullPointerException e) { + return Response.wrongData(null); + } + } + + Response onLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, LoginInterface.Session session, Panel panel); + + default Response onNotLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) { + return Response.unauthorized(null); + } +} diff --git a/src/erfinderlabyrinth/panel/website/api/interfaces/LoginInterface.java b/src/erfinderlabyrinth/panel/website/api/interfaces/LoginInterface.java new file mode 100644 index 0000000..6566607 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/interfaces/LoginInterface.java @@ -0,0 +1,92 @@ +package erfinderlabyrinth.panel.website.api.interfaces; +import erfinderlabyrinth.panel.database.UserDatabase; +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.website.Connection; +import erfinderlabyrinth.panel.website.Response; +import erfinderlabyrinth.panel.website.api.APIInterface; +import erfinderlabyrinth.panel.website.api.APIManager; +import org.json.JSONObject; + +import java.security.SecureRandom; + +public class LoginInterface implements APIInterface { + @Override + public Response onCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) { + if (path.equals("login/") || path.equals("login")) { + if (connectionType == Connection.ConnectionType.POST) { + try { + JSONObject json = new JSONObject(new String(data)); + String name = json.getString("name"); + String password = json.getString("password"); + + User user = UserDatabase.getUser(name, password); + + //if (name.equals("root") && UserDatabase.getSHA256(password).equals("2a7044b477960dd525b7f5990a3746a097cd189769603e670b7a5040ec797ffd")) { + // user = new User("root"); + //} + if (user == null) { + return Response.ok(null); + } + + Session session = new Session(user); + APIManager.sessions.put(session.getToken(), session); + + JSONObject response = new JSONObject(); + response.put("token", session.getToken()); + response.put("user", session.getUser().getName()); + return Response.ok(response); + + }catch (IllegalStateException|NullPointerException e) { + return Response.wrongData(null); + } + } + return Response.wrongConnectionType(null); + } + return Response.missing(null); + } + + public static class Session{ + User user; + long sessionCreated; + String token; + Session(User user) { + this.user = user; + this.sessionCreated = System.currentTimeMillis(); + this.token = generateToken(); + } + + public String getToken() { + return token; + } + + public User getUser() { + return user; + } + + private String generateToken() { + SecureRandom random = new SecureRandom(); + long randomLong1 = random.nextLong(); + long randomLong2 = random.nextLong(); + return Long.toHexString(randomLong1) + Long.toHexString(randomLong2); + } + } + + public static class User{ + String name; + + public User(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "User{" + + "name='" + name + '\'' + + '}'; + } + } +} diff --git a/src/erfinderlabyrinth/panel/website/api/interfaces/RequestServerListInterface.java b/src/erfinderlabyrinth/panel/website/api/interfaces/RequestServerListInterface.java new file mode 100644 index 0000000..15e5f78 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/interfaces/RequestServerListInterface.java @@ -0,0 +1,25 @@ +package erfinderlabyrinth.panel.website.api.interfaces; + +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.panel.server.Server; +import erfinderlabyrinth.panel.website.Connection; +import erfinderlabyrinth.panel.website.Response; +import erfinderlabyrinth.panel.website.api.LoginRequireAPIInterface; +import org.json.JSONArray; +import org.json.JSONObject; + +public class RequestServerListInterface implements LoginRequireAPIInterface { + @Override + public Response onLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, LoginInterface.Session session, Panel panel) { + JSONObject object = new JSONObject(); + JSONArray serverList = new JSONArray(); + object.append("servers", serverList); + + for (Server server : panel.getServers()) { + + } + + + return Response.ok(object); + } +} diff --git a/src/erfinderlabyrinth/panel/website/api/interfaces/SubInterface.java b/src/erfinderlabyrinth/panel/website/api/interfaces/SubInterface.java new file mode 100644 index 0000000..7810201 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/interfaces/SubInterface.java @@ -0,0 +1,24 @@ +package erfinderlabyrinth.panel.website.api.interfaces; + +import erfinderlabyrinth.panel.website.Connection; +import erfinderlabyrinth.panel.website.Response; +import erfinderlabyrinth.panel.website.api.APIInterface; + +import java.util.HashMap; + +public class SubInterface implements APIInterface { + static HashMap subInterfaces = new HashMap<>(); + @Override + public Response onCall(String path, Connection.ConnectionType connectionType, byte[] data) { + String secondPath = path.split("/")[1]; + if (subInterfaces.containsKey(secondPath)) { + return subInterfaces.get(secondPath).onCall(path, connectionType, data); + }else { + return Response.missing(null); + } + } + + public void registerSubInterface(String path, APIInterface apiInterface) { + subInterfaces.put(path, apiInterface); + } +} diff --git a/src/erfinderlabyrinth/panel/website/api/interfaces/SubInterfaces.java b/src/erfinderlabyrinth/panel/website/api/interfaces/SubInterfaces.java new file mode 100644 index 0000000..b68a8c5 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/interfaces/SubInterfaces.java @@ -0,0 +1,5 @@ +package erfinderlabyrinth.panel.website.api.interfaces; + +public class SubInterfaces { + public static final SubInterface PLAYER = new SubInterface(); +} diff --git a/src/erfinderlabyrinth/panel/website/api/interfaces/TokenCheckInterface.java b/src/erfinderlabyrinth/panel/website/api/interfaces/TokenCheckInterface.java new file mode 100644 index 0000000..c37b6b2 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/api/interfaces/TokenCheckInterface.java @@ -0,0 +1,22 @@ +package erfinderlabyrinth.panel.website.api.interfaces; + + +import erfinderlabyrinth.panel.panel.Panel; +import erfinderlabyrinth.panel.website.Connection; +import erfinderlabyrinth.panel.website.Response; +import erfinderlabyrinth.panel.website.api.LoginRequireAPIInterface; +import org.json.JSONObject; + +public class TokenCheckInterface implements LoginRequireAPIInterface { + @Override + public Response onLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, LoginInterface.Session session, Panel panel) { + JSONObject object = new JSONObject(); + object.put("username", session.getUser().getName()); + return Response.ok(object); + } + + @Override + public Response onNotLoggedinCall(String path, Connection.ConnectionType connectionType, byte[] data, Panel panel) { + return Response.ok(null); + } +} diff --git a/src/erfinderlabyrinth/panel/website/command/FileCommand.java b/src/erfinderlabyrinth/panel/website/command/FileCommand.java new file mode 100644 index 0000000..b51363a --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/command/FileCommand.java @@ -0,0 +1,35 @@ +package erfinderlabyrinth.panel.website.command; + +import erfinderlabyrinth.panel.website.page.PageParser; +import erfinderlabyrinth.panel.website.page.RequestContext; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +public class FileCommand implements PageParser.PageCommand { + public static final String FILE_PATH_START = "insert/"; + + @Override + public String apply(String args, RequestContext context) { + Path path = Path.of(FILE_PATH_START, args); + path = path.normalize(); + + if(path.startsWith(FILE_PATH_START)) { + try (InputStream in = getClass().getClassLoader().getResourceAsStream(FILE_PATH_START + args.replace("..", ""))) { + if (in != null) { + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return ""; + } + + @Override + public boolean needsEscaping() { + return false; + } +} diff --git a/src/erfinderlabyrinth/panel/website/page/FilePage.java b/src/erfinderlabyrinth/panel/website/page/FilePage.java new file mode 100644 index 0000000..d1caa42 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/page/FilePage.java @@ -0,0 +1,37 @@ +package erfinderlabyrinth.panel.website.page; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class FilePage implements Page{ + byte[] data; + public FilePage(byte[] data) { + this.data = data; + } + + public static FilePage load(String path) { + try (InputStream input = FilePage.class.getResourceAsStream("/website/" + path.replace("..", ""))) { + + if (input != null) { + byte[] bytes = input.readAllBytes(); + + if(path.endsWith(".html")) { + String text = new String(bytes, StandardCharsets.UTF_8); + String parsed = PageParser.parsePage(text, new RequestContext()); + + bytes = parsed.getBytes(StandardCharsets.UTF_8); + } + return new FilePage(bytes); + } + } catch (IOException ignored) { + } + + return null; + } + + @Override + public byte[] getPageData() { + return data; + } +} diff --git a/src/erfinderlabyrinth/panel/website/page/Page.java b/src/erfinderlabyrinth/panel/website/page/Page.java new file mode 100644 index 0000000..389ce15 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/page/Page.java @@ -0,0 +1,5 @@ +package erfinderlabyrinth.panel.website.page; + +public interface Page { + byte[] getPageData(); +} diff --git a/src/erfinderlabyrinth/panel/website/page/PageParser.java b/src/erfinderlabyrinth/panel/website/page/PageParser.java new file mode 100644 index 0000000..bbd17d9 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/page/PageParser.java @@ -0,0 +1,94 @@ +package erfinderlabyrinth.panel.website.page; + +import erfinderlabyrinth.panel.website.command.FileCommand; +import org.apache.commons.text.StringEscapeUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class PageParser { + private static final Map commands = new HashMap<>( + Map.of( + "file", new FileCommand() + ) + ); + + public static String parsePage(String original, RequestContext context) { + int exclamationCount = 0; + + StringBuilder output = new StringBuilder(); + StringBuilder command = null; + + boolean wasInTag = false; + boolean inTag = false; + + for(int i = 0; i < original.length(); i++) { + + char c = original.charAt(i); + if(c == '!') { + exclamationCount++; + if(exclamationCount == 3) { + inTag = !inTag; + if(inTag) { + command = new StringBuilder(); + } else { + output.append(interpretCommand(command.toString(), context)); + } + } + } else { + if(!inTag && !wasInTag && exclamationCount > 0) { + for(int x = 0; x < exclamationCount; x++) { + output.append('!'); + } + } + + exclamationCount = 0; + + if(inTag) { + command.append(c); + } else { + output.append(c); + } + + wasInTag = inTag; + } + } + + return output.toString(); + } + + public static String interpretCommand(String command, RequestContext context) { + int firstSpace = command.indexOf(' '); + String name = command.substring(0, firstSpace); + String cmdArgs = command.substring(firstSpace + 1, command.length() - 1); + + PageCommand pageCommand = commands.get(name); + + if(pageCommand == null) { + throw new RuntimeException("Command not found " + name); + } + + String out = pageCommand.apply(cmdArgs, context); + + if(pageCommand.needsEscaping()) { + return StringEscapeUtils.escapeHtml4(out); + } else { + return out; + } + } + + public interface PageCommand { + String apply(String args, RequestContext context); + + default boolean needsEscaping() { + return true; + } + } + + public static void main(String[] args) { + System.out.println(parsePage("fjjalöfjjwm!!f!kwsöqwlsd2,!!!p95!!!fjdqkpk47 ßz8dkß2kqla4qoj9cf69p576aop! ah faöohfaöh !!!testcommand1!!!", new RequestContext())); + } +} diff --git a/src/erfinderlabyrinth/panel/website/page/RequestContext.java b/src/erfinderlabyrinth/panel/website/page/RequestContext.java new file mode 100644 index 0000000..1439268 --- /dev/null +++ b/src/erfinderlabyrinth/panel/website/page/RequestContext.java @@ -0,0 +1,4 @@ +package erfinderlabyrinth.panel.website.page; + +public class RequestContext { +}