Add automatic RPC configuration.

This commit is contained in:
Hector Jusforgues 2016-06-04 09:14:15 +07:00 committed by Dave Collins
parent 6229e35835
commit ff4ada0b0e
3 changed files with 180 additions and 0 deletions

View file

@ -6,9 +6,11 @@ package main
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/btcsuite/btcd/btcjson"
@ -215,6 +217,13 @@ func loadConfig() (*config, []string, error) {
os.Exit(0)
}
if _, err := os.Stat(preCfg.ConfigFile); os.IsNotExist(err) {
err := createDefaultConfigFile(preCfg.ConfigFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating a default config file: %v\n", err)
}
}
// Load additional config from file.
parser := flags.NewParser(&cfg, flags.Default)
err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
@ -268,3 +277,56 @@ func loadConfig() (*config, []string, error) {
return &cfg, remainingArgs, nil
}
// createDefaultConfig creates a basic config file at the given destination path.
// For this it tries to read the btcd config file at its default path, and extract
// the RPC user and password from it.
func createDefaultConfigFile(destinationPath string) error {
// Create the destination directory if it does not exists
os.MkdirAll(filepath.Dir(destinationPath), 0700)
// Read btcd.conf from its default path
btcdConfigPath := filepath.Join(btcdHomeDir, "btcd.conf")
btcdConfigFile, err := os.Open(btcdConfigPath)
if err != nil {
return err
}
defer btcdConfigFile.Close()
content, err := ioutil.ReadAll(btcdConfigFile)
if err != nil {
return err
}
// Extract the rpcuser
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser=([^\s]+)`)
if err != nil {
return err
}
userSubmatches := rpcUserRegexp.FindSubmatch(content)
if userSubmatches == nil {
// No user found, nothing to do
return nil
}
// Extract the rpcpass
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpass=([^\s]+)`)
if err != nil {
return err
}
passSubmatches := rpcPassRegexp.FindSubmatch(content)
if passSubmatches == nil {
// No password found, nothing to do
return nil
}
// Create the destination file and write the rpcuser and rpcpass to it
dest, err := os.OpenFile(destinationPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700)
if err != nil {
return err
}
defer dest.Close()
dest.WriteString(fmt.Sprintf("rpcuser=%s\nrpcpass=%s", string(userSubmatches[1]), string(passSubmatches[1])))
return nil
}

View file

@ -5,8 +5,12 @@
package main
import (
"bufio"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"os"
"path/filepath"
@ -395,6 +399,13 @@ func loadConfig() (*config, []string, error) {
if !(preCfg.RegressionTest || preCfg.SimNet) || preCfg.ConfigFile !=
defaultConfigFile {
if _, err := os.Stat(preCfg.ConfigFile); os.IsNotExist(err) {
err := createDefaultConfigFile(preCfg.ConfigFile)
if err != nil {
btcdLog.Warnf("Error creating a default config file: %v", err)
}
}
err := flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
if err != nil {
if _, ok := err.(*os.PathError); !ok {
@ -880,6 +891,66 @@ func loadConfig() (*config, []string, error) {
return &cfg, remainingArgs, nil
}
// createDefaultConfig copies the file sample-btcd.conf to the given destination path,
// and populates it with some randomly generated RPC username and password.
func createDefaultConfigFile(destinationPath string) error {
// Create the destination directory if it does not exists
os.MkdirAll(filepath.Dir(destinationPath), 0700)
// We get the sample config file path, which is in the same directory as this file.
_, path, _, _ := runtime.Caller(0)
sampleConfigPath := filepath.Join(filepath.Dir(path), "sample-btcd.conf")
// We generate a random user and password
randomBytes := make([]byte, 20)
_, err := rand.Read(randomBytes)
if err != nil {
return err
}
generatedRPCUser := base64.StdEncoding.EncodeToString(randomBytes)
_, err = rand.Read(randomBytes)
if err != nil {
return err
}
generatedRPCPass := base64.StdEncoding.EncodeToString(randomBytes)
src, err := os.Open(sampleConfigPath)
if err != nil {
return err
}
defer src.Close()
dest, err := os.OpenFile(destinationPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700)
if err != nil {
return err
}
defer dest.Close()
// We copy every line from the sample config file to the destination,
// only replacing the two lines for rpcuser and rpcpass
reader := bufio.NewReader(src)
for err != io.EOF {
var line string
line, err = reader.ReadString('\n')
if err != nil && err != io.EOF {
return err
}
if strings.Contains(line, "rpcuser=") {
line = "rpcuser=" + string(generatedRPCUser) + "\n"
} else if strings.Contains(line, "rpcpass=") {
line = "rpcpass=" + string(generatedRPCPass) + "\n"
}
if _, err := dest.WriteString(line); err != nil {
return err
}
}
return nil
}
// btcdDial connects to the address on the named network using the appropriate
// dial function depending on the address and configuration options. For
// example, .onion addresses will be dialed using the onion specific proxy if

47
config_test.go Normal file
View file

@ -0,0 +1,47 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"testing"
)
var (
rpcuserRegexp = regexp.MustCompile("(?m)^rpcuser=.+$")
rpcpassRegexp = regexp.MustCompile("(?m)^rpcpass=.+$")
)
func TestCreateDefaultConfigFile(t *testing.T) {
// Setup a temporary directory
tmpDir, err := ioutil.TempDir("", "btcd")
if err != nil {
t.Fatalf("Failed creating a temporary directory: %v", err)
}
testpath := filepath.Join(tmpDir, "test.conf")
// Clean-up
defer func() {
os.Remove(testpath)
os.Remove(tmpDir)
}()
err = createDefaultConfigFile(testpath)
if err != nil {
t.Fatalf("Failed to create a default config file: %v", err)
}
content, err := ioutil.ReadFile(testpath)
if err != nil {
t.Fatalf("Failed to read generated default config file: %v", err)
}
if !rpcuserRegexp.Match(content) {
t.Error("Could not find rpcuser in generated default config file.")
}
if !rpcpassRegexp.Match(content) {
t.Error("Could not find rpcpass in generated default config file.")
}
}