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