mirror of
https://github.com/btcsuite/btcd.git
synced 2025-02-24 06:47:59 +01:00
rpcclient: Add cookie auth
Based on Hugo Landau's cookie auth implementation for Namecoin's ncdns. Fixes https://github.com/btcsuite/btcd/issues/1054
This commit is contained in:
parent
9f0179fd2c
commit
280845a8a4
2 changed files with 103 additions and 2 deletions
64
rpcclient/cookiefile.go
Normal file
64
rpcclient/cookiefile.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readCookieFile(path string) (username, password string, err error) {
|
||||||
|
b, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := strings.TrimSpace(string(b))
|
||||||
|
parts := strings.SplitN(s, ":", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
err = fmt.Errorf("malformed cookie file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, password = parts[0], parts[1]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cookieRetriever(path string) func() (username, password string, err error) {
|
||||||
|
lastCheckTime := time.Time{}
|
||||||
|
lastModTime := time.Time{}
|
||||||
|
|
||||||
|
curUsername, curPassword := "", ""
|
||||||
|
var curError error
|
||||||
|
|
||||||
|
doUpdate := func() {
|
||||||
|
if !lastCheckTime.IsZero() && time.Now().Before(lastCheckTime.Add(30*time.Second)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCheckTime = time.Now()
|
||||||
|
|
||||||
|
st, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
curError = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modTime := st.ModTime()
|
||||||
|
if !modTime.Equal(lastModTime) {
|
||||||
|
lastModTime = modTime
|
||||||
|
curUsername, curPassword, curError = readCookieFile(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() (username, password string, err error) {
|
||||||
|
doUpdate()
|
||||||
|
return curUsername, curPassword, curError
|
||||||
|
}
|
||||||
|
}
|
|
@ -851,7 +851,12 @@ func (c *Client) sendPost(jReq *jsonRequest) {
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
// Configure basic access authorization.
|
// 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)
|
log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id)
|
||||||
c.sendPostRequest(httpReq, jReq)
|
c.sendPostRequest(httpReq, jReq)
|
||||||
|
@ -1096,6 +1101,15 @@ type ConnConfig struct {
|
||||||
// Pass is the passphrase to use to authenticate to the RPC server.
|
// Pass is the passphrase to use to authenticate to the RPC server.
|
||||||
Pass string
|
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
|
||||||
|
|
||||||
|
// retrieveCookie is a function that returns the cookie username and
|
||||||
|
// passphrase.
|
||||||
|
retrieveCookie func() (username, passphrase string, err error)
|
||||||
|
|
||||||
// Params is the string representing the network that the server
|
// Params is the string representing the network that the server
|
||||||
// is running. If there is no parameter set in the config, then
|
// is running. If there is no parameter set in the config, then
|
||||||
// mainnet will be used by default.
|
// mainnet will be used by default.
|
||||||
|
@ -1149,6 +1163,25 @@ type ConnConfig struct {
|
||||||
EnableBCInfoHacks bool
|
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) {
|
||||||
|
// If cookie auth isn't in use, just use the supplied
|
||||||
|
// username/passphrase.
|
||||||
|
if config.CookiePath == "" {
|
||||||
|
return config.User, config.Pass, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the cookie retriever on first run.
|
||||||
|
if config.retrieveCookie == nil {
|
||||||
|
config.retrieveCookie = cookieRetriever(config.CookiePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.retrieveCookie()
|
||||||
|
}
|
||||||
|
|
||||||
// newHTTPClient returns a new http client that is configured according to the
|
// newHTTPClient returns a new http client that is configured according to the
|
||||||
// proxy and TLS settings in the associated connection configuration.
|
// proxy and TLS settings in the associated connection configuration.
|
||||||
func newHTTPClient(config *ConnConfig) (*http.Client, error) {
|
func newHTTPClient(config *ConnConfig) (*http.Client, error) {
|
||||||
|
@ -1218,7 +1251,11 @@ func dial(config *ConnConfig) (*websocket.Conn, error) {
|
||||||
|
|
||||||
// The RPC server requires basic authorization, so create a custom
|
// The RPC server requires basic authorization, so create a custom
|
||||||
// request header with the Authorization header set.
|
// 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))
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
||||||
requestHeader := make(http.Header)
|
requestHeader := make(http.Header)
|
||||||
requestHeader.Add("Authorization", auth)
|
requestHeader.Add("Authorization", auth)
|
||||||
|
|
Loading…
Add table
Reference in a new issue