1// Copyright 2014 The go-ethereum Authors
2// This file is part of the go-ethereum library.
3//
4// The go-ethereum library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Lesser General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// The go-ethereum library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Lesser General Public License for more details.
13//
14// You should have received a copy of the GNU Lesser General Public License
15// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16
17package node
18
19import (
20	"crypto/ecdsa"
21	"fmt"
22	"io/ioutil"
23	"os"
24	"path/filepath"
25	"runtime"
26	"strings"
27	"sync"
28
29	"github.com/ethereum/go-ethereum/common"
30	"github.com/ethereum/go-ethereum/crypto"
31	"github.com/ethereum/go-ethereum/log"
32	"github.com/ethereum/go-ethereum/p2p"
33	"github.com/ethereum/go-ethereum/p2p/enode"
34	"github.com/ethereum/go-ethereum/rpc"
35)
36
37const (
38	datadirPrivateKey      = "nodekey"            // Path within the datadir to the node's private key
39	datadirDefaultKeyStore = "keystore"           // Path within the datadir to the keystore
40	datadirStaticNodes     = "static-nodes.json"  // Path within the datadir to the static node list
41	datadirTrustedNodes    = "trusted-nodes.json" // Path within the datadir to the trusted node list
42	datadirNodeDatabase    = "nodes"              // Path within the datadir to store the node infos
43)
44
45// Config represents a small collection of configuration values to fine tune the
46// P2P network layer of a protocol stack. These values can be further extended by
47// all registered services.
48type Config struct {
49	// Name sets the instance name of the node. It must not contain the / character and is
50	// used in the devp2p node identifier. The instance name of geth is "geth". If no
51	// value is specified, the basename of the current executable is used.
52	Name string `toml:"-"`
53
54	// UserIdent, if set, is used as an additional component in the devp2p node identifier.
55	UserIdent string `toml:",omitempty"`
56
57	// Version should be set to the version number of the program. It is used
58	// in the devp2p node identifier.
59	Version string `toml:"-"`
60
61	// DataDir is the file system folder the node should use for any data storage
62	// requirements. The configured data directory will not be directly shared with
63	// registered services, instead those can use utility methods to create/access
64	// databases or flat files. This enables ephemeral nodes which can fully reside
65	// in memory.
66	DataDir string
67
68	// Configuration of peer-to-peer networking.
69	P2P p2p.Config
70
71	// KeyStoreDir is the file system folder that contains private keys. The directory can
72	// be specified as a relative path, in which case it is resolved relative to the
73	// current directory.
74	//
75	// If KeyStoreDir is empty, the default location is the "keystore" subdirectory of
76	// DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory
77	// is created by New and destroyed when the node is stopped.
78	KeyStoreDir string `toml:",omitempty"`
79
80	// ExternalSigner specifies an external URI for a clef-type signer
81	ExternalSigner string `toml:",omitempty"`
82
83	// UseLightweightKDF lowers the memory and CPU requirements of the key store
84	// scrypt KDF at the expense of security.
85	UseLightweightKDF bool `toml:",omitempty"`
86
87	// InsecureUnlockAllowed allows user to unlock accounts in unsafe http environment.
88	InsecureUnlockAllowed bool `toml:",omitempty"`
89
90	// NoUSB disables hardware wallet monitoring and connectivity.
91	// Deprecated: USB monitoring is disabled by default and must be enabled explicitly.
92	NoUSB bool `toml:",omitempty"`
93
94	// USB enables hardware wallet monitoring and connectivity.
95	USB bool `toml:",omitempty"`
96
97	// SmartCardDaemonPath is the path to the smartcard daemon's socket
98	SmartCardDaemonPath string `toml:",omitempty"`
99
100	// IPCPath is the requested location to place the IPC endpoint. If the path is
101	// a simple file name, it is placed inside the data directory (or on the root
102	// pipe path on Windows), whereas if it's a resolvable path name (absolute or
103	// relative), then that specific path is enforced. An empty path disables IPC.
104	IPCPath string
105
106	// HTTPHost is the host interface on which to start the HTTP RPC server. If this
107	// field is empty, no HTTP API endpoint will be started.
108	HTTPHost string
109
110	// HTTPPort is the TCP port number on which to start the HTTP RPC server. The
111	// default zero value is/ valid and will pick a port number randomly (useful
112	// for ephemeral nodes).
113	HTTPPort int `toml:",omitempty"`
114
115	// HTTPCors is the Cross-Origin Resource Sharing header to send to requesting
116	// clients. Please be aware that CORS is a browser enforced security, it's fully
117	// useless for custom HTTP clients.
118	HTTPCors []string `toml:",omitempty"`
119
120	// HTTPVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
121	// This is by default {'localhost'}. Using this prevents attacks like
122	// DNS rebinding, which bypasses SOP by simply masquerading as being within the same
123	// origin. These attacks do not utilize CORS, since they are not cross-domain.
124	// By explicitly checking the Host-header, the server will not allow requests
125	// made against the server with a malicious host domain.
126	// Requests using ip address directly are not affected
127	HTTPVirtualHosts []string `toml:",omitempty"`
128
129	// HTTPModules is a list of API modules to expose via the HTTP RPC interface.
130	// If the module list is empty, all RPC API endpoints designated public will be
131	// exposed.
132	HTTPModules []string
133
134	// HTTPTimeouts allows for customization of the timeout values used by the HTTP RPC
135	// interface.
136	HTTPTimeouts rpc.HTTPTimeouts
137
138	// HTTPPathPrefix specifies a path prefix on which http-rpc is to be served.
139	HTTPPathPrefix string `toml:",omitempty"`
140
141	// WSHost is the host interface on which to start the websocket RPC server. If
142	// this field is empty, no websocket API endpoint will be started.
143	WSHost string
144
145	// WSPort is the TCP port number on which to start the websocket RPC server. The
146	// default zero value is/ valid and will pick a port number randomly (useful for
147	// ephemeral nodes).
148	WSPort int `toml:",omitempty"`
149
150	// WSPathPrefix specifies a path prefix on which ws-rpc is to be served.
151	WSPathPrefix string `toml:",omitempty"`
152
153	// WSOrigins is the list of domain to accept websocket requests from. Please be
154	// aware that the server can only act upon the HTTP request the client sends and
155	// cannot verify the validity of the request header.
156	WSOrigins []string `toml:",omitempty"`
157
158	// WSModules is a list of API modules to expose via the websocket RPC interface.
159	// If the module list is empty, all RPC API endpoints designated public will be
160	// exposed.
161	WSModules []string
162
163	// WSExposeAll exposes all API modules via the WebSocket RPC interface rather
164	// than just the public ones.
165	//
166	// *WARNING* Only set this if the node is running in a trusted network, exposing
167	// private APIs to untrusted users is a major security risk.
168	WSExposeAll bool `toml:",omitempty"`
169
170	// GraphQLCors is the Cross-Origin Resource Sharing header to send to requesting
171	// clients. Please be aware that CORS is a browser enforced security, it's fully
172	// useless for custom HTTP clients.
173	GraphQLCors []string `toml:",omitempty"`
174
175	// GraphQLVirtualHosts is the list of virtual hostnames which are allowed on incoming requests.
176	// This is by default {'localhost'}. Using this prevents attacks like
177	// DNS rebinding, which bypasses SOP by simply masquerading as being within the same
178	// origin. These attacks do not utilize CORS, since they are not cross-domain.
179	// By explicitly checking the Host-header, the server will not allow requests
180	// made against the server with a malicious host domain.
181	// Requests using ip address directly are not affected
182	GraphQLVirtualHosts []string `toml:",omitempty"`
183
184	// Logger is a custom logger to use with the p2p.Server.
185	Logger log.Logger `toml:",omitempty"`
186
187	staticNodesWarning     bool
188	trustedNodesWarning    bool
189	oldGethResourceWarning bool
190
191	// AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
192	AllowUnprotectedTxs bool `toml:",omitempty"`
193}
194
195// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
196// account the set data folders as well as the designated platform we're currently
197// running on.
198func (c *Config) IPCEndpoint() string {
199	// Short circuit if IPC has not been enabled
200	if c.IPCPath == "" {
201		return ""
202	}
203	// On windows we can only use plain top-level pipes
204	if runtime.GOOS == "windows" {
205		if strings.HasPrefix(c.IPCPath, `\\.\pipe\`) {
206			return c.IPCPath
207		}
208		return `\\.\pipe\` + c.IPCPath
209	}
210	// Resolve names into the data directory full paths otherwise
211	if filepath.Base(c.IPCPath) == c.IPCPath {
212		if c.DataDir == "" {
213			return filepath.Join(os.TempDir(), c.IPCPath)
214		}
215		return filepath.Join(c.DataDir, c.IPCPath)
216	}
217	return c.IPCPath
218}
219
220// NodeDB returns the path to the discovery node database.
221func (c *Config) NodeDB() string {
222	if c.DataDir == "" {
223		return "" // ephemeral
224	}
225	return c.ResolvePath(datadirNodeDatabase)
226}
227
228// DefaultIPCEndpoint returns the IPC path used by default.
229func DefaultIPCEndpoint(clientIdentifier string) string {
230	if clientIdentifier == "" {
231		clientIdentifier = strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
232		if clientIdentifier == "" {
233			panic("empty executable name")
234		}
235	}
236	config := &Config{DataDir: DefaultDataDir(), IPCPath: clientIdentifier + ".ipc"}
237	return config.IPCEndpoint()
238}
239
240// HTTPEndpoint resolves an HTTP endpoint based on the configured host interface
241// and port parameters.
242func (c *Config) HTTPEndpoint() string {
243	if c.HTTPHost == "" {
244		return ""
245	}
246	return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort)
247}
248
249// DefaultHTTPEndpoint returns the HTTP endpoint used by default.
250func DefaultHTTPEndpoint() string {
251	config := &Config{HTTPHost: DefaultHTTPHost, HTTPPort: DefaultHTTPPort}
252	return config.HTTPEndpoint()
253}
254
255// WSEndpoint resolves a websocket endpoint based on the configured host interface
256// and port parameters.
257func (c *Config) WSEndpoint() string {
258	if c.WSHost == "" {
259		return ""
260	}
261	return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort)
262}
263
264// DefaultWSEndpoint returns the websocket endpoint used by default.
265func DefaultWSEndpoint() string {
266	config := &Config{WSHost: DefaultWSHost, WSPort: DefaultWSPort}
267	return config.WSEndpoint()
268}
269
270// ExtRPCEnabled returns the indicator whether node enables the external
271// RPC(http, ws or graphql).
272func (c *Config) ExtRPCEnabled() bool {
273	return c.HTTPHost != "" || c.WSHost != ""
274}
275
276// NodeName returns the devp2p node identifier.
277func (c *Config) NodeName() string {
278	name := c.name()
279	// Backwards compatibility: previous versions used title-cased "Geth", keep that.
280	if name == "geth" || name == "geth-testnet" {
281		name = "Geth"
282	}
283	if c.UserIdent != "" {
284		name += "/" + c.UserIdent
285	}
286	if c.Version != "" {
287		name += "/v" + c.Version
288	}
289	name += "/" + runtime.GOOS + "-" + runtime.GOARCH
290	name += "/" + runtime.Version()
291	return name
292}
293
294func (c *Config) name() string {
295	if c.Name == "" {
296		progname := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
297		if progname == "" {
298			panic("empty executable name, set Config.Name")
299		}
300		return progname
301	}
302	return c.Name
303}
304
305// These resources are resolved differently for "geth" instances.
306var isOldGethResource = map[string]bool{
307	"chaindata":          true,
308	"nodes":              true,
309	"nodekey":            true,
310	"static-nodes.json":  false, // no warning for these because they have their
311	"trusted-nodes.json": false, // own separate warning.
312}
313
314// ResolvePath resolves path in the instance directory.
315func (c *Config) ResolvePath(path string) string {
316	if filepath.IsAbs(path) {
317		return path
318	}
319	if c.DataDir == "" {
320		return ""
321	}
322	// Backwards-compatibility: ensure that data directory files created
323	// by geth 1.4 are used if they exist.
324	if warn, isOld := isOldGethResource[path]; isOld {
325		oldpath := ""
326		if c.name() == "geth" {
327			oldpath = filepath.Join(c.DataDir, path)
328		}
329		if oldpath != "" && common.FileExist(oldpath) {
330			if warn {
331				c.warnOnce(&c.oldGethResourceWarning, "Using deprecated resource file %s, please move this file to the 'geth' subdirectory of datadir.", oldpath)
332			}
333			return oldpath
334		}
335	}
336	return filepath.Join(c.instanceDir(), path)
337}
338
339func (c *Config) instanceDir() string {
340	if c.DataDir == "" {
341		return ""
342	}
343	return filepath.Join(c.DataDir, c.name())
344}
345
346// NodeKey retrieves the currently configured private key of the node, checking
347// first any manually set key, falling back to the one found in the configured
348// data folder. If no key can be found, a new one is generated.
349func (c *Config) NodeKey() *ecdsa.PrivateKey {
350	// Use any specifically configured key.
351	if c.P2P.PrivateKey != nil {
352		return c.P2P.PrivateKey
353	}
354	// Generate ephemeral key if no datadir is being used.
355	if c.DataDir == "" {
356		key, err := crypto.GenerateKey()
357		if err != nil {
358			log.Crit(fmt.Sprintf("Failed to generate ephemeral node key: %v", err))
359		}
360		return key
361	}
362
363	keyfile := c.ResolvePath(datadirPrivateKey)
364	if key, err := crypto.LoadECDSA(keyfile); err == nil {
365		return key
366	}
367	// No persistent key found, generate and store a new one.
368	key, err := crypto.GenerateKey()
369	if err != nil {
370		log.Crit(fmt.Sprintf("Failed to generate node key: %v", err))
371	}
372	instanceDir := filepath.Join(c.DataDir, c.name())
373	if err := os.MkdirAll(instanceDir, 0700); err != nil {
374		log.Error(fmt.Sprintf("Failed to persist node key: %v", err))
375		return key
376	}
377	keyfile = filepath.Join(instanceDir, datadirPrivateKey)
378	if err := crypto.SaveECDSA(keyfile, key); err != nil {
379		log.Error(fmt.Sprintf("Failed to persist node key: %v", err))
380	}
381	return key
382}
383
384// StaticNodes returns a list of node enode URLs configured as static nodes.
385func (c *Config) StaticNodes() []*enode.Node {
386	return c.parsePersistentNodes(&c.staticNodesWarning, c.ResolvePath(datadirStaticNodes))
387}
388
389// TrustedNodes returns a list of node enode URLs configured as trusted nodes.
390func (c *Config) TrustedNodes() []*enode.Node {
391	return c.parsePersistentNodes(&c.trustedNodesWarning, c.ResolvePath(datadirTrustedNodes))
392}
393
394// parsePersistentNodes parses a list of discovery node URLs loaded from a .json
395// file from within the data directory.
396func (c *Config) parsePersistentNodes(w *bool, path string) []*enode.Node {
397	// Short circuit if no node config is present
398	if c.DataDir == "" {
399		return nil
400	}
401	if _, err := os.Stat(path); err != nil {
402		return nil
403	}
404	c.warnOnce(w, "Found deprecated node list file %s, please use the TOML config file instead.", path)
405
406	// Load the nodes from the config file.
407	var nodelist []string
408	if err := common.LoadJSON(path, &nodelist); err != nil {
409		log.Error(fmt.Sprintf("Can't load node list file: %v", err))
410		return nil
411	}
412	// Interpret the list as a discovery node array
413	var nodes []*enode.Node
414	for _, url := range nodelist {
415		if url == "" {
416			continue
417		}
418		node, err := enode.Parse(enode.ValidSchemes, url)
419		if err != nil {
420			log.Error(fmt.Sprintf("Node URL %s: %v\n", url, err))
421			continue
422		}
423		nodes = append(nodes, node)
424	}
425	return nodes
426}
427
428// KeyDirConfig determines the settings for keydirectory
429func (c *Config) KeyDirConfig() (string, error) {
430	var (
431		keydir string
432		err    error
433	)
434	switch {
435	case filepath.IsAbs(c.KeyStoreDir):
436		keydir = c.KeyStoreDir
437	case c.DataDir != "":
438		if c.KeyStoreDir == "" {
439			keydir = filepath.Join(c.DataDir, datadirDefaultKeyStore)
440		} else {
441			keydir, err = filepath.Abs(c.KeyStoreDir)
442		}
443	case c.KeyStoreDir != "":
444		keydir, err = filepath.Abs(c.KeyStoreDir)
445	}
446	return keydir, err
447}
448
449// getKeyStoreDir retrieves the key directory and will create
450// and ephemeral one if necessary.
451func getKeyStoreDir(conf *Config) (string, bool, error) {
452	keydir, err := conf.KeyDirConfig()
453	if err != nil {
454		return "", false, err
455	}
456	isEphemeral := false
457	if keydir == "" {
458		// There is no datadir.
459		keydir, err = ioutil.TempDir("", "go-ethereum-keystore")
460		isEphemeral = true
461	}
462
463	if err != nil {
464		return "", false, err
465	}
466	if err := os.MkdirAll(keydir, 0700); err != nil {
467		return "", false, err
468	}
469
470	return keydir, isEphemeral, nil
471}
472
473var warnLock sync.Mutex
474
475func (c *Config) warnOnce(w *bool, format string, args ...interface{}) {
476	warnLock.Lock()
477	defer warnLock.Unlock()
478
479	if *w {
480		return
481	}
482	l := c.Logger
483	if l == nil {
484		l = log.Root()
485	}
486	l.Warn(fmt.Sprintf(format, args...))
487	*w = true
488}
489