From 2e90d32ce2914040ef3a9f3c1b7888d008f5a4d8 Mon Sep 17 00:00:00 2001 From: Thomas Preisner Date: Sun, 9 Sep 2018 21:38:37 +0200 Subject: [PATCH] intial import --- auth.go | 35 +++++++++++++++++++++++++ backend.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ data.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 19 ++++++++++++++ nameserver.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 auth.go create mode 100644 backend.go create mode 100644 data.go create mode 100644 main.go create mode 100644 nameserver.go diff --git a/auth.go b/auth.go new file mode 100644 index 0000000..6e830f6 --- /dev/null +++ b/auth.go @@ -0,0 +1,35 @@ +package main + +import ( + "crypto/subtle" + "database/sql" + "fmt" + "net/http" +) + +func authenticateUser(db *sql.DB, username, password string) bool { + pass, ok := getPasswordForUser(db, username) + if ok { + return subtle.ConstantTimeCompare([]byte(pass), []byte(password)) == 1 + } else { + return false + } +} + +func basicAuth(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, pass, ok := r.BasicAuth() + if !ok || !authenticateUser(db, user, pass) { + w.Header().Set("WWW-Authenticate", `Basic realm="dyndns"`) + w.WriteHeader(401) + w.Write([]byte("badauth")) + return + } + + userdata, err := getDataForUser(db, user) + if err != nil { + fmt.Println(err) + } + handleRequest(w, r, userdata) + } +} diff --git a/backend.go b/backend.go new file mode 100644 index 0000000..6c52172 --- /dev/null +++ b/backend.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "strings" +) + +// check if net.ParseIP returns nil!! +func getIP(r *http.Request) net.IP { + addr := r.URL.Query().Get("myip") + if len(addr) > 0 { + return net.ParseIP(addr) + } + + addr = r.Header.Get(http.CanonicalHeaderKey("X-Forwarded-For")) + if len(addr) > 0 { + end := strings.Index(addr, ", ") + if end == -1 { + end = len(addr) + } + return net.ParseIP(addr[:end]) + } + + addr = r.Header.Get(http.CanonicalHeaderKey("X-Real-IP")) + if len(addr) > 0 { + return net.ParseIP(addr) + } + + addr, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + panic(err) + } + return net.ParseIP(addr) +} + +func handleRequest(w http.ResponseWriter, r *http.Request, data userData) { + msg := "good" + defer func() { + w.Write([]byte(msg)) + }() + + hostname := r.URL.Query().Get("hostname") + if len(hostname) <= 0 || hostname != data.hostname { + msg = "nohost" + return + } + + ipaddr := getIP(r) + if ipaddr == nil { + //TODO: + msg = "error" + return + } + msg = "Hostname: " + hostname + " , IP: " + ipaddr.String() + + updated, err := updateNameserver(data, ipaddr) + if err != nil { + msg = "dnserr" + fmt.Println(err) + return + } + if !updated { + msg = "nochg" + return + } +} diff --git a/data.go b/data.go new file mode 100644 index 0000000..736c99a --- /dev/null +++ b/data.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "database/sql" + _ "github.com/lib/pq" +) + +//TODO: move into separate config file? +const ( + DBHost = "127.0.0.1" + DBPort = 5432 + DBUser = "username" + DBPass = "password" + DBName = "database" +) + +type userData struct { + nameserver string + zonename string + hostname string + tsigkey string +} + +func prepareDatabase() (*sql.DB, error) { + psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s " + + "sslmode=disable", DBHost, DBPort, DBUser, DBPass, + DBName) + db, err := sql.Open("postgres", psqlInfo) + if err != nil { + return nil, err + } + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS users " + + "(id SERIAL PRIMARY KEY, username VARCHAR(255) NULL, " + + "password VARCHAR(255) NULL, salt VARCHAR(255) NULL, " + + "nameserver VARCHAR(255) NULL, zone VARCHAR(255) NULL, " + + "hostname VARCHAR(255) NULL, tsig VARCHAR(255) NULL)") + if err != nil { + db.Close() + return nil, err + } + return db, nil +} + +func getPasswordForUser(db *sql.DB, username string) (string, bool) { + var password string + + row := db.QueryRow("SELECT password FROM users WHERE username=$1", username) + err := row.Scan(&password) + if err != nil { + if err == sql.ErrNoRows { + return "", false + } else { + panic(err) + } + } + return password, true +} + +func getDataForUser(db *sql.DB, username string) (userData, error) { + var data userData + + row := db.QueryRow("SELECT nameserver, zone, hostname, tsig " + + "FROM users WHERE username=$1", username) + err := row.Scan(&data.nameserver, &data.zonename, &data.hostname, + &data.tsigkey) + if err != nil { + return data, err + } + return data, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..28b8df8 --- /dev/null +++ b/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "net/http" +) + +func main() { + db, err := prepareDatabase() + if err != nil { + panic(err) + } + defer db.Close() + + http.HandleFunc("/", basicAuth(db)) + err = http.ListenAndServe(":3002", nil) + if err != nil { + panic(err) + } +} diff --git a/nameserver.go b/nameserver.go new file mode 100644 index 0000000..c68e45d --- /dev/null +++ b/nameserver.go @@ -0,0 +1,70 @@ +package main + +import ( + "bytes" + "fmt" + "net" + "os/exec" + "strings" +) + +func createQuery(data userData, addr net.IP, deleteRecord bool) string { + var query strings.Builder + + query.WriteString(fmt.Sprintf("key %s\n", data.tsigkey)) + query.WriteString(fmt.Sprintf("server %s\n", data.nameserver)) + query.WriteString(fmt.Sprintf("zone %s\n", data.zonename)) + if deleteRecord { + query.WriteString(fmt.Sprintf("update delete %s. A\n", data.hostname)) + } + query.WriteString(fmt.Sprintf("update add %s. A %s\n", data.hostname, + addr.String())) + query.WriteString("show\n") + query.WriteString("send\n") + + return query.String() +} + +func queryNameserver(query string) error { + var ( + stdout bytes.Buffer + stderr bytes.Buffer + ) + + cmd := exec.Command("knsupdate", "-v") + cmd.Stdin = strings.NewReader(query) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + fmt.Printf(stdout.String()) + fmt.Printf(stderr.String()) + + return err +} + +func updateNameserver(data userData, addr net.IP) (bool, error) { + updateRecord := true + deleteRecord := true + + addrs, err := net.LookupIP(data.hostname) + if err != nil { + deleteRecord = false + } else { + for _, ip := range addrs { + if ip.Equal(addr) { + updateRecord = false + break + } + } + } + if !updateRecord { + return false, nil + } else { + query := createQuery(data, addr, deleteRecord) + err = queryNameserver(query) + if err != nil { + return true, err + } + return true, nil + } +}