55 Commits

Author SHA1 Message Date
UnlegitDqrk
f81cb0ee15 Updated frontend to new Protocol 2026-02-27 20:40:59 +01:00
UnlegitDqrk
09dd207bb1 Updated frontend to new Protocol 2026-02-22 17:21:23 +01:00
UnlegitDqrk
2d829fe341 Updated to lates Protocol 2026-02-22 16:15:22 +01:00
UnlegitDqrk
de22a8ab67 Updated to lates Protocol 2026-02-22 16:08:16 +01:00
UnlegitDqrk
a365b57638 Removed unused licenses 2026-02-14 19:22:18 +01:00
UnlegitDqrk
eda1b24a26 Small bug fix 2026-02-14 17:25:22 +01:00
0ab292ba72 Update frontend/dashboard.java 2026-02-11 23:01:13 +00:00
UnlegitDqrk
f2c35c1e2c Finished 2026-02-11 23:23:10 +01:00
UnlegitDqrk
64ce55ea7b Finished 2026-02-11 23:22:20 +01:00
UnlegitDqrk
9483c36a66 Added mariadb 2026-02-10 22:09:14 +01:00
UnlegitDqrk
7f2865a5d8 Added mariadb 2026-02-10 22:08:48 +01:00
9d2670ebbe Update backend/database.sql 2026-02-10 20:13:17 +00:00
f978895be8 Update backend/database.sql 2026-02-10 19:38:49 +00:00
49764f445a Add backend/database 2026-02-10 19:38:40 +00:00
911a166d48 Delete ins_backend.sql 2026-02-10 19:38:24 +00:00
UnlegitDqrk
3b7301e974 Updated Protocol 2026-02-08 21:42:09 +01:00
UnlegitDqrk
39461fc07a Updated Protocol 2026-02-08 19:05:55 +01:00
UnlegitDqrk
26c1427dc6 Updated Protocol 2026-02-08 18:28:00 +01:00
UnlegitDqrk
ee40b4785a Updated Protocol 2026-02-08 18:26:30 +01:00
UnlegitDqrk
3180cde3c9 Protocol update 2026-02-06 18:02:13 +01:00
UnlegitDqrk
0c21d04488 Protocol update 2026-02-06 18:00:32 +01:00
UnlegitDqrk
323c256050 Updated Readme 2026-02-06 17:50:19 +01:00
UnlegitDqrk
bf6a26a5c5 Updated Protocol 2026-02-06 17:39:03 +01:00
UnlegitDqrk
10aafd1218 Updated Protocol 2026-02-06 17:38:02 +01:00
UnlegitDqrk
3b962af0d3 Updated Protocol 2026-02-06 17:37:43 +01:00
Finn
fce45566fb Updated to latest Protocol Version 2026-02-01 19:06:45 +01:00
Finn
77eb54afc2 Added JavaDocs 2026-01-19 19:16:35 +01:00
Finn
c4dac2373f Added JavaDocs 2026-01-19 19:01:17 +01:00
Finn
76d16209ff Updated Protocol Version 2026-01-19 14:28:12 +01:00
Finn
3848126854 Bug fix 2026-01-18 23:13:38 +01:00
Finn
7c8efa597b Added StopCommand 2026-01-18 22:56:12 +01:00
Finn
5e7f8b7cf3 Fixed Scanner Input 2026-01-18 22:48:36 +01:00
Finn
db66ce0a21 Updated to latest Protocol Version 2026-01-18 22:02:10 +01:00
Finn
6589160632 Updated to latest Protocol Version 2026-01-18 22:01:14 +01:00
Finn
63ac0c2d89 Changed to new ProtocolVersion 2026-01-18 18:34:29 +01:00
Finn
978d30a82c Version changing 2026-01-18 14:57:04 +01:00
Finn
8cd38d18a5 Version changing 2026-01-18 14:56:41 +01:00
5f4889a3db Update LICENSE 2026-01-16 22:15:43 +00:00
70375fb286 Update pom.xml 2026-01-16 22:12:26 +00:00
Finn
c25d223e32 Bug fixes 2025-12-13 16:48:28 +01:00
Finn
b36f96b9b9 Changed DNS to INS 2025-12-13 16:04:42 +01:00
Finn
f9b53f1192 Added licenses 2025-12-13 15:53:57 +01:00
Finn
181274d818 Fixed some links 2025-12-13 14:55:41 +01:00
Finn
c7ca67176d Registered listener 2025-12-13 14:44:45 +01:00
Finn
5fec181a74 Clean up 2025-12-13 14:43:49 +01:00
Finn
887587f5de Added Build Properties 2025-12-13 14:42:51 +01:00
Finn
f74e7100aa Updated to latest protocol version 2025-12-12 21:03:10 +01:00
Finn
17cc3449d2 Added default port and implemented ProtocolSettings 2025-12-12 19:46:00 +01:00
Finn
16ea18d95f Updated Protocol Version 2025-12-12 19:23:31 +01:00
Finn
41884d9d39 Updated Version 2025-12-12 18:47:10 +01:00
Finn
5f1bc8285d Changed OAPL link 2025-12-11 12:54:59 +01:00
Finn
829a998331 Changed OAPL link 2025-12-11 12:53:51 +01:00
Finn
509ed5d1bf Changed OAPL link 2025-12-11 12:52:33 +01:00
Finn
25d6f1de21 Finished INS 2025-12-11 12:36:33 +01:00
Finn
6f5d355f79 Updated everything to new INS 2025-12-11 12:01:09 +01:00
29 changed files with 2843 additions and 393 deletions

6
.idea/GitLink.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="uk.co.ben_gibson.git.link.SettingsState">
<option name="host" value="e0f86390-1091-4871-8aeb-f534fbc99cf0" />
</component>
</project>

2
.idea/misc.xml generated
View File

@@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="23" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

70
LICENSE
View File

@@ -1,68 +1,2 @@
Open Autonomous Public License (OAPL) v1.0
Copyright (C) 2024-2025 Open Autonomous Connection (OAC)
Projekt-URL: https://open-autonomous-connection.org/
---
1. Nutzungsrechte
Diese Software darf sowohl für private als auch kommerzielle Zwecke genutzt werden. Die Nutzung ist unter den Bedingungen dieser Lizenz gestattet.
---
2. Verkaufsverbot
Es ist nicht gestattet, diese Software oder abgeleitete Werke davon zu verkaufen oder kommerziell zu vertreiben.
Dies umfasst auch jede Form der direkten oder indirekten Monetarisierung der Software selbst.
Es ist gestattet, die Software im Rahmen von Dienstleistungen kommerziell zu nutzen, solange der Quellcode und die Originaldateien kostenlos verfügbar bleiben und nicht gegen Entgelt verkauft oder monetarisiert werden.
Jede Form der Monetarisierung der Software selbst, wie der Verkauf oder die Lizenzierung der Software, ist untersagt.
---
3. Offenlegung des Quellcodes
Die Software ist dauerhaft quelloffen. Der vollständige Quellcode muss bei jeder Verbreitung, auch in geänderter Form, mitgeliefert oder öffentlich zugänglich gemacht werden.
Jede Version, auch veränderte, muss einen klar sichtbaren Verweis auf das Originalprojekt enthalten:
→ https://github.com/Open-Autonomous-Connection/
---
4. Weitergabe & Lizenzvererbung
Die Software darf frei kopiert, verteilt und verändert werden, unter folgenden Bedingungen:
- Die Original-Lizenz (OAPL v1.0) muss vollständig und unverändert mitgeliefert werden.
- Alle Änderungen am Quellcode müssen klar kenntlich gemacht werden (z.B. durch Kommentare oder Änderungsprotokolle).
- Abgeleitete Werke müssen unter derselben Lizenz (OAPL v1.0) veröffentlicht werden, es sei denn, die Lizenzierung erfolgt in einem Kontext,
in dem dies durch geltendes Recht oder technische Einschränkungen nicht möglich ist. In diesem Fall muss der Quellcode der Änderungen weiterhin offen und zugänglich gemacht werden.
- Es dürfen keine zusätzlichen Einschränkungen oder Bedingungen auferlegt werden, die den Bedingungen dieser Lizenz widersprechen.
---
5. Keine proprietäre Nutzung
Die Software oder ihre abgeleiteten Werke dürfen nicht in proprietäre Software integriert oder unter einer Lizenz weitergegeben werden, die die Bedingungen dieser Lizenz einschränkt oder die Offenlegung des Quellcodes unterlässt.
---
6. Keine Veränderung dieser Lizenz
Diese Lizenz darf nicht verändert oder durch andere Lizenzen ersetzt werden. Eine Modifikation oder Re-Lizenzierung ist ausdrücklich untersagt.
---
7. Haftungsausschluss ("as-is")
DIE SOFTWARE WIRD 'WIE BESEHEN' BEREITGESTELLT, OHNE AUSDRÜCKLICHE ODER IMPLIZIERTE GARANTIEN, EINSCHLIESSLICH, ABER NICHT BESCHRÄNKT AUF GARANTIEN DER MARKTGÄNGIGKEIT,
DER EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND DER NICHTVERLETZUNG VON RECHTEN DRITTER. SOWEIT ES GESCHÄFTSRECHTLICH ZULÄSSIG IST,
WIRD DIE HAFTUNG DER AUTOREN FÜR SCHÄDEN ODER VERLUSTE AUFGRUND DER NUTZUNG DER SOFTWARE AUSGESCHLOSSEN.
---
8. Salvatorische Klausel
Sollte eine Bestimmung dieser Lizenz als unwirksam, undurchsetzbar oder nicht durchsetzbar erklärt werden,
bleibt die Gültigkeit der übrigen Bestimmungen davon unberührt. In diesem Fall wird die unwirksame Klausel durch eine wirksame und durchsetzbare Bestimmung ersetzt,
die dem ursprünglichen wirtschaftlichen Zweck der unwirksamen Bestimmung am nächsten kommt.
---
Ende der Lizenzbedingungen.
Additional Notice regarding UnlegitLibrary:
UnlegitLibrary is primarily distributed under the GNU GPLv3.
For the purposes of the Open Autonomous Connection (OAC) project,
the author has also licensed UnlegitLibrary under the Open Autonomous Public License (OAPL).
Within OAC, the OAPL terms apply to UnlegitLibrary.
Please read the license here: https://open-autonomous-connection.org/license.html
Download all third parties licenses here: https://open-autonomous-connection.org/assets/licenses.zip

View File

