From 4e8e8e3e82b5046b31032d4db3cbfbad99191eb6 Mon Sep 17 00:00:00 2001 From: Finn Date: Fri, 12 Dec 2025 18:48:39 +0100 Subject: [PATCH] Updated to new standard --- .../webserver/AuthManager.java | 48 --- .../webserver/Main.java | 382 +----------------- .../webserver/RuleManager.java | 39 -- .../webserver/SessionManager.java | 79 ---- .../webserver/api/WebModule.java | 15 + 5 files changed, 17 insertions(+), 546 deletions(-) delete mode 100644 src/main/java/github/openautonomousconnection/webserver/AuthManager.java delete mode 100644 src/main/java/github/openautonomousconnection/webserver/RuleManager.java delete mode 100644 src/main/java/github/openautonomousconnection/webserver/SessionManager.java create mode 100644 src/main/java/github/openautonomousconnection/webserver/api/WebModule.java diff --git a/src/main/java/github/openautonomousconnection/webserver/AuthManager.java b/src/main/java/github/openautonomousconnection/webserver/AuthManager.java deleted file mode 100644 index 5433630..0000000 --- a/src/main/java/github/openautonomousconnection/webserver/AuthManager.java +++ /dev/null @@ -1,48 +0,0 @@ -package github.openautonomousconnection.webserver; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.security.MessageDigest; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; - -public class AuthManager { - - private static Map users = new HashMap<>(); - - public static void loadAuthFile(File authFile) throws IOException { - if (!authFile.exists()) authFile.createNewFile(); - for (String line : Files.readAllLines(authFile.toPath(), StandardCharsets.UTF_8)) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#")) continue; - String[] parts = line.split(":", 2); - if (parts.length == 2) { - users.put(parts[0], parts[1]); - } - } - } - - public static boolean checkAuth(String login, String password) { - - String storedHash = users.get(login); - if (storedHash == null) return false; - - String hash = sha256(password); - return storedHash.equalsIgnoreCase(hash); - } - - private static String sha256(String input) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); - StringBuilder sb = new StringBuilder(); - for (byte b : digest) sb.append(String.format("%02x", b)); - return sb.toString(); - } catch (Exception e) { - return ""; - } - } -} - diff --git a/src/main/java/github/openautonomousconnection/webserver/Main.java b/src/main/java/github/openautonomousconnection/webserver/Main.java index 331e3f2..622cebc 100644 --- a/src/main/java/github/openautonomousconnection/webserver/Main.java +++ b/src/main/java/github/openautonomousconnection/webserver/Main.java @@ -1,387 +1,9 @@ package github.openautonomousconnection.webserver; -import lombok.Getter; -import me.finn.unlegitlibrary.event.EventManager; -import me.finn.unlegitlibrary.file.ConfigurationManager; -import me.finn.unlegitlibrary.file.FileUtils; -import me.finn.unlegitlibrary.network.system.packets.PacketHandler; -import me.finn.unlegitlibrary.network.system.server.NetworkServer; -import me.finn.unlegitlibrary.network.utils.NetworkUtils; -import me.finn.unlegitlibrary.utils.Logger; -import org.apache.commons.fileupload2.core.DiskFileItemFactory; -import org.apache.commons.fileupload2.core.FileItem; -import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload; -import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletRequestContext; - -import javax.net.ssl.SSLServerSocket; -import javax.net.ssl.SSLSocket; -import java.io.*; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.security.MessageDigest; -import java.security.cert.CertificateException; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - public class Main { - public static void main(String[] args) throws Exception { - AuthManager.loadAuthFile(authFile); - RuleManager.loadRules(rulesFile); + public static void main(String[] args) { - pipelineServer = new NetworkServer.ServerBuilder(). - setPort(configurationManager.getInt("port.pipeline")).setTimeout(0). - setPacketHandler(new PacketHandler()).setEventManager(new EventManager()). - setLogger(new Logger(new File("logs"), false, true)). - setServerCertificate(certFile, keyFile).setRootCAFolder(folderStructure.publicCAFolder). - build(); - - pipelineServer.start(); - - SSLServerSocket webServer = (SSLServerSocket)NetworkServer.ServerBuilder. - createSSLServerSocketFactory(folderStructure.publicCAFolder, certFile,keyFile). - createServerSocket(configurationManager.getInt("port")); - webServer.setSoTimeout(0); - webServer.setEnabledProtocols(pipelineServer.getServerSocket().getEnabledProtocols()); - - // Stop WebServer if pipelineServer dies - new Thread(() -> { - while (true) { - if (!pipelineServer.getServerSocket().isBound()) { - try { - webServer.close(); - } catch (IOException e) { - pipelineServer.getLogger().exception("Failed to stop WebServer", e); - } finally { - Thread.currentThread().interrupt(); - break; - } - } - } - }).start(); - - new Thread(() -> { - while (true) { - try { - SSLSocket client = (SSLSocket) webServer.accept(); - threadPool.submit(() -> handleBrowser(client)); - } catch (IOException e) { - e.printStackTrace(); - } - } - }).start(); } - private static void handleBrowser(SSLSocket client) { - try (InputStream in = client.getInputStream(); OutputStream out = client.getOutputStream()) { - BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); - String line; - String path = "/main.html"; - Map headers = new HashMap<>(); - while ((line = reader.readLine()) != null && !line.isEmpty()) { - if (line.toLowerCase().startsWith("get") || line.toLowerCase().startsWith("post")) { - path = line.split(" ")[1]; - } - if (line.contains(":")) { - String[] parts = line.split(":", 2); - headers.put(parts[0].trim().toLowerCase(), parts[1].trim()); - } - } - - path = URLDecoder.decode(path, StandardCharsets.UTF_8); - path = normalizePath(path); - - File file = new File(contentFolder, path); - - String sessionId = null; - if (headers.containsKey("cookie")) { - for (String cookie : headers.get("cookie").split(";")) { - cookie = cookie.trim(); - if (cookie.startsWith("SESSIONID=")) { - sessionId = cookie.substring("SESSIONID=".length()); - } - } - } - - if (!file.exists() || !file.isFile()) { - sendResponse(out, 404, new File(errorsFolder, "404.html")); - return; - } - - String clientIp = client.getInetAddress().getHostAddress(); - String userAgent = headers.getOrDefault("user-agent", null); - - boolean loggedIn = sessionId != null && SessionManager.isValid(sessionId, clientIp, userAgent); - - if (path.equals("/403-login") && headers.getOrDefault("content-type","").startsWith("application/x-www-form-urlencoded")) { - Map postParams = parsePostParams(in); - String login = postParams.get("login"); - String password = postParams.get("password"); - - if (AuthManager.checkAuth(login, password)) { - String newSessionId = SessionManager.create(login, clientIp, userAgent); - Map cookies = Map.of("Set-Cookie", "SESSIONID=" + newSessionId + "; HttpOnly; Path=/"); - sendRedirect(out, "/main.html", cookies); - return; - } else { - sendRedirect(out, "/403.php", null); - return; - } - } - - if (isMultipart(headers)) { - handleMultipart(in, headers, new File(contentFolder, "uploads")); - } - - if (RuleManager.requiresAuth(path) && !loggedIn) { - PHPResponse phpResp = renderPHPWithCookies(new File(contentFolder, "403.php")); - sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies); - return; - } - - - if (RuleManager.isDenied(path) && !RuleManager.isAllowed(path)) { - sendResponse(out, 403, new File(errorsFolder, "403.php")); - return; - } - - if (path.endsWith(".php")) { - PHPResponse phpResp = renderPHPWithCookies(file); - sendResponse(out, 200, phpResp.body.getBytes(StandardCharsets.UTF_8), "text/html", phpResp.cookies); - } else { - sendResponse(out, 200, Files.readAllBytes(file.toPath()), getContentType(path), null); - } - - } catch (Exception e) { - e.printStackTrace(); - } finally { - try { client.close(); } catch (IOException ignored) {} - } - } - - private static void sendRedirect(OutputStream out, String location, Map cookies) throws IOException { - out.write(("HTTP/1.1 302 Found\r\n").getBytes()); - out.write(("Location: " + location + "\r\n").getBytes()); - if (cookies != null) { - for (var entry : cookies.entrySet()) { - out.write((entry.getKey() + ": " + entry.getValue() + "\r\n").getBytes()); - } - } - out.write("\r\n".getBytes()); - out.flush(); - } - - private static Map parsePostParams(InputStream in) throws IOException { - BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); - StringBuilder sb = new StringBuilder(); - while (reader.ready()) { - sb.append((char) reader.read()); - } - Map map = new HashMap<>(); - String[] pairs = sb.toString().split("&"); - for (String p : pairs) { - String[] kv = p.split("=",2); - if (kv.length == 2) map.put(URLDecoder.decode(kv[0], StandardCharsets.UTF_8), URLDecoder.decode(kv[1], StandardCharsets.UTF_8)); - } - return map; - } - - private static String normalizePath(String path) { - path = path.replace("/", File.separator).replace("\\","/"); - while (path.contains("..")) path = path.replace("..", ""); - if (path.startsWith("/")) path = path.substring(1); - return path; - } - - private static Map parseQueryParams(String rawPath) { - Map map = new HashMap<>(); - if (rawPath.contains("?")) { - String[] params = rawPath.substring(rawPath.indexOf("?") + 1).split("&"); - for (String p : params) { - String[] kv = p.split("="); - if (kv.length == 2) map.put(kv[0], kv[1]); - } - } - return map; - } - - private static boolean isMultipart(Map headers) { - String contentType = headers.get("content-type"); - return contentType != null && contentType.startsWith("multipart/form-data"); - } - - private static void handleMultipart(InputStream in, Map headers, File uploadDir) throws IOException { - if (!uploadDir.exists()) uploadDir.mkdirs(); - - String contentType = headers.get("content-type"); - String boundary = "--" + contentType.split("boundary=")[1]; - - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - byte[] lineBuffer = new byte[8192]; - int read; - while ((read = in.read(lineBuffer)) != -1) { - buffer.write(lineBuffer, 0, read); - if (buffer.size() > 10 * 1024 * 1024) break; // 10 MB max - } - - String data = buffer.toString(StandardCharsets.UTF_8); - String[] parts = data.split(boundary); - - for (String part : parts) { - if (part.contains("Content-Disposition")) { - String name = null; - String filename = null; - - for (String headerLine : part.split("\r\n")) { - if (headerLine.startsWith("Content-Disposition")) { - if (headerLine.contains("filename=\"")) { - int start = headerLine.indexOf("filename=\"") + 10; - int end = headerLine.indexOf("\"", start); - filename = headerLine.substring(start, end); - } - if (headerLine.contains("name=\"")) { - int start = headerLine.indexOf("name=\"") + 6; - int end = headerLine.indexOf("\"", start); - name = headerLine.substring(start, end); - } - } - } - - if (filename != null && !filename.isEmpty()) { - int headerEnd = part.indexOf("\r\n\r\n"); - byte[] fileData = part.substring(headerEnd + 4).getBytes(StandardCharsets.UTF_8); - File outFile = new File(uploadDir, filename); - Files.write(outFile.toPath(), fileData); - } - } - } - } - - private static String renderPHP(File file) throws IOException, InterruptedException { - ProcessBuilder pb = new ProcessBuilder("php", file.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - InputStream processIn = p.getInputStream(); - byte[] buf = new byte[8192]; - int read; - while ((read = processIn.read(buf)) != -1) { - output.write(buf, 0, read); - } - p.waitFor(); - return output.toString(StandardCharsets.UTF_8); - } - - private static void sendResponse(OutputStream out, int code, File file, Map headers) throws IOException { - byte[] body = Files.readAllBytes(file.toPath()); - sendResponse(out, code, body, "text/html", headers); - } - - private static void sendResponse(OutputStream out, int code, File file) throws IOException { - sendResponse(out, code, Files.readString(file.toPath()), "text/html"); - } - - private static void sendResponse(OutputStream out, int code, String body, String contentType) throws IOException { - sendResponse(out, code, body.getBytes(StandardCharsets.UTF_8), contentType, null); - } - - private static void sendResponse(OutputStream out, int code, File file, String contentType) throws IOException { - byte[] bytes = Files.readAllBytes(file.toPath()); - sendResponse(out, code, bytes, contentType, null); - } - - private static void sendResponse(OutputStream out, int code, byte[] body, String contentType, Map headers) throws IOException { - out.write(("HTTP/1.1 " + code + " " + getStatusText(code) + "\r\n").getBytes()); - out.write(("Content-Type: " + contentType + "\r\n").getBytes()); - out.write(("Content-Length: " + body.length + "\r\n").getBytes()); - if (headers != null) headers.forEach((k, v) -> { - try { out.write((k + ": " + v + "\r\n").getBytes()); } catch (IOException ignored) {} - }); - out.write("\r\n".getBytes()); - out.write(body); - out.flush(); - } - - private static String getStatusText(int code) { - return switch (code) { - case 200 -> "OK"; - case 301 -> "Moved Permanently"; - case 302 -> "Found"; - case 400 -> "Bad Request"; - case 401 -> "Unauthorized"; - case 403 -> "Forbidden"; - case 404 -> "Not Found"; - case 500 -> "Internal Server Error"; - default -> "Unknown"; - }; - } - - private static String getContentType(String name) { - return switch (name.substring(name.lastIndexOf('.') + 1).toLowerCase()) { - case "html", "php" -> "text/html"; - case "js" -> "text/javascript"; - case "css" -> "text/css"; - case "json" -> "application/json"; - case "png" -> "image/png"; - case "jpg", "jpeg" -> "image/jpeg"; - case "mp4" -> "video/mp4"; - case "mp3" -> "audio/mpeg3"; - case "wav" -> "audio/wav"; - case "pdf" -> "application/pdf"; - default -> "text/plain"; - }; - } - - private static PHPResponse renderPHPWithCookies(File file) throws IOException, InterruptedException { - ProcessBuilder pb = new ProcessBuilder("php", file.getAbsolutePath()); - pb.redirectErrorStream(true); - Process p = pb.start(); - - ByteArrayOutputStream output = new ByteArrayOutputStream(); - InputStream processIn = p.getInputStream(); - byte[] buf = new byte[8192]; - int read; - while ((read = processIn.read(buf)) != -1) { - output.write(buf, 0, read); - } - p.waitFor(); - - String fullOutput = output.toString(StandardCharsets.UTF_8); - Map cookies = new HashMap<>(); - - String[] parts = fullOutput.split("\r\n\r\n", 2); - String body; - if (parts.length == 2) { - String headers = parts[0]; - body = parts[1]; - for (String headerLine : headers.split("\r\n")) { - if (headerLine.toLowerCase().startsWith("set-cookie:")) { - String cookie = headerLine.substring("set-cookie:".length()).trim(); - String[] kv = cookie.split(";", 2); - String[] pair = kv[0].split("=", 2); - if (pair.length == 2) cookies.put(pair[0], pair[1]); - } - } - } else { - body = fullOutput; - } - - return new PHPResponse(body, cookies); - } - - private static class PHPResponse { - String body; - Map cookies; - - public PHPResponse(String body, Map cookies) { - this.body = body; - this.cookies = cookies; - } - } - -} \ No newline at end of file +} diff --git a/src/main/java/github/openautonomousconnection/webserver/RuleManager.java b/src/main/java/github/openautonomousconnection/webserver/RuleManager.java deleted file mode 100644 index b0e7a87..0000000 --- a/src/main/java/github/openautonomousconnection/webserver/RuleManager.java +++ /dev/null @@ -1,39 +0,0 @@ -package github.openautonomousconnection.webserver; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import java.io.File; -import java.nio.file.Files; -import java.util.List; -import java.util.Map; - -public class RuleManager { - private static List allow; - private static List deny; - private static List auth; - - public static void loadRules(File rulesFile) throws Exception { - String json = new String(Files.readAllBytes(rulesFile.toPath())); - Map> map = new Gson().fromJson(json, new TypeToken>>(){}.getType()); - allow = map.getOrDefault("allow", List.of()); - deny = map.getOrDefault("deny", List.of()); - auth = map.getOrDefault("auth", List.of()); - } - - public static boolean isAllowed(String path) { - return allow.stream().anyMatch(p -> pathMatches(path, p)); - } - - public static boolean isDenied(String path) { - return deny.stream().anyMatch(p -> pathMatches(path, p)); - } - - public static boolean requiresAuth(String path) { - return auth.stream().anyMatch(p -> pathMatches(path, p)); - } - - private static boolean pathMatches(String path, String pattern) { - pattern = pattern.replace("/", File.separator).replace("*", ".*"); - return path.matches(pattern); - } -} diff --git a/src/main/java/github/openautonomousconnection/webserver/SessionManager.java b/src/main/java/github/openautonomousconnection/webserver/SessionManager.java deleted file mode 100644 index 19b9ae9..0000000 --- a/src/main/java/github/openautonomousconnection/webserver/SessionManager.java +++ /dev/null @@ -1,79 +0,0 @@ -package github.openautonomousconnection.webserver; - -import lombok.Getter; - -import java.io.IOException; -import java.security.SecureRandom; -import java.util.Base64; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class SessionManager { - - private static final Map sessions = new ConcurrentHashMap<>(); - private static final SecureRandom secureRandom = new SecureRandom(); - - private static class Session { - @Getter - String login; - String ip; - String userAgent; - long expiresAt; - - Session(String login, String ip, String userAgent) throws IOException { - this.login = login; - this.ip = ip; - this.userAgent = userAgent; - this.expiresAt = System.currentTimeMillis() + (long) Main.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;;; - } - - boolean isExpired() { - return System.currentTimeMillis() > expiresAt; - } - - boolean matches(String ip, String userAgent) { - return this.ip.equals(ip) && this.userAgent.equals(userAgent); - } - - void refresh() throws IOException { - this.expiresAt = System.currentTimeMillis() + (long) Main.getConfigurationManager().getInt("sessionexpireminutes") * 60 * 1000;;; - } - } - - public static String create(String login, String ip, String userAgent) throws IOException { - byte[] bytes = new byte[32]; - secureRandom.nextBytes(bytes); - String sessionId = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); - sessions.put(sessionId, new Session(login, ip, userAgent)); - return sessionId; - } - - public static boolean isValid(String sessionId, String ip, String userAgent) throws IOException { - Session session = sessions.get(sessionId); - if (session == null || session.isExpired() || !session.matches(ip, userAgent)) { - sessions.remove(sessionId); - return false; - } - - session.refresh(); - return true; - } - - public static void invalidate(String sessionId) { - sessions.remove(sessionId); - } - - public static String getUser(String sessionId) { - Session session = sessions.get(sessionId); - if (session == null || session.isExpired()) { - sessions.remove(sessionId); - return null; - } - return session.getLogin(); - } - - public static void cleanupExpiredSessions() { - long now = System.currentTimeMillis(); - sessions.entrySet().removeIf(entry -> entry.getValue().isExpired()); - } -} diff --git a/src/main/java/github/openautonomousconnection/webserver/api/WebModule.java b/src/main/java/github/openautonomousconnection/webserver/api/WebModule.java new file mode 100644 index 0000000..598b5a1 --- /dev/null +++ b/src/main/java/github/openautonomousconnection/webserver/api/WebModule.java @@ -0,0 +1,15 @@ +package github.openautonomousconnection.webserver.api; + +public interface WebModule { + + /** + * @return Path this module handles (e.g. "/api/user") + */ + String path(); + + /** + * Handle a web request. + */ + WebResponsePacket handle(ConnectedWebClient client, WebRequestPacket request) throws Exception; +} +