github.com/palcoin-project/palcd@v1.0.0/cmd/palcctl/config.go (about) 1 // Copyright (c) 2013-2015 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "fmt" 9 "io/ioutil" 10 "net" 11 "os" 12 "path/filepath" 13 "regexp" 14 "strings" 15 16 flags "github.com/jessevdk/go-flags" 17 "github.com/palcoin-project/palcd/btcjson" 18 "github.com/palcoin-project/palcd/chaincfg" 19 "github.com/palcoin-project/palcutil" 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 = palcutil.AppDataDir("palcd", false) 31 btcctlHomeDir = palcutil.AppDataDir("palcctl", false) 32 btcwalletHomeDir = palcutil.AppDataDir("palcwallet", false) 33 defaultConfigFile = filepath.Join(btcctlHomeDir, "palcctl.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 palcctl. 93 // 94 // See loadConfig for details on the configuration load process. 95 type config struct { 96 ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` 97 ListCommands bool `short:"l" long:"listcommands" description:"List all of the supported commands and exit"` 98 NoTLS bool `long:"notls" description:"Disable TLS"` 99 Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"` 100 ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"` 101 ProxyUser string `long:"proxyuser" description:"Username for proxy server"` 102 RegressionTest bool `long:"regtest" description:"Connect to the regression test network"` 103 RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"` 104 RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"` 105 RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"` 106 RPCUser string `short:"u" long:"rpcuser" description:"RPC username"` 107 SimNet bool `long:"simnet" description:"Connect to the simulation test network"` 108 TLSSkipVerify bool `long:"skipverify" description:"Do not verify tls certificates (not recommended!)"` 109 TestNet3 bool `long:"testnet" description:"Connect to testnet"` 110 SigNet bool `long:"signet" description:"Connect to signet"` 111 ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` 112 Wallet bool `long:"wallet" description:"Connect to wallet"` 113 } 114 115 // normalizeAddress returns addr with the passed default port appended if 116 // there is not already a port specified. 117 func normalizeAddress(addr string, chain *chaincfg.Params, useWallet bool) (string, error) { 118 _, _, err := net.SplitHostPort(addr) 119 if err != nil { 120 var defaultPort string 121 switch chain { 122 case &chaincfg.TestNet3Params: 123 if useWallet { 124 defaultPort = "11987" //18332" 125 } else { 126 defaultPort = "11967" //18334 127 } 128 case &chaincfg.SimNetParams: 129 if useWallet { 130 defaultPort = "11916" //18554" 131 } else { 132 defaultPort = "11918" //18556" 133 } 134 case &chaincfg.RegressionNetParams: 135 if useWallet { 136 // TODO: add port once regtest is supported in palcwallet 137 paramErr := fmt.Errorf("cannot use -wallet with -regtest, palcwallet not yet compatible with regtest") 138 return "", paramErr 139 } else { 140 defaultPort = "11967" //18334" 141 } 142 case &chaincfg.SigNetParams: 143 if useWallet { 144 defaultPort = "31987" //38332" 145 } else { 146 defaultPort = "31987" //"38332" 147 } 148 default: 149 if useWallet { 150 defaultPort = "1987" //8332" 151 } else { 152 defaultPort = "1967" //8334" 153 } 154 } 155 156 return net.JoinHostPort(addr, defaultPort), nil 157 } 158 return addr, nil 159 } 160 161 // cleanAndExpandPath expands environement variables and leading ~ in the 162 // passed path, cleans the result, and returns it. 163 func cleanAndExpandPath(path string) string { 164 // Expand initial ~ to OS specific home directory. 165 if strings.HasPrefix(path, "~") { 166 homeDir := filepath.Dir(btcctlHomeDir) 167 path = strings.Replace(path, "~", homeDir, 1) 168 } 169 170 // NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%, 171 // but they variables can still be expanded via POSIX-style $VARIABLE. 172 return filepath.Clean(os.ExpandEnv(path)) 173 } 174 175 // loadConfig initializes and parses the config using a config file and command 176 // line options. 177 // 178 // The configuration proceeds as follows: 179 // 1) Start with a default config with sane settings 180 // 2) Pre-parse the command line to check for an alternative config file 181 // 3) Load configuration file overwriting defaults with any specified options 182 // 4) Parse CLI options and overwrite/add any specified options 183 // 184 // The above results in functioning properly without any config settings 185 // while still allowing the user to override settings with config files and 186 // command line options. Command line options always take precedence. 187 func loadConfig() (*config, []string, error) { 188 // Default config. 189 cfg := config{ 190 ConfigFile: defaultConfigFile, 191 RPCServer: defaultRPCServer, 192 RPCCert: defaultRPCCertFile, 193 } 194 195 // Pre-parse the command line options to see if an alternative config 196 // file, the version flag, or the list commands flag was specified. Any 197 // errors aside from the help message error can be ignored here since 198 // they will be caught by the final parse below. 199 preCfg := cfg 200 preParser := flags.NewParser(&preCfg, flags.HelpFlag) 201 _, err := preParser.Parse() 202 if err != nil { 203 if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { 204 fmt.Fprintln(os.Stderr, err) 205 fmt.Fprintln(os.Stderr, "") 206 fmt.Fprintln(os.Stderr, "The special parameter `-` "+ 207 "indicates that a parameter should be read "+ 208 "from the\nnext unread line from standard "+ 209 "input.") 210 return nil, nil, err 211 } 212 } 213 214 // Show the version and exit if the version flag was specified. 215 appName := filepath.Base(os.Args[0]) 216 appName = strings.TrimSuffix(appName, filepath.Ext(appName)) 217 usageMessage := fmt.Sprintf("Use %s -h to show options", appName) 218 if preCfg.ShowVersion { 219 fmt.Println(appName, "version", version()) 220 os.Exit(0) 221 } 222 223 // Show the available commands and exit if the associated flag was 224 // specified. 225 if preCfg.ListCommands { 226 listCommands() 227 os.Exit(0) 228 } 229 230 if _, err := os.Stat(preCfg.ConfigFile); os.IsNotExist(err) { 231 // Use config file for RPC server to create default palcctl config 232 var serverConfigPath string 233 if preCfg.Wallet { 234 serverConfigPath = filepath.Join(btcwalletHomeDir, "palcwallet.conf") 235 } else { 236 serverConfigPath = filepath.Join(btcdHomeDir, "palcd.conf") 237 } 238 239 err := createDefaultConfigFile(preCfg.ConfigFile, serverConfigPath) 240 if err != nil { 241 fmt.Fprintf(os.Stderr, "Error creating a default config file: %v\n", err) 242 } 243 } 244 245 // Load additional config from file. 246 parser := flags.NewParser(&cfg, flags.Default) 247 err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile) 248 if err != nil { 249 if _, ok := err.(*os.PathError); !ok { 250 fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n", 251 err) 252 fmt.Fprintln(os.Stderr, usageMessage) 253 return nil, nil, err 254 } 255 } 256 257 // Parse command line options again to ensure they take precedence. 258 remainingArgs, err := parser.Parse() 259 if err != nil { 260 if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { 261 fmt.Fprintln(os.Stderr, usageMessage) 262 } 263 return nil, nil, err 264 } 265 266 // default network is mainnet 267 network := &chaincfg.MainNetParams 268 269 // Multiple networks can't be selected simultaneously. 270 numNets := 0 271 if cfg.TestNet3 { 272 numNets++ 273 network = &chaincfg.TestNet3Params 274 } 275 if cfg.SimNet { 276 numNets++ 277 network = &chaincfg.SimNetParams 278 } 279 if cfg.RegressionTest { 280 numNets++ 281 network = &chaincfg.RegressionNetParams 282 } 283 if cfg.SigNet { 284 numNets++ 285 network = &chaincfg.SigNetParams 286 } 287 288 if numNets > 1 { 289 str := "%s: Multiple network params can't be used " + 290 "together -- choose one" 291 err := fmt.Errorf(str, "loadConfig") 292 fmt.Fprintln(os.Stderr, err) 293 return nil, nil, err 294 } 295 296 // Override the RPC certificate if the --wallet flag was specified and 297 // the user did not specify one. 298 if cfg.Wallet && cfg.RPCCert == defaultRPCCertFile { 299 cfg.RPCCert = defaultWalletCertFile 300 } 301 302 // Handle environment variable expansion in the RPC certificate path. 303 cfg.RPCCert = cleanAndExpandPath(cfg.RPCCert) 304 305 // Add default port to RPC server based on --testnet and --wallet flags 306 // if needed. 307 cfg.RPCServer, err = normalizeAddress(cfg.RPCServer, network, cfg.Wallet) 308 if err != nil { 309 return nil, nil, err 310 } 311 312 return &cfg, remainingArgs, nil 313 } 314 315 // createDefaultConfig creates a basic config file at the given destination path. 316 // For this it tries to read the config file for the RPC server (either btcd or 317 // palcwallet), and extract the RPC user and password from it. 318 func createDefaultConfigFile(destinationPath, serverConfigPath string) error { 319 // Read the RPC server config 320 serverConfigFile, err := os.Open(serverConfigPath) 321 if err != nil { 322 return err 323 } 324 defer serverConfigFile.Close() 325 content, err := ioutil.ReadAll(serverConfigFile) 326 if err != nil { 327 return err 328 } 329 330 // Extract the rpcuser 331 rpcUserRegexp := regexp.MustCompile(`(?m)^\s*rpcuser=([^\s]+)`) 332 userSubmatches := rpcUserRegexp.FindSubmatch(content) 333 if userSubmatches == nil { 334 // No user found, nothing to do 335 return nil 336 } 337 338 // Extract the rpcpass 339 rpcPassRegexp := regexp.MustCompile(`(?m)^\s*rpcpass=([^\s]+)`) 340 passSubmatches := rpcPassRegexp.FindSubmatch(content) 341 if passSubmatches == nil { 342 // No password found, nothing to do 343 return nil 344 } 345 346 // Extract the notls 347 noTLSRegexp := regexp.MustCompile(`(?m)^\s*notls=(0|1)(?:\s|$)`) 348 noTLSSubmatches := noTLSRegexp.FindSubmatch(content) 349 350 // Create the destination directory if it does not exists 351 err = os.MkdirAll(filepath.Dir(destinationPath), 0700) 352 if err != nil { 353 return err 354 } 355 356 // Create the destination file and write the rpcuser and rpcpass to it 357 dest, err := os.OpenFile(destinationPath, 358 os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 359 if err != nil { 360 return err 361 } 362 defer dest.Close() 363 364 destString := fmt.Sprintf("rpcuser=%s\nrpcpass=%s\n", 365 string(userSubmatches[1]), string(passSubmatches[1])) 366 if noTLSSubmatches != nil { 367 destString += fmt.Sprintf("notls=%s\n", noTLSSubmatches[1]) 368 } 369 370 dest.WriteString(destString) 371 372 return nil 373 }