From f988150eb414d0692f7cb673d89b8eab4a1bf8de Mon Sep 17 00:00:00 2001 From: JettScythe Date: Wed, 22 Jul 2020 17:14:11 -0300 Subject: [PATCH] Support Cookie Authorization --- rpcclient/cookiefile.go | 38 +++++++++++++++++++++++ rpcclient/infrastructure.go | 62 +++++++++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 rpcclient/cookiefile.go diff --git a/rpcclient/cookiefile.go b/rpcclient/cookiefile.go new file mode 100644 index 000000000..c3f7068b3 --- /dev/null +++ b/rpcclient/cookiefile.go @@ -0,0 +1,38 @@ +// Copyright (c) 2017 The Namecoin developers +// Copyright (c) 2019 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package rpcclient + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +func readCookieFile(path string) (username, password string, err error) { + f, err := os.Open(path) + if err != nil { + return + } + defer f.Close() + + scanner := bufio.NewScanner(f) + scanner.Scan() + err = scanner.Err() + if err != nil { + return + } + s := scanner.Text() + + parts := strings.SplitN(s, ":", 2) + if len(parts) != 2 { + err = fmt.Errorf("malformed cookie file") + return + } + + username, password = parts[0], parts[1] + return +} diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go index fbc8b9950..959ac50f9 100644 --- a/rpcclient/infrastructure.go +++ b/rpcclient/infrastructure.go @@ -19,6 +19,7 @@ import ( "net" "net/http" "net/url" + "os" "sync" "sync/atomic" "time" @@ -825,7 +826,12 @@ func (c *Client) sendPost(jReq *jsonRequest) { httpReq.Header.Set("Content-Type", "application/json") // Configure basic access authorization. - httpReq.SetBasicAuth(c.config.User, c.config.Pass) + user, pass, err := c.config.getAuth() + if err != nil { + jReq.responseChan <- &response{result: nil, err: err} + return + } + httpReq.SetBasicAuth(user, pass) log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id) c.sendPostRequest(httpReq, jReq) @@ -1070,6 +1076,17 @@ type ConnConfig struct { // Pass is the passphrase to use to authenticate to the RPC server. Pass string + // CookiePath is the path to a cookie file containing the username and + // passphrase to use to authenticate to the RPC server. It is used + // instead of User and Pass if non-empty. + CookiePath string + + cookieLastCheckTime time.Time + cookieLastModTime time.Time + cookieLastUser string + cookieLastPass string + cookieLastErr error + // Params is the string representing the network that the server is running. Params string @@ -1121,6 +1138,43 @@ type ConnConfig struct { EnableBCInfoHacks bool } +// getAuth returns the username and passphrase that will actually be used for +// this connection. This will be the result of checking the cookie if a cookie +// path is configured; if not, it will be the user-configured username and +// passphrase. +func (config *ConnConfig) getAuth() (username, passphrase string, err error) { + // Try username+passphrase auth first. + if config.Pass != "" { + return config.User, config.Pass, nil + } + + // If no username or passphrase is set, try cookie auth. + return config.retrieveCookie() +} + +// retrieveCookie returns the cookie username and passphrase. +func (config *ConnConfig) retrieveCookie() (username, passphrase string, err error) { + if !config.cookieLastCheckTime.IsZero() && time.Now().Before(config.cookieLastCheckTime.Add(30*time.Second)) { + return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr + } + + config.cookieLastCheckTime = time.Now() + + st, err := os.Stat(config.CookiePath) + if err != nil { + config.cookieLastErr = err + return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr + } + + modTime := st.ModTime() + if !modTime.Equal(config.cookieLastModTime) { + config.cookieLastModTime = modTime + config.cookieLastUser, config.cookieLastPass, config.cookieLastErr = readCookieFile(config.CookiePath) + } + + return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr +} + // newHTTPClient returns a new http client that is configured according to the // proxy and TLS settings in the associated connection configuration. func newHTTPClient(config *ConnConfig) (*http.Client, error) { @@ -1190,7 +1244,11 @@ func dial(config *ConnConfig) (*websocket.Conn, error) { // The RPC server requires basic authorization, so create a custom // request header with the Authorization header set. - login := config.User + ":" + config.Pass + user, pass, err := config.getAuth() + if err != nil { + return nil, err + } + login := user + ":" + pass auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login)) requestHeader := make(http.Header) requestHeader.Add("Authorization", auth)