dyndns-server/web.go

126 lines
3 KiB
Go

package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
"log"
"net"
"net/http"
"strings"
)
func isAuthenticated(cfg *Config, r *http.Request) *RRConfig {
user, pw, ok := r.BasicAuth()
if !ok {
// no basic auth header detected
return nil
}
entry, ok := cfg.rrconfigs[user]
if !ok {
// non-existent username
return nil
}
err := bcrypt.CompareHashAndPassword([]byte(entry.Password), []byte(pw))
if err != nil {
log.Printf("Config contains invalid password hash for user %q", user)
return nil
}
return entry
}
func getIpAddress(r *http.Request) net.IP {
addr := r.URL.Query().Get("myip")
if len(addr) > 0 {
// If myip was supplied, parse it and return the result regardless of
// success, failure will be handled later.
return net.ParseIP(addr)
}
// check http headers and request for other possible ip address sources
addr = r.Header.Get(http.CanonicalHeaderKey("X-Forwarded-For"))
if len(addr) > 0 {
tokens := strings.Split(addr, ", ")
// tokens is always at least 1 element long
// -> return first element as it contains the client's ip address
return net.ParseIP(tokens[0])
}
// TODO: support newer standarized "Forwarded"-Header
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 {
log.Printf("Retrieving IP-Address failed: %s", err)
return nil
}
return net.ParseIP(addr)
}
// returns api-response on failure
func verifyHostname(entry *RRConfig, hostname string) string {
if len(hostname) <= 0 {
return "nohost"
}
// TODO: allow single user to update multiple hostnames
// TODO: return ntfqdn -> differentiate between 'hostname doesnt exist' and
// 'hostname is not fqdn'
if hostname != entry.Hostname {
return "nohost"
}
return ""
}
func RequestHandler(cfg *Config) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
userdata := isAuthenticated(cfg, r)
if userdata == nil {
w.Header().Set("WWW-Authenticate", `Basic realm="dyndns"`)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("badauth"))
fmt.Fprintln(w, "badauth")
return
}
// all remaining responses should come with status code 200
w.WriteHeader(http.StatusOK)
// check for 'valid' useragent, should not be strictly necessary but
// the protocol demands it
if len(r.UserAgent()) <= 0 {
fmt.Fprintln(w, "badagent")
return
}
hostname := r.URL.Query().Get("hostname")
response := verifyHostname(userdata, hostname)
if response != "" {
fmt.Fprintln(w, response)
return
}
ipaddr := getIpAddress(r)
if ipaddr == nil {
fmt.Fprintln(w, "911")
return
}
if !requiresRRUpdate(userdata, ipaddr) {
fmt.Fprintf(w, "nochg %s\n", ipaddr.String())
return
}
err := updateRR(userdata, ipaddr)
if err != nil {
log.Printf("updating RR failed: %s", err)
fmt.Fprintln(w, "911")
return
}
fmt.Fprintf(w, "good %s\n", ipaddr.String())
}
}