intial import

This commit is contained in:
Thomas Preisner 2018-09-09 21:38:37 +02:00
commit 2e90d32ce2
5 changed files with 264 additions and 0 deletions

35
auth.go Normal file
View file

@ -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)
}
}

68
backend.go Normal file
View file

@ -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
}
}

72
data.go Normal file
View file

@ -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
}

19
main.go Normal file
View file

@ -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)
}
}

70
nameserver.go Normal file
View file

@ -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
}
}