commit d40bfca474692b104982402369a71e837f4d43ad
Author: Jarkko Toivanen <jt@jakest.us>
Date:   Fri Jan 31 05:59:20 2025 +0200

    Simple FI-domain availability checking

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;
+}