Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix cookie authentication; always show version information #175

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (
_ "github.com/pkt-cash/pktd/database/ffldb"
"github.com/pkt-cash/pktd/mempool"
"github.com/pkt-cash/pktd/peer"
"github.com/pkt-cash/pktd/pktconfig/version"
)

const (
Expand Down Expand Up @@ -448,12 +447,11 @@ func loadConfig() (*config, []string, er.R) {
}
}

// Show the version and exit if the version flag was specified.
// Just need to exit if the version flag was specified
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
if preCfg.ShowVersion {
fmt.Println(appName, "version", version.Version())
os.Exit(0)
}

Expand Down Expand Up @@ -740,8 +738,8 @@ func loadConfig() (*config, []string, er.R) {
}

if cfg.RPCUser == "" || cfg.RPCPass == "" {
pktdLog.Infof("Creating a .cookie file")
cookiePath := filepath.Join(defaultHomeDir, ".cookie")
pktdLog.Infof("Creating a new .pktcookie authorization file")
cookiePath := filepath.Join(defaultHomeDir, ".pktcookie")
var buf [32]byte
if _, errr := rand.Read(buf[:]); errr != nil {
err := er.E(errr)
Expand All @@ -753,7 +751,7 @@ func loadConfig() (*config, []string, er.R) {
cookie := cfg.RPCUser + ":" + cfg.RPCPass
if errr := ioutil.WriteFile(cookiePath, []byte(cookie), 0600); errr != nil {
err := er.E(errr)
err.AddMessage("Could not write cookie")
err.AddMessage("Could not write .pktcookie file")
return nil, nil, err
}
}
Expand Down
4 changes: 2 additions & 2 deletions pktconfig/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ func ReadUserPass(filePath string) ([]string, er.R) {
return nil, er.E(errr)
}
// Couldn't find the file or didn't have user/pass
// Lets see if there's a .cookie file
cookiePath := strings.ReplaceAll(filePath, "pktd.conf", ".cookie")
// Lets see if there's a cookie file
cookiePath := strings.ReplaceAll(filePath, "pktd.conf", ".pktcookie")
if cookiePath == filePath {
return nil, nil
} else if cookie, errr := ioutil.ReadFile(cookiePath); errr != nil {
Expand Down
14 changes: 10 additions & 4 deletions pktd.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ var winServiceMain func() (bool, er.R)
// notified with the server once it is setup so it can gracefully stop it when
// requested from the service control manager.
func pktdMain(serverChan chan<- *server) er.R {

// Unconditionally show the version informatin at startup.
pktdLog.Infof("Version %s", version.Version())

// Load configuration and parse command line. This function also
// initializes logging and configures it accordingly.
tcfg, _, err := loadConfig()
Expand All @@ -53,16 +57,18 @@ func pktdMain(serverChan chan<- *server) er.R {
}
cfg = tcfg

// Warn if running a pre-released pktd
version.WarnIfPrerelease(pktdLog)

// Get a channel that will be closed when a shutdown signal has been
// triggered either from an OS signal such as SIGINT (Ctrl+C) or from
// another subsystem such as the RPC server.
interrupt := interruptListener()
defer pktdLog.Info("Shutdown complete")

// Show version at startup.
pktdLog.Infof("Version %s", version.Version())

version.WarnIfPrerelease(pktdLog)




// Enable http profiling server if requested.
if cfg.Profile != "" {
Expand Down
41 changes: 41 additions & 0 deletions rpcclient/cookiefile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2017 The Namecoin developers
// Copyright (c) 2019 The btcsuite developers
// Copyright (c) 2020 Jeffrey H. Johnson
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package rpcclient

import (
"bufio"
"os"
"strings"

"github.com/pkt-cash/pktd/btcutil/er"
)

func readCookieFile(path string) (username, password string, err er.R) {
f, errr := os.Open(path)
if errr != nil {
return
}
defer f.Close()

scanner := bufio.NewScanner(f)
scanner.Scan()
errr = scanner.Err()
if errr != nil {
return
}
s := scanner.Text()

parts := strings.SplitN(s, ":", 2)
if len(parts) != 2 {
err := er.E(errr)
err.AddMessage("Corrupt or malformed pktcookie file")
return "", "", err
}

username, password = parts[0], parts[1]
return
}
61 changes: 59 additions & 2 deletions rpcclient/infrastructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package rpcclient

import (
"os"
"bytes"
"container/list"
"crypto/tls"
Expand Down Expand Up @@ -838,7 +839,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, errr := c.config.getAuth()
if errr != nil {
jReq.responseChan <- &response{result: nil, err: errr}
return
}
httpReq.SetBasicAuth(user, pass)

log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id)
c.sendPostRequest(httpReq, jReq)
Expand Down Expand Up @@ -1074,6 +1080,16 @@ 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 the User and Pass, if non-empty. cookieLast* is used for caching.
CookiePath string
cookieLastCheckTime time.Time
cookieLastModTime time.Time
cookieLastUser string
cookieLastPass string
cookieLastErr er.R

// DisableTLS specifies whether transport layer security should be
// disabled. It is recommended to always use TLS if the RPC server
// supports it as otherwise your username and password is sent across
Expand Down Expand Up @@ -1151,7 +1167,11 @@ func dial(config *ConnConfig) (*websocket.Conn, er.R) {

// 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)
Expand Down Expand Up @@ -1184,6 +1204,43 @@ func dial(config *ConnConfig) (*websocket.Conn, er.R) {
return wsConn, nil
}

// getAuth returns the username and passphrase that will actually be used for
// this connection. This will be the result of checking for a pktcookie file
// if the cookie path is configured; if not, it will be the user-configured
// username and passphrase.
func (config *ConnConfig) getAuth() (username, passphrase string, error er.R) {
// Try standard authorization first.
if config.Pass != "" {
return config.User, config.Pass, nil
}

// Now we try cookie auth
return config.retrieveCookie()
}

// retrieveCookie returns the username and passphrase from the cookie
func (config *ConnConfig) retrieveCookie() (username, passphrase string, err er.R) {
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, errr := os.Stat(config.CookiePath)
if errr != nil {
err.AddMessage("Error reading pktcookie file")
config.cookieLastErr = er.E(errr)
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
}

// New creates a new RPC client based on the provided connection configuration
// details. The notification handlers parameter may be nil if you are not
// interested in receiving notifications and will be ignored if the
Expand Down