@@ -1,51 +1,24 @@
# Open Autonomous Connection Protocol
# Open Autonomous Connection INS
This is the Protocol for our Open Autonomous Connection project.<br />
You can easily implement this Protocol via Maven.<br />
This is the INS for our Open Autonomous Connection project.<br />
Feel free to join our Discord.
<br />
## License Notice
This project (OAC) is licensed under the [Open Autonomous Public License (OAPL)](https://repo.open-autonomous-connection.org/open-autonomous-connection/OAPL/).
This project (OAC) is licensed under
the [Open Autonomous Public License (OAPL)](https://open-autonomous-connection.org/license.html).
**Third-party components:**
<br />
Download all license here: https://open-autonomous-connection.org/assets/licenses.zip
- *UnlegitLibrary* is authored by the same copyright holder and is used here under a special agreement:
While [UnlegitLibrary](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/) is generally distributed under the [GNU GPLv3](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master/LICENSE),
While [UnlegitLibrary](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/) is generally distributed under
the [GNU GPLv3](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master/LICENSE),
it is additionally licensed under OAPL **exclusively for the OAC project**.
Therefore, within OAC, the OAPL terms apply to UnlegitLibrary as well.
# Bugs/Problems
# In progress
# TODO
## Certificate generation for NetworkSystem
### Creating Root-CA:
````
openssl genrsa -out myCA.key 4096
openssl req -x509 -new -nodes -key myCA.key -sha256 -days 3650 -out myCA.pem
myCA.key = private Key for CA (keep secret)
myCA.pem = public Root-Certificate for signing server and client certificates
````
### Creating (DNS-/Web-)Server Certificate based on Root-CA:
````
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -in server.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out server.crt -days 825 -sha256
server.key = private Key for Server
server.crt = Server-Certificate signed by Root-CA
````
### Optional: Creating Client Certificate based on Root-CA:
````
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
openssl x509 -req -in client.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out client.crt -days 825 -sha256
client.key = private Key for Client
client.crt = Client-Certificate signed by Root-CA
````
### Take a look into [Certificate Generation](https://repo.unlegitdqrk.dev/UnlegitDqrk/unlegitlibrary/src/branch/master#certificate-generation-for-networksystem).
> [!NOTE]
> All certificate registrars require the Root CA to issue a server/client certificate

216
database.sql Normal file
View File

@@ -0,0 +1,216 @@
-- phpMyAdmin SQL Dump
-- version 5.2.1deb1+deb12u1
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Generation Time: Feb 10, 2026 at 08:12 PM
-- Server version: 10.11.14-MariaDB-0+deb12u2
-- PHP Version: 8.2.29
SET
SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET
time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `oac_ins`
--
-- --------------------------------------------------------
--
-- Table structure for table `infonames`
--
CREATE TABLE `infonames`
(
`id` int(11) NOT NULL,
`info` varchar(63) NOT NULL,
`tln_id` int(11) NOT NULL,
`uid` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `records`
--
CREATE TABLE `records`
(
`id` int(11) NOT NULL,
`infoname_id` int(11) NOT NULL,
`subname_id` int(11) DEFAULT NULL,
`type` enum('A','AAAA','TXT','CNAME','MX','SRV','NS') NOT NULL,
`value` varchar(2048) NOT NULL,
`ttl` int(11) DEFAULT 3600,
`priority` int(11) DEFAULT NULL,
`port` int(11) DEFAULT NULL,
`weight` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `subnames`
--
CREATE TABLE `subnames`
(
`id` int(11) NOT NULL,
`name` varchar(63) NOT NULL,
`infoname_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `tln`
--
CREATE TABLE `tln`
(
`id` int(11) NOT NULL,
`name` varchar(63) NOT NULL,
`info` text DEFAULT NULL,
`owner_id` int(11) DEFAULT NULL,
`is_public` tinyint(1) DEFAULT 1,
`allow_subdomains` tinyint(1) DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users`
(
`id` int(11) NOT NULL,
`uid` varchar(36) NOT NULL,
`username` varchar(64) NOT NULL,
`password` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `infonames`
--
ALTER TABLE `infonames`
ADD PRIMARY KEY (`id`),
ADD KEY `tln_id` (`tln_id`),
ADD KEY `uid` (`uid`);
--
-- Indexes for table `records`
--
ALTER TABLE `records`
ADD PRIMARY KEY (`id`),
ADD KEY `infoname_id` (`infoname_id`),
ADD KEY `subname_id` (`subname_id`);
--
-- Indexes for table `subnames`
--
ALTER TABLE `subnames`
ADD PRIMARY KEY (`id`),
ADD KEY `infoname_id` (`infoname_id`);
--
-- Indexes for table `tln`
--
ALTER TABLE `tln`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`),
ADD KEY `owner_id` (`owner_id`);
--
-- Indexes for table `users`
--
ALTER TABLE `users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `uid` (`uid`),
ADD UNIQUE KEY `username` (`username`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `infonames`
--
ALTER TABLE `infonames`
MODIFY `id` int (11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `records`
--
ALTER TABLE `records`
MODIFY `id` int (11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `subnames`
--
ALTER TABLE `subnames`
MODIFY `id` int (11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `tln`
--
ALTER TABLE `tln`
MODIFY `id` int (11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
MODIFY `id` int (11) NOT NULL AUTO_INCREMENT;
--
-- Constraints for dumped tables
--
--
-- Constraints for table `infonames`
--
ALTER TABLE `infonames`
ADD CONSTRAINT `infonames_ibfk_1` FOREIGN KEY (`tln_id`) REFERENCES `tln` (`id`) ON DELETE CASCADE,
ADD CONSTRAINT `infonames_ibfk_2` FOREIGN KEY (`uid`) REFERENCES `users` (`id`) ON
DELETE
CASCADE;
--
-- Constraints for table `records`
--
ALTER TABLE `records`
ADD CONSTRAINT `records_ibfk_1` FOREIGN KEY (`infoname_id`) REFERENCES `infonames` (`id`) ON DELETE CASCADE,
ADD CONSTRAINT `records_ibfk_2` FOREIGN KEY (`subname_id`) REFERENCES `subnames` (`id`) ON
DELETE
CASCADE;
--
-- Constraints for table `subnames`
--
ALTER TABLE `subnames`
ADD CONSTRAINT `subnames_ibfk_1` FOREIGN KEY (`infoname_id`) REFERENCES `infonames` (`id`) ON DELETE CASCADE;
--
-- Constraints for table `tln`
--
ALTER TABLE `tln`
ADD CONSTRAINT `tln_ibfk_1` FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON DELETE SET NULL;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

120
dependency-reduced-pom.xml Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openautonomousconnection</groupId>
<artifactId>INSServer</artifactId>
<version>1.0.1-BETA.0.2</version>
<description>The default INS-Server</description>
<url>https://open-autonomous-connection.org/</url>
<issueManagement>
<system>Issue Tracker</system>
<url>https://repo.open-autonomous-connection.org/open-autonomous-connection/INSServer/issues</url>
</issueManagement>
<developers>
<developer>
<name>UnlegitDqrk</name>
<url>https://unlegitdqrk.dev/</url>
<organization>Open Autonomous Connection</organization>
<organizationUrl>https://open-autonomous-connection.org/</organizationUrl>
<roles>
<role>Owner</role>
<role>Head Developer</role>
</roles>
</developer>
<developer>
<name>Maple</name>
<url>https://niumaple.carrd.co/</url>
<organization>Open Autonomous Connection</organization>
<organizationUrl>https://open-autonomous-connection.org/</organizationUrl>
<roles>
<role>Owner</role>
<role>Head Developer</role>
</roles>
</developer>
</developers>
<licenses>
<license>
<name>Open Autonomous Public License (OAPL)</name>
<url>https://open-autonomous-connection.org/license.html</url>
</license>
</licenses>
<organization>
<name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url>
</organization>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>org.openautonomousconnection.insserver.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports</arg>
<arg>java.base/sun.security.x509=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>java.base/sun.security.util=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
<doclint>none</doclint>
<locale>en_US</locale>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
<charset>UTF-8</charset>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<snapshots />
<id>oac</id>
<url>https://repo.open-autonomous-connection.org/api/packages/open-autonomous-connection/maven</url>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>25</maven.compiler.target>
<maven.compiler.source>25</maven.compiler.source>
</properties>
</project>

389
frontend/dashboard.java Normal file
View File

@@ -0,0 +1,389 @@
package ins.frontend;
import ins.frontend.utils.RegistrarDao;
import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html;
import org.openautonomousconnection.webserver.utils.MergedRequestParams;
import org.openautonomousconnection.webserver.utils.WebUrlUtil;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* INS registrar dashboard (TLN / InfoName / Records) for protocol v1.0.1 resource packets.
*
* <p>Supported actions (POST recommended for mutations):</p>
* <ul>
* <li>create_tln</li>
* <li>update_tln</li>
* <li>delete_tln</li>
* <li>create_infoname</li>
* <li>delete_infoname</li>
* <li>add_record</li>
* </ul>
*/
@Route(path = "/dashboard.html")
public final class dashboard implements WebPage {
private static Integer normalizeNullableInt(String s) {
if (s == null) return null;
String t = s.trim();
if (t.isEmpty()) return null;
try {
return Integer.parseInt(t);
} catch (Exception ignored) {
return null;
}
}
@Override
public WebResourceResponsePacket handle(WebPageContext ctx) throws Exception {
WebApp.init();
if (ctx.session == null || !ctx.session.isValid() || ctx.session.getUser() == null) {
return plain(ctx, 401, "Authentication required (session).");
}
int userId;
try {
userId = Integer.parseInt(ctx.session.getUser());
} catch (Exception e) {
return plain(ctx, 401, "Invalid session user.");
}
RegistrarDao dao = WebApp.get().dao();
// Build target for param merging: "/path?query"
String rawTarget = WebUrlUtil.extractPathAndQuery(ctx.request.getUrl());
if (rawTarget == null) rawTarget = "/dashboard.html";
Map<String, String> headers = ctx.request.getHeaders();
byte[] body = ctx.request.getBody();
MergedRequestParams p = MergedRequestParams.from(rawTarget, headers, body);
String msg = null;
String err = null;
String action = p.getOr("action", "").trim();
if (!action.isBlank()) {
try {
ActionResult r = executeAction(action, p, userId, dao);
msg = r.msg();
err = r.err();
} catch (Exception e) {
err = "Action failed: " + safeMsg(e);
}
}
return render(ctx, userId, msg, err, dao);
}
private ActionResult executeAction(String action, MergedRequestParams p, int userId, RegistrarDao dao) throws Exception {
String a = action.trim().toLowerCase(Locale.ROOT);
if ("create_tln".equals(a)) {
String name = p.get("name");
String info = p.getOr("info", "");
boolean isPublic = p.getBool("is_public");
boolean allowSubs = p.getBool("allow_subdomains");
if (name == null || name.isBlank()) return ActionResult.err("Missing TLN name.");
int id = dao.createTln(name.trim(), info, userId, isPublic, allowSubs);
return ActionResult.ok("Created TLN id=" + id + " (" + name.trim() + ")");
}
if ("update_tln".equals(a)) {
int id = p.getInt("id", -1);
String info = p.getOr("info", "");
boolean isPublic = p.getBool("is_public");
boolean allowSubs = p.getBool("allow_subdomains");
if (id <= 0) return ActionResult.err("Invalid TLN id.");
boolean ok = dao.updateTlnOwned(id, userId, info, isPublic, allowSubs);
return ActionResult.ok(ok ? ("Updated TLN id=" + id) : "Not owner / not found.");
}
if ("delete_tln".equals(a)) {
int id = p.getInt("id", -1);
if (id <= 0) return ActionResult.err("Invalid TLN id.");
boolean ok = dao.deleteTlnOwned(id, userId);
return ActionResult.ok(ok ? ("Deleted TLN id=" + id) : "Not owner / not found.");
}
if ("create_infoname".equals(a)) {
String tlnName = p.get("tln");
String info = p.get("info");
if (tlnName == null || tlnName.isBlank() || info == null || info.isBlank()) {
return ActionResult.err("Missing tln / info.");
}
RegistrarDao.TlnRow tln = dao.findTlnByName(tlnName.trim()).orElse(null);
if (tln == null) return ActionResult.err("Unknown TLN: " + tlnName);
if (!RegistrarDao.canUseTln(tln, userId)) {
return ActionResult.err("TLN not public and not owned by you.");
}
int id = dao.createInfoName(tln, info.trim(), userId);
return ActionResult.ok("Created infoname id=" + id + " (" + info.trim() + "." + tln.name() + ")");
}
if ("delete_infoname".equals(a)) {
int id = p.getInt("id", -1);
if (id <= 0) return ActionResult.err("Invalid infoname id.");
boolean ok = dao.deleteInfoNameOwned(id, userId);
return ActionResult.ok(ok ? ("Deleted infoname id=" + id) : "Not owner / not found.");
}
if ("add_record".equals(a)) {
int infonameId = p.getInt("infoname_id", -1);
String sub = p.get("sub");
String type = p.getOr("type", "").trim().toUpperCase(Locale.ROOT);
String value = p.get("value");
int ttl = p.getInt("ttl", 3600);
Integer priority = normalizeNullableInt(p.get("priority"));
Integer port = normalizeNullableInt(p.get("port"));
Integer weight = normalizeNullableInt(p.get("weight"));
if (infonameId <= 0) return ActionResult.err("Invalid infoname_id.");
if (!dao.isOwnerOfInfoName(infonameId, userId)) return ActionResult.err("Not owner of this infoname.");
if (type.isBlank() || value == null || value.isBlank()) return ActionResult.err("Missing type/value.");
RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId);
RegistrarDao.InfoNameRow row = null;
for (RegistrarDao.InfoNameRow r : owned) {
if (r.id() == infonameId) {
row = r;
break;
}
}
if (row == null) return ActionResult.err("Infoname not found in your ownership list.");
if (!RegistrarDao.canUseSubname(row.tln(), userId, sub)) {
return ActionResult.err("Subnames are not allowed for this TLN (allow_subdomains=0) unless you own the TLN.");
}
Integer subId = dao.ensureSubname(infonameId, sub);
int rid = dao.addRecord(infonameId, subId, type, value.trim(), ttl, priority, port, weight);
return ActionResult.ok("Added record id=" + rid + " type=" + type);
}
return ActionResult.err("Unknown action: " + action);
}
private WebResourceResponsePacket render(WebPageContext ctx, int userId, String msg, String err, RegistrarDao dao) throws Exception {
RegistrarDao.TlnRow[] tlns = dao.listVisibleTlns(userId);
RegistrarDao.InfoNameRow[] owned = dao.listOwnedInfoNames(userId);
String tlnHtml = renderTlnSection(tlns, userId);
String infoHtml = renderInfoNamesSection(owned);
String body = """
<div class="card">
<h2>INS Registrar</h2>
%s
%s
<h3>Create TLN</h3>
<form method="post" action="dashboard.html" class="form">
<input type="hidden" name="action" value="create_tln">
<label><span class="muted">name</span><input type="text" name="name" placeholder="com" required></label>
<label><span class="muted">info</span><input type="text" name="info" required placeholder="ip[:port]"></label>
<label><span class="muted">is_public</span><input type="text" name="is_public" value="1" required></label>
<label><span class="muted">allow_subdomains</span><input type="text" name="allow_subdomains" value="1" required></label>
<button type="submit">Create TLN</button>
</form>
<h3>TLNs (public + owned)</h3>
%s
<h3>Create InfoName</h3>
<p class="muted">Allowed if TLN is public or owned by you.</p>
<form method="post" action="dashboard.html" class="form">
<input type="hidden" name="action" value="create_infoname">
<label><span class="muted">tln</span><input type="text" name="tln" placeholder="com" required></label>
<label><span class="muted">info</span><input type="text" name="info" placeholder="example" required></label>
<button type="submit">Create InfoName</button>
</form>
<h3>Add Record</h3>
<p class="muted">Subname requires allow_subdomains=1 unless you own the TLN. Root (no sub) always allowed.</p>
<form method="post" action="dashboard.html" class="form">
<input type="hidden" name="action" value="add_record">
<label><span class="muted">infoname_id</span><input type="number" name="infoname_id" min="1" required></label>
<label><span class="muted">sub (optional)</span><input type="text" name="sub" placeholder="www"></label>
<label><span class="muted">type</span><input type="text" name="type" placeholder="A/AAAA/TXT/CNAME/MX/SRV/NS" required></label>
<label><span class="muted">value</span><input type="text" name="value" required></label>
<label><span class="muted">ttl</span><input type="number" name="ttl" value="3600"></label>
<label><span class="muted">priority (MX/SRV)</span><input type="number" name="priority"></label>
<label><span class="muted">port (SRV)</span><input type="number" placeholder="Default: 1028" value="1028" name="port"></label>
<label><span class="muted">weight (SRV)</span><input type="number" name="weight"></label>
<button type="submit">Add Record</button>
</form>
<h3>Your InfoNames</h3>
%s
<div class="row">
<div class="col"><a href="index.html">Home</a></div>
</div>
</div>
""".formatted(
msg == null ? "" : "<p class='ok'>" + Html.esc(msg) + "</p>",
err == null ? "" : "<p class='err'>" + Html.esc(err) + "</p>",
tlnHtml,
infoHtml
);
String html = Html.page("INS Registrar", body);
return html(ctx, 200, html);
}
private String renderTlnSection(RegistrarDao.TlnRow[] tlns, int userId) {
if (tlns == null || tlns.length == 0) {
return "<p class='muted'>No TLNs available.</p>";
}
StringBuilder sb = new StringBuilder();
sb.append("<ul>");
for (RegistrarDao.TlnRow t : tlns) {
boolean ownedByMe = (t.ownerId() != null && t.ownerId() == userId);
sb.append("<li>")
.append("<code>").append(Html.esc(t.name())).append("</code>")
.append(" <span class='muted'>(id=").append(t.id()).append(")</span> ")
.append(ownedByMe ? "<span class='muted'>(owner)</span>" : "<span class='muted'>(public)</span>")
.append(" <span class='muted'>is_public=").append(t.isPublic() ? "1" : "0")
.append(", allow_subdomains=").append(t.allowSubdomains() ? "1" : "0")
.append("</span>");
if (ownedByMe) {
sb.append("""
<div class="row" style="margin-top:8px; gap:10px; flex-wrap:wrap;">
<form method="post" action="dashboard.html" class="form" style="margin:0;">
<input type="hidden" name="action" value="update_tln">
<input type="hidden" name="id" value="%d">
<label>
<span class="muted">info</span>
<input type="text" name="info" value="%s">
</label>
<label>
<span class="muted">is_public</span>
<input type="text" name="is_public" value="%s" style="width:70px;">
</label>
<label>
<span class="muted">allow_subdomains</span>
<input type="text" name="allow_subdomains" value="%s" style="width:70px;">
</label>
<button type="submit">Update</button>
</form>
<form method="post" action="dashboard.html" style="margin:0;">
<input type="hidden" name="action" value="delete_tln">
<input type="hidden" name="id" value="%d">
<button type="submit" class="muted">Delete</button>
</form>
</div>
""".formatted(
t.id(),
Html.esc(t.info() == null ? "" : t.info()),
t.isPublic() ? "1" : "0",
t.allowSubdomains() ? "1" : "0",
t.id()
));
}
sb.append("</li>");
}
sb.append("</ul>");
return sb.toString();
}
private String renderInfoNamesSection(RegistrarDao.InfoNameRow[] owned) {
if (owned == null || owned.length == 0) {
return "<p class='muted'>No infonames yet.</p>";
}
StringBuilder sb = new StringBuilder();
sb.append("<ul>");
for (RegistrarDao.InfoNameRow r : owned) {
sb.append("<li>")
.append("<code>")
.append(Html.esc(r.info())).append(".").append(Html.esc(r.tln().name()))
.append("</code>")
.append(" <span class='muted'>(id=").append(r.id()).append(")</span>")
.append("""
<form method="post" action="dashboard.html" style="display:inline; margin-left:10px;">
<input type="hidden" name="action" value="delete_infoname">
<input type="hidden" name="id" value="%d">
<button type="submit" class="muted">Delete</button>
</form>
""".formatted(r.id()))
.append("</li>");
}
sb.append("</ul>");
return sb.toString();
}
private WebResourceResponsePacket plain(WebPageContext ctx, int code, String text) {
byte[] body = (text == null ? "" : text).getBytes(StandardCharsets.UTF_8);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), code, "text/plain; charset=utf-8", headers, body, null);
}
private WebResourceResponsePacket html(WebPageContext ctx, int code, String html) {
byte[] body = Html.utf8(html);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), code, "text/html; charset=utf-8", headers, body, null);
}
private WebPacketHeader outHeader(WebPageContext ctx) {
WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null;
if (in == null) {
return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis());
}
return new WebPacketHeader(
in.getRequestId(),
in.getTabId(),
in.getPageId(),
in.getFrameId(),
in.getFlags() | WebPacketFlags.RESOURCE,
System.currentTimeMillis()
);
}
private String safeMsg(Exception e) {
String m = e.getMessage();
if (m == null || m.isBlank()) return e.getClass().getSimpleName();
return m;
}
private record ActionResult(String msg, String err) {
static ActionResult ok(String msg) {
return new ActionResult(msg, null);
}
static ActionResult err(String err) {
return new ActionResult(null, err);
}
}
}

59
frontend/index.java Normal file
View File

@@ -0,0 +1,59 @@
package ins.frontend;
import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html;
import java.util.Map;
/**
* Landing page for INS registrar frontend (v1.0.1).
*/
@Route(path = "/index.html")
public final class index implements WebPage {
@Override
public WebResourceResponsePacket handle(WebPageContext ctx) {
WebApp.init();
String html = Html.page("OAC INS Registrar", """
<div class="card">
<h2>OAC INS Registrar</h2>
<p class="muted">What you want to do?</p>
<div class="col"><a href="info.html">Info</a></div><br />
<div class="row">
<div class="col"><a href="login.html">Login</a></div>
<div class="col"><a href="register.html">Register</a></div>
<div class="col"><a href="dashboard.html">Dashboard</a></div>
</div>
</div>
""");
byte[] body = Html.utf8(html);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), 200, "text/html; charset=utf-8", headers, body, null);
}
private WebPacketHeader outHeader(WebPageContext ctx) {
WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null;
if (in == null) {
return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis());
}
return new WebPacketHeader(
in.getRequestId(),
in.getTabId(),
in.getPageId(),
in.getFrameId(),
in.getFlags() | WebPacketFlags.RESOURCE,
System.currentTimeMillis()
);
}
}

73
frontend/info.java Normal file
View File

@@ -0,0 +1,73 @@
package ins.frontend;
import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html;
import java.util.Map;
/**
* Info page for INS registrar frontend (v1.0.1).
*/
@Route(path = "/info.html")
public final class info implements WebPage {
@Override
public WebResourceResponsePacket handle(WebPageContext ctx) {
WebApp.init();
String html = Html.page("INS Info", """
<section class="card">
<h2>OAC Default INS Server</h2>
<p>
The <strong>Default INS Server</strong> is the official, project-operated
name service of the Open Autonomous Connection (OAC) network.
</p>
<p>
It provides a trusted reference point for resolving InfoNames
and enables initial client connections
to the OAC ecosystem.
</p>
<p>
This server is maintained by the OAC project and is intended
as the recommended entry point for public services and new clients.
</p>
<p class="muted">
Note: Alternative or private INS servers may exist, but the default INS
server represents the official and stable reference instance.
</p>
</section>
""");
byte[] body = Html.utf8(html);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), 200, "text/html; charset=utf-8", headers, body, null);
}
private WebPacketHeader outHeader(WebPageContext ctx) {
WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null;
if (in == null) {
return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis());
}
return new WebPacketHeader(
in.getRequestId(),
in.getTabId(),
in.getPageId(),
in.getFrameId(),
in.getFlags() | WebPacketFlags.RESOURCE,
System.currentTimeMillis()
);
}
}

