2019-06-10 08:33:19 +00:00
|
|
|
package home
|
2018-08-30 14:25:33 +00:00
|
|
|
|
|
|
|
import (
|
2019-04-17 11:53:52 +00:00
|
|
|
"context"
|
2019-01-29 17:41:57 +00:00
|
|
|
"fmt"
|
|
|
|
"net"
|
2018-08-30 14:25:33 +00:00
|
|
|
"net/http"
|
2019-02-19 18:19:27 +00:00
|
|
|
"net/url"
|
2018-09-26 14:47:23 +00:00
|
|
|
"os"
|
2020-02-05 14:38:23 +00:00
|
|
|
"os/exec"
|
2018-09-14 13:50:56 +00:00
|
|
|
"path"
|
2018-10-29 23:17:24 +00:00
|
|
|
"path/filepath"
|
2018-09-14 13:50:56 +00:00
|
|
|
"runtime"
|
2019-02-01 16:25:04 +00:00
|
|
|
"strconv"
|
2018-08-30 14:25:33 +00:00
|
|
|
"strings"
|
2019-04-17 11:53:52 +00:00
|
|
|
"time"
|
2019-02-22 14:59:42 +00:00
|
|
|
|
2019-04-17 11:53:52 +00:00
|
|
|
"github.com/AdguardTeam/golibs/log"
|
2019-02-22 14:59:42 +00:00
|
|
|
"github.com/joomcode/errorx"
|
2018-08-30 14:25:33 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ----------------------------------
|
|
|
|
// 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) {
|
2019-08-21 11:39:37 +00:00
|
|
|
log.Debug("%s %v", r.Method, r.URL)
|
|
|
|
|
2018-08-30 14:25:33 +00:00
|
|
|
if r.Method != method {
|
2019-01-25 13:01:27 +00:00
|
|
|
http.Error(w, "This request must be "+method, http.StatusMethodNotAllowed)
|
2018-08-30 14:25:33 +00:00
|
|
|
return
|
|
|
|
}
|
2019-03-05 15:04:49 +00:00
|
|
|
|
|
|
|
if method == "POST" || method == "PUT" || method == "DELETE" {
|
2019-07-09 15:25:26 +00:00
|
|
|
config.controlLock.Lock()
|
|
|
|
defer config.controlLock.Unlock()
|
2019-03-05 15:04:49 +00:00
|
|
|
}
|
|
|
|
|
2018-08-30 14:25:33 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-04-15 13:21:12 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2019-08-21 11:39:37 +00:00
|
|
|
func ensureHandler(method string, handler func(http.ResponseWriter, *http.Request)) http.Handler {
|
2019-04-15 13:21:12 +00:00
|
|
|
h := httpHandler{}
|
2019-08-21 11:39:37 +00:00
|
|
|
h.handler = ensure(method, handler)
|
2019-04-15 13:21:12 +00:00
|
|
|
return &h
|
|
|
|
}
|
|
|
|
|
2019-01-29 17:41:57 +00:00
|
|
|
// -------------------
|
|
|
|
// first run / install
|
|
|
|
// -------------------
|
|
|
|
func detectFirstRun() bool {
|
|
|
|
configfile := config.ourConfigFilename
|
|
|
|
if !filepath.IsAbs(configfile) {
|
2019-02-10 17:47:43 +00:00
|
|
|
configfile = filepath.Join(config.ourWorkingDir, config.ourConfigFilename)
|
2019-01-29 17:41:57 +00:00
|
|
|
}
|
|
|
|
_, err := os.Stat(configfile)
|
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
// do nothing, file exists
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 !config.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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-07 11:22:08 +00:00
|
|
|
// preInstallStruct wraps preInstall into a struct that can be returned as an interface where necessary
|
2019-01-29 17:41:57 +00:00
|
|
|
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
|
2019-02-19 18:19:27 +00:00
|
|
|
// it also enforces HTTPS if it is enabled and configured
|
2019-01-29 17:41:57 +00:00
|
|
|
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2019-05-23 13:28:20 +00:00
|
|
|
if config.firstRun &&
|
|
|
|
!strings.HasPrefix(r.URL.Path, "/install.") &&
|
2019-05-31 13:36:48 +00:00
|
|
|
r.URL.Path != "/favicon.png" {
|
2019-01-29 17:41:57 +00:00
|
|
|
http.Redirect(w, r, "/install.html", http.StatusSeeOther) // should not be cacheable
|
|
|
|
return
|
|
|
|
}
|
2019-02-19 18:19:27 +00:00
|
|
|
// enforce https?
|
2019-12-11 09:38:58 +00:00
|
|
|
if config.TLS.ForceHTTPS && r.TLS == nil && config.TLS.Enabled && config.TLS.PortHTTPS != 0 && Context.httpsServer.server != nil {
|
2019-02-19 18:19:27 +00:00
|
|
|
// 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(config.TLS.PortHTTPS)),
|
|
|
|
Path: r.URL.Path,
|
|
|
|
RawQuery: r.URL.RawQuery,
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, newURL.String(), http.StatusTemporaryRedirect)
|
|
|
|
return
|
|
|
|
}
|
2019-02-20 07:40:18 +00:00
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
2019-01-29 17:41:57 +00:00
|
|
|
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}
|
|
|
|
}
|
|
|
|
|
2019-04-17 11:53:52 +00:00
|
|
|
// Connect to a remote server resolving hostname using our own DNS server
|
|
|
|
func customDialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
|
|
log.Tracef("network:%v addr:%v", network, addr)
|
|
|
|
|
|
|
|
host, port, err := net.SplitHostPort(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dialer := &net.Dialer{
|
|
|
|
Timeout: time.Minute * 5,
|
|
|
|
}
|
|
|
|
|
2019-04-25 12:37:10 +00:00
|
|
|
if net.ParseIP(host) != nil || config.DNS.Port == 0 {
|
2019-04-17 11:53:52 +00:00
|
|
|
con, err := dialer.DialContext(ctx, network, addr)
|
|
|
|
return con, err
|
|
|
|
}
|
|
|
|
|
2019-12-11 09:38:58 +00:00
|
|
|
addrs, e := Context.dnsServer.Resolve(host)
|
|
|
|
log.Debug("dnsServer.Resolve: %s: %v", host, addrs)
|
2019-04-17 11:53:52 +00:00
|
|
|
if e != nil {
|
|
|
|
return nil, e
|
|
|
|
}
|
|
|
|
|
2019-06-18 13:18:13 +00:00
|
|
|
if len(addrs) == 0 {
|
|
|
|
return nil, fmt.Errorf("couldn't lookup host: %s", host)
|
|
|
|
}
|
|
|
|
|
|
|
|
var dialErrs []error
|
2019-04-17 11:53:52 +00:00
|
|
|
for _, a := range addrs {
|
2019-06-05 10:03:07 +00:00
|
|
|
addr = net.JoinHostPort(a.String(), port)
|
2019-04-17 11:53:52 +00:00
|
|
|
con, err := dialer.DialContext(ctx, network, addr)
|
|
|
|
if err != nil {
|
2019-06-18 13:18:13 +00:00
|
|
|
dialErrs = append(dialErrs, err)
|
2019-04-17 11:53:52 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
return con, err
|
|
|
|
}
|
2019-06-18 13:18:13 +00:00
|
|
|
return nil, errorx.DecorateMany(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
|
2019-04-17 11:53:52 +00:00
|
|
|
}
|
|
|
|
|
2020-02-05 14:38:23 +00:00
|
|
|
// ---------------------
|
|
|
|
// general helpers
|
|
|
|
// ---------------------
|
|
|
|
|
|
|
|
// fileExists returns TRUE if file exists
|
|
|
|
func fileExists(fn string) bool {
|
|
|
|
_, err := os.Stat(fn)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// runCommand runs shell command
|
|
|
|
func runCommand(command string, arguments ...string) (int, string, error) {
|
|
|
|
cmd := exec.Command(command, arguments...)
|
|
|
|
out, err := cmd.Output()
|
|
|
|
if err != nil {
|
|
|
|
return 1, "", fmt.Errorf("exec.Command(%s) failed: %s", command, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmd.ProcessState.ExitCode(), string(out), nil
|
|
|
|
}
|
|
|
|
|
2018-09-14 13:50:56 +00:00
|
|
|
// ---------------------
|
|
|
|
// debug logging helpers
|
|
|
|
// ---------------------
|
2018-11-28 10:21:39 +00:00
|
|
|
func _Func() string {
|
|
|
|
pc := make([]uintptr, 10) // at least 1 entry needed
|
|
|
|
runtime.Callers(2, pc)
|
|
|
|
f := runtime.FuncForPC(pc[0])
|
|
|
|
return path.Base(f.Name())
|
|
|
|
}
|
2019-05-14 12:29:52 +00:00
|
|
|
|
2019-10-07 16:13:06 +00:00
|
|
|
// SplitNext - split string by a byte and return the first chunk
|
|
|
|
// Whitespace is trimmed
|
|
|
|
func SplitNext(str *string, splitBy byte) string {
|
|
|
|
i := strings.IndexByte(*str, splitBy)
|
|
|
|
s := ""
|
|
|
|
if i != -1 {
|
|
|
|
s = (*str)[0:i]
|
|
|
|
*str = (*str)[i+1:]
|
|
|
|
} else {
|
|
|
|
s = *str
|
|
|
|
*str = ""
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(s)
|
|
|
|
}
|