bf4c256c72
Merge in DNS/adguard-home from 2295-dhcp-windows to master Updates #2295. Squashed commit of the following: commit 3b00a90c3d9bc33e9af478e4062c0f938d4f327d Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Mon Nov 16 16:45:43 2020 +0300 all: use the 501 handlers instead of the real ones, revert other changes commit 0a3b37736a21abd6181e0d28c32069e8d7a576d0 Merge: 45feba7556358240e9
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Mon Nov 16 15:59:15 2020 +0300 Merge branch 'master' into 2295-dhcp-windows and update commit 45feba755dde37e43cc8075b896e1576157341e6 Merge: cd987d8bca19523b25
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Mon Nov 16 15:51:16 2020 +0300 Merge branch 'master' into 2295-dhcp-windows commit cd987d8bc2cd524b7454d9037b595069714645f9 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 15:55:23 2020 +0300 all: improve tests and refactor dhcp checking code even more commit 3aad675443f325b5909523bcc1c987aa04ac61d9 Merge: 70c477e6109196118e
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 14:44:43 2020 +0300 Merge branch 'master' into 2295-dhcp-windows commit 70c477e61cdc1237603918f1c44470c1549f1136 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 14:34:06 2020 +0300 home: fix dhcpd test on windows commit e59597d783fb9304e63f94eee2b5a5d67a5b2169 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 13:38:25 2020 +0300 all: mention the feature in the changelog commit 5555c8d881b1c20b5b0a0cb096a17cf56e209c06 Merge: c3b6a5a93e802e6645
Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Nov 13 13:35:35 2020 +0300 Merge branch 'master' into 2295-dhcp-windows commit c3b6a5a930693090838eb1ef9f75a09b5b223ba6 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Thu Nov 12 20:37:09 2020 +0300 util: fix comment commit ed92dfdb5d3a6c4ba5d032cbe781e7fd87882813 Author: ArtemBaskal <asbaskal@miem.hse.ru> Date: Thu Nov 12 20:24:14 2020 +0300 Adapt client commit e6f0494c20a4ad5388492af9091568eea5c6e2d6 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Thu Nov 12 13:35:25 2020 +0300 return 501 when we don't support features
247 lines
7.2 KiB
Go
247 lines
7.2 KiB
Go
package home
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
|
"github.com/AdguardTeam/golibs/log"
|
|
"github.com/NYTimes/gziphandler"
|
|
)
|
|
|
|
// ----------------
|
|
// helper functions
|
|
// ----------------
|
|
|
|
func returnOK(w http.ResponseWriter) {
|
|
_, err := fmt.Fprintf(w, "OK\n")
|
|
if err != nil {
|
|
httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err)
|
|
}
|
|
}
|
|
|
|
func httpError(w http.ResponseWriter, code int, format string, args ...interface{}) {
|
|
text := fmt.Sprintf(format, args...)
|
|
log.Info(text)
|
|
http.Error(w, text, code)
|
|
}
|
|
|
|
// ---------------
|
|
// dns run control
|
|
// ---------------
|
|
func addDNSAddress(dnsAddresses *[]string, addr string) {
|
|
if config.DNS.Port != 53 {
|
|
addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port)
|
|
}
|
|
*dnsAddresses = append(*dnsAddresses, addr)
|
|
}
|
|
|
|
func handleStatus(w http.ResponseWriter, r *http.Request) {
|
|
c := dnsforward.FilteringConfig{}
|
|
if Context.dnsServer != nil {
|
|
Context.dnsServer.WriteDiskConfig(&c)
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"dns_addresses": getDNSAddresses(),
|
|
"http_port": config.BindPort,
|
|
"dns_port": config.DNS.Port,
|
|
"running": isRunning(),
|
|
"version": versionString,
|
|
"language": config.Language,
|
|
|
|
"protection_enabled": c.ProtectionEnabled,
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
// Set the DHCP to false explicitly, because Context.dhcpServer
|
|
// is probably not nil, despite the fact that there is no
|
|
// support for DHCP on Windows in AdGuardHome.
|
|
//
|
|
// See also the TODO in dhcpd.Create.
|
|
data["dhcp_available"] = false
|
|
} else {
|
|
data["dhcp_available"] = (Context.dhcpServer != nil)
|
|
}
|
|
|
|
jsonVal, err := json.Marshal(data)
|
|
if err != nil {
|
|
httpError(w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, err = w.Write(jsonVal)
|
|
if err != nil {
|
|
httpError(w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
type profileJSON struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
|
pj := profileJSON{}
|
|
u := Context.auth.GetCurrentUser(r)
|
|
pj.Name = u.Name
|
|
|
|
data, err := json.Marshal(pj)
|
|
if err != nil {
|
|
httpError(w, http.StatusInternalServerError, "json.Marshal: %s", err)
|
|
return
|
|
}
|
|
_, _ = w.Write(data)
|
|
}
|
|
|
|
// ------------------------
|
|
// registration of handlers
|
|
// ------------------------
|
|
func registerControlHandlers() {
|
|
httpRegister(http.MethodGet, "/control/status", handleStatus)
|
|
httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
|
|
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
|
|
http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
|
|
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
|
httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
|
|
|
|
// No auth is necessary for DOH/DOT configurations
|
|
http.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh))
|
|
http.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot))
|
|
RegisterAuthHandlers()
|
|
}
|
|
|
|
func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) {
|
|
if len(method) == 0 {
|
|
// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
|
|
http.HandleFunc(url, postInstall(handler))
|
|
return
|
|
}
|
|
|
|
http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
|
}
|
|
|
|
// ----------------------------------
|
|
// helper functions for HTTP handlers
|
|
// ----------------------------------
|
|
func ensure(method string, handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
log.Debug("%s %v", r.Method, r.URL)
|
|
|
|
if r.Method != method {
|
|
http.Error(w, "This request must be "+method, http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
if method == "POST" || method == "PUT" || method == "DELETE" {
|
|
Context.controlLock.Lock()
|
|
defer Context.controlLock.Unlock()
|
|
}
|
|
|
|
handler(w, r)
|
|
}
|
|
}
|
|
|
|
func ensurePOST(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
|
return ensure("POST", handler)
|
|
}
|
|
|
|
func ensureGET(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
|
return ensure("GET", handler)
|
|
}
|
|
|
|
// Bridge between http.Handler object and Go function
|
|
type httpHandler struct {
|
|
handler func(http.ResponseWriter, *http.Request)
|
|
}
|
|
|
|
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
h.handler(w, r)
|
|
}
|
|
|
|
func ensureHandler(method string, handler func(http.ResponseWriter, *http.Request)) http.Handler {
|
|
h := httpHandler{}
|
|
h.handler = ensure(method, handler)
|
|
return &h
|
|
}
|
|
|
|
// preInstall lets the handler run only if firstRun is true, no redirects
|
|
func preInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if !Context.firstRun {
|
|
// if it's not first run, don't let users access it (for example /install.html when configuration is done)
|
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
return
|
|
}
|
|
handler(w, r)
|
|
}
|
|
}
|
|
|
|
// preInstallStruct wraps preInstall into a struct that can be returned as an interface where necessary
|
|
type preInstallHandlerStruct struct {
|
|
handler http.Handler
|
|
}
|
|
|
|
func (p *preInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
preInstall(p.handler.ServeHTTP)(w, r)
|
|
}
|
|
|
|
// preInstallHandler returns http.Handler interface for preInstall wrapper
|
|
func preInstallHandler(handler http.Handler) http.Handler {
|
|
return &preInstallHandlerStruct{handler}
|
|
}
|
|
|
|
// postInstall lets the handler run only if firstRun is false, and redirects to /install.html otherwise
|
|
// it also enforces HTTPS if it is enabled and configured
|
|
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if Context.firstRun &&
|
|
!strings.HasPrefix(r.URL.Path, "/install.") &&
|
|
!strings.HasPrefix(r.URL.Path, "/assets/") {
|
|
http.Redirect(w, r, "/install.html", http.StatusFound)
|
|
return
|
|
}
|
|
|
|
// enforce https?
|
|
if r.TLS == nil && Context.web.forceHTTPS && Context.web.httpsServer.server != nil {
|
|
// yes, and we want host from host:port
|
|
host, _, err := net.SplitHostPort(r.Host)
|
|
if err != nil {
|
|
// no port in host
|
|
host = r.Host
|
|
}
|
|
// construct new URL to redirect to
|
|
newURL := url.URL{
|
|
Scheme: "https",
|
|
Host: net.JoinHostPort(host, strconv.Itoa(Context.web.portHTTPS)),
|
|
Path: r.URL.Path,
|
|
RawQuery: r.URL.RawQuery,
|
|
}
|
|
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
handler(w, r)
|
|
}
|
|
}
|
|
|
|
type postInstallHandlerStruct struct {
|
|
handler http.Handler
|
|
}
|
|
|
|
func (p *postInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
postInstall(p.handler.ServeHTTP)(w, r)
|
|
}
|
|
|
|
func postInstallHandler(handler http.Handler) http.Handler {
|
|
return &postInstallHandlerStruct{handler}
|
|
}
|