266
frontend/login.java Normal file
View File

@@ -0,0 +1,266 @@
package ins.frontend;
import ins.frontend.utils.UserDao;
import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Login page (v1.0.1).
*/
@Route(path = "/login.html")
public final class login implements WebPage {
private enum ReqMethod { GET, POST, OTHER }
@Override
public WebResourceResponsePacket handle(WebPageContext ctx) throws Exception {
WebApp.init();
// If a valid session already exists -> go dashboard (keep session)
if (ctx.session != null && ctx.session.isValid() && ctx.session.getUser() != null) {
return redirect302(ctx, "dashboard.html", ctx.session.getSessionId());
}
ReqMethod method = detectMethod(ctx);
if (method == ReqMethod.GET) {
return ok(ctx, renderForm(null));
}
if (method != ReqMethod.POST) {
return text(ctx, 405, "Method Not Allowed");
}
String contentType = headerIgnoreCase(ctx.request.getHeaders(), "content-type");
String ctLower = (contentType == null) ? "" : contentType.toLowerCase(Locale.ROOT);
if (!ctLower.startsWith("application/x-www-form-urlencoded")) {
return ok(ctx, renderForm("Unsupported content-type: " + Html.esc(contentType)));
}
Map<String, List<String>> form = parseFormUrlEncoded(ctx.request.getBody());
String username = first(form, "username");
String password = first(form, "password");
if (username == null || password == null) {
return ok(ctx, renderForm("Missing username/password."));
}
String lookupUsername = username.trim();
UserDao.UserRow user = WebApp.get().users().findByUsername(lookupUsername).orElse(null);
if (user == null) {
return ok(ctx, renderForm("Invalid credentials."));
}
boolean okPw = WebApp.get().passwordHasher().verify(password, user.passwordEncoded());
if (!okPw) {
return ok(ctx, renderForm("Invalid credentials."));
}
String ip = resolveIp(ctx);
String ua = headerIgnoreCase(ctx.request.getHeaders(), "user-agent");
if (ua == null) ua = "";
String session = SessionManager.create(
String.valueOf(user.id()),
ip,
ua,
(ProtocolWebServer) ctx.client.getServer()
);
return redirect302(ctx, "dashboard.html", session);
}
private ReqMethod detectMethod(WebPageContext ctx) {
// Preferred: request.getMethod() via reflection (avoids depending on specific enum package).
try {
Method m = ctx.request.getClass().getMethod("getMethod");
Object v = m.invoke(ctx.request);
if (v != null) {
String s = String.valueOf(v).trim().toUpperCase(Locale.ROOT);
if ("GET".equals(s)) return ReqMethod.GET;
if ("POST".equals(s)) return ReqMethod.POST;
return ReqMethod.OTHER;
}
} catch (Exception ignored) {
// Fall back below.
}
// Fallback: treat presence of body as POST-like.
byte[] body = ctx.request.getBody();
if (body != null && body.length > 0) return ReqMethod.POST;
return ReqMethod.GET;
}
private static WebResourceResponsePacket ok(WebPageContext ctx, String html) {
byte[] body = Html.utf8(html);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), 200, "text/html; charset=utf-8", headers, body, null);
}
private static WebResourceResponsePacket text(WebPageContext ctx, int code, String msg) {
byte[] body = (msg == null ? "" : msg).getBytes(StandardCharsets.UTF_8);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), code, "text/plain; charset=utf-8", headers, body, null);
}
private static WebResourceResponsePacket redirect302(WebPageContext ctx, String location, String session) {
Map<String, String> headers = HeaderMaps.mutable();
headers.put("Location", location);
if (session != null && !session.isBlank()) {
headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax");
// optional fallback for stacks that read a direct header:
headers.put("session", session);
}
return new WebResourceResponsePacket(outHeader(ctx), 302, "text/plain; charset=utf-8", headers, new byte[0], null);
}
private static String renderForm(String errOrOk) {
String line = "";
if (errOrOk != null && !errOrOk.isBlank()) {
boolean ok = errOrOk.startsWith("OK:");
line = "<p class='" + (ok ? "ok" : "err") + "'>" + errOrOk + "</p>";
}
String body = """
<div class="card">
<h2>Login</h2>
%s
<form method="post" action="login.html" class="form">
<label><span class="muted">Username</span><input type="text" name="username" autocomplete="username" required></label>
<label><span class="muted">Password</span><input type="password" name="password" autocomplete="current-password" required></label>
<button type="submit">Login</button>
</form>
<div class="row" style="margin-top: 12px;">
<div class="col"><a href="register.html">Register</a></div>
<div class="col"><a href="index.html">Home</a></div>
</div>
</div>
""".formatted(line);
return Html.page("Login", body);
}
private static String resolveIp(WebPageContext ctx) {
if (ctx.client == null || ctx.client.getConnection() == null) return "";
if (ctx.client.getConnection().getTcpSocket() == null) return "";
if (ctx.client.getConnection().getTcpSocket().getInetAddress() == null) return "";
return ctx.client.getConnection().getTcpSocket().getInetAddress().getHostAddress();
}
private static String headerIgnoreCase(Map<String, String> headers, String key) {
if (headers == null || headers.isEmpty() || key == null) return null;
for (Map.Entry<String, String> e : headers.entrySet()) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue();
}
return null;
}
private static Map<String, List<String>> parseFormUrlEncoded(byte[] body) {
if (body == null || body.length == 0) return Map.of();
String raw = new String(body, StandardCharsets.UTF_8);
Map<String, List<String>> out = new LinkedHashMap<>();
int start = 0;
while (start <= raw.length()) {
int amp = raw.indexOf('&', start);
if (amp < 0) amp = raw.length();
String pair = raw.substring(start, amp);
if (!pair.isEmpty()) {
int eq = pair.indexOf('=');
String k = (eq < 0) ? pair : pair.substring(0, eq);
String v = (eq < 0) ? "" : pair.substring(eq + 1);
String key = decodeFormToken(k);
String val = decodeFormToken(v);
if (!key.isEmpty()) out.computeIfAbsent(key, __ -> new ArrayList<>(1)).add(val);
}
start = amp + 1;
if (amp == raw.length()) break;
}
Map<String, List<String>> frozen = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> e : out.entrySet()) frozen.put(e.getKey(), List.copyOf(e.getValue()));
return Map.copyOf(frozen);
}
private static String first(Map<String, List<String>> params, String key) {
if (params == null || key == null) return null;
List<String> v = params.get(key);
if (v == null || v.isEmpty()) return null;
String t = v.getFirst();
if (t == null) return null;
String s = t.trim();
return s.isEmpty() ? null : s;
}
private static String decodeFormToken(String s) {
if (s == null || s.isEmpty()) return "";
byte[] tmp = new byte[s.length()];
int n = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '+') {
tmp[n++] = (byte) ' ';
continue;
}
if (c == '%' && i + 2 < s.length()) {
int hi = hex(c = s.charAt(i + 1));
int lo = hex(s.charAt(i + 2));
if (hi >= 0 && lo >= 0) {
tmp[n++] = (byte) ((hi << 4) | lo);
i += 2;
continue;
}
}
byte[] b = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8);
for (byte bb : b) tmp[n++] = bb;
}
return new String(tmp, 0, n, StandardCharsets.UTF_8);
}
private static int hex(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
return -1;
}
private static WebPacketHeader outHeader(WebPageContext ctx) {
WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null;
if (in == null) {
return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis());
}
return new WebPacketHeader(
in.getRequestId(),
in.getTabId(),
in.getPageId(),
in.getFrameId(),
in.getFlags() | WebPacketFlags.RESOURCE,
System.currentTimeMillis()
);
}
}

