func main() { var opts Options // create parsers parser := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash) iniParser := flags.NewIniParser(parser) // parse command-line arguments _, err := parser.ParseArgs(os.Args) checkError("parsing arguments", err) if _, err := os.Stat(opts.IniFile); err == nil { err = iniParser.ParseFile(opts.IniFile) checkError("parsing ini file", err) } if opts.RiemannHost == "" { parser.WriteHelp(os.Stdout) log.Fatal("riemann host not provided") } if opts.Event.Service == "" { parser.WriteHelp(os.Stdout) log.Fatal("event service name not provided") } if opts.Debug { // Only log the warning severity or above. log.SetLevel(log.DebugLevel) } if opts.Event.Description == "" { bytes, err := ioutil.ReadAll(os.Stdin) checkError("error reading from stdin", err) opts.Event.Description = string(bytes) } addr := fmt.Sprintf("%s:%d", opts.RiemannHost, opts.RiemannPort) log.Debugf("connecting to %s with %s", addr, opts.Proto) riemann, err := raidman.Dial(opts.Proto, addr) checkError("connecting to Riemann", err) defer riemann.Close() log.Debug("creating sender") sender := riemannSender.NewSender(riemann) err = sender.Send(&raidman.Event{ Ttl: opts.Event.Ttl, Time: opts.Event.Time, Tags: opts.Event.Tags, Host: opts.Event.Host, State: opts.Event.State, Service: opts.Event.Service, Metric: opts.Event.Metric, Description: opts.Event.Description, Attributes: opts.Event.Attributes, }) checkError("sending event", err) log.Debug("done") }
// LoadConfig reads a Config type from the command-line options and config file. func LoadConfig(appName string, args []string) (*Config, []string, error) { // Default config. cfg := Config{ DebugLevel: defaultLogLevel, ConfigFile: defaultConfigFile, DataDir: defaultDataDir, LogDir: defaultLogDir, TLSKey: defaultTLSKeyFile, TLSCert: defaultTLSCertFile, PowThreads: runtime.NumCPU(), ProofOfWork: defaultPowHandler, MsgExpiry: defaultMsgExpiry, BroadcastExpiry: defaultBroadcastExpiry, LogConsole: defaultLogConsole, GenKeys: defaultGenKeys, } // Pre-parse the command line options to see if an alternative config // file or the version flag was specified. preCfg := cfg preParser := newConfigParser(&preCfg, appName, flags.HelpFlag) _, err := preParser.ParseArgs(args) if err != nil { if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { fmt.Fprintln(os.Stderr, err) return nil, nil, err } return nil, nil, err } // Show the version and exit if the version flag was specified. funcName := "loadConfig" usageMessage := fmt.Sprintf("Use %s -h to show usage", appName) if preCfg.ShowVersion { fmt.Println(appName, "version", version()) os.Exit(0) } // Load additional config from file. var configFileError error parser := newConfigParser(&cfg, appName, flags.Default) if preCfg.ConfigFile != defaultConfigFile || fileExists(preCfg.ConfigFile) { err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile) if err != nil { if _, ok := err.(*os.PathError); !ok { fmt.Fprintln(os.Stderr, err) parser.WriteHelp(os.Stderr) return nil, nil, err } configFileError = err } } // Parse command line options again to ensure they take precedence. remainingArgs, err := parser.ParseArgs(args) if err != nil { if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { parser.WriteHelp(os.Stderr) } return nil, nil, err } // Warn about missing config file after the final command line parse // succeeds. This prevents the warning on help messages and invalid // options. if configFileError != nil { log.Warnf("%v", configFileError) } // If an alternate data directory was specified, and paths with defaults // relative to the data dir are unchanged, modify each path to be // relative to the new data dir. if cfg.DataDir != defaultDataDir { if cfg.TLSKey == defaultTLSKeyFile { cfg.TLSKey = filepath.Join(cfg.DataDir, "rpc.key") } if cfg.TLSCert == defaultTLSCertFile { cfg.TLSCert = filepath.Join(cfg.DataDir, "rpc.cert") } cfg.DataDir = cleanAndExpandPath(cfg.DataDir) } // Ensure the data directory exists. if err := checkCreateDir(cfg.DataDir); err != nil { fmt.Fprintln(os.Stderr, err) return nil, nil, err } // Expand environment variable and leading ~ for filepaths. cfg.LogDir = cleanAndExpandPath(cfg.LogDir) // Special show command to list supported subsystems and exit. if cfg.DebugLevel == "show" { fmt.Println("Supported subsystems", supportedSubsystems()) os.Exit(0) } // Initialize logging at the default logging level. initSeelogLogger(filepath.Join(cfg.LogDir, defaultLogFilename), cfg.LogConsole) setLogLevels(defaultLogLevel) // Parse, validate, and set debug log level(s). if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil { err := fmt.Errorf("%s: %v", "loadConfig", err.Error()) fmt.Fprintln(os.Stderr, err) parser.WriteHelp(os.Stderr) return nil, nil, err } // Verify proof-of-work parameters. switch cfg.ProofOfWork { case "sequential": cfg.powHandler = pow.DoSequential case "parallel": if cfg.PowThreads < 2 { err := errors.New("Number of threads for proof-of-work cannot be less than 2") fmt.Fprintln(os.Stderr, err) return nil, nil, err } cfg.powHandler = func(target uint64, hash []byte) pow.Nonce { return pow.DoParallel(target, hash, cfg.PowThreads) } default: err := errors.New("Unknown proof-of-work handler") fmt.Fprintln(os.Stderr, err) return nil, nil, err } // Ensure the key file and data store exist or create them when the create // flag is set. cfg.keyfilePath = filepath.Join(cfg.DataDir, keyfileName) cfg.storePath = filepath.Join(cfg.DataDir, storeDbName) if cfg.Create { // Error if the create flag is set and the key file or data store // already exist. if fileExists(cfg.keyfilePath) { err := fmt.Errorf("The key file already exists.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } if fileExists(cfg.storePath) { err := fmt.Errorf("The data store already exists.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } // Create databases. if err := createDatabases(&cfg); err != nil { fmt.Fprintln(os.Stderr, "Unable to create data:", err) return nil, nil, err } // Created successfully, so exit now with success. os.Exit(0) } else if !fileExists(cfg.keyfilePath) || !fileExists(cfg.storePath) { err := errors.New("The key file and/or data store do not exist. " + "Run with the --create option to\ninitialize and create them.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } // Import private keys from PyBitmessage's keys.dat file. if cfg.ImportKeyFile != "" { cfg.ImportKeyFile = cleanAndExpandPath(cfg.ImportKeyFile) // We need to open the keyfile and store. keymgr, store, _, err := openDatabases(&cfg) if err != nil { fmt.Fprintln(os.Stderr, "Unable to open databases:", err) return nil, nil, err } err = importKeyfile(keymgr, cfg.ImportKeyFile) if err != nil { fmt.Fprintln(os.Stderr, err) return nil, nil, err } u := &User{keymgr, cfg.keyfilePath, cfg.Username, cfg.keyfilePass} u.SaveKeyfile() store.Close() // Imported successfully, so exit now with success. os.Exit(0) } // Username and password must be specified. if cfg.Username == "" || cfg.Password == "" { err := errors.New("Username and password cannot be left blank.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } if cfg.RPCConnect == "" { cfg.RPCConnect = "127.0.0.1" } // Add default port to connect flag if missing. cfg.RPCConnect = normalizeAddress(cfg.RPCConnect, defaultBmdPort) RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect) if err != nil { return nil, nil, err } if cfg.DisableClientTLS { if _, ok := localhostListeners[RPCHost]; !ok { str := "%s: the --noclienttls option may not be used when" + " connecting RPC to non localhost addresses: %s" err := fmt.Errorf(str, funcName, cfg.RPCConnect) fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, usageMessage) return nil, nil, err } } else { // If CAFile is unset, choose either the copy or local bmd cert. if cfg.CAFile == "" { cfg.CAFile = filepath.Join(cfg.DataDir, defaultCAFilename) // If the CA copy does not exist, check if we're connecting to // a local bmd and switch to its RPC cert if it exists. if !fileExists(cfg.CAFile) { if _, ok := localhostListeners[RPCHost]; ok { if fileExists(bmdHomedirCAFile) { cfg.CAFile = bmdHomedirCAFile } } } } cfg.CAFile = cleanAndExpandPath(cfg.CAFile) } // Default RPC, IMAP, SMTP to listen on localhost only. addrs, err := net.LookupHost("localhost") if err != nil { return nil, nil, err } if cfg.EnableRPC && len(cfg.RPCListeners) == 0 { cfg.RPCListeners = make([]string, 0, len(addrs)) for _, addr := range addrs { addr = net.JoinHostPort(addr, strconv.Itoa(defaultRPCPort)) cfg.RPCListeners = append(cfg.RPCListeners, addr) } } if len(cfg.IMAPListeners) == 0 { cfg.IMAPListeners = make([]string, 0, len(addrs)) for _, addr := range addrs { addr = net.JoinHostPort(addr, strconv.Itoa(defaultIMAPPort)) cfg.IMAPListeners = append(cfg.IMAPListeners, addr) } } if len(cfg.SMTPListeners) == 0 { cfg.SMTPListeners = make([]string, 0, len(addrs)) for _, addr := range addrs { addr = net.JoinHostPort(addr, strconv.Itoa(defaultSMTPPort)) cfg.SMTPListeners = append(cfg.SMTPListeners, addr) } } // Add default port to all RPC, IMAP and SMTP listener addresses if needed // and remove duplicate addresses. cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners, defaultRPCPort) cfg.IMAPListeners = normalizeAddresses(cfg.IMAPListeners, defaultIMAPPort) cfg.SMTPListeners = normalizeAddresses(cfg.SMTPListeners, defaultSMTPPort) // Only allow server TLS to be disabled if the RPC is bound to localhost // addresses. if cfg.DisableServerTLS { err = verifyListeners(cfg.RPCListeners, "RPC", funcName, usageMessage) if err != nil { return nil, nil, err } err = verifyListeners(cfg.IMAPListeners, "IMAP", funcName, usageMessage) if err != nil { return nil, nil, err } err = verifyListeners(cfg.SMTPListeners, "SMTP", funcName, usageMessage) if err != nil { return nil, nil, err } } // If the bmd username or password are unset, use the same auth as for // the client. if cfg.BmdUsername == "" { cfg.BmdUsername = cfg.Username } if cfg.BmdPassword == "" { cfg.BmdPassword = cfg.Password } return &cfg, remainingArgs, nil }
// loadConfig initializes and parses the config using a config file and command // line options. // // The configuration proceeds as follows: // 1) Start with a default config with sane settings // 2) Pre-parse the command line to check for an alternative config file // 3) Load configuration file overwriting defaults with any specified options // 4) Parse CLI options and overwrite/add any specified options // // The above results in dcrwallet functioning properly without any config // settings while still allowing the user to override settings with config files // and command line options. Command line options always take precedence. // The bool returned indicates whether or not the wallet was recreated from a // seed and needs to perform the initial resync. The []byte is the private // passphrase required to do the sync for this special case. func loadConfig() (*config, []string, error) { loadConfigError := func(err error) (*config, []string, error) { return nil, nil, err } // Default config. cfg := config{ DebugLevel: defaultLogLevel, ConfigFile: defaultConfigFile, AppDataDir: defaultAppDataDir, LogDir: defaultLogDir, WalletPass: wallet.InsecurePubPassphrase, PromptPass: defaultPromptPass, RPCKey: defaultRPCKeyFile, RPCCert: defaultRPCCertFile, LegacyRPCMaxClients: defaultRPCMaxClients, LegacyRPCMaxWebsockets: defaultRPCMaxWebsockets, EnableStakeMining: defaultEnableStakeMining, VoteBits: defaultVoteBits, VoteBitsExtended: defaultVoteBitsExtended, BalanceToMaintain: defaultBalanceToMaintain, ReuseAddresses: defaultReuseAddresses, RollbackTest: defaultRollbackTest, PruneTickets: defaultPruneTickets, TicketMaxPrice: defaultTicketMaxPrice, TicketBuyFreq: defaultTicketBuyFreq, AutomaticRepair: defaultAutomaticRepair, UnsafeMainNet: defaultUnsafeMainNet, AddrIdxScanLen: defaultAddrIdxScanLen, StakePoolColdExtKey: defaultStakePoolColdExtKey, AllowHighFees: defaultAllowHighFees, DataDir: defaultAppDataDir, } // Pre-parse the command line options to see if an alternative config // file or the version flag was specified. preCfg := cfg preParser := flags.NewParser(&preCfg, flags.Default) _, err := preParser.Parse() if err != nil { if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { preParser.WriteHelp(os.Stderr) } return loadConfigError(err) } // Show the version and exit if the version flag was specified. funcName := "loadConfig" 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()) os.Exit(0) } // Load additional config from file. var configFileError error parser := flags.NewParser(&cfg, flags.Default) configFilePath := preCfg.ConfigFile if configFilePath == defaultConfigFile { appDataDir := preCfg.AppDataDir if appDataDir == defaultAppDataDir && preCfg.DataDir != defaultAppDataDir { appDataDir = cleanAndExpandPath(preCfg.DataDir) } if appDataDir != defaultAppDataDir { configFilePath = filepath.Join(appDataDir, defaultConfigFilename) } } else { configFilePath = cleanAndExpandPath(configFilePath) } err = flags.NewIniParser(parser).ParseFile(configFilePath) if err != nil { if _, ok := err.(*os.PathError); !ok { fmt.Fprintln(os.Stderr, err) parser.WriteHelp(os.Stderr) return loadConfigError(err) } configFileError = err } // Parse command line options again to ensure they take precedence. remainingArgs, err := parser.Parse() if err != nil { if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { parser.WriteHelp(os.Stderr) } return loadConfigError(err) } // Warn about missing config file after the final command line parse // succeeds. This prevents the warning on help messages and invalid // options. if configFileError != nil { log.Warnf("%v", configFileError) } // Check deprecated aliases. The new options receive priority when both // are changed from the default. if cfg.DataDir != defaultAppDataDir { fmt.Fprintln(os.Stderr, "datadir option has been replaced by "+ "appdata -- please update your config") if cfg.AppDataDir == defaultAppDataDir { cfg.AppDataDir = cfg.DataDir } } // If an alternate data directory was specified, and paths with defaults // relative to the data dir are unchanged, modify each path to be // relative to the new data dir. if cfg.AppDataDir != defaultAppDataDir { cfg.AppDataDir = cleanAndExpandPath(cfg.AppDataDir) if cfg.RPCKey == defaultRPCKeyFile { cfg.RPCKey = filepath.Join(cfg.AppDataDir, "rpc.key") } if cfg.RPCCert == defaultRPCCertFile { cfg.RPCCert = filepath.Join(cfg.AppDataDir, "rpc.cert") } if cfg.LogDir == defaultLogDir { cfg.LogDir = filepath.Join(cfg.AppDataDir, defaultLogDirname) } } // Choose the active network params based on the selected network. // Multiple networks can't be selected simultaneously. numNets := 0 if cfg.TestNet { activeNet = &netparams.TestNetParams numNets++ } if cfg.SimNet { activeNet = &netparams.SimNetParams numNets++ } if numNets > 1 { str := "%s: The testnet and simnet params can't be used " + "together -- choose one" err := fmt.Errorf(str, "loadConfig") fmt.Fprintln(os.Stderr, err) parser.WriteHelp(os.Stderr) return loadConfigError(err) } // Append the network type to the log directory so it is "namespaced" // per network. cfg.LogDir = cleanAndExpandPath(cfg.LogDir) cfg.LogDir = filepath.Join(cfg.LogDir, activeNet.Params.Name) // Special show command to list supported subsystems and exit. if cfg.DebugLevel == "show" { fmt.Println("Supported subsystems", supportedSubsystems()) os.Exit(0) } // Initialize logging at the default logging level. initSeelogLogger(filepath.Join(cfg.LogDir, defaultLogFilename)) setLogLevels(defaultLogLevel) // Parse, validate, and set debug log level(s). if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil { err := fmt.Errorf("%s: %v", "loadConfig", err.Error()) fmt.Fprintln(os.Stderr, err) parser.WriteHelp(os.Stderr) return loadConfigError(err) } // Exit if you try to use a simulation wallet with a standard // data directory. if cfg.AppDataDir == defaultAppDataDir && cfg.CreateTemp { fmt.Fprintln(os.Stderr, "Tried to create a temporary simulation "+ "wallet, but failed to specify data directory!") os.Exit(0) } // Exit if you try to use a simulation wallet on anything other than // simnet or testnet. if !cfg.SimNet && cfg.CreateTemp { fmt.Fprintln(os.Stderr, "Tried to create a temporary simulation "+ "wallet for network other than simnet!") os.Exit(0) } // Exit if you tried to do rollback testing on a network other than // simnet. if cfg.RollbackTest && !cfg.SimNet { fmt.Fprintln(os.Stderr, "Tried to do rollback testing of "+ "wallet for network other than simnet!") os.Exit(0) } // Ensure the wallet exists or create it when the create flag is set. netDir := networkDir(cfg.AppDataDir, activeNet.Params) dbPath := filepath.Join(netDir, walletDbName) if cfg.CreateTemp && cfg.Create { err := fmt.Errorf("The flags --create and --createtemp can not " + "be specified together. Use --help for more information.") fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } dbFileExists, err := cfgutil.FileExists(dbPath) if err != nil { fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } if cfg.CreateTemp { tempWalletExists := false if dbFileExists { str := fmt.Sprintf("The wallet already exists. Loading this " + "wallet instead.") fmt.Fprintln(os.Stdout, str) tempWalletExists = true } // Ensure the data directory for the network exists. if err := checkCreateDir(netDir); err != nil { fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } if !tempWalletExists { // Perform the initial wallet creation wizard. if err := createSimulationWallet(&cfg); err != nil { fmt.Fprintln(os.Stderr, "Unable to create wallet:", err) return loadConfigError(err) } } } else if cfg.Create { // Error if the create flag is set and the wallet already // exists. if dbFileExists { err := fmt.Errorf("The wallet database file `%v` "+ "already exists.", dbPath) fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } // Ensure the data directory for the network exists. if err := checkCreateDir(netDir); err != nil { fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } // Perform the initial wallet creation wizard. if !cfg.CreateWatchingOnly { if err = createWallet(&cfg); err != nil { fmt.Fprintln(os.Stderr, "Unable to create wallet:", err) return loadConfigError(err) } } else if cfg.CreateWatchingOnly { if err = createWatchingOnlyWallet(&cfg); err != nil { fmt.Fprintln(os.Stderr, "Unable to create wallet:", err) return loadConfigError(err) } } // Created successfully, so exit now with success. os.Exit(0) } else if !dbFileExists && !cfg.NoInitialLoad { keystorePath := filepath.Join(netDir, keystore.Filename) keystoreExists, err := cfgutil.FileExists(keystorePath) if err != nil { fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } if !keystoreExists { err = fmt.Errorf("The wallet does not exist. Run with the " + "--create option to initialize and create it.") } else { err = fmt.Errorf("The wallet is in legacy format. Run with the " + "--create option to import it.") } fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } if len(cfg.TicketAddress) != 0 { _, err := dcrutil.DecodeAddress(cfg.TicketAddress, activeNet.Params) if err != nil { err := fmt.Errorf("ticketaddress '%s' failed to decode: %v", cfg.TicketAddress, err) fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, usageMessage) return loadConfigError(err) } } if len(cfg.PoolAddress) != 0 { _, err := dcrutil.DecodeAddress(cfg.PoolAddress, activeNet.Params) if err != nil { err := fmt.Errorf("pooladdress '%s' failed to decode: %v", cfg.PoolAddress, err) fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, usageMessage) return loadConfigError(err) } } if cfg.PoolFees != 0.0 { err := txrules.IsValidPoolFeeRate(cfg.PoolFees) if err != nil { err := fmt.Errorf("poolfees '%v' failed to decode: %v", cfg.PoolFees, err) fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, usageMessage) return loadConfigError(err) } } if cfg.RPCConnect == "" { cfg.RPCConnect = net.JoinHostPort("localhost", activeNet.RPCClientPort) } // Set ticketfee and txfee to defaults if none are set. Avoiding using default // confs because they are dcrutil.Amounts if cfg.TicketFee == 0.0 { cfg.TicketFee = wallet.DefaultTicketFeeIncrement.ToCoin() } if cfg.RelayFee == 0.0 { cfg.RelayFee = txrules.DefaultRelayFeePerKb.ToCoin() } // Add default port to connect flag if missing. cfg.RPCConnect, err = cfgutil.NormalizeAddress(cfg.RPCConnect, activeNet.RPCClientPort) if err != nil { fmt.Fprintf(os.Stderr, "Invalid rpcconnect network address: %v\n", err) return loadConfigError(err) } localhostListeners := map[string]struct{}{ "localhost": struct{}{}, "127.0.0.1": struct{}{}, "::1": struct{}{}, } RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect) if err != nil { return loadConfigError(err) } if cfg.DisableClientTLS { if _, ok := localhostListeners[RPCHost]; !ok { str := "%s: the --noclienttls option may not be used " + "when connecting RPC to non localhost " + "addresses: %s" err := fmt.Errorf(str, funcName, cfg.RPCConnect) fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, usageMessage) return loadConfigError(err) } } else { // If CAFile is unset, choose either the copy or local dcrd cert. if cfg.CAFile == "" { cfg.CAFile = filepath.Join(cfg.AppDataDir, defaultCAFilename) // If the CA copy does not exist, check if we're connecting to // a local dcrd and switch to its RPC cert if it exists. certExists, err := cfgutil.FileExists(cfg.CAFile) if err != nil { fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } if !certExists { if _, ok := localhostListeners[RPCHost]; ok { dcrdCertExists, err := cfgutil.FileExists( dcrdDefaultCAFile) if err != nil { fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } if dcrdCertExists { cfg.CAFile = dcrdDefaultCAFile } } } } } // Only set default RPC listeners when there are no listeners set for // the experimental RPC server. This is required to prevent the old RPC // server from sharing listen addresses, since it is impossible to // remove defaults from go-flags slice options without assigning // specific behavior to a particular string. if len(cfg.ExperimentalRPCListeners) == 0 && len(cfg.LegacyRPCListeners) == 0 { addrs, err := net.LookupHost("localhost") if err != nil { return loadConfigError(err) } cfg.LegacyRPCListeners = make([]string, 0, len(addrs)) for _, addr := range addrs { addr = net.JoinHostPort(addr, activeNet.RPCServerPort) cfg.LegacyRPCListeners = append(cfg.LegacyRPCListeners, addr) } } // Add default port to all rpc listener addresses if needed and remove // duplicate addresses. cfg.LegacyRPCListeners, err = cfgutil.NormalizeAddresses( cfg.LegacyRPCListeners, activeNet.RPCServerPort) if err != nil { fmt.Fprintf(os.Stderr, "Invalid network address in legacy RPC listeners: %v\n", err) return loadConfigError(err) } cfg.ExperimentalRPCListeners, err = cfgutil.NormalizeAddresses( cfg.ExperimentalRPCListeners, activeNet.RPCServerPort) if err != nil { fmt.Fprintf(os.Stderr, "Invalid network address in RPC listeners: %v\n", err) return loadConfigError(err) } // Both RPC servers may not listen on the same interface/port. if len(cfg.LegacyRPCListeners) > 0 && len(cfg.ExperimentalRPCListeners) > 0 { seenAddresses := make(map[string]struct{}, len(cfg.LegacyRPCListeners)) for _, addr := range cfg.LegacyRPCListeners { seenAddresses[addr] = struct{}{} } for _, addr := range cfg.ExperimentalRPCListeners { _, seen := seenAddresses[addr] if seen { err := fmt.Errorf("Address `%s` may not be "+ "used as a listener address for both "+ "RPC servers", addr) fmt.Fprintln(os.Stderr, err) return loadConfigError(err) } } } // Only allow server TLS to be disabled if the RPC server is bound to // localhost addresses. if cfg.DisableServerTLS { allListeners := append(cfg.LegacyRPCListeners, cfg.ExperimentalRPCListeners...) for _, addr := range allListeners { host, _, err := net.SplitHostPort(addr) if err != nil { str := "%s: RPC listen interface '%s' is " + "invalid: %v" err := fmt.Errorf(str, funcName, addr, err) fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, usageMessage) return loadConfigError(err) } if _, ok := localhostListeners[host]; !ok { str := "%s: the --noservertls option may not be used " + "when binding RPC to non localhost " + "addresses: %s" err := fmt.Errorf(str, funcName, addr) fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, usageMessage) return loadConfigError(err) } } } // Expand environment variable and leading ~ for filepaths. cfg.CAFile = cleanAndExpandPath(cfg.CAFile) cfg.RPCCert = cleanAndExpandPath(cfg.RPCCert) cfg.RPCKey = cleanAndExpandPath(cfg.RPCKey) // If the dcrd username or password are unset, use the same auth as for // the client. The two settings were previously shared for dcrd and // client auth, so this avoids breaking backwards compatibility while // allowing users to use different auth settings for dcrd and wallet. if cfg.DcrdUsername == "" { cfg.DcrdUsername = cfg.Username } if cfg.DcrdPassword == "" { cfg.DcrdPassword = cfg.Password } return &cfg, remainingArgs, nil }
// loadConfig initializes and parses the config using a config file and command // line options. // // The configuration proceeds as follows: // 1) Start with a default config with sane settings // 2) Pre-parse the command line to check for an alternative config file // 3) Load configuration file overwriting defaults with any specified options // 4) Parse CLI options and overwrite/add any specified options // // The above results in btcwallet functioning properly without any config // settings while still allowing the user to override settings with config files // and command line options. Command line options always take precedence. func loadConfig() (*config, []string, error) { // Default config. cfg := config{ DebugLevel: defaultLogLevel, ConfigFile: defaultConfigFile, DataDir: defaultDataDir, LogDir: defaultLogDir, TLSKey: defaultTLSKeyFile, TLSCert: defaultTLSCertFile, } // A config file in the current directory takes precedence. if fileExists(defaultConfigFilename) { cfg.ConfigFile = defaultConfigFile } // Pre-parse the command line options to see if an alternative config // file or the version flag was specified. preCfg := cfg preParser := flags.NewParser(&preCfg, flags.Default) _, err := preParser.Parse() if err != nil { if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { preParser.WriteHelp(os.Stderr) } return nil, nil, err } // Show the version and exit if the version flag was specified. funcName := "loadConfig" 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()) os.Exit(0) } // Load additional config from file. var configFileError error parser := flags.NewParser(&cfg, flags.Default) err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile) if err != nil { if _, ok := err.(*os.PathError); !ok { fmt.Fprintln(os.Stderr, err) parser.WriteHelp(os.Stderr) return nil, nil, err } configFileError = err } // Parse command line options again to ensure they take precedence. remainingArgs, err := parser.Parse() if err != nil { if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp { parser.WriteHelp(os.Stderr) } return nil, nil, err } // Warn about missing config file after the final command line parse // succeeds. This prevents the warning on help messages and invalid // options. if configFileError != nil { log.Warnf("%v", configFileError) } // If an alternate data directory was specified, and paths with defaults // relative to the data dir are unchanged, modify each path to be // relative to the new data dir. if cfg.DataDir != defaultDataDir { if cfg.TLSKey == defaultTLSKeyFile { cfg.TLSKey = filepath.Join(cfg.DataDir, "rpc.key") } if cfg.TLSCert == defaultTLSCertFile { cfg.TLSCert = filepath.Join(cfg.DataDir, "rpc.cert") } cfg.DataDir = cleanAndExpandPath(cfg.DataDir) } // Ensure the data directory exists. if err := checkCreateDir(cfg.DataDir); err != nil { fmt.Fprintln(os.Stderr, err) return nil, nil, err } // Expand environment variable and leading ~ for filepaths. cfg.CAFile = cleanAndExpandPath(cfg.CAFile) cfg.LogDir = cleanAndExpandPath(cfg.LogDir) // Special show command to list supported subsystems and exit. if cfg.DebugLevel == "show" { fmt.Println("Supported subsystems", supportedSubsystems()) os.Exit(0) } // Initialize logging at the default logging level. initSeelogLogger(filepath.Join(cfg.LogDir, defaultLogFilename)) setLogLevels(defaultLogLevel) // Parse, validate, and set debug log level(s). if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil { err := fmt.Errorf("%s: %v", "loadConfig", err.Error()) fmt.Fprintln(os.Stderr, err) parser.WriteHelp(os.Stderr) return nil, nil, err } // Ensure the keys database exists or create it when the create flag is set. keysDbPath := filepath.Join(cfg.DataDir, keysDbName) msgDbPath := filepath.Join(cfg.DataDir, msgDbName) if cfg.Create { // Error if the create flag is set and the key or message databases // already exist. if fileExists(keysDbPath) { err := fmt.Errorf("The key database already exists.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } if fileExists(msgDbPath) { err := fmt.Errorf("The message database already exists.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } // TODO create databases /* if err := createWallet(&cfg); err != nil { fmt.Fprintln(os.Stderr, "Unable to create wallet:", err) return nil, nil, err } */ // Created successfully, so exit now with success. os.Exit(0) } else if !fileExists(keysDbPath) || !fileExists(msgDbPath) { err := errors.New("The keys database and/or message database do not" + " exist. Run with the --create option to initialize and create" + " them.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } // Import private keys from PyBitmessage's keys.dat file. if cfg.ImportKeyFile != "" { // TODO } // Username and password must be specified. if cfg.Username == "" || cfg.Password == "" { err := errors.New("Username and password cannot be left blank.") fmt.Fprintln(os.Stderr, err) return nil, nil, err } if cfg.RPCConnect == "" { cfg.RPCConnect = "127.0.0.1" } // Add default port to connect flag if missing. cfg.RPCConnect = normalizeAddress(cfg.RPCConnect, defaultBmdPort) RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect) if err != nil { return nil, nil, err } if cfg.DisableClientTLS { if _, ok := localhostListeners[RPCHost]; !ok { str := "%s: the --noclienttls option may not be used when" + " connecting RPC to non localhost addresses: %s" err := fmt.Errorf(str, funcName, cfg.RPCConnect) fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, usageMessage) return nil, nil, err } } else { // If CAFile is unset, choose either the copy or local bmd cert. if cfg.CAFile == "" { cfg.CAFile = filepath.Join(cfg.DataDir, defaultCAFilename) // If the CA copy does not exist, check if we're connecting to // a local bmd and switch to its RPC cert if it exists. if !fileExists(cfg.CAFile) { if _, ok := localhostListeners[RPCHost]; ok { if fileExists(bmdHomedirCAFile) { cfg.CAFile = bmdHomedirCAFile } } } } } // Default RPC, IMAP, SMTP to listen on localhost only. addrs, err := net.LookupHost("localhost") if err != nil { return nil, nil, err } if cfg.EnableRPC && len(cfg.RPCListeners) == 0 { cfg.RPCListeners = make([]string, 0, len(addrs)) for _, addr := range addrs { addr = net.JoinHostPort(addr, strconv.Itoa(defaultRPCPort)) cfg.RPCListeners = append(cfg.RPCListeners, addr) } } if len(cfg.IMAPListeners) == 0 { cfg.IMAPListeners = make([]string, 0, len(addrs)) for _, addr := range addrs { addr = net.JoinHostPort(addr, strconv.Itoa(defaultIMAPPort)) cfg.IMAPListeners = append(cfg.IMAPListeners, addr) } } if len(cfg.SMTPListeners) == 0 { cfg.SMTPListeners = make([]string, 0, len(addrs)) for _, addr := range addrs { addr = net.JoinHostPort(addr, strconv.Itoa(defaultSMTPPort)) cfg.SMTPListeners = append(cfg.SMTPListeners, addr) } } // Add default port to all RPC, IMAP and SMTP listener addresses if needed // and remove duplicate addresses. cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners, defaultRPCPort) cfg.IMAPListeners = normalizeAddresses(cfg.IMAPListeners, defaultIMAPPort) cfg.SMTPListeners = normalizeAddresses(cfg.SMTPListeners, defaultSMTPPort) // Only allow server TLS to be disabled if the RPC is bound to localhost // addresses. if cfg.DisableServerTLS { err = verifyListeners(cfg.RPCListeners, "RPC", funcName, usageMessage) if err != nil { return nil, nil, err } err = verifyListeners(cfg.IMAPListeners, "IMAP", funcName, usageMessage) if err != nil { return nil, nil, err } err = verifyListeners(cfg.SMTPListeners, "SMTP", funcName, usageMessage) if err != nil { return nil, nil, err } } // If the bmd username or password are unset, use the same auth as for // the client. if cfg.BmdUsername == "" { cfg.BmdUsername = cfg.Username } if cfg.BmdPassword == "" { cfg.BmdPassword = cfg.Password } return &cfg, remainingArgs, nil }