github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/cmd/btcctl/config.go (about)

     1  // Copyright (c) 2013-2015 The btcsuite developers
     2  // Copyright (c) 2016 The Dash developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package main
     7  
     8  import (
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net"
    12  	"os"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strings"
    16  
    17  	flags "github.com/btcsuite/go-flags"
    18  	"github.com/dashpay/godash/btcjson"
    19  	"github.com/dashpay/godashutil"
    20  )
    21  
    22  const (
    23  	// unusableFlags are the command usage flags which this utility are not
    24  	// able to use.  In particular it doesn't support websockets and
    25  	// consequently notifications.
    26  	unusableFlags = btcjson.UFWebsocketOnly | btcjson.UFNotification
    27  )
    28  
    29  var (
    30  	btcdHomeDir           = godashutil.AppDataDir("btcd", false)
    31  	btcctlHomeDir         = godashutil.AppDataDir("btcctl", false)
    32  	btcwalletHomeDir      = godashutil.AppDataDir("btcwallet", false)
    33  	defaultConfigFile     = filepath.Join(btcctlHomeDir, "btcctl.conf")
    34  	defaultRPCServer      = "localhost"
    35  	defaultRPCCertFile    = filepath.Join(btcdHomeDir, "rpc.cert")
    36  	defaultWalletCertFile = filepath.Join(btcwalletHomeDir, "rpc.cert")
    37  )
    38  
    39  // listCommands categorizes and lists all of the usable commands along with
    40  // their one-line usage.
    41  func listCommands() {
    42  	const (
    43  		categoryChain uint8 = iota
    44  		categoryWallet
    45  		numCategories
    46  	)
    47  
    48  	// Get a list of registered commands and categorize and filter them.
    49  	cmdMethods := btcjson.RegisteredCmdMethods()
    50  	categorized := make([][]string, numCategories)
    51  	for _, method := range cmdMethods {
    52  		flags, err := btcjson.MethodUsageFlags(method)
    53  		if err != nil {
    54  			// This should never happen since the method was just
    55  			// returned from the package, but be safe.
    56  			continue
    57  		}
    58  
    59  		// Skip the commands that aren't usable from this utility.
    60  		if flags&unusableFlags != 0 {
    61  			continue
    62  		}
    63  
    64  		usage, err := btcjson.MethodUsageText(method)
    65  		if err != nil {
    66  			// This should never happen since the method was just
    67  			// returned from the package, but be safe.
    68  			continue
    69  		}
    70  
    71  		// Categorize the command based on the usage flags.
    72  		category := categoryChain
    73  		if flags&btcjson.UFWalletOnly != 0 {
    74  			category = categoryWallet
    75  		}
    76  		categorized[category] = append(categorized[category], usage)
    77  	}
    78  
    79  	// Display the command according to their categories.
    80  	categoryTitles := make([]string, numCategories)
    81  	categoryTitles[categoryChain] = "Chain Server Commands:"
    82  	categoryTitles[categoryWallet] = "Wallet Server Commands (--wallet):"
    83  	for category := uint8(0); category < numCategories; category++ {
    84  		fmt.Println(categoryTitles[category])
    85  		for _, usage := range categorized[category] {
    86  			fmt.Println(usage)
    87  		}
    88  		fmt.Println()
    89  	}
    90  }
    91  
    92  // config defines the configuration options for btcctl.
    93  //
    94  // See loadConfig for details on the configuration load process.
    95  type config struct {
    96  	ShowVersion   bool   `short:"V" long:"version" description:"Display version information and exit"`
    97  	ListCommands  bool   `short:"l" long:"listcommands" description:"List all of the supported commands and exit"`
    98  	ConfigFile    string `short:"C" long:"configfile" description:"Path to configuration file"`
    99  	RPCUser       string `short:"u" long:"rpcuser" description:"RPC username"`
   100  	RPCPassword   string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"`
   101  	RPCServer     string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
   102  	RPCCert       string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
   103  	NoTLS         bool   `long:"notls" description:"Disable TLS"`
   104  	Proxy         string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
   105  	ProxyUser     string `long:"proxyuser" description:"Username for proxy server"`
   106  	ProxyPass     string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
   107  	TestNet3      bool   `long:"testnet" description:"Connect to testnet"`
   108  	SimNet        bool   `long:"simnet" description:"Connect to the simulation test network"`
   109  	TLSSkipVerify bool   `long:"skipverify" description:"Do not verify tls certificates (not recommended!)"`
   110  	Wallet        bool   `long:"wallet" description:"Connect to wallet"`
   111  }
   112  
   113  // normalizeAddress returns addr with the passed default port appended if
   114  // there is not already a port specified.
   115  func normalizeAddress(addr string, useTestNet3, useSimNet, useWallet bool) string {
   116  	_, _, err := net.SplitHostPort(addr)
   117  	if err != nil {
   118  		var defaultPort string
   119  		switch {
   120  		case useTestNet3:
   121  			if useWallet {
   122  				defaultPort = "18332"
   123  			} else {
   124  				defaultPort = "18334"
   125  			}
   126  		case useSimNet:
   127  			if useWallet {
   128  				defaultPort = "18554"
   129  			} else {
   130  				defaultPort = "18556"
   131  			}
   132  		default:
   133  			if useWallet {
   134  				defaultPort = "8332"
   135  			} else {
   136  				defaultPort = "8334"
   137  			}
   138  		}
   139  
   140  		return net.JoinHostPort(addr, defaultPort)
   141  	}
   142  	return addr
   143  }
   144  
   145  // cleanAndExpandPath expands environement variables and leading ~ in the
   146  // passed path, cleans the result, and returns it.
   147  func cleanAndExpandPath(path string) string {
   148  	// Expand initial ~ to OS specific home directory.
   149  	if strings.HasPrefix(path, "~") {
   150  		homeDir := filepath.Dir(btcctlHomeDir)
   151  		path = strings.Replace(path, "~", homeDir, 1)
   152  	}
   153  
   154  	// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
   155  	// but they variables can still be expanded via POSIX-style $VARIABLE.
   156  	return filepath.Clean(os.ExpandEnv(path))
   157  }
   158  
   159  // loadConfig initializes and parses the config using a config file and command
   160  // line options.
   161  //
   162  // The configuration proceeds as follows:
   163  // 	1) Start with a default config with sane settings
   164  // 	2) Pre-parse the command line to check for an alternative config file
   165  // 	3) Load configuration file overwriting defaults with any specified options
   166  // 	4) Parse CLI options and overwrite/add any specified options
   167  //
   168  // The above results in functioning properly without any config settings
   169  // while still allowing the user to override settings with config files and
   170  // command line options.  Command line options always take precedence.
   171  func loadConfig() (*config, []string, error) {
   172  	// Default config.
   173  	cfg := config{
   174  		ConfigFile: defaultConfigFile,
   175  		RPCServer:  defaultRPCServer,
   176  		RPCCert:    defaultRPCCertFile,
   177  	}
   178  
   179  	// Create the home directory if it doesn't already exist.
   180  	err := os.MkdirAll(btcdHomeDir, 0700)
   181  	if err != nil {
   182  		fmt.Fprintf(os.Stderr, "%v\n", err)
   183  		os.Exit(-1)
   184  	}
   185  
   186  	// Pre-parse the command line options to see if an alternative config
   187  	// file, the version flag, or the list commands flag was specified.  Any
   188  	// errors aside from the help message error can be ignored here since
   189  	// they will be caught by the final parse below.
   190  	preCfg := cfg
   191  	preParser := flags.NewParser(&preCfg, flags.HelpFlag)
   192  	_, err = preParser.Parse()
   193  	if err != nil {
   194  		if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
   195  			fmt.Fprintln(os.Stderr, err)
   196  			fmt.Fprintln(os.Stderr, "")
   197  			fmt.Fprintln(os.Stderr, "The special parameter `-` "+
   198  				"indicates that a parameter should be read "+
   199  				"from the\nnext unread line from standard "+
   200  				"input.")
   201  			return nil, nil, err
   202  		}
   203  	}
   204  
   205  	// Show the version and exit if the version flag was specified.
   206  	appName := filepath.Base(os.Args[0])
   207  	appName = strings.TrimSuffix(appName, filepath.Ext(appName))
   208  	usageMessage := fmt.Sprintf("Use %s -h to show options", appName)
   209  	if preCfg.ShowVersion {
   210  		fmt.Println(appName, "version", version())
   211  		os.Exit(0)
   212  	}
   213  
   214  	// Show the available commands and exit if the associated flag was
   215  	// specified.
   216  	if preCfg.ListCommands {
   217  		listCommands()
   218  		os.Exit(0)
   219  	}
   220  
   221  	if _, err := os.Stat(preCfg.ConfigFile); os.IsNotExist(err) {
   222  		err := createDefaultConfigFile(preCfg.ConfigFile)
   223  		if err != nil {
   224  			fmt.Fprintf(os.Stderr, "Error creating a default config file: %v\n", err)
   225  		}
   226  	}
   227  
   228  	// Load additional config from file.
   229  	parser := flags.NewParser(&cfg, flags.Default)
   230  	err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
   231  	if err != nil {
   232  		if _, ok := err.(*os.PathError); !ok {
   233  			fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n",
   234  				err)
   235  			fmt.Fprintln(os.Stderr, usageMessage)
   236  			return nil, nil, err
   237  		}
   238  	}
   239  
   240  	// Parse command line options again to ensure they take precedence.
   241  	remainingArgs, err := parser.Parse()
   242  	if err != nil {
   243  		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
   244  			fmt.Fprintln(os.Stderr, usageMessage)
   245  		}
   246  		return nil, nil, err
   247  	}
   248  
   249  	// Multiple networks can't be selected simultaneously.
   250  	numNets := 0
   251  	if cfg.TestNet3 {
   252  		numNets++
   253  	}
   254  	if cfg.SimNet {
   255  		numNets++
   256  	}
   257  	if numNets > 1 {
   258  		str := "%s: The testnet and simnet params can't be used " +
   259  			"together -- choose one of the two"
   260  		err := fmt.Errorf(str, "loadConfig")
   261  		fmt.Fprintln(os.Stderr, err)
   262  		return nil, nil, err
   263  	}
   264  
   265  	// Override the RPC certificate if the --wallet flag was specified and
   266  	// the user did not specify one.
   267  	if cfg.Wallet && cfg.RPCCert == defaultRPCCertFile {
   268  		cfg.RPCCert = defaultWalletCertFile
   269  	}
   270  
   271  	// Handle environment variable expansion in the RPC certificate path.
   272  	cfg.RPCCert = cleanAndExpandPath(cfg.RPCCert)
   273  
   274  	// Add default port to RPC server based on --testnet and --wallet flags
   275  	// if needed.
   276  	cfg.RPCServer = normalizeAddress(cfg.RPCServer, cfg.TestNet3,
   277  		cfg.SimNet, cfg.Wallet)
   278  
   279  	return &cfg, remainingArgs, nil
   280  }
   281  
   282  // createDefaultConfig creates a basic config file at the given destination path.
   283  // For this it tries to read the btcd config file at its default path, and extract
   284  // the RPC user and password from it.
   285  func createDefaultConfigFile(destinationPath string) error {
   286  	// Create the destination directory if it does not exists
   287  	os.MkdirAll(filepath.Dir(destinationPath), 0700)
   288  
   289  	// Read btcd.conf from its default path
   290  	btcdConfigPath := filepath.Join(btcdHomeDir, "btcd.conf")
   291  	btcdConfigFile, err := os.Open(btcdConfigPath)
   292  	if err != nil {
   293  		return err
   294  	}
   295  	defer btcdConfigFile.Close()
   296  	content, err := ioutil.ReadAll(btcdConfigFile)
   297  	if err != nil {
   298  		return err
   299  	}
   300  
   301  	// Extract the rpcuser
   302  	rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser=([^\s]+)`)
   303  	if err != nil {
   304  		return err
   305  	}
   306  	userSubmatches := rpcUserRegexp.FindSubmatch(content)
   307  	if userSubmatches == nil {
   308  		// No user found, nothing to do
   309  		return nil
   310  	}
   311  
   312  	// Extract the rpcpass
   313  	rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpass=([^\s]+)`)
   314  	if err != nil {
   315  		return err
   316  	}
   317  	passSubmatches := rpcPassRegexp.FindSubmatch(content)
   318  	if passSubmatches == nil {
   319  		// No password found, nothing to do
   320  		return nil
   321  	}
   322  
   323  	// Create the destination file and write the rpcuser and rpcpass to it
   324  	dest, err := os.OpenFile(destinationPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700)
   325  	if err != nil {
   326  		return err
   327  	}
   328  	defer dest.Close()
   329  
   330  	dest.WriteString(fmt.Sprintf("rpcuser=%s\nrpcpass=%s", string(userSubmatches[1]), string(passSubmatches[1])))
   331  
   332  	return nil
   333  }