254
frontend/register.java Normal file
View File

@@ -0,0 +1,254 @@
package ins.frontend;
import ins.frontend.utils.RegistrationService;
import ins.frontend.utils.WebApp;
import org.openautonomousconnection.protocol.packets.v1_0_1.beta.web.impl.resource.WebResourceResponsePacket;
import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.protocol.side.web.managers.SessionManager;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketFlags;
import org.openautonomousconnection.protocol.versions.v1_0_1.beta.WebPacketHeader;
import org.openautonomousconnection.webserver.api.Route;
import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext;
import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.Html;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Register page (v1.0.1).
*/
@Route(path = "/register.html")
public final class register implements WebPage {
private enum ReqMethod { GET, POST, OTHER }
@Override
public WebResourceResponsePacket handle(WebPageContext ctx) throws Exception {
WebApp.init();
// If a valid session already exists -> go dashboard (keep session)
if (ctx.session != null && ctx.session.isValid() && ctx.session.getUser() != null) {
return redirect302(ctx, "dashboard.html", ctx.session.getSessionId());
}
ReqMethod method = detectMethod(ctx);
if (method == ReqMethod.GET) {
return ok(ctx, renderForm(null));
}
if (method != ReqMethod.POST) {
return text(ctx, 405, "Method Not Allowed");
}
String contentType = headerIgnoreCase(ctx.request.getHeaders(), "content-type");
String ctLower = (contentType == null) ? "" : contentType.toLowerCase(Locale.ROOT);
if (!ctLower.startsWith("application/x-www-form-urlencoded")) {
return ok(ctx, renderForm("Unsupported content-type: " + Html.esc(contentType)));
}
Map<String, List<String>> form = parseFormUrlEncoded(ctx.request.getBody());
String username = first(form, "username");
String password = first(form, "password");
RegistrationService service = new RegistrationService(WebApp.get().users(), WebApp.get().passwordHasher());
RegistrationService.Result r = service.register(username, password);
if (!r.ok()) {
return ok(ctx, renderForm(r.error()));
}
// Create new session (user just registered)
String ip = resolveIp(ctx);
String ua = headerIgnoreCase(ctx.request.getHeaders(), "user-agent");
if (ua == null) ua = "";
String session = SessionManager.create(
String.valueOf(r.userId()),
ip,
ua,
(ProtocolWebServer) ctx.client.getServer()
);
return redirect302(ctx, "dashboard.html", session);
}
private ReqMethod detectMethod(WebPageContext ctx) {
try {
Method m = ctx.request.getClass().getMethod("getMethod");
Object v = m.invoke(ctx.request);
if (v != null) {
String s = String.valueOf(v).trim().toUpperCase(Locale.ROOT);
if ("GET".equals(s)) return ReqMethod.GET;
if ("POST".equals(s)) return ReqMethod.POST;
return ReqMethod.OTHER;
}
} catch (Exception ignored) {
}
byte[] body = ctx.request.getBody();
if (body != null && body.length > 0) return ReqMethod.POST;
return ReqMethod.GET;
}
private static WebResourceResponsePacket ok(WebPageContext ctx, String html) {
byte[] body = Html.utf8(html);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), 200, "text/html; charset=utf-8", headers, body, null);
}
private static WebResourceResponsePacket text(WebPageContext ctx, int code, String msg) {
byte[] body = (msg == null ? "" : msg).getBytes(StandardCharsets.UTF_8);
Map<String, String> headers = HeaderMaps.mutable();
headers.put("content-length", String.valueOf(body.length));
return new WebResourceResponsePacket(outHeader(ctx), code, "text/plain; charset=utf-8", headers, body, null);
}
private static WebResourceResponsePacket redirect302(WebPageContext ctx, String location, String session) {
Map<String, String> headers = HeaderMaps.mutable();
headers.put("Location", location);
if (session != null && !session.isBlank()) {
headers.put("Set-Cookie", "session=" + session + "; Path=/; HttpOnly; SameSite=Lax");
headers.put("session", session);
}
return new WebResourceResponsePacket(outHeader(ctx), 302, "text/plain; charset=utf-8", headers, new byte[0], null);
}
private static String renderForm(String errOrOk) {
String line = "";
if (errOrOk != null && !errOrOk.isBlank()) {
boolean ok = errOrOk.startsWith("OK:");
line = "<p class='" + (ok ? "ok" : "err") + "'>" + errOrOk + "</p>";
}
String body = """
<div class="card">
<h2>Register</h2>
%s
<form method="post" action="register.html" class="form">
<label><span class="muted">Username</span><input type="text" name="username" autocomplete="username" required></label>
<label><span class="muted">Password</span><input type="password" name="password" autocomplete="new-password" required></label>
<button type="submit">Create account</button>
</form>
<div class="row" style="margin-top: 12px;">
<div class="col"><a href="login.html">Login</a></div>
<div class="col"><a href="index.html">Home</a></div>
</div>
</div>
""".formatted(line);
return Html.page("Register", body);
}
private static String resolveIp(WebPageContext ctx) {
if (ctx.client == null || ctx.client.getConnection() == null) return "";
if (ctx.client.getConnection().getTcpSocket() == null) return "";
if (ctx.client.getConnection().getTcpSocket().getInetAddress() == null) return "";
return ctx.client.getConnection().getTcpSocket().getInetAddress().getHostAddress();
}
private static String headerIgnoreCase(Map<String, String> headers, String key) {
if (headers == null || headers.isEmpty() || key == null) return null;
for (Map.Entry<String, String> e : headers.entrySet()) {
if (e.getKey() != null && e.getKey().equalsIgnoreCase(key)) return e.getValue();
}
return null;
}
private static Map<String, List<String>> parseFormUrlEncoded(byte[] body) {
if (body == null || body.length == 0) return Map.of();
String raw = new String(body, StandardCharsets.UTF_8);
Map<String, List<String>> out = new LinkedHashMap<>();
int start = 0;
while (start <= raw.length()) {
int amp = raw.indexOf('&', start);
if (amp < 0) amp = raw.length();
String pair = raw.substring(start, amp);
if (!pair.isEmpty()) {
int eq = pair.indexOf('=');
String k = (eq < 0) ? pair : pair.substring(0, eq);
String v = (eq < 0) ? "" : pair.substring(eq + 1);
String key = decodeFormToken(k);
String val = decodeFormToken(v);
if (!key.isEmpty()) out.computeIfAbsent(key, __ -> new ArrayList<>(1)).add(val);
}
start = amp + 1;
if (amp == raw.length()) break;
}
Map<String, List<String>> frozen = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> e : out.entrySet()) frozen.put(e.getKey(), List.copyOf(e.getValue()));
return Map.copyOf(frozen);
}
private static String first(Map<String, List<String>> params, String key) {
if (params == null || key == null) return null;
List<String> v = params.get(key);
if (v == null || v.isEmpty()) return null;
String t = v.getFirst();
if (t == null) return null;
String s = t.trim();
return s.isEmpty() ? null : s;
}
private static String decodeFormToken(String s) {
if (s == null || s.isEmpty()) return "";
byte[] tmp = new byte[s.length()];
int n = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '+') {
tmp[n++] = (byte) ' ';
continue;
}
if (c == '%' && i + 2 < s.length()) {
int hi = hex(c = s.charAt(i + 1));
int lo = hex(s.charAt(i + 2));
if (hi >= 0 && lo >= 0) {
tmp[n++] = (byte) ((hi << 4) | lo);
i += 2;
continue;
}
}
byte[] b = String.valueOf(s.charAt(i)).getBytes(StandardCharsets.UTF_8);
for (byte bb : b) tmp[n++] = bb;
}
return new String(tmp, 0, n, StandardCharsets.UTF_8);
}
private static int hex(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
return -1;
}
private static WebPacketHeader outHeader(WebPageContext ctx) {
WebPacketHeader in = (ctx != null && ctx.request != null) ? ctx.request.getHeader() : null;
if (in == null) {
return new WebPacketHeader(0, 0, 0, 0, WebPacketFlags.RESOURCE, System.currentTimeMillis());
}
return new WebPacketHeader(
in.getRequestId(),
in.getTabId(),
in.getPageId(),
in.getFrameId(),
in.getFlags() | WebPacketFlags.RESOURCE,
System.currentTimeMillis()
);
}
}

View File

@@ -0,0 +1,490 @@
package ins.frontend.utils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Registrar DAO for schema:
* <ul>
* <li>tln(id, name, info, owner_id, is_public, allow_subdomains)</li>
* <li>infonames(id, info, tln_id, uid)</li>
* <li>subnames(id, name, infoname_id)</li>
* <li>records(id, infoname_id, subname_id, type, value, ttl, priority, port, weight)</li>
* </ul>
*
* <p>Rules:</p>
* <ul>
* <li>Create InfoName under TLN allowed if TLN is public OR owned by user.</li>
* <li>Subnames allowed for non-owner only if allow_subdomains=1. Owner always allowed.</li>
* <li>TLN name is not editable; only info/is_public/allow_subdomains are editable (owner-only).</li>
* </ul>
*/
public final class RegistrarDao {
private final UserDao.DataSourceProvider dataSource;
/**
* Creates the DAO.
*
* @param dataSource JDBC connection provider
*/
public RegistrarDao(UserDao.DataSourceProvider dataSource) {
this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
}
// ---------- TLN ----------
/**
* TLN usable for creating InfoNames if public OR owned.
*
* @param tln TLN row
* @param userId current user id
* @return allowed
*/
public static boolean canUseTln(TlnRow tln, int userId) {
if (tln == null || userId <= 0) return false;
if (tln.isPublic) return true;
return tln.ownerId != null && tln.ownerId == userId;
}
/**
* Subdomain/subname allowed if:
* <ul>
* <li>no sub requested (root) OR</li>
* <li>TLN allows subdomains OR</li>
* <li>user is TLN owner</li>
* </ul>
*
* @param tln TLN row
* @param userId current user id
* @param sub sub label (nullable)
* @return allowed
*/
public static boolean canUseSubname(TlnRow tln, int userId, String sub) {
if (tln == null || userId <= 0) return false;
boolean wantsSub = sub != null && !sub.isBlank();
if (!wantsSub) return true;
if (tln.allowSubdomains) return true;
return tln.ownerId != null && tln.ownerId == userId;
}
private static TlnRow mapTln(ResultSet rs) throws SQLException {
return new TlnRow(
rs.getInt("id"),
rs.getString("name"),
rs.getString("info"),
(Integer) rs.getObject("owner_id"),
rs.getInt("is_public") == 1,
rs.getInt("allow_subdomains") == 1
);
}
/**
* Creates a TLN.
*
* @param name tln.name (unique)
* @param info tln.info (editable, can be null/blank)
* @param ownerId owner user id (nullable in DB, pass null to create unowned TLN)
* @param isPublic tln.is_public
* @param allowSubdomains tln.allow_subdomains
* @return new tln.id
* @throws SQLException on SQL errors
*/
public int createTln(String name, String info, Integer ownerId, boolean isPublic, boolean allowSubdomains) throws SQLException {
if (name == null || name.isBlank()) throw new IllegalArgumentException("name must not be blank");
String sql = "INSERT INTO tln (name, info, owner_id, is_public, allow_subdomains) VALUES (?, ?, ?, ?, ?)";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, name.trim());
ps.setString(2, info);
if (ownerId == null) ps.setNull(3, Types.INTEGER);
else ps.setInt(3, ownerId);
ps.setInt(4, isPublic ? 1 : 0);
ps.setInt(5, allowSubdomains ? 1 : 0);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) return rs.getInt(1);
}
}
throw new SQLException("No generated key returned for tln.id");
}
/**
* Finds TLN by name.
*
* @param name tln.name
* @return optional row
* @throws SQLException on SQL errors
*/
public Optional<TlnRow> findTlnByName(String name) throws SQLException {
if (name == null || name.isBlank()) return Optional.empty();
String sql = "SELECT id, name, info, owner_id, is_public, allow_subdomains FROM tln WHERE name = ? LIMIT 1";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, name.trim());
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) return Optional.empty();
return Optional.of(mapTln(rs));
}
}
}
// ---------- Permissions ----------
/**
* Lists TLNs visible to a user: owned OR public OR unowned+public.
*
* @param userId current user id
* @return rows
* @throws SQLException on SQL errors
*/
public TlnRow[] listVisibleTlns(int userId) throws SQLException {
String sql = """
SELECT id, name, info, owner_id, is_public, allow_subdomains
FROM tln
WHERE is_public = 1 OR owner_id = ?
ORDER BY name ASC
""";
List<TlnRow> out = new ArrayList<>();
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) out.add(mapTln(rs));
}
}
return out.toArray(new TlnRow[0]);
}
/**
* Updates TLN fields except name (owner-only).
*
* @param tlnId tln.id
* @param ownerUserId users.id (must match tln.owner_id)
* @param newInfo new info text (nullable)
* @param isPublic is_public
* @param allowSubdomains allow_subdomains
* @return true if updated, false if not owned/not found
* @throws SQLException on SQL errors
*/
public boolean updateTlnOwned(int tlnId, int ownerUserId, String newInfo, boolean isPublic, boolean allowSubdomains) throws SQLException {
if (tlnId <= 0) throw new IllegalArgumentException("tlnId must be > 0");
if (ownerUserId <= 0) throw new IllegalArgumentException("ownerUserId must be > 0");
String sql = "UPDATE tln SET info = ?, is_public = ?, allow_subdomains = ? WHERE id = ? AND owner_id = ?";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, newInfo);
ps.setInt(2, isPublic ? 1 : 0);
ps.setInt(3, allowSubdomains ? 1 : 0);
ps.setInt(4, tlnId);
ps.setInt(5, ownerUserId);
return ps.executeUpdate() > 0;
}
}
// ---------- InfoNames ----------
/**
* Deletes TLN (owner-only).
*
* @param tlnId tln.id
* @param ownerUserId users.id
* @return true if deleted
* @throws SQLException on SQL errors
*/
public boolean deleteTlnOwned(int tlnId, int ownerUserId) throws SQLException {
if (tlnId <= 0) throw new IllegalArgumentException("tlnId must be > 0");
if (ownerUserId <= 0) throw new IllegalArgumentException("ownerUserId must be > 0");
String sql = "DELETE FROM tln WHERE id = ? AND owner_id = ?";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setInt(1, tlnId);
ps.setInt(2, ownerUserId);
return ps.executeUpdate() > 0;
}
}
/**
* Creates an InfoName under the given TLN.
*
* @param tln TLN row
* @param info infonames.info
* @param userId infonames.uid (users.id)
* @return new infonames.id
* @throws SQLException on SQL errors
*/
public int createInfoName(TlnRow tln, String info, int userId) throws SQLException {
if (tln == null) throw new IllegalArgumentException("tln must not be null");
if (userId <= 0) throw new IllegalArgumentException("userId must be > 0");
if (!canUseTln(tln, userId)) throw new SQLException("TLN not public and not owned by user.");
if (info == null || info.isBlank()) throw new IllegalArgumentException("info must not be blank");
String sql = "INSERT INTO infonames (info, tln_id, uid) VALUES (?, ?, ?)";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, info.trim());
ps.setInt(2, tln.id);
ps.setInt(3, userId);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) return rs.getInt(1);
}
}
throw new SQLException("No generated key returned for infonames.id");
}
/**
* Checks whether a user owns the infoname.
*
* @param infonameId infonames.id
* @param userId users.id
* @return true if owned
* @throws SQLException on SQL errors
*/
public boolean isOwnerOfInfoName(int infonameId, int userId) throws SQLException {
if (infonameId <= 0 || userId <= 0) return false;
String sql = "SELECT 1 FROM infonames WHERE id = ? AND uid = ? LIMIT 1";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setInt(1, infonameId);
ps.setInt(2, userId);
try (ResultSet rs = ps.executeQuery()) {
return rs.next();
}
}
}
/**
* Deletes an InfoName (owner-only).
*
* @param infonameId infonames.id
* @param ownerUserId users.id
* @return true if deleted
* @throws SQLException on SQL errors
*/
public boolean deleteInfoNameOwned(int infonameId, int ownerUserId) throws SQLException {
if (infonameId <= 0) throw new IllegalArgumentException("infonameId must be > 0");
if (ownerUserId <= 0) throw new IllegalArgumentException("ownerUserId must be > 0");
String sql = "DELETE FROM infonames WHERE id = ? AND uid = ?";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setInt(1, infonameId);
ps.setInt(2, ownerUserId);
return ps.executeUpdate() > 0;
}
}
// ---------- Subnames + Records ----------
/**
* Lists InfoNames owned by user (joined with TLN data).
*
* @param userId users.id
* @return rows
* @throws SQLException on SQL errors
*/
public InfoNameRow[] listOwnedInfoNames(int userId) throws SQLException {
if (userId <= 0) return new InfoNameRow[0];
String sql = """
SELECT i.id AS iid, i.info AS info, i.tln_id AS tln_id,
t.name AS tln_name, t.info AS tln_info, t.owner_id AS owner_id,
t.is_public AS is_public, t.allow_subdomains AS allow_subdomains
FROM infonames i
INNER JOIN tln t ON t.id = i.tln_id
WHERE i.uid = ?
ORDER BY t.name ASC, i.info ASC
""";
List<InfoNameRow> out = new ArrayList<>();
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setInt(1, userId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
TlnRow tln = new TlnRow(
rs.getInt("tln_id"),
rs.getString("tln_name"),
rs.getString("tln_info"),
(Integer) rs.getObject("owner_id"),
rs.getInt("is_public") == 1,
rs.getInt("allow_subdomains") == 1
);
out.add(new InfoNameRow(
rs.getInt("iid"),
rs.getString("info"),
tln
));
}
}
}
return out.toArray(new InfoNameRow[0]);
}
/**
* Ensures a subname exists for an infoname; returns subnames.id.
*
* <p>If sub is null/blank, returns null (root).</p>
*
* @param infonameId infonames.id
* @param sub sub label (nullable)
* @return subnames.id or null for root
* @throws SQLException on SQL errors
*/
public Integer ensureSubname(int infonameId, String sub) throws SQLException {
if (sub == null || sub.isBlank()) return null;
if (infonameId <= 0) throw new IllegalArgumentException("infonameId must be > 0");
String name = sub.trim();
// 1) find
String find = "SELECT id FROM subnames WHERE infoname_id = ? AND name = ? LIMIT 1";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(find)) {
ps.setInt(1, infonameId);
ps.setString(2, name);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("id");
}
}
// 2) insert
String ins = "INSERT INTO subnames (name, infoname_id) VALUES (?, ?)";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(ins, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, name);
ps.setInt(2, infonameId);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) return rs.getInt(1);
}
}
throw new SQLException("No generated key returned for subnames.id");
}
/**
* Adds a DNS-like record.
*
* @param infonameId infonames.id
* @param subnameId subnames.id or null for root
* @param type enum type (A/AAAA/TXT/CNAME/MX/SRV/NS)
* @param value record value
* @param ttl ttl (default 3600)
* @param priority priority (MX/SRV)
* @param port port (SRV)
* @param weight weight (SRV)
* @return new records.id
* @throws SQLException on SQL errors
*/
public int addRecord(int infonameId,
Integer subnameId,
String type,
String value,
int ttl,
Integer priority,
Integer port,
Integer weight) throws SQLException {
if (infonameId <= 0) throw new IllegalArgumentException("infonameId must be > 0");
if (type == null || type.isBlank()) throw new IllegalArgumentException("type must not be blank");
if (value == null || value.isBlank()) throw new IllegalArgumentException("value must not be blank");
if (ttl <= 0) ttl = 3600;
String t = type.trim().toUpperCase();
String sql = """
INSERT INTO records (infoname_id, subname_id, type, value, ttl, priority, port, weight)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setInt(1, infonameId);
if (subnameId == null) ps.setNull(2, Types.INTEGER);
else ps.setInt(2, subnameId);
ps.setString(3, t);
ps.setString(4, value.trim());
ps.setInt(5, ttl);
if (priority == null) ps.setNull(6, Types.INTEGER);
else ps.setInt(6, priority);
if (port == null) ps.setNull(7, Types.INTEGER);
else ps.setInt(7, port);
if (weight == null) ps.setNull(8, Types.INTEGER);
else ps.setInt(8, weight);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) return rs.getInt(1);
}
}
throw new SQLException("No generated key returned for records.id");
}
/**
* TLN row.
*/
public record TlnRow(int id, String name, String info, Integer ownerId, boolean isPublic, boolean allowSubdomains) {
}
/**
* InfoName row (includes TLN).
*/
public record InfoNameRow(int id, String info, TlnRow tln) {
}
}

View File

@@ -0,0 +1,122 @@
package ins.frontend.utils;
import org.openautonomousconnection.webserver.utils.PasswordHasher;
import java.util.Objects;
/**
* Registration service for creating users with secure password hashing and basic validation.
*/
public final class RegistrationService {
private static final int USERNAME_MIN = 5;
private static final int USERNAME_MAX = 256;
private static final int PASSWORD_MIN = 6;
private static final int PASSWORD_MAX = 256;
private final UserDao userDao;
private final PasswordHasher hasher;
/**
* Creates a registration service.
*
* @param userDao user DAO
* @param hasher password hasher
*/
public RegistrationService(UserDao userDao, PasswordHasher hasher) {
this.userDao = Objects.requireNonNull(userDao, "userDao");
this.hasher = Objects.requireNonNull(hasher, "hasher");
}
private static String normalizeUsername(String u) {
if (u == null) return null;
String t = u.trim();
return t.isEmpty() ? null : t;
}
private static String validate(String username, String password) {
if (username == null) return "Missing username.";
if (password == null) return "Missing password.";
if (username.length() < USERNAME_MIN) return "Username too short (min " + USERNAME_MIN + ").";
if (username.length() > USERNAME_MAX) return "Username too long (max " + USERNAME_MAX + ").";
// Allow only a safe subset to avoid weird edge cases in UI and future.
for (int i = 0; i < username.length(); i++) {
char c = username.charAt(i);
boolean ok = (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '_' || c == '-' || c == '.';
if (!ok) return "Username contains invalid characters.";
}
if (password.length() < PASSWORD_MIN) return "Password too short (min " + PASSWORD_MIN + ").";
if (password.length() > PASSWORD_MAX) return "Password too long.";
return null;
}
/**
* Registers a new user.
*
* @param usernameRaw raw username (from form)
* @param passwordRaw raw password (from form)
* @return result containing either userId or an error message
*/
public Result register(String usernameRaw, String passwordRaw) {
String username = normalizeUsername(usernameRaw);
String password = (passwordRaw == null) ? "" : passwordRaw;
String validationError = validate(username, password);
if (validationError != null) {
return Result.error(validationError);
}
try {
// Choose: store username as-is OR store sha256(username).
// Your schema says: username(sha256 hex or plain) -> keep it plain for now.
String usernameStored = username;
if (userDao.findByUsername(usernameStored).isPresent()) {
return Result.error("Username already exists.");
}
String passwordEncoded = hasher.hash(password);
int userId = userDao.createUserWithNewUuid(usernameStored, passwordEncoded);
return Result.ok(userId);
} catch (Exception e) {
return Result.error("Registration failed: " + e.getMessage());
}
}
/**
* Registration result.
*
* @param ok whether succeeded
* @param userId created user id, or -1
* @param error error message, or null
*/
public record Result(boolean ok, int userId, String error) {
/**
* Creates a success result.
*
* @param userId user id
* @return result
*/
public static Result ok(int userId) {
return new Result(true, userId, null);
}
/**
* Creates an error result.
*
* @param error error message
* @return result
*/
public static Result error(String error) {
return new Result(false, -1, error);
}
}
}

154
frontend/utils/UserDao.java Normal file
View File

@@ -0,0 +1,154 @@
package ins.frontend.utils;
import org.openautonomousconnection.webserver.utils.Sha256;
import java.sql.*;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
/**
* DAO for users table:
* users(id, uid(uuid string), username(sha256 hex or plain), password(pbkdf2...))
*/
public final class UserDao {
private final DataSourceProvider dataSource;
/**
* Creates a UserDao.
*
* @param dataSource connection provider
*/
public UserDao(DataSourceProvider dataSource) {
this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
}
/**
* Finds a user by username.
*
* @param uid exact uid match
* @return optional row
* @throws SQLException on SQL errors
*/
public Optional<UserRow> findByUid(UUID uid) throws SQLException {
if (uid == null) return Optional.empty();
String sql = "SELECT id, uid, username, password FROM users WHERE uid = ? LIMIT 1";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, uid.toString());
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) return Optional.empty();
return Optional.of(new UserRow(
rs.getInt("id"),
rs.getString("uid"),
rs.getString("username"),
rs.getString("password")
));
}
}
}
/**
* Creates a new user.
*
* @param uid uuid string (36 chars)
* @param username username string (your choice: raw or sha256 hex)
* @param passwordEncoded encoded password
* @return generated users.id
* @throws SQLException on SQL errors
*/
public int createUser(String uid, String username, String passwordEncoded) throws SQLException {
if (uid == null || uid.isBlank()) throw new IllegalArgumentException("uid must not be blank");
if (username == null || username.isBlank()) throw new IllegalArgumentException("username must not be blank");
if (passwordEncoded == null || passwordEncoded.isBlank())
throw new IllegalArgumentException("passwordEncoded must not be blank");
String sql = "INSERT INTO users (uid, username, password) VALUES (?, ?, ?)";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, uid);
ps.setString(2, Sha256.hex(username));
ps.setString(3, passwordEncoded);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) return rs.getInt(1);
}
}
throw new SQLException("No generated key returned for users.id");
}
/**
* Convenience: creates a user with a new random UUID.
*
* @param username username
* @param passwordEncoded encoded password
* @return generated users.id
* @throws SQLException on SQL errors
*/
public int createUserWithNewUuid(String username, String passwordEncoded) throws SQLException {
UUID uuid = UUID.randomUUID();
while (findByUid(uuid).isPresent()) uuid = UUID.randomUUID();
return createUser(uuid.toString(), username, passwordEncoded);
}
/**
* Finds a user by username.
*
* @param username exact username match
* @return optional row
* @throws SQLException on SQL errors
*/
public Optional<UserRow> findByUsername(String username) throws SQLException {
if (username == null || username.isBlank()) return Optional.empty();
username = Sha256.hex(username);
String sql = "SELECT id, uid, username, password FROM users WHERE username = ? LIMIT 1";
try (Connection c = dataSource.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, username);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) return Optional.empty();
return Optional.of(new UserRow(
rs.getInt("id"),
rs.getString("uid"),
rs.getString("username"),
rs.getString("password")
));
}
}
}
/**
* Connection provider abstraction.
*/
public interface DataSourceProvider {
/**
* @return open SQL connection
* @throws SQLException on errors
*/
Connection getConnection() throws SQLException;
}
/**
* User row.
*
* @param id users.id
* @param uid users.uid (uuid)
* @param username users.username
* @param passwordEncoded users.password
*/
public record UserRow(int id, String uid, String username, String passwordEncoded) {
}
}

113
frontend/utils/WebApp.java Normal file
View File

@@ -0,0 +1,113 @@
package ins.frontend.utils;
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
import org.openautonomousconnection.webserver.utils.PasswordHasher;
import org.openautonomousconnection.webserver.utils.Pbkdf2Sha256Hasher;
import java.io.File;
import java.io.IOException;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Objects;
/**
* Application singleton holding DAO + password hasher.
*/
public final class WebApp {
private static volatile WebApp INSTANCE;
private static boolean isInit;
private static UserDao.DataSourceProvider dsp = null;
private final UserDao userDao;
private final RegistrarDao registrarDao;
private final PasswordHasher passwordHasher;
private WebApp(UserDao userDao, RegistrarDao registrarDao, PasswordHasher passwordHasher) {
this.userDao = Objects.requireNonNull(userDao, "userDao");
this.registrarDao = Objects.requireNonNull(registrarDao, "registrarDao");
this.passwordHasher = Objects.requireNonNull(passwordHasher, "passwordHasher");
}
/**
* Initializes the singleton (call once at server startup).
*/
public static void init() {
if (isInit) return;
try {
if (!new File(new File(".").getParent(), "db.properties").exists()) {
new File(new File(".").getParent(), "db.properties").createNewFile();
}
ConfigurationManager config = new ConfigurationManager(new File(new File(".").getParent(), "db.properties"));
config.loadProperties();
if (!config.isSet("db.url")) {
config.set(
"db.url",
"jdbc:mariadb://localhost:3306/ins?useUnicode=true&characterEncoding=utf8"
);
config.saveProperties();
}
if (!config.isSet("db.user")) {
config.set("db.user", "username");
config.saveProperties();
}
if (!config.isSet("db.password")) {
config.set("db.password", "password");
config.saveProperties();
}
dsp = () -> {
try {
isInit = true;
return DriverManager.getConnection(config.getString("db.url"), config.getString("db.user"), config.getString("db.password"));
} catch (SQLException e) {
throw new SQLException("Failed to open DB connection", e);
}
};
} catch (IOException e) {
throw new RuntimeException("Failed to create config", e);
}
UserDao udao = new UserDao(dsp);
RegistrarDao rdao = new RegistrarDao(dsp);
PasswordHasher hasher = new Pbkdf2Sha256Hasher(150_000, 16, 32);
INSTANCE = new WebApp(udao, rdao, hasher);
}
/**
* Returns the initialized instance.
*
* @return app
*/
public static WebApp get() {
WebApp v = INSTANCE;
if (v == null) throw new IllegalStateException("Oac2WebApp not initialized. Call Oac2WebApp.init(...) first.");
return v;
}
/**
* @return user DAO
*/
public UserDao users() {
return userDao;
}
/**
* @return registrar DAO
*/
public RegistrarDao dao() {
return registrarDao;
}
/**
* @return password hasher
*/
public PasswordHasher passwordHasher() {
return passwordHasher;
}
}

103
pom.xml
View File

@@ -1,22 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.openautonomousconnection</groupId>
<artifactId>DNSServer</artifactId>
<version>1.0-SNAPSHOT</version>
<artifactId>INSServer</artifactId>
<version>1.0.1-BETA.0.2</version>
<organization>
<name>Open Autonomous Connection</name>
<url>https://open-autonomous-connection.org/</url>
</organization>
<url>https://open-autonomous-connection.org/</url>
<description>The default DNS-Server</description>
<description>The default INS-Server</description>
<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@@ -45,14 +45,13 @@
<issueManagement>
<system>Issue Tracker</system>
<url>https://repo.open-autonomous-connection.org/open-autonomous-connection/DNSServer/issues</url>
<url>https://repo.open-autonomous-connection.org/open-autonomous-connection/INSServer/issues</url>
</issueManagement>
<licenses>
<license>
<name>Open Autonomous Public License</name>
<url>https://repo.open-autonomous-connection.org/Open-Autonomous-Connection/OAPL/</url>
<distribution>repo</distribution>
<name>Open Autonomous Public License (OAPL)</name>
<url>https://open-autonomous-connection.org/license.html</url>
</license>
</licenses>
@@ -69,8 +68,86 @@
<dependencies>
<dependency>
<groupId>org.openautonomousconnection</groupId>
<artifactId>protocol</artifactId>
<version>1.0.0-BETA.7</version>
<artifactId>Protocol</artifactId>
<version>1.0.1-BETA.0.5</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openautonomousconnection.insserver.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports</arg>
<arg>java.base/sun.security.x509=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>java.base/sun.security.util=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<configuration>
<failOnError>false</failOnError>
<failOnWarnings>false</failOnWarnings>
<doclint>none</doclint>
<locale>en_US</locale>
<encoding>UTF-8</encoding>
<docencoding>UTF-8</docencoding>
<charset>UTF-8</charset>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,52 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC9Uu9/sNevUGr1
k6LI5jdI/HAbsgaS2eFat0mdPKhD0IrDMozhZ1zDh6w/4iOhNC6ypwy0+1l/r6cT
6xL2s3x+DxoaeskMpRD4kd97cE7YrvxXMSwFj5exVnyps03QXFQigZryW5z26xyF
Jw3i6XiHkhpz1fwMKBXynkUROy5IuZu5oTsNko+dcjZWtOI9Tnf0HqmEW8Jrhpjw
JnZX8HBs0Ydd+gr7/nITLF6Eq0Gd1uRupsAOSsZfdXofqgxfVy5CsAyfK/163cnL
BZ5ynKMzqyhxS/9wLQfBLq6499e8w5+5/WHPIZLFeVOfdunudEFmE5aQwjnNBESI
EyaaYlhXQdKhTnAU2JYEg4pnp43DXMxsPeDY/OXDoQAJbuj1ip6jFwIyt8cj6uGv
diFrY1Nqa1Ua2YrN0xRvZ6WioYyrNO7TOkgCC90aop7gxps/ZgYTl2VsTzAtDZXY
mKjc+ZNx5F+h5gLhJMNTbzQ2KweE/7/5ghjHEwc2Z28gh5wOkmK7knuyv1mv4s5Z
UfOXs+Xv0sI4fZiNKyaESoyh4zoHPfUpTMhYPjzpa5g6PDaxs2ewq7Zr0Lvjk12p
+vM6xUeO70RQAK8tftoHmTxZDfh7NxMCnViD1q/2qRLOlSmOyxmH+hmy/4eNkROj
Mn8pyDHPEC3kg0ZuvS/Zutnm+WkXuQIDAQABAoICAAOcd/gWjenZsQkhu7fj0iNX
H//GLEWG0CHPCmXSqxq2+QLhnm0cXr+aLAgRg/xVYmkLbgEWAooE5RsGW1Ngqszn
sCwcMkJ50bAvS9tiK33GuTmbeTx82fOs3lBFAzfTOOOdNR/gD1hXtXT1AOGJvy8C
BKAVrSafi0Btq7903CCumA6OdCUKMo/AdZ8lo7i0TzJduX4sORhpKSvp5kzp6cSq
qr6KWdZaZpcJCOO0aNGq7CJDHkIJqikBLC1/6GRf1FXdLBB1+78LWjsEilpwGtWf
IjjRJfPXei2FvuX4cqnhSRlnb4u05AjN+oJKonCaamn/btpBezrDroqyMCbp2bZs
uxO0PnFTl26LlQSpJlKGRHQiGK+yD/Szw0AbivxsgtIZcpFmDtqm7frs2Sl7uda8
eCe/1OrFWpEv2M8lYU5t0b/vGVpEeum5+LRxLOjIjhCzurfSvZluKEOa2mH+IwAz
ZrkBdMvjzmkOCYWOfQh8AexvS+4uNycoc5RkeeX5rf+wlpHwlgFnr+5Dmw6SthcE
fb6dcpZA9SE9PJ5NbRFHfFwveo2uvM0xPAHczvC87h8E+rAfVJex7eKDWMySGCkM
k9TuD8rjHw2jF2s0IbO6knRDJdQonZPeqIKPDyv7nRMacAtchNgFbiYOxNn+/QyW
4sqOYLfbt6htdVyDv4s1AoIBAQDy/JWYA3V7RMHqZx823N1zpp8aPIjsXo7ELjqn
Cg0bdRRcWSpfeB/70vxxpRoleuOZx1g3EKTSp/Pc9LVdZAu51qBuWIZLgKHkX+yk
rihI5cmYIlKMNOreLglFdS1ZitVdGlmZ+3zbr1jLt/EtU52QGDBPU4o+opxeJ4Ne
4LoGCCi2JAW1Pz2pUvhjoTHS0W0ozvNsjjQ1kve6DZoeidJSw26yDk1yaqawCptI
1O4QdV/rjOyJgF1IUmYt8YcT7qnxYSpU50bsoYuQ9vSvM0ILwXFKuE/nbR40LWnZ
Sw9a+4VpkUfHc3DlCAuFimP/jpDiWWqaMl/+Ju0gF8rwvWhvAoIBAQDHdp7c6IMm
ty0XrXrBsgQcxcvMP131tDL3x43Ls5vSv7vrP/BkmU3Pt9SHYfDdDRcQQU1HkIXI
reCG9vrTUqTSEOPjYvbIMCrn7z84FKL8NqBu0DzBQbLCgbP0cRKKhLn2hxmmfZfn
yF8/opR5c7qNPS1/+CxgLpVytEsU9TTu7lCUef/rduPLk/aalE2t/il5qzQHmydQ
7ecWihL3z1Amzp+sa1tCePktvmG+LVXlZTLirmJtMMZXAf/tXh6gzP6B7rBcwwYE
YdxEZjwCdQd44e/aXmAh916//VKxAJDqkb8cgpbfu0Jrg/2N+Dve3lX9IpZ6YabQ
t+9BsN8s6AZXAoIBAQDxjykodkfUxAYDgYEGiYud7Yc+DfARC+/iGBM2/EcLhNk4
9WuqfUFOl/FfCUN0/zZFKmgIVgOFaHKGtr+WmF8P4M6c5GSdqsNGxhX7oSdrUQWY
uIZX3EOhnSKaamMrIVn7tLZe3iTCQQ+FdtfMt9Nr7KAaPHKy0fWhpKZ1K/PDC1lt
uWHzTWt/aXyFjzp42roqV5Kn2LcOi4y66crNkTYGEUN6v70+gcg0OlclkIka05UI
FpEQIQybWU87XWFr71gDHxV2UnWvyOl2tAuMIOkHxsdaAOFusIvWU09nNbeOO33l
9QfmwFz7U4QgvfOrm49tdncD1BCaGRijTwPxUFsBAoIBAEpWikSYn3CJalPdwtd3
qhKzIQ5BO5p8C1bPV6hoixWTgczeapCDlj1pLNs8BFHONB9Jxyx5z3KIYxrJ55dk
wKrNwrEXTBQ153fDcdNL1aacEVTbZRk9jArz3ganMZrQdqJLnaKwQjIPOnhz9lQB
brB+8Xs1GgzKr8YtLaJk9NJrnTqI8XlnOOTBg3H+/ahwBjMjPGPtTkzaLXr4ALO8
3wlnjpjq9fsjLPMAdlszeETjX4CeL260c+cvhpuHrXCOlfgE5lPcF10Av9/6Hjlo
Nl22DvdanwdpszVoiy4OoPPbV9efT+2YU9vQOGp17pJUWMXb4ys/Q7WcJ55a9gI9
g0cCggEBAL+B6vL4ipi9ioGCePPC4Klew/GTxy3dqJtCw7woHlz76zHJsQqllY8S
wqzcydRvvpkxI8HGNWtMSGiPWePss7qUrAdq0jZh921buXtuD3aqqUjaLDnm3rYz
v844JFJyKhgUqwkXHOeOw9ktsMxpBOsqa0vcYVmZ2dalvFC47b3pHplJmATLgV/6
lCHUXi5HZ53Yd5lyduUuavnWuWnnwbovc4vKBz7zMHb70oQSn/6wx5f7Bbgy1ZSk
7vlNYomNKgl5dtRlphBi3GFxyrE5EDFzis6UX7lNjADZrqYSeBCT8EFBoEfc/eVN
c5RF/V4VtgM7jggSLlUUgO0L2/Hw5bM=
-----END PRIVATE KEY-----

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCGKkc5obUKeeX8
gDZff4YUoKIoWHMkdxNxpt2UjR7I0TcFVXbOdpEFG1jBLrlcPvTs1LaJ7exQ9657
wma9OMzhn6Lmtp6ecahKGMpnMHSUjFUe45nRtNur6g5i5T5rYcnzaxJBaSxzcV/r
hI/KXRIbxEh+w7/X39wN3ze6EKdQWgHkv7Wsw9R5aGQBtWOQLnj+5wnveJDl3te+
B4oQ60fokW6Dt4RRe4u/YRyu+2VvG8x3RF/OQpDz4qFZHO6hGcS+hbNRyiIkhvdB
a8Qqu1bbDyVc80bkiiPDTO/FQVCxalDMTF7N00ng80OelLs4T+cwR/KQO9NWwgzM
6gJ38aplAgMBAAECggEAEQE5ztWunxnNKyBLA/ajVWj4tJllWqlXBxApxILQdI6D
5UKsUMXZYqbRLIcSCl2WaJAbZMcJrUd/T4NXx0L0QOKdYJWj7BwmwuF6gfKzzAIv
Zb9eRySftR3w3dsFetHFqXsYML82WZl0mWLPYMxEF83cjGieH6vpdb6ljwk+U6It
4WkyxUPojBsTdhmc+T1wI8NpegeRvNy/7euXZXaoo//Rf/fBUFRfSvVfDSMcKDIq
H4dcMAFc2ZHVE2wUuwqLbNbI8L/yuBgP5CHxnHSqG5nZQNMX3hg5winnAyp/iwnR
VA072TeuxqLx68qHEMhhZqO6Te63VtgBpYPapGT4UQKBgQC6Hwq9vXeL7JWsiZqX
Suh8kj/puy/CSeuWBopJVBJkGjyP3DDKtoJqEeOeMajL8rSW3RaCUdv4FvilGfc4
9zTuJBSZTMpoUaCpuX1k6jYOtwcMe17KdJB7Sr1bjKef3VdOpSA+pG3lElz1sH7t
sG6hdIx3mhJB/HpYKGN4A4h8GQKBgQC4iX/E1mvdDVnXcE4cFN/exSYQFmtNDBYe
o6QbLsjEqISAOxipLRTZhQjvePzSD4Y4F7jKqNcvK6LZnQiE43C6VImMT8UT7C2a
ht96l3/bDEGEls1HrzqVVepCWQGnKltkOU69HDRYXJCGJwZ5hfx66UtsXR0ICSNH
EHBoZE3qLQKBgAx4Xfjeg+79F/4qbhAq9a67ActAPm/vEfjIUWWeW2kXlO0ynJZI
ai5/KlgEDcI0bcMZ5xMuNuXFbD1rovPPJF28TyECUyFwLqqQggVL5/lObAt7DJvV
+YQ5OryyjNyaMOpVB/cKf050z4OqoqZ8Rr1MmMi+qvB+4RedBSUaX3+pAoGACwbM
R6q4T0EY+TKQuETXC6ykFZoBV109hR8qEyW8gWPAZxkg5Br5f/XfDtAf6z1aO8fP
Fuz3zq3A0Vf8xlQAzGF4xpWNpR8bqnwcpmqfDSuyToXkRkBGM94qXUSMQLzbMSXr
eolQ52bAjAOQ83n6GC4Qf60gqvZA0WI+FT7JGRECgYAi54HhK7HFogAlX58EX77h
YHL5KHBlG+l8v4amIKvD0JclYylwyLhYkmIM+T4mh8I+T1nabcNeNWit+CYoLwQ8
A3P0ij67TkEV3Ud7KKOtYty+getkM9/hwm14LlMzfLZKay9HCsbb5E2TgmShF68F
VtECuunYyfN4wJSE0aWRPg==
-----END PRIVATE KEY-----

View File

@@ -1,31 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIUQ67NCnk5KBqDD7fHdzKVi+AfRR8wDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTA5MjExMTU2NTlaFw0zNTA5
MTkxMTU2NTlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQC9Uu9/sNevUGr1k6LI5jdI/HAbsgaS2eFat0mdPKhD
0IrDMozhZ1zDh6w/4iOhNC6ypwy0+1l/r6cT6xL2s3x+DxoaeskMpRD4kd97cE7Y
rvxXMSwFj5exVnyps03QXFQigZryW5z26xyFJw3i6XiHkhpz1fwMKBXynkUROy5I
uZu5oTsNko+dcjZWtOI9Tnf0HqmEW8JrhpjwJnZX8HBs0Ydd+gr7/nITLF6Eq0Gd
1uRupsAOSsZfdXofqgxfVy5CsAyfK/163cnLBZ5ynKMzqyhxS/9wLQfBLq6499e8
w5+5/WHPIZLFeVOfdunudEFmE5aQwjnNBESIEyaaYlhXQdKhTnAU2JYEg4pnp43D
XMxsPeDY/OXDoQAJbuj1ip6jFwIyt8cj6uGvdiFrY1Nqa1Ua2YrN0xRvZ6WioYyr
NO7TOkgCC90aop7gxps/ZgYTl2VsTzAtDZXYmKjc+ZNx5F+h5gLhJMNTbzQ2KweE
/7/5ghjHEwc2Z28gh5wOkmK7knuyv1mv4s5ZUfOXs+Xv0sI4fZiNKyaESoyh4zoH
PfUpTMhYPjzpa5g6PDaxs2ewq7Zr0Lvjk12p+vM6xUeO70RQAK8tftoHmTxZDfh7
NxMCnViD1q/2qRLOlSmOyxmH+hmy/4eNkROjMn8pyDHPEC3kg0ZuvS/Zutnm+WkX
uQIDAQABo1MwUTAdBgNVHQ4EFgQUEZqY1o2roD7YNKIYE7V/odSz1uwwHwYDVR0j
BBgwFoAUEZqY1o2roD7YNKIYE7V/odSz1uwwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEAAV1Mafun3XwC2cs4Xgq1vzJw69I24y8dXm45qFdLl5Vv
hkIecwDm2+Ongg82sPPmR1TwsRgdUytywal5nsa1aH/v70MJ7Ic5pBIXdv54kZ8v
qgaItmHF+twb+aqFz/NY0BMv7nNc2MyYEyDwbJUIGwGt+yOlQfHUIwNc+PvVL6Sn
mkm7//EIfiU3HxmusCnbYC+9kmLbeds10qDzTNIHP4ffNAFgnMgauNID8X9RoPBT
TVqLjD5WDHridLMF8n0m18cp2MqV33gXbg4pT/rYvh40p9jJuGDJbcrn5WtKvbMn
lm27oI6bcwKF2i+VHRBxW/c/DZaG0QKo4PaDCd/kF2ix1ymkFG4MtvHmu0Q3SLXi
p7fTL4WZSEUPXe/E7fUuPwof54auXLYNhSR9HJo0ZS2R0S9pursBkBEXTXyiG8T+
iUq4VtBny1Ylr2rJ8Qr1TiaB5ud/IeEv70uDgA9XyUhikfs7t5bYrqdXjANtCvZY
j322+I3wyDdosu2Z1Lrn2w95ZyJhc/rluftc1lKnxgYwKndEJfBlhn8tpHlk2J9T
rPqAezvNhSXH1W6G7WZAvHfdfJahLDhfJ15YQcqqPK+DXEaJeOnUompV+/MEaEOE
3LwN4B78mTgVOcabMeo1NN/2nGObaOm/62t+3B9NwcUi9q1LT/oifLVBh8bEFug=
-----END CERTIFICATE-----

View File

@@ -1 +0,0 @@
5FB2F5A2CA8357D37C84CC406B5F81617348EB58

View File

@@ -1,26 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEWjCCAkKgAwIBAgIUX7L1osqDV9N8hMxAa1+BYXNI61gwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTA5MjExMTU3MzFaFw0yNzEy
MjUxMTU3MzFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCGKkc5obUKeeX8gDZff4YUoKIoWHMkdxNxpt2UjR7I
0TcFVXbOdpEFG1jBLrlcPvTs1LaJ7exQ9657wma9OMzhn6Lmtp6ecahKGMpnMHSU
jFUe45nRtNur6g5i5T5rYcnzaxJBaSxzcV/rhI/KXRIbxEh+w7/X39wN3ze6EKdQ
WgHkv7Wsw9R5aGQBtWOQLnj+5wnveJDl3te+B4oQ60fokW6Dt4RRe4u/YRyu+2Vv
G8x3RF/OQpDz4qFZHO6hGcS+hbNRyiIkhvdBa8Qqu1bbDyVc80bkiiPDTO/FQVCx
alDMTF7N00ng80OelLs4T+cwR/KQO9NWwgzM6gJ38aplAgMBAAGjQjBAMB0GA1Ud
DgQWBBTW7Nng9W84V+q0TknoTempPZo2KjAfBgNVHSMEGDAWgBQRmpjWjaugPtg0
ohgTtX+h1LPW7DANBgkqhkiG9w0BAQsFAAOCAgEAguDRKxW8QFV7fJlzAgzjwsBu
97Bk5IsmcIU7sOlmOFYZAG0hwqfuvzU/Tv48tuXOvGQsJtdNSfe4qybd5HnkxmTs
ZmqzCsIh9P4PL/KrKTCykEP935468wg/X9QkykBOJx2zOgBEllxo33BNG4ie6R3H
tYy/1y/hsGfi4Ma2jqBaPfblI86t9VoKiPqqKqFgGk32F4NqIJ2hMKxSypznHIC7
IPikKx9Gt8EpwT+ytJKOnd/A7nBEiaDw5ubcR1s6pmlcGLAE1+A9TX2ncRUcvMI4
w2M5i7X/Vz2zQiPEWmQliYDG1ZFeD1dth973muM6H6NvFyXvEzzRZl5K5DwAcgqA
uMvEKDiDPwKROBTzjq3WIHBe49fz96Fpxld5JxH3RhC2PzIYjUquU2P8Ah6SN6SZ
+Mf/61QxNInnEcxNjknGhcfiyhoqu2B+1/N5cfpXyBfgTQfVzV3HiUvAHzcza9yp
mnPrrxRodkXwncMJNJYTr36yTyZPxC0TZUycvOPwEfBQ1vESAeiJ7+QgFGFu0lgQ
1XAJVpCfHpDrkT184/pszyHHzydWvP7GcrPgJVNlXbMvpenhm2QTCaft5TUDj9+m
8quLjm+bj0OQq1Fmv/OlWvycC5fxnYA0JlRDz56QPHkEL7aZ/+apZhB6Eb77/etv
A5go7PJCZ9vnblPW/HE=
-----END CERTIFICATE-----

View File

@@ -1,3 +0,0 @@
#Sat Oct 04 01:25:22 CEST 2025
server.site.info=DNS-SERVER INFO SITE IP
server.site.register=SERVER IP TO DNS-FRONTENT WEBSITE

View File

@@ -1,31 +0,0 @@
package org.openautonomousconnection.dns;
import org.openautonomousconnection.protocol.side.dns.ConnectedProtocolClient;
import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.ClassicHandlerDNSServer;
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.SQLException;
public class ClassicHandler extends ClassicHandlerDNSServer {
@Override
public void handleMessage(ConnectedProtocolClient client, String message, Classic_ProtocolVersion protocolVersion) {
}
@Override
public Classic_Domain getDomain(Classic_RequestDomain requestDomain) throws SQLException {
return null;
}
@Override
public Classic_Domain ping(Classic_RequestDomain requestDomain) throws SQLException {
return null;
}
@Override
public void unsupportedClassicPacket(String className, Object[] content, ConnectedProtocolClient client) {
}
}

View File

@@ -1,35 +0,0 @@
package org.openautonomousconnection.dns;
import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor;
import dev.unlegitdqrk.unlegitlibrary.command.CommandManager;
import dev.unlegitdqrk.unlegitlibrary.command.CommandPermission;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolSettings;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateException;
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;
public static void main(String[] args) throws Exception {
ProtocolSettings settings = new ProtocolSettings();
new ProtocolBridge(new Server(), settings, ProtocolVersion.PV_1_0_0_BETA, new File("logs"));
ProtocolBridge.getInstance().setClassicHandlerDNSServer(new ClassicHandler());
commandManager = new CommandManager(ProtocolBridge.getInstance().getProtocolSettings().eventManager);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println(commandExecutor.getName() + "> ");
String line = scanner.nextLine();
commandManager.execute(commandExecutor, line);
}
}
}

View File

@@ -1,64 +0,0 @@
package org.openautonomousconnection.dns;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.side.dns.ConnectedProtocolClient;
import org.openautonomousconnection.protocol.side.dns.ProtocolDNSServer;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.DNSResponseCode;
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.Domain;
import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.List;
public class Server extends ProtocolDNSServer {
/**
* Constructs a ProtocolDNSServer with the specified configuration file.
*
* @throws IOException If an I/O error occurs.
* @throws CertificateException If a certificate error occurs.
*/
public Server() throws IOException, CertificateException {
super(new File("config.properties"));
}
@Override
public List<Domain> getDomains() {
return List.of();
}
@Override
public String getDomainDestination(Domain domain) {
return "";
}
@Override
public String getSubnameDestination(Domain domain, String subname) {
return "";
}
@Override
public String getTLNInfoSite(String topLevelName) {
return "";
}
@Override
public DNSResponseCode validateDomain(Domain requestedDomain) {
if (!requestedDomain.getProtocol().equalsIgnoreCase("oac")) return DNSResponseCode.RESPONSE_INVALID_REQUEST;
return DNSResponseCode.RESPONSE_DOMAIN_FULLY_NOT_EXIST;
}
@Override
public void validationPacketSendFailed(Domain domain, ConnectedProtocolClient client, Exception exception) {
ProtocolBridge.getInstance().getLogger().exception("Failed to send ValidationPacket. (" +
"Domain: " + domain.getProtocol() + "." + (domain.hasSubname() ? domain.getSubname() : "") + "." + domain.getTopLevelName() + "/" + domain.getPath() + "?" + domain.getQuery() + "#" + domain.getFragment() + ";" +
";Client: " + client.getConnectionHandler().getClientID() + ")", exception);
}
@Override
public void domainDestinationPacketFailedSend(ConnectedProtocolClient client, Domain domain, DNSResponseCode validationResponse, Exception exception) {
ProtocolBridge.getInstance().getLogger().exception("Failed to send DomainDestinationPacket. (" +
"Domain: " + domain.getProtocol() + "." + (domain.hasSubname() ? domain.getSubname() : "") + "." + domain.getTopLevelName() + "/" + domain.getPath() + "?" + domain.getQuery() + "#" + domain.getFragment() + ";" +
"Validation response: " + validationResponse + ";Client: " + client.getConnectionHandler().getClientID() + ")", exception);
}
}

View File

@@ -0,0 +1,346 @@
package org.openautonomousconnection.insserver;
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.IOException;
import java.security.cert.CertificateException;
import java.sql.*;
import java.util.*;
/**
* Database-backed INS server.
* <p>
* This implementation resolves records from the SQL schema and performs CNAME recursion (with loop detection + depth limit).
* Returned records are deterministically sorted so that callers can safely select index 0 as the "best" record.
*/
public final class DatabaseINSServer extends ProtocolINSServer {
private final String jdbcUrl;
private final String jdbcUser;
private final String jdbcPassword;
/**
* 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(String insInfoSite, String insFrontendSite, String jdbcUrl, String jdbcUser, String jdbcPassword) throws Exception {
super(insInfoSite, insFrontendSite);
this.jdbcUrl = jdbcUrl;
this.jdbcUser = jdbcUser;
this.jdbcPassword = jdbcPassword;
}
private static int safeInt(int v) {
return v;
}
private static String safeString(String s) {
return s == null ? "" : s;
}
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>
* The 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 loop detection + depth limit) when no direct records for the requested type exist</li>
* <li>Returns deterministically sorted results (so index 0 can be used as "best record")</li>
* </ul>
*
* @param tln The top-level name.
* @param name The InfoName.
* @param sub Optional subname, may be {@code null}.
* @param type The requested record type.
* @return Resolved records (possibly empty).
*/
@Override
public List<INSRecord> resolve(String tln, String name, String sub, INSRecordType type) {
try {
List<INSRecord> out = resolveInternal(tln, name, sub, type, 0, new HashSet<>());
out.sort(recordComparator(type));
return out;
} catch (SQLException ex) {
getProtocolBridge().getProtocolValues().logger.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"}.
*
* @param tln The TLN name.
* @return The configured info target ("host:port") or {@code null} if not present.
*/
@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");
}
} catch (SQLException ex) {
getProtocolBridge().getProtocolValues().logger.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;
}
/**
* Internal recursive resolver with CNAME handling.
*
* @param depth Current recursion depth.
* @param visited Loop detection set of canonical names (sub.name.tln).
*/
private List<INSRecord> resolveInternal(
String tln,
String name,
String sub,
INSRecordType requestedType,
int depth,
Set<String> visited
) throws SQLException {
final int MAX_CNAME_DEPTH = 16;
String canonical = formatName(tln, name, sub).toLowerCase(Locale.ROOT);
if (!visited.add(canonical)) {
// loop detected
getProtocolBridge().getProtocolValues().logger.warn("CNAME loop detected for " + canonical + " type=" + requestedType);
return new ArrayList<>();
}
if (depth > MAX_CNAME_DEPTH) {
getProtocolBridge().getProtocolValues().logger.warn("CNAME recursion limit exceeded for " + canonical + " type=" + requestedType);
return new ArrayList<>();
}
try (Connection conn = openConnection()) {
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);
// 1) direct records
List<INSRecord> direct = loadRecords(conn, infoNameId, subNameId, requestedType);
direct.sort(recordComparator(requestedType));
// If the requested type is CNAME, do not recurse.
if (requestedType == INSRecordType.CNAME) return direct;
if (!direct.isEmpty()) return direct;
// 2) fallback to CNAME if no direct records exist
List<INSRecord> cnames = loadRecords(conn, infoNameId, subNameId, INSRecordType.CNAME);
cnames.sort(recordComparator(INSRecordType.CNAME));
if (cnames.isEmpty()) return new ArrayList<>();
List<INSRecord> aggregated = new ArrayList<>();
for (INSRecord cname : cnames) {
TargetName target = parseCnameTarget(cname.value);
if (target == null) {
getProtocolBridge().getProtocolValues().logger.warn("Invalid CNAME target '" + cname.value + "' on " + canonical);
continue;
}
// recurse on the target name to fetch the original requested type
List<INSRecord> resolvedTarget = resolveInternal(
target.tln,
target.name,
target.sub,
requestedType,
depth + 1,
visited
);
aggregated.addAll(resolvedTarget);
}
aggregated.sort(recordComparator(requestedType));
return aggregated;
} finally {
// important: visited is shared across the recursion chain on purpose
// (do not remove canonical here; loop detection should stay for the whole chain)
}
}
/**
* Deterministic ordering for returned records. The goal is stable ranking so callers can pick index 0.
*
* <p>Rules:
* <ul>
* <li>priority ASC (smaller is better)</li>
* <li>weight DESC (larger is better)</li>
* <li>port ASC (smaller is better)</li>
* <li>ttl DESC (larger is better)</li>
* <li>value ASC (case-insensitive) as final tie-breaker</li>
* </ul>
*
* @param requestedType The type being requested (kept for future type-specific tuning).
* @return Comparator for {@link INSRecord}.
*/
private Comparator<INSRecord> recordComparator(INSRecordType requestedType) {
return Comparator
.comparingInt((INSRecord r) -> safeInt(r.priority))
.thenComparingInt(r -> -safeInt(r.weight))
.thenComparingInt(r -> safeInt(r.port))
.thenComparingInt(r -> -safeInt(r.ttl))
.thenComparing(r -> safeString(r.value), String.CASE_INSENSITIVE_ORDER);
}
/**
* 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.
*
* @param value Raw CNAME value (e.g. "sub.app.example" or "example.net").
* @return Parsed {@link TargetName} or {@code null} if 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;
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);
}
private static final class TargetName {
final String tln;
final String name;
final String sub;
TargetName(String tln, String name, String sub) {
this.tln = tln;
this.name = name;
this.sub = sub;
}
}
}

View File

@@ -1,9 +1,8 @@
package org.openautonomousconnection.dns;
package org.openautonomousconnection.insserver;
import dev.unlegitdqrk.unlegitlibrary.command.events.CommandExecutorMissingPermissionEvent;
import dev.unlegitdqrk.unlegitlibrary.command.events.CommandNotFoundEvent;
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import org.openautonomousconnection.protocol.ProtocolBridge;
public class Listener extends EventListener {
@@ -11,11 +10,11 @@ public class Listener extends EventListener {
public void onCommandNotFound(CommandNotFoundEvent event) {
StringBuilder argsBuilder = new StringBuilder();
for (String arg : event.getArgs()) argsBuilder.append(arg).append(" ");
ProtocolBridge.getInstance().getLogger().error("Command '" + event.getName() + argsBuilder.toString() + "' not found!");
Main.getProtocolBridge().getProtocolValues().logger.error("Command '" + event.getName() + argsBuilder.toString() + "' not found!");
}
@dev.unlegitdqrk.unlegitlibrary.event.Listener
public void onMissingCommandPermission(CommandExecutorMissingPermissionEvent event) {
ProtocolBridge.getInstance().getLogger().error("You do not have enough permissions to execute this command!");
Main.getProtocolBridge().getProtocolValues().logger.error("You do not have enough permissions to execute this command!");
}
}

View File

@@ -0,0 +1,109 @@
package org.openautonomousconnection.insserver;
import dev.unlegitdqrk.unlegitlibrary.addon.AddonLoader;
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.file.ConfigurationManager;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
import dev.unlegitdqrk.unlegitlibrary.utils.Logger;
import lombok.Getter;
import org.openautonomousconnection.insserver.commands.StopCommand;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolValues;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import java.io.File;
import java.util.Scanner;
public class Main {
public static final CommandPermission PERMISSION_ALL = new CommandPermission("all", 1);
private static final CommandExecutor commandExecutor = new CommandExecutor("INS", PERMISSION_ALL) {
};
@Getter
private static CommandManager commandManager;
@Getter
private static ProtocolBridge protocolBridge;
@Getter
private static ProtocolValues values;
public static void main(String[] args) throws Exception {
if (!new File("logs").exists()) new File("logs").mkdir();
Logger logger = new Logger(new File("logs"), false, true);
values = new ProtocolValues();
values.packetHandler = new PacketHandler();
values.eventManager = new EventManager();
values.protocolVersion = ProtocolVersion.PV_1_0_1_BETA;
values.eventManager.registerListener(new Listener());
values.logger = logger;
if (!new File("config.properties").exists()) {
new File("config.properties").createNewFile();
}
ConfigurationManager config = new ConfigurationManager(new File("config.properties"));
config.loadProperties();
if (!config.isSet("db.url")) {
config.set(
"db.url",
"jdbc:mariadb://localhost:3306/ins?useUnicode=true&characterEncoding=utf8"
);
config.saveProperties();
}
if (!config.isSet("db.user")) {
config.set("db.user", "username");
config.saveProperties();
}
if (!config.isSet("db.password")) {
config.set("db.password", "password");
config.saveProperties();
}
if (!config.isSet("port")) {
config.set("port", 1026);
config.saveProperties();
}
if (!config.isSet("ins.info")) {
config.set("ins.info", "INS INFO SITE (HOST)");
config.saveProperties();
}
if (!config.isSet("ins.frontend")) {
config.set("ins.frontend", "INS FRONTEND SITE (HOST)");
config.saveProperties();
}
String url = config.getString("db.url");
String user = config.getString("db.user");
String password = config.getString("db.password");
int tcpPort = config.getInt("port");
String info = config.getString("ins.info");
String frontend = config.getString("ins.frontend");
DatabaseINSServer server = new DatabaseINSServer(info, frontend, url, user, password);
protocolBridge = new ProtocolBridge(server, values);
server.start(tcpPort);
commandManager = new CommandManager(values.eventManager);
commandManager.registerCommand(StopCommand.class);
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print(commandExecutor.getName() + "> ");
String line = scanner.nextLine();
commandManager.execute(commandExecutor, line);
}
}
}

View File

@@ -0,0 +1,21 @@
package org.openautonomousconnection.insserver.commands;
import dev.unlegitdqrk.unlegitlibrary.command.Command;
import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor;
import org.openautonomousconnection.insserver.Main;
import javax.management.InstanceAlreadyExistsException;
import java.util.ArrayList;
import java.util.Collections;
public class StopCommand extends Command {
public StopCommand() throws InstanceAlreadyExistsException {
super(Main.getCommandManager(), "stop", "Stop the Server", "stop", Collections.singletonList(Main.PERMISSION_ALL), new ArrayList<>());
}
@Override
public void execute(CommandExecutor commandExecutor, String s, String[] strings) {
System.exit(0);
}
}