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