1 #include "configdir.h"
2 #include <assert.h>
3 #include <bitcoin/chainparams.h>
4 #include <ccan/cast/cast.h>
5 #include <ccan/err/err.h>
6 #include <ccan/opt/opt.h>
7 #include <ccan/tal/grab_file/grab_file.h>
8 #include <ccan/tal/path/path.h>
9 #include <ccan/tal/str/str.h>
10 #include <common/utils.h>
11 #include <common/version.h>
12 
13 bool deprecated_apis = true;
14 
15 /* The regrettable globals */
16 static const tal_t *options_ctx;
17 
18 /* Override a tal string; frees the old one. */
opt_set_talstr(const char * arg,char ** p)19 char *opt_set_talstr(const char *arg, char **p)
20 {
21 	tal_free(*p);
22 	return opt_set_charp(tal_strdup(options_ctx, arg), p);
23 }
24 
opt_set_abspath(const char * arg,char ** p)25 static char *opt_set_abspath(const char *arg, char **p)
26 {
27 	tal_free(*p);
28 	return opt_set_charp(path_join(options_ctx, take(path_cwd(NULL)), arg),
29 			     p);
30 }
31 
32 /* Tal wrappers for opt. */
opt_allocfn(size_t size)33 static void *opt_allocfn(size_t size)
34 {
35 	return tal_arr_label(NULL, char, size,
36 			     TAL_LABEL(opt_allocfn_notleak, ""));
37 }
38 
tal_reallocfn(void * ptr,size_t size)39 static void *tal_reallocfn(void *ptr, size_t size)
40 {
41 	if (!ptr)
42 		return opt_allocfn(size);
43 	tal_resize_(&ptr, 1, size, false);
44 	return ptr;
45 }
46 
tal_freefn(void * ptr)47 static void tal_freefn(void *ptr)
48 {
49 	tal_free(ptr);
50 }
51 
52 static int config_parse_line_number;
53 
config_log_stderr_exit(const char * fmt,...)54 static void config_log_stderr_exit(const char *fmt, ...)
55 {
56 	char *msg;
57 	va_list ap;
58 
59 	va_start(ap, fmt);
60 
61 	/* This is the format we expect:*/
62 	if (streq(fmt, "%s: %.*s: %s")) {
63 		const char *argv0 = va_arg(ap, const char *);
64 		unsigned int len = va_arg(ap, unsigned int);
65 		const char *arg = va_arg(ap, const char *);
66 		const char *problem = va_arg(ap, const char *);
67 
68 		assert(argv0 != NULL);
69 		assert(arg != NULL);
70 		assert(problem != NULL);
71 		/*mangle it to remove '--' and add the line number.*/
72 		msg = tal_fmt(NULL, "%s line %d: %.*s: %s",
73 			      argv0,
74 			      config_parse_line_number, len-2, arg+2, problem);
75 	} else {
76 		msg = tal_vfmt(NULL, fmt, ap);
77 	}
78 	va_end(ap);
79 
80 	errx(1, "%s", msg);
81 }
82 
parse_include(const char * filename,bool must_exist,bool early,size_t depth)83 static void parse_include(const char *filename, bool must_exist, bool early,
84 			  size_t depth)
85 {
86 	char *contents, **lines;
87 	char **all_args; /*For each line: either `--`argument, include file, or NULL*/
88 	char *argv[3];
89 	int i, argc;
90 
91 	contents = grab_file(NULL, filename);
92 
93 	/* The default config doesn't have to exist, but if the config was
94 	 * specified on the command line it has to exist. */
95 	if (!contents) {
96 		if (must_exist)
97 			err(1, "Opening and reading %s", filename);
98 		return;
99 	}
100 
101 	lines = tal_strsplit(contents, contents, "\r\n", STR_EMPTY_OK);
102 
103 	/* We have to keep all_args around, since opt will point into it: use
104 	 * magic tal name to tell memleak this isn't one. */
105 	all_args = tal_arr_label(options_ctx, char *, tal_count(lines) - 1,
106 				 TAL_LABEL(options_array_notleak, ""));
107 
108 	for (i = 0; i < tal_count(lines) - 1; i++) {
109 		if (strstarts(lines[i], "#")) {
110 			all_args[i] = NULL;
111 		} else if (strstarts(lines[i], "include ")) {
112 			/* If relative, it's relative to current config file */
113 			all_args[i] = path_join(all_args,
114 						take(path_dirname(NULL,
115 								  filename)),
116 						lines[i] + strlen("include "));
117 		} else {
118 			/* Only valid forms are "foo" and "foo=bar" */
119 			all_args[i] = tal_fmt(all_args, "--%s", lines[i]);
120 		}
121 		/* This isn't a leak either */
122 		if (all_args[i])
123 			tal_set_name(all_args[i], TAL_LABEL(config_notleak, ""));
124 	}
125 
126 	/*
127 	For each line we construct a fake argc,argv commandline.
128 	argv[1] is the only element that changes between iterations.
129 	*/
130 	argc = 2;
131 	argv[0] = cast_const(char *, filename);
132 	argv[argc] = NULL;
133 
134 	for (i = 0; i < tal_count(all_args); i++) {
135 		if (all_args[i] == NULL)
136 			continue;
137 
138 		if (!strstarts(all_args[i], "--")) {
139 			/* There could be more, but this gives a hint. */
140 			if (depth > 100)
141 				errx(1, "Include loop with %s and %s",
142 				     filename, all_args[i]);
143 			parse_include(all_args[i], true, early, ++depth);
144 			continue;
145 		}
146 
147 		config_parse_line_number = i + 1;
148 		argv[1] = all_args[i];
149 		if (early) {
150 			opt_early_parse_incomplete(argc, argv,
151 						   config_log_stderr_exit);
152 		} else {
153 			opt_parse(&argc, argv, config_log_stderr_exit);
154 			argc = 2; /* opt_parse might have changed it  */
155 		}
156 	}
157 
158 	tal_free(contents);
159 }
160 
default_base_configdir(const tal_t * ctx)161 static char *default_base_configdir(const tal_t *ctx)
162 {
163 	char *path;
164 	const char *env = getenv("HOME");
165 	if (!env)
166 		return path_cwd(ctx);
167 
168 	path = path_join(ctx, env, ".lightning");
169 	return path;
170 }
171 
default_rpcfile(const tal_t * ctx)172 static char *default_rpcfile(const tal_t *ctx)
173 {
174 	return tal_strdup(ctx, "lightning-rpc");
175 }
176 
opt_set_network(const char * arg,void * unused)177 static char *opt_set_network(const char *arg, void *unused)
178 {
179 	assert(arg != NULL);
180 
181 	/* Set the global chainparams instance */
182 	chainparams = chainparams_for_network(arg);
183 	if (!chainparams)
184 		return tal_fmt(NULL, "Unknown network name '%s'", arg);
185 	return NULL;
186 }
187 
opt_set_specific_network(const char * network)188 static char *opt_set_specific_network(const char *network)
189 {
190 	return opt_set_network(network, NULL);
191 }
192 
opt_show_network(char buf[OPT_SHOW_LEN],const void * unused)193 static void opt_show_network(char buf[OPT_SHOW_LEN], const void *unused)
194 {
195 	snprintf(buf, OPT_SHOW_LEN, "%s", chainparams->network_name);
196 }
197 
198 /* We track where we're getting options from, so we can detect misuse */
199 enum parse_state {
200 	CMDLINE = 1,
201 	FORCED_CONFIG = 2,
202 	TOPLEVEL_CONFIG = 4,
203 	NETWORK_CONFIG = 8,
204 };
205 static enum parse_state parse_state = CMDLINE;
206 
opt_restricted_cmdline(const char * arg,const void * unused)207 static char *opt_restricted_cmdline(const char *arg, const void *unused)
208 {
209 	if (parse_state != CMDLINE)
210 		return "not permitted in configuration files";
211 	return NULL;
212 }
213 
opt_restricted_toplevel_noarg(const void * unused)214 static char *opt_restricted_toplevel_noarg(const void *unused)
215 {
216 	if (parse_state == NETWORK_CONFIG)
217 		return "not permitted in network-specific configuration files";
218 	return NULL;
219 }
220 
opt_restricted_toplevel(const char * arg,const void * unused)221 static char *opt_restricted_toplevel(const char *arg, const void *unused)
222 {
223 	return opt_restricted_toplevel_noarg(NULL);
224 }
225 
opt_restricted_forceconf_only(const char * arg,const void * unused)226 static char *opt_restricted_forceconf_only(const char *arg, const void *unused)
227 {
228 	if (parse_state != CMDLINE && parse_state != FORCED_CONFIG)
229 		return "not permitted in implicit configuration files";
230 	return NULL;
231 }
232 
is_restricted_ignored(const void * fn)233 bool is_restricted_ignored(const void *fn)
234 {
235 	return fn == opt_restricted_toplevel_noarg
236 		|| fn == opt_restricted_toplevel
237 		|| fn == opt_restricted_forceconf_only;
238 }
239 
is_restricted_print_if_nonnull(const void * fn)240 bool is_restricted_print_if_nonnull(const void *fn)
241 {
242 	return fn == opt_restricted_cmdline;
243 }
244 
setup_option_allocators(void)245 void setup_option_allocators(void)
246 {
247 	/*~ These functions make ccan/opt use tal for allocations */
248 	opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn);
249 }
250 
251 /* network is NULL for parsing top-level config file. */
parse_implied_config_file(const char * config_basedir,const char * network,bool early)252 static void parse_implied_config_file(const char *config_basedir,
253 				      const char *network,
254 				      bool early)
255 {
256 	const char *dir, *filename;
257 
258 	if (config_basedir)
259 		dir = path_join(NULL, take(path_cwd(NULL)), config_basedir);
260 	else
261 		dir = default_base_configdir(NULL);
262 
263 	if (network)
264 		dir = path_join(NULL, take(dir), network);
265 
266 	filename = path_join(NULL, take(dir), "config");
267 	parse_include(filename, false, early, 0);
268 	tal_free(filename);
269 }
270 
271 /* If they specify --conf, we just read that.
272  * Otherwise we read <lightning-dir>/config then <lightning-dir>/<network>/config
273  */
parse_config_files(const char * config_filename,const char * config_basedir,bool early)274 void parse_config_files(const char *config_filename,
275 			const char *config_basedir,
276 			bool early)
277 {
278 	if (config_filename) {
279 		parse_state = FORCED_CONFIG;
280 		parse_include(config_filename, true, early, 0);
281 		parse_state = CMDLINE;
282 		return;
283 	}
284 
285 	parse_state = TOPLEVEL_CONFIG;
286 	parse_implied_config_file(config_basedir, NULL, early);
287 	parse_state = NETWORK_CONFIG;
288 	parse_implied_config_file(config_basedir, chainparams->network_name, early);
289 	parse_state = CMDLINE;
290 }
291 
initial_config_opts(const tal_t * ctx,int argc,char * argv[],char ** config_filename,char ** config_basedir,char ** config_netdir,char ** rpc_filename)292 void initial_config_opts(const tal_t *ctx,
293 			 int argc, char *argv[],
294 			 char **config_filename,
295 			 char **config_basedir,
296 			 char **config_netdir,
297 			 char **rpc_filename)
298 {
299 	options_ctx = ctx;
300 
301 	/* First, they could specify a config, which specifies a lightning dir
302 	 * or a network. */
303 	*config_filename = NULL;
304 	opt_register_early_arg("--conf=<file>", opt_set_abspath, NULL,
305 			       config_filename,
306 			       "Specify configuration file");
307 
308 	/* Cmdline can also set lightning-dir. */
309 	*config_basedir = NULL;
310 	opt_register_early_arg("--lightning-dir=<dir>",
311 			       opt_set_abspath, NULL,
312 			       config_basedir,
313 			       "Set base directory: network-specific subdirectory is under here");
314 
315 	/* Handle --version (and exit) here too */
316 	opt_register_version();
317 
318 	opt_early_parse_incomplete(argc, argv, opt_log_stderr_exit);
319 
320 	/* Now, reset and ignore --conf option from now on. */
321 	opt_free_table();
322 
323 	/* This is only ever valid on cmdline */
324 	opt_register_early_arg("--conf=<file>",
325 			       opt_restricted_cmdline, NULL,
326 			       config_filename,
327 			       "Specify configuration file");
328 
329 	/* If they set --conf it can still set --lightning-dir */
330 	if (!*config_filename) {
331 		opt_register_early_arg("--lightning-dir=<dir>",
332 				       opt_restricted_forceconf_only, opt_show_charp,
333 				       config_basedir,
334 				       "Set base directory: network-specific subdirectory is under here");
335 	} else {
336 		opt_register_early_arg("--lightning-dir=<dir>",
337 				       opt_set_abspath, NULL,
338 				       config_basedir,
339 				       "Set base directory: network-specific subdirectory is under here");
340 	}
341 
342 	/* Now, config file (or cmdline) can set network and lightning-dir */
343 
344 	/* We need to know network early, so we can set defaults (which normal
345 	 * options can change) and default config_netdir */
346 	opt_register_early_arg("--network", opt_set_network, opt_show_network,
347 			       NULL,
348 			       "Select the network parameters (bitcoin, testnet,"
349 			       " signet, regtest, litecoin or litecoin-testnet)");
350 	opt_register_early_noarg("--testnet",
351 				 opt_set_specific_network, "testnet",
352 				 "Alias for --network=testnet");
353 	opt_register_early_noarg("--signet",
354 				 opt_set_specific_network, "signet",
355 				 "Alias for --network=signet");
356 	opt_register_early_noarg("--mainnet",
357 				 opt_set_specific_network, "bitcoin",
358 				 "Alias for --network=bitcoin");
359 	opt_register_early_arg("--allow-deprecated-apis",
360 			       opt_set_bool_arg, opt_show_bool,
361 			       &deprecated_apis,
362 			       "Enable deprecated options, JSONRPC commands, fields, etc.");
363 
364 	/* Read config file first, since cmdline must override */
365 	if (*config_filename)
366 		parse_include(*config_filename, true, true, 0);
367 	else
368 		parse_implied_config_file(*config_basedir, NULL, true);
369 	opt_early_parse_incomplete(argc, argv, opt_log_stderr_exit);
370 
371 	/* We use a global (in common/utils.h) for the chainparams. */
372 	if (!chainparams)
373 		chainparams = chainparams_for_network("bitcoin");
374 
375 	if (!*config_basedir)
376 		*config_basedir = default_base_configdir(ctx);
377 
378 	*config_netdir
379 		= path_join(NULL, *config_basedir, chainparams->network_name);
380 
381 	/* Make sure it's absolute */
382 	*config_netdir = path_join(ctx, take(path_cwd(NULL)), take(*config_netdir));
383 
384 	/* Now, reset and ignore those options from now on. */
385 	opt_free_table();
386 
387 	opt_register_early_arg("--conf=<file>",
388 			       opt_restricted_cmdline, NULL,
389 			       config_filename,
390 			       "Specify configuration file");
391 
392 	/* This is never in a default config file (since we used the defaults to find it!). */
393 	opt_register_early_arg("--lightning-dir=<dir>",
394 			       opt_restricted_forceconf_only, opt_show_charp,
395 			       config_basedir,
396 			       "Set base directory: network-specific subdirectory is under here");
397 	opt_register_early_arg("--network",
398 			       opt_restricted_toplevel, opt_show_network,
399 			       NULL,
400 			       "Select the network parameters (bitcoin, testnet,"
401 			       " signet, regtest, litecoin or litecoin-testnet)");
402 	opt_register_early_noarg("--mainnet",
403 				 opt_restricted_toplevel_noarg, NULL,
404 				 "Alias for --network=bitcoin");
405 	opt_register_early_noarg("--testnet",
406 				 opt_restricted_toplevel_noarg, NULL,
407 				 "Alias for --network=testnet");
408 	opt_register_early_noarg("--signet",
409 				 opt_restricted_toplevel_noarg, NULL,
410 				 "Alias for --network=signet");
411 
412 	/* They can set this later, it's just less effective. */
413 	opt_register_early_arg("--allow-deprecated-apis",
414 			       opt_set_bool_arg, opt_show_bool,
415 			       &deprecated_apis,
416 			       "Enable deprecated options, JSONRPC commands, fields, etc.");
417 
418 	/* Set this up for when they parse cmdline proper. */
419 	*rpc_filename = default_rpcfile(ctx);
420 	opt_register_arg("--rpc-file", opt_set_talstr, opt_show_charp,
421 			 rpc_filename,
422 			 "Set JSON-RPC socket (or /dev/tty)");
423 }
424