"Fossies" - the Fresh Open Source Software Archive

Member "AdGuardHome-0.104.3/internal/home/control.go" (19 Nov 2020, 7348 Bytes) of package /linux/misc/dns/AdGuardHome-0.104.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Go source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "control.go": 0.104.1_vs_0.104.3.

    1 package home
    2 
    3 import (
    4     "encoding/json"
    5     "fmt"
    6     "net"
    7     "net/http"
    8     "net/url"
    9     "runtime"
   10     "strconv"
   11     "strings"
   12 
   13     "github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
   14     "github.com/AdguardTeam/golibs/log"
   15     "github.com/NYTimes/gziphandler"
   16 )
   17 
   18 // ----------------
   19 // helper functions
   20 // ----------------
   21 
   22 func returnOK(w http.ResponseWriter) {
   23     _, err := fmt.Fprintf(w, "OK\n")
   24     if err != nil {
   25         httpError(w, http.StatusInternalServerError, "Couldn't write body: %s", err)
   26     }
   27 }
   28 
   29 func httpError(w http.ResponseWriter, code int, format string, args ...interface{}) {
   30     text := fmt.Sprintf(format, args...)
   31     log.Info(text)
   32     http.Error(w, text, code)
   33 }
   34 
   35 // ---------------
   36 // dns run control
   37 // ---------------
   38 func addDNSAddress(dnsAddresses *[]string, addr string) {
   39     if config.DNS.Port != 53 {
   40         addr = fmt.Sprintf("%s:%d", addr, config.DNS.Port)
   41     }
   42     *dnsAddresses = append(*dnsAddresses, addr)
   43 }
   44 
   45 func handleStatus(w http.ResponseWriter, r *http.Request) {
   46     c := dnsforward.FilteringConfig{}
   47     if Context.dnsServer != nil {
   48         Context.dnsServer.WriteDiskConfig(&c)
   49     }
   50 
   51     data := map[string]interface{}{
   52         "dns_addresses": getDNSAddresses(),
   53         "http_port":     config.BindPort,
   54         "dns_port":      config.DNS.Port,
   55         "running":       isRunning(),
   56         "version":       versionString,
   57         "language":      config.Language,
   58 
   59         "protection_enabled": c.ProtectionEnabled,
   60     }
   61 
   62     if runtime.GOOS == "windows" {
   63         // Set the DHCP to false explicitly, because Context.dhcpServer
   64         // is probably not nil, despite the fact that there is no
   65         // support for DHCP on Windows in AdGuardHome.
   66         //
   67         // See also the TODO in dhcpd.Create.
   68         data["dhcp_available"] = false
   69     } else {
   70         data["dhcp_available"] = (Context.dhcpServer != nil)
   71     }
   72 
   73     jsonVal, err := json.Marshal(data)
   74     if err != nil {
   75         httpError(w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
   76         return
   77     }
   78     w.Header().Set("Content-Type", "application/json")
   79     _, err = w.Write(jsonVal)
   80     if err != nil {
   81         httpError(w, http.StatusInternalServerError, "Unable to write response json: %s", err)
   82         return
   83     }
   84 }
   85 
   86 type profileJSON struct {
   87     Name string `json:"name"`
   88 }
   89 
   90 func handleGetProfile(w http.ResponseWriter, r *http.Request) {
   91     pj := profileJSON{}
   92     u := Context.auth.GetCurrentUser(r)
   93     pj.Name = u.Name
   94 
   95     data, err := json.Marshal(pj)
   96     if err != nil {
   97         httpError(w, http.StatusInternalServerError, "json.Marshal: %s", err)
   98         return
   99     }
  100     _, _ = w.Write(data)
  101 }
  102 
  103 // ------------------------
  104 // registration of handlers
  105 // ------------------------
  106 func registerControlHandlers() {
  107     httpRegister(http.MethodGet, "/control/status", handleStatus)
  108     httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
  109     httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
  110     http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
  111     httpRegister(http.MethodPost, "/control/update", handleUpdate)
  112     httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
  113 
  114     // No auth is necessary for DOH/DOT configurations
  115     http.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh))
  116     http.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot))
  117     RegisterAuthHandlers()
  118 }
  119 
  120 func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) {
  121     if len(method) == 0 {
  122         // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
  123         http.HandleFunc(url, postInstall(handler))
  124         return
  125     }
  126 
  127     http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
  128 }
  129 
  130 // ----------------------------------
  131 // helper functions for HTTP handlers
  132 // ----------------------------------
  133 func ensure(method string, handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  134     return func(w http.ResponseWriter, r *http.Request) {
  135         log.Debug("%s %v", r.Method, r.URL)
  136 
  137         if r.Method != method {
  138             http.Error(w, "This request must be "+method, http.StatusMethodNotAllowed)
  139             return
  140         }
  141 
  142         if method == "POST" || method == "PUT" || method == "DELETE" {
  143             Context.controlLock.Lock()
  144             defer Context.controlLock.Unlock()
  145         }
  146 
  147         handler(w, r)
  148     }
  149 }
  150 
  151 func ensurePOST(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  152     return ensure("POST", handler)
  153 }
  154 
  155 func ensureGET(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  156     return ensure("GET", handler)
  157 }
  158 
  159 // Bridge between http.Handler object and Go function
  160 type httpHandler struct {
  161     handler func(http.ResponseWriter, *http.Request)
  162 }
  163 
  164 func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  165     h.handler(w, r)
  166 }
  167 
  168 func ensureHandler(method string, handler func(http.ResponseWriter, *http.Request)) http.Handler {
  169     h := httpHandler{}
  170     h.handler = ensure(method, handler)
  171     return &h
  172 }
  173 
  174 // preInstall lets the handler run only if firstRun is true, no redirects
  175 func preInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  176     return func(w http.ResponseWriter, r *http.Request) {
  177         if !Context.firstRun {
  178             // if it's not first run, don't let users access it (for example /install.html when configuration is done)
  179             http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
  180             return
  181         }
  182         handler(w, r)
  183     }
  184 }
  185 
  186 // preInstallStruct wraps preInstall into a struct that can be returned as an interface where necessary
  187 type preInstallHandlerStruct struct {
  188     handler http.Handler
  189 }
  190 
  191 func (p *preInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  192     preInstall(p.handler.ServeHTTP)(w, r)
  193 }
  194 
  195 // preInstallHandler returns http.Handler interface for preInstall wrapper
  196 func preInstallHandler(handler http.Handler) http.Handler {
  197     return &preInstallHandlerStruct{handler}
  198 }
  199 
  200 // postInstall lets the handler run only if firstRun is false, and redirects to /install.html otherwise
  201 // it also enforces HTTPS if it is enabled and configured
  202 func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  203     return func(w http.ResponseWriter, r *http.Request) {
  204 
  205         if Context.firstRun &&
  206             !strings.HasPrefix(r.URL.Path, "/install.") &&
  207             !strings.HasPrefix(r.URL.Path, "/assets/") {
  208             http.Redirect(w, r, "/install.html", http.StatusFound)
  209             return
  210         }
  211 
  212         // enforce https?
  213         if r.TLS == nil && Context.web.forceHTTPS && Context.web.httpsServer.server != nil {
  214             // yes, and we want host from host:port
  215             host, _, err := net.SplitHostPort(r.Host)
  216             if err != nil {
  217                 // no port in host
  218                 host = r.Host
  219             }
  220             // construct new URL to redirect to
  221             newURL := url.URL{
  222                 Scheme:   "https",
  223                 Host:     net.JoinHostPort(host, strconv.Itoa(Context.web.portHTTPS)),
  224                 Path:     r.URL.Path,
  225                 RawQuery: r.URL.RawQuery,
  226             }
  227             http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
  228             return
  229         }
  230 
  231         w.Header().Set("Access-Control-Allow-Origin", "*")
  232         handler(w, r)
  233     }
  234 }
  235 
  236 type postInstallHandlerStruct struct {
  237     handler http.Handler
  238 }
  239 
  240 func (p *postInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  241     postInstall(p.handler.ServeHTTP)(w, r)
  242 }
  243 
  244 func postInstallHandler(handler http.Handler) http.Handler {
  245     return &postInstallHandlerStruct{handler}
  246 }