package org.openautonomousconnection.ins; import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager; import org.openautonomousconnection.ins.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. *

* This implementation: *

*/ @Override public List 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.} without any sub-name. *

* 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 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 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 cnames = loadRecords(conn, infoNameId, subNameId, INSRecordType.CNAME); if (cnames.isEmpty()) return direct; // empty List 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 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 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. *

* * @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 "." 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); } }