Example #1
0
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")
}
Example #2
0
// 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
}
Example #3
0
// 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
}
Example #4
0
// 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
}