From dab0a9d87a6f49ebbf1f61fee805775acca82b99 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 3 Jul 2020 20:34:08 +0300 Subject: [PATCH] + "--glinet" command-line argument: Run in GL-Inet compatibility mode Close #1853 Squashed commit of the following: commit 3730cafabe8fa1dbf2bf75915079d2effe4ff9a3 Merge: 533ae3c2 6b134469 Author: Simon Zolin Date: Fri Jul 3 19:21:08 2020 +0300 Merge remote-tracking branch 'origin/master' into 1853-glinet commit 533ae3c2678cd6cfd26bf9560bee4eb5a015a615 Merge: 3521992b 21dfb5ff Author: Simon Zolin Date: Fri Jul 3 19:14:45 2020 +0300 Merge remote-tracking branch 'origin/master' into 1853-glinet commit 3521992b4609fa3400942c89f7a7546dade459bc Author: Simon Zolin Date: Fri Jul 3 18:04:53 2020 +0300 logs commit 3e0258782b1a14c08156fe65940ae7b661a42b54 Author: Simon Zolin Date: Fri Jul 3 17:11:47 2020 +0300 fix commit bb814db9df1c770d0ea02eafd12bedd122bef894 Author: Simon Zolin Date: Fri Jul 3 16:58:37 2020 +0300 minor commit b161bbc5749ce76b16600e0153120935ad20077e Author: Simon Zolin Date: Fri Jul 3 16:53:22 2020 +0300 move code commit c506e81265bdee140c0f61255f927c13738efc1a Author: Simon Zolin Date: Thu Jul 2 10:50:56 2020 +0300 test commit c09f201cbd88498a2328be332197e4d96e5fb115 Author: Simon Zolin Date: Thu Jul 2 10:42:23 2020 +0300 + "--glinet" command-line argument: Run in GL-Inet compatibility mode --- home/auth.go | 20 ++++++-- home/auth_glinet.go | 102 +++++++++++++++++++++++++++++++++++++++ home/auth_glinet_test.go | 43 +++++++++++++++++ home/home.go | 4 ++ 4 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 home/auth_glinet.go create mode 100644 home/auth_glinet_test.go diff --git a/home/auth.go b/home/auth.go index 3b524d29..3166052b 100644 --- a/home/auth.go +++ b/home/auth.go @@ -365,6 +365,7 @@ func parseCookie(cookie string) string { return "" } +// nolint(gocyclo) func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { @@ -391,7 +392,11 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re // redirect to login page if not authenticated ok := false cookie, err := r.Cookie(sessionCookieName) - if err == nil { + + if glProcessCookie(r) { + log.Debug("Auth: authentification was handled by GL-Inet submodule") + + } else if err == nil { r := Context.auth.CheckSession(cookie.Value) if r == 0 { ok = true @@ -412,8 +417,13 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re } if !ok { if r.URL.Path == "/" || r.URL.Path == "/index.html" { - w.Header().Set("Location", "/login.html") - w.WriteHeader(http.StatusFound) + if glProcessRedirect(w, r) { + log.Debug("Auth: redirected to login page by GL-Inet submodule") + + } else { + w.Header().Set("Location", "/login.html") + w.WriteHeader(http.StatusFound) + } } else { w.WriteHeader(http.StatusForbidden) _, _ = w.Write([]byte("Forbidden")) @@ -510,6 +520,10 @@ func (a *Auth) GetUsers() []User { // AuthRequired - if authentication is required func (a *Auth) AuthRequired() bool { + if GLMode { + return true + } + a.lock.Lock() r := (len(a.users) != 0) a.lock.Unlock() diff --git a/home/auth_glinet.go b/home/auth_glinet.go new file mode 100644 index 00000000..7dd2790d --- /dev/null +++ b/home/auth_glinet.go @@ -0,0 +1,102 @@ +package home + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "net" + "net/http" + "os" + "time" + "unsafe" + + "github.com/AdguardTeam/golibs/log" +) + +// GLMode - enable GL-Inet compatibility mode +var GLMode bool + +var glFilePrefix = "/tmp/gl_token_" + +const glTokenTimeoutSeconds = 3600 +const glCookieName = "Admin-Token" + +func glProcessRedirect(w http.ResponseWriter, r *http.Request) bool { + if !GLMode { + return false + } + // redirect to gl-inet login + host, _, _ := net.SplitHostPort(r.Host) + url := "http://" + host + log.Debug("Auth: redirecting to %s", url) + http.Redirect(w, r, url, http.StatusFound) + return true +} + +func glProcessCookie(r *http.Request) bool { + if !GLMode { + return false + } + + glCookie, glerr := r.Cookie(glCookieName) + if glerr != nil { + return false + } + + log.Debug("Auth: GL cookie value: %s", glCookie.Value) + if glCheckToken(glCookie.Value) { + return true + } + log.Info("Auth: invalid GL cookie value: %s", glCookie) + return false +} + +func glCheckToken(sess string) bool { + tokenName := glFilePrefix + sess + _, err := os.Stat(tokenName) + if err != nil { + log.Error("os.Stat: %s", err) + return false + } + tokenDate := glGetTokenDate(tokenName) + now := uint32(time.Now().UTC().Unix()) + return now <= (tokenDate + glTokenTimeoutSeconds) +} + +func archIsLittleEndian() bool { + var i int32 = 0x01020304 + u := unsafe.Pointer(&i) + pb := (*byte)(u) + b := *pb + return (b == 0x04) +} + +func glGetTokenDate(file string) uint32 { + f, err := os.Open(file) + if err != nil { + log.Error("os.Open: %s", err) + return 0 + } + var dateToken uint32 + bs, err := ioutil.ReadAll(f) + if err != nil { + log.Error("ioutil.ReadAll: %s", err) + return 0 + } + buf := bytes.NewBuffer(bs) + + if archIsLittleEndian() { + err := binary.Read(buf, binary.LittleEndian, &dateToken) + if err != nil { + log.Error("binary.Read: %s", err) + return 0 + } + } else { + err := binary.Read(buf, binary.BigEndian, &dateToken) + if err != nil { + log.Error("binary.Read: %s", err) + return 0 + } + } + return dateToken +} diff --git a/home/auth_glinet_test.go b/home/auth_glinet_test.go new file mode 100644 index 00000000..171bb84e --- /dev/null +++ b/home/auth_glinet_test.go @@ -0,0 +1,43 @@ +package home + +import ( + "encoding/binary" + "io/ioutil" + "net/http" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestAuthGL(t *testing.T) { + dir := prepareTestDir() + defer func() { _ = os.RemoveAll(dir) }() + + GLMode = true + glFilePrefix = dir + "/gl_token_" + + tval := uint32(1) + data := make([]byte, 4) + if archIsLittleEndian() { + binary.LittleEndian.PutUint32(data, tval) + } else { + binary.BigEndian.PutUint32(data, tval) + } + assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644)) + assert.False(t, glCheckToken("test")) + + tval = uint32(time.Now().UTC().Unix() + 60) + data = make([]byte, 4) + if archIsLittleEndian() { + binary.LittleEndian.PutUint32(data, tval) + } else { + binary.BigEndian.PutUint32(data, tval) + } + assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644)) + r, _ := http.NewRequest("GET", "http://localhost/", nil) + r.AddCookie(&http.Cookie{Name: glCookieName, Value: "test"}) + assert.True(t, glProcessCookie(r)) + GLMode = false +} diff --git a/home/home.go b/home/home.go index e1338038..077455e8 100644 --- a/home/home.go +++ b/home/home.go @@ -263,6 +263,7 @@ func run(args options) { } sessFilename := filepath.Join(Context.getDataDir(), "sessions.db") + GLMode = args.glinetMode Context.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60) if Context.auth == nil { log.Fatalf("Couldn't initialize Auth module") @@ -525,6 +526,8 @@ type options struct { // runningAsService flag is set to true when options are passed from the service runner runningAsService bool + + glinetMode bool // Activate GL-Inet mode } // loadOptions reads command line arguments and initializes configuration @@ -559,6 +562,7 @@ func loadOptions() options { {"check-config", "", "Check configuration and exit", nil, func() { o.checkConfig = true }}, {"no-check-update", "", "Don't check for updates", nil, func() { o.disableUpdate = true }}, {"verbose", "v", "Enable verbose output", nil, func() { o.verbose = true }}, + {"glinet", "", "Run in GL-Inet compatibility mode", nil, func() { o.glinetMode = true }}, {"version", "", "Show the version and exit", nil, func() { fmt.Printf("AdGuardHome %s\n", versionString) os.Exit(0)