From d40bfca474692b104982402369a71e837f4d43ad Mon Sep 17 00:00:00 2001
From: Jarkko Toivanen <jt@jakest.us>
Date: Fri, 31 Jan 2025 05:59:20 +0200
Subject: [PATCH] Simple FI-domain availability checking

---
 .gitignore          |   2 +
 Makefile            |   4 +
 config.json.example |   4 +
 go.mod              |  16 ++++
 go.sum              |  21 +++++
 index.html          |  23 ++++++
 main.go             | 183 ++++++++++++++++++++++++++++++++++++++++++++
 static/style.css    |  14 ++++
 8 files changed, 267 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 Makefile
 create mode 100644 config.json.example
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 index.html
 create mode 100644 main.go
 create mode 100644 static/style.css

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ff6a9c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/config.json
+/nexsis
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6c802db
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,4 @@
+run:
+	go run main.go
+build:
+	go build -v -ldflags "-s -w"
diff --git a/config.json.example b/config.json.example
new file mode 100644
index 0000000..c910018
--- /dev/null
+++ b/config.json.example
@@ -0,0 +1,4 @@
+{
+	"port": 6900,
+	"siteName": "NexSIS"
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..0905b39
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,16 @@
+module nexsis
+
+go 1.23.0
+
+require (
+	github.com/dustin/go-humanize v1.0.1 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/ncruces/go-strftime v0.1.9 // indirect
+	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+	golang.org/x/sys v0.22.0 // indirect
+	modernc.org/libc v1.55.3 // indirect
+	modernc.org/mathutil v1.6.0 // indirect
+	modernc.org/memory v1.8.0 // indirect
+	modernc.org/sqlite v1.34.5 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..2b43db3
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,21 @@
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
+github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
+modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
+modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
+modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
+modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
+modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
+modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
+modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..a0d0277
--- /dev/null
+++ b/index.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8">
+    <link rel="stylesheet" type="text/css" href="/static/style.css">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>{{.Title}}</title>
+  </head>
+  <body>
+    <div class="cntrbox">
+      <h1>{{.Title}}</h1>
+      <p>
+	Will be able to manage. Or not.
+      </p>
+      <footer>
+	<hr>
+	<small>
+	  2015 Jarkko Toivanen
+	</small>
+      </footer>
+    </div>
+  </body>
+</html>
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..1d540ee
--- /dev/null
+++ b/main.go
@@ -0,0 +1,183 @@
+package main
+
+import (
+	"database/sql"
+	"encoding/json"
+	"encoding/xml"
+	"fmt"
+	"html/template"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"io"
+	"strings"
+	"bytes"
+	"errors"
+	_ "modernc.org/sqlite"
+)
+
+type Config struct {
+	Port uint
+	SiteName string
+}
+
+type page struct {
+	Title string
+}
+
+type xmlDomain struct {
+	authority string
+	registryType string
+	entityClass string
+	entityName string
+}
+
+var C = Config{}
+var db *sql.DB
+
+func main() {	
+	var err error
+
+	domain := "snuff.fi"
+	av, err := checkDomainAvailability(domain)
+	if err != nil {
+		log.Fatal(err)
+		return
+	}
+	fmt.Printf("Domain '%s' available: %t\n", domain, av)
+	return
+	
+	// Load config
+	fmt.Println("Loading config.json")
+	cfgfile, err := os.Open("config.json")
+	if err != nil {
+		log.Fatal("Error reading config.json!")
+		return
+	}
+	defer cfgfile.Close()
+	
+	decoder := json.NewDecoder(cfgfile)
+	//Config := Config{}
+	err = decoder.Decode(&C)
+	if err != nil {
+		log.Fatal("Can't decode config.json!")
+		return
+	}
+
+
+
+	
+	db, err = sql.Open("sqlite", "database.db")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Printf("Starting listener on :%v", C.Port)
+	http.Handle("/static/", http.FileServer(http.Dir("./")))
+	http.HandleFunc("/", httpRootHandler)
+	err = http.ListenAndServe(fmt.Sprintf(":%v", C.Port), nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	db.Close()
+}
+
+func httpRootHandler(w http.ResponseWriter, r *http.Request) {
+	t, _ := template.ParseFiles("index.html")
+	// var err := do_get_stuff()
+	// if err != nil {
+	// 	log.Fatal(err)
+	// 	http.Error(w, http.StatusText(500), 500)
+	// 	return
+	// }
+
+	
+	
+	var p page
+	p.Title = C.SiteName;
+	t.Execute(w, p)
+}
+
+func checkDomainAvailability(domain string) (bool, error) {
+	dasHost := "das.domain.fi";
+	dasPort := 715
+	dasAddr := fmt.Sprintf("%s:%d", dasHost, dasPort)
+	request := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
+<iris1:request xmlns:iris1="urn:ietf:params:xml:ns:iris1">
+<iris1:searchSet>
+<iris1:lookupEntity registryType="dchk1" entityClass="domain-name" entityName="%s"/>
+</iris1:searchSet>
+</iris1:request>`, domain)
+	
+	conn, err := net.Dial("udp", dasAddr)
+	if err != nil {
+		log.Fatal(err)
+		return false, err
+	}
+	defer conn.Close()
+
+	fmt.Fprintf(conn, request)
+	response := make([]byte, 1024)
+	_, err = conn.Read(response)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	response = bytes.Trim(response, "\x00")
+
+	// Response can be:
+	// - active: already in use
+	// - available: go ahead and grab it while you can
+	// - invalid: malformed request: maybe check domain has .fi in the end
+	isAvailable, err := xmlCheckTag(string(response), "available")
+	if err != nil {
+		log.Fatal(err)
+		return false, err
+	} else if isAvailable {
+		// Domain is available
+		return true, nil
+	}
+	
+	isActive, err := xmlCheckTag(string(response), "active")
+	if err != nil {
+		log.Fatal(err)
+		return false, err
+	} else if isActive {
+		// Domain is already registered taken
+		return false, nil
+	}
+	
+	isInvalid, err := xmlCheckTag(string(response), "invalid")
+	if err != nil {
+		log.Fatal(err)
+		return false, err
+	} else if isInvalid {
+		// We got bonked for our request
+		return false, errors.New("Request or domain invalid")
+	}
+
+	// Execution should not lead here but hey might as well error it out
+	return false, errors.New("Error: Status field not found")
+}
+
+func xmlCheckTag(src, tag string) (bool, error) {
+	// Thanks icza for your stackoverflow
+	// https://stackoverflow.com/a/51649834
+	decoder := xml.NewDecoder(strings.NewReader(src))
+	for {
+		t, err := decoder.Token()
+		if err != nil {
+			if err == io.EOF {
+				return false, nil
+			}
+			return false, err
+		}
+		if se, ok := t.(xml.StartElement); ok {
+			if se.Name.Local == tag {
+				return true, nil
+			}
+		}
+	}
+}
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..db4a1f6
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,14 @@
+body {
+    background-color: #000000;
+    color: #ffffff;
+    font-family: "sans-serif";
+}
+
+a, a:visited, a:active {
+    color: #00ffff;
+}
+
+.cntrbox {
+    display: grid;
+    place-content: center;
+}