Added licenses
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
package org.openautonomousconnection.insserver;
|
||||
|
||||
import org.openautonomousconnection.insserver.utils.ClassicHelper;
|
||||
import org.openautonomousconnection.insserver.utils.ParsedDomain;
|
||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||
import org.openautonomousconnection.protocol.side.ins.ConnectedProtocolClient;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerINSServer;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.utils.Classic_ProtocolVersion;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.List;
|
||||
|
||||
public class ClassicHandler extends ClassicHandlerINSServer {
|
||||
|
||||
private ProtocolBridge bridge() {
|
||||
return Main.getProtocolBridge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(ConnectedProtocolClient client, String message, Classic_ProtocolVersion protocolVersion) {
|
||||
bridge().getLogger().info("[ClassicHandler] Message received from ClientID " + client.getConnectionHandler().getClientID() +
|
||||
" (Classic Version " + protocolVersion.version + ", Client Version: " +
|
||||
client.getClientVersion().toString() + "): " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Classic_Domain getDomain(Classic_RequestDomain requestDomain) throws SQLException {
|
||||
|
||||
ParsedDomain pd = ClassicHelper.parseDomain(requestDomain);
|
||||
|
||||
// FIRST try A-record
|
||||
List<INSRecord> a = bridge().getProtocolINSServer().resolve(pd.tln, pd.name, pd.sub, INSRecordType.A);
|
||||
|
||||
if (!a.isEmpty()) {
|
||||
return ClassicHelper.buildClassicDomain(pd, a.get(0));
|
||||
}
|
||||
|
||||
// If no A, try CNAME
|
||||
List<INSRecord> cnameList = bridge().getProtocolINSServer().resolve(pd.tln, pd.name, pd.sub, INSRecordType.CNAME);
|
||||
|
||||
if (cnameList.isEmpty()) return null; // completely not found
|
||||
|
||||
INSRecord cname = cnameList.get(0);
|
||||
|
||||
// CNAME target = "www.example.net" or "app.dev"
|
||||
String[] cnameParts = cname.value.split("\\.");
|
||||
if (cnameParts.length < 2) return null;
|
||||
|
||||
String cnameTln = cnameParts[cnameParts.length - 1];
|
||||
String cnameName = cnameParts[cnameParts.length - 2];
|
||||
String cnameSub = cnameParts.length > 2
|
||||
? String.join(".", java.util.Arrays.copyOfRange(cnameParts, 0, cnameParts.length - 2))
|
||||
: null;
|
||||
|
||||
List<INSRecord> aAfterCname = bridge().getProtocolINSServer().resolve(cnameTln, cnameName, cnameSub, INSRecordType.A);
|
||||
|
||||
if (aAfterCname.isEmpty()) return null;
|
||||
|
||||
return ClassicHelper.buildClassicDomain(pd, aAfterCname.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Classic_Domain ping(Classic_RequestDomain req) {
|
||||
return new Classic_Domain(req.name, req.topLevelDomain, null, req.path, bridge());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsupportedClassicPacket(String className, Object[] content, ConnectedProtocolClient client) {
|
||||
bridge().getLogger().warn(
|
||||
"[Classic UnsupportedPacket] From client " + client.getConnectionHandler().getClientID() +
|
||||
": packet=" + className + " content=" + java.util.Arrays.toString(content)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
package org.openautonomousconnection.insserver;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
|
||||
import org.openautonomousconnection.insserver.utils.TargetName;
|
||||
import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class DatabaseINSServer extends ProtocolINSServer {
|
||||
|
||||
private final String jdbcUrl;
|
||||
private final String jdbcUser;
|
||||
private final String jdbcPassword;
|
||||
|
||||
private final ConfigurationManager configurationManager;
|
||||
|
||||
/**
|
||||
* Creates a new database-backed INS server.
|
||||
*
|
||||
* @throws IOException If the base server initialization fails.
|
||||
* @throws CertificateException If required certificate files are missing or invalid.
|
||||
*/
|
||||
public DatabaseINSServer() throws IOException, CertificateException {
|
||||
super(new File("config.properties"));
|
||||
|
||||
configurationManager = new ConfigurationManager(new File("config.properties"));
|
||||
configurationManager.loadProperties();
|
||||
|
||||
if (!configurationManager.isSet("db.url")) {
|
||||
configurationManager.set(
|
||||
"db.url",
|
||||
"jdbc:mysql://localhost:3306/ins?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"
|
||||
);
|
||||
configurationManager.saveProperties();
|
||||
}
|
||||
|
||||
if (!configurationManager.isSet("db.user")) {
|
||||
configurationManager.set("db.user", "username");
|
||||
configurationManager.saveProperties();
|
||||
}
|
||||
|
||||
if (!configurationManager.isSet("db.password")) {
|
||||
configurationManager.set("db.password", "password");
|
||||
configurationManager.saveProperties();
|
||||
}
|
||||
|
||||
if (!configurationManager.isSet("port")) {
|
||||
configurationManager.set("port", 1023);
|
||||
configurationManager.saveProperties();
|
||||
}
|
||||
|
||||
Main.getProtocolBridge().getProtocolSettings().port = configurationManager.getInt("db.port");
|
||||
|
||||
jdbcUrl = configurationManager.getString("db.url");
|
||||
jdbcUser = configurationManager.getString("db.user");
|
||||
jdbcPassword = configurationManager.getString("db.password");
|
||||
}
|
||||
|
||||
private Connection openConnection() throws SQLException {
|
||||
return DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a request for an INS record based on TLN, name, subname and record type.
|
||||
* <p>
|
||||
* This implementation:
|
||||
* <ul>
|
||||
* <li>Locates the corresponding InfoName in the SQL schema</li>
|
||||
* <li>Returns all matching {@link INSRecord} entries</li>
|
||||
* <li>Performs CNAME recursion (with a depth limit) when no direct
|
||||
* records for the requested type exist</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public List<INSRecord> resolve(String tln, String name, String sub, INSRecordType type) {
|
||||
try {
|
||||
return resolveInternal(tln, name, sub, type, 0);
|
||||
} catch (SQLException ex) {
|
||||
getProtocolBridge().getLogger().exception("INS resolve failed for " + formatName(tln, name, sub) + " type=" + type, ex);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the TLN info site,
|
||||
* which is used when a client queries {@code info.<tln>} without any sub-name.
|
||||
* <p>
|
||||
* The value is read from {@code tln.info} and must be of the form
|
||||
* {@code "host:port"}.
|
||||
*/
|
||||
@Override
|
||||
public String resolveTLNInfoSite(String tln) {
|
||||
String sql = "SELECT info FROM tln WHERE name = ?";
|
||||
|
||||
try (Connection conn = openConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
|
||||
ps.setString(1, tln);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getString("info"); // expected "host:port"
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
getProtocolBridge().getLogger().exception("Failed to resolve TLN info site for tln=" + tln, ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String formatName(String tln, String name, String sub) {
|
||||
if (sub == null || sub.isEmpty()) return name + "." + tln;
|
||||
return sub + "." + name + "." + tln;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive resolver with CNAME handling and depth limit.
|
||||
*/
|
||||
private List<INSRecord> resolveInternal(String tln, String name, String sub, INSRecordType requestedType, int depth) throws SQLException {
|
||||
final int MAX_CNAME_DEPTH = 8;
|
||||
|
||||
if (depth > MAX_CNAME_DEPTH) {
|
||||
getProtocolBridge().getLogger().warn("CNAME recursion limit exceeded for " + formatName(tln, name, sub) + " type=" + requestedType);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
try (Connection conn = openConnection()) {
|
||||
// Get IDs
|
||||
Integer tlnId = findTLNId(conn, tln);
|
||||
if (tlnId == null) return new ArrayList<>();
|
||||
|
||||
Integer infoNameId = findInfoNameId(conn, tlnId, name);
|
||||
if (infoNameId == null) return new ArrayList<>();
|
||||
|
||||
Integer subNameId = findSubNameId(conn, infoNameId, sub);
|
||||
|
||||
// Get records
|
||||
List<INSRecord> direct = loadRecords(conn, infoNameId, subNameId, requestedType);
|
||||
|
||||
// No more recursion when CNAME received
|
||||
if (requestedType == INSRecordType.CNAME) return direct;
|
||||
|
||||
if (!direct.isEmpty()) return direct;
|
||||
|
||||
// CNAME lookup
|
||||
List<INSRecord> cnames = loadRecords(conn, infoNameId, subNameId, INSRecordType.CNAME);
|
||||
if (cnames.isEmpty()) return direct; // empty
|
||||
|
||||
List<INSRecord> aggregated = new ArrayList<>();
|
||||
for (INSRecord cname : cnames) {
|
||||
TargetName target = parseCnameTarget(cname.value);
|
||||
|
||||
if (target == null) {
|
||||
getProtocolBridge().getLogger().warn("Invalid CNAME target '" + cname.value + "' on " + formatName(tln, name, sub));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rekursiv den Zielnamen auflösen
|
||||
aggregated.addAll(resolveInternal(target.tln, target.name, target.sub, requestedType, depth + 1));
|
||||
}
|
||||
|
||||
return aggregated;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all records of a given type for (infoname_id, subname_id).
|
||||
*
|
||||
* @param type May be {@code null} to load all types.
|
||||
*/
|
||||
private List<INSRecord> loadRecords(Connection conn, int infonameId, Integer subnameId, INSRecordType type) throws SQLException {
|
||||
|
||||
StringBuilder sql = new StringBuilder("SELECT type, value, ttl, priority, port, weight FROM records WHERE infoname_id = ? ");
|
||||
|
||||
if (subnameId == null) sql.append("AND subname_id IS NULL ");
|
||||
else sql.append("AND subname_id = ? ");
|
||||
|
||||
if (type != null) sql.append("AND type = ? ");
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql.toString())) {
|
||||
int idx = 1;
|
||||
ps.setInt(idx++, infonameId);
|
||||
|
||||
if (subnameId != null) ps.setInt(idx++, subnameId);
|
||||
|
||||
if (type != null) ps.setString(idx, type.name());
|
||||
|
||||
List<INSRecord> result = new ArrayList<>();
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
INSRecordType rType = INSRecordType.valueOf(rs.getString("type"));
|
||||
String value = rs.getString("value");
|
||||
int ttl = rs.getInt("ttl");
|
||||
int priority = rs.getInt("priority");
|
||||
int port = rs.getInt("port");
|
||||
int weight = rs.getInt("weight");
|
||||
|
||||
result.add(new INSRecord(rType, value, priority, weight, port, ttl));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Integer findTLNId(Connection conn, String tln) throws SQLException {
|
||||
String sql = "SELECT id FROM tln WHERE name = ?";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, tln);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getInt("id");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer findInfoNameId(Connection conn, int tlnId, String infoName) throws SQLException {
|
||||
String sql = "SELECT id FROM infonames WHERE tln_id = ? AND info = ?";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setInt(1, tlnId);
|
||||
ps.setString(2, infoName);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getInt("id");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer findSubNameId(Connection conn, int infoNameId, String sub) throws SQLException {
|
||||
if (sub == null || sub.isEmpty()) return null;
|
||||
|
||||
String sql = "SELECT id FROM subnames WHERE infoname_id = ? AND name = ?";
|
||||
|
||||
try (PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setInt(1, infoNameId);
|
||||
ps.setString(2, sub);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) return rs.getInt("id");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a CNAME target string into TLN, InfoName and optional subname.
|
||||
* <p>
|
||||
*
|
||||
* @param value Raw CNAME value from the {@code records} table.
|
||||
* @return Parsed {@link TargetName} or {@code null} if the value is invalid.
|
||||
*/
|
||||
private TargetName parseCnameTarget(String value) {
|
||||
if (value == null) return null;
|
||||
String trimmed = value.trim();
|
||||
if (trimmed.isEmpty()) return null;
|
||||
|
||||
String[] parts = trimmed.split("\\.");
|
||||
if (parts.length < 2) return null; // at least "<name>.<tln>"
|
||||
|
||||
String tln = parts[parts.length - 1];
|
||||
String name = parts[parts.length - 2];
|
||||
String sub = null;
|
||||
|
||||
if (parts.length > 2) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < parts.length - 2; i++) {
|
||||
if (i > 0) sb.append('.');
|
||||
sb.append(parts[i]);
|
||||
}
|
||||
|
||||
sub = sb.toString();
|
||||
}
|
||||
|
||||
return new TargetName(tln, name, sub);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.openautonomousconnection.insserver;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.command.events.CommandExecutorMissingPermissionEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.command.events.CommandNotFoundEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||
|
||||
public class Listener extends EventListener {
|
||||
|
||||
@dev.unlegitdqrk.unlegitlibrary.event.Listener
|
||||
public void onCommandNotFound(CommandNotFoundEvent event) {
|
||||
StringBuilder argsBuilder = new StringBuilder();
|
||||
for (String arg : event.getArgs()) argsBuilder.append(arg).append(" ");
|
||||
Main.getProtocolBridge().getLogger().error("Command '" + event.getName() + argsBuilder.toString() + "' not found!");
|
||||
}
|
||||
|
||||
@dev.unlegitdqrk.unlegitlibrary.event.Listener
|
||||
public void onMissingCommandPermission(CommandExecutorMissingPermissionEvent event) {
|
||||
Main.getProtocolBridge().getLogger().error("You do not have enough permissions to execute this command!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.openautonomousconnection.insserver;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor;
|
||||
import dev.unlegitdqrk.unlegitlibrary.command.CommandManager;
|
||||
import dev.unlegitdqrk.unlegitlibrary.command.CommandPermission;
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||
import lombok.Getter;
|
||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||
import org.openautonomousconnection.protocol.ProtocolSettings;
|
||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class Main {
|
||||
private static final CommandPermission PERMISSION_ALL = new CommandPermission("all", 1);
|
||||
private static final CommandExecutor commandExecutor = new CommandExecutor("DNS", PERMISSION_ALL) {};
|
||||
private static CommandManager commandManager;
|
||||
|
||||
@Getter
|
||||
private static ProtocolBridge protocolBridge;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ProtocolSettings settings = new ProtocolSettings();
|
||||
settings.packetHandler = new PacketHandler();
|
||||
settings.eventManager = new EventManager();
|
||||
settings.eventManager.registerListener(Listener.class);
|
||||
settings.port = 1023;
|
||||
|
||||
protocolBridge = new ProtocolBridge(new DatabaseINSServer(), settings, ProtocolVersion.PV_1_0_0_BETA, new File("logs"));
|
||||
commandManager = new CommandManager(protocolBridge.getProtocolSettings().eventManager);
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
|
||||
while (true) {
|
||||
System.out.println(commandExecutor.getName() + "> ");
|
||||
String line = scanner.nextLine();
|
||||
commandManager.execute(commandExecutor, line);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.openautonomousconnection.insserver.utils;
|
||||
|
||||
import org.openautonomousconnection.insserver.Main;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
|
||||
|
||||
public class ClassicHelper {
|
||||
public static Classic_Domain buildClassicDomain(ParsedDomain req, INSRecord rec) {
|
||||
Classic_Domain info = new Classic_Domain(req.name, req.tln, req.sub, req.path, Main.getProtocolBridge());
|
||||
|
||||
String host = rec.value;
|
||||
int port = rec.port > 0 ? rec.port : 80;
|
||||
|
||||
return new Classic_Domain(
|
||||
req.name, req.tln,
|
||||
host.contains(":") ? host : host + ":" + port,
|
||||
req.path, Main.getProtocolBridge());
|
||||
}
|
||||
|
||||
public static ParsedDomain parseDomain(Classic_RequestDomain req) {
|
||||
String tln = req.topLevelDomain; // example: "net"
|
||||
String full = req.name; // example: "api.v1.example"
|
||||
|
||||
String[] parts = full.split("\\.");
|
||||
|
||||
if (parts.length == 1) {
|
||||
return new ParsedDomain(tln, full, null, req.path);
|
||||
}
|
||||
|
||||
String name = parts[parts.length - 1];
|
||||
String sub = parts.length > 1
|
||||
? String.join(".", java.util.Arrays.copyOfRange(parts, 0, parts.length - 1))
|
||||
: null;
|
||||
|
||||
return new ParsedDomain(tln, name, sub, req.path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.openautonomousconnection.insserver.utils;
|
||||
|
||||
public final class ParsedDomain {
|
||||
public final String tln;
|
||||
public final String name;
|
||||
public final String sub;
|
||||
public final String path;
|
||||
|
||||
public ParsedDomain(String tln, String name, String sub, String path) {
|
||||
this.tln = tln;
|
||||
this.name = name;
|
||||
this.sub = sub;
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.openautonomousconnection.insserver.utils;
|
||||
|
||||
/**
|
||||
* Represents a parsed CNAME target in (tln, name, sub) form.
|
||||
*/
|
||||
public final class TargetName {
|
||||
public final String tln;
|
||||
public final String name;
|
||||
public final String sub;
|
||||
|
||||
public TargetName(String tln, String name, String sub) {
|
||||
this.tln = tln;
|
||||
this.name = name;
|
||||
this.sub = sub;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user