/* Code for JSON_RPC API. * * Each socket connection is represented by a `struct json_connection`. * * This can have zero, one or more `struct command` in progress at a time: * because the json_connection can be closed at any point, these `struct command` * have a independent lifetimes. * * Each `struct command` writes into a `struct json_stream`, which is created * the moment they start writing output (see attach_json_stream). Initially * the struct command owns it since they're writing into it. When they're * done, the `json_connection` needs to drain it (if it's still around). At * that point, the `json_connection` becomes the owner (or it's simply freed). */ /* eg: { "jsonrpc":"2.0", "method" : "dev-echo", "params" : [ "hello", "Arabella!" ], "id" : "1" } */ #include "ccan/config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Dummy structure. */ struct command_result { char c; }; static struct command_result param_failed, complete, pending, unknown; struct command_result *command_param_failed(void) { return ¶m_failed; } struct command_result *command_its_complicated(const char *relationship_details UNNEEDED) { return &unknown; } /* This represents a JSON RPC connection. It can invoke multiple commands, but * a command can outlive the connection, which could close any time. */ struct json_connection { /* The global state */ struct lightningd *ld; /* This io_conn (and our owner!) */ struct io_conn *conn; /* Logging for this json connection. */ struct log *log; /* The buffer (required to interpret tokens). */ char *buffer; /* Internal state: */ /* How much is already filled. */ size_t used; /* How much has just been filled. */ size_t len_read; /* Our commands */ struct list_head commands; /* Our json_streams (owned by the commands themselves while running). * Since multiple streams could start returning data at once, we * always service these in order, freeing once empty. */ struct json_stream **js_arr; }; /** * `jsonrpc` encapsulates the entire state of the JSON-RPC interface, * including a list of methods that the interface supports (can be * appended dynamically, e.g., for plugins, and logs. It also serves * as a convenient `tal`-parent for all JSON-RPC related allocations. */ struct jsonrpc { struct io_listener *rpc_listener; struct json_command **commands; struct log *log; /* Map from json command names to usage strings: we don't put this inside * struct json_command as it's good practice to have those const. */ STRMAP(const char *) usagemap; }; /* The command itself usually owns the stream, because jcon may get closed. * The command transfers ownership once it's done though. */ static struct json_stream *jcon_new_json_stream(const tal_t *ctx, struct json_connection *jcon, struct command *writer) { struct json_stream *js = new_json_stream(ctx, writer, jcon->log); /* Wake writer to start streaming, in case it's not already. */ io_wake(jcon); /* FIXME: Keep streams around for recycling. */ tal_arr_expand(&jcon->js_arr, js); return js; } static void jcon_remove_json_stream(struct json_connection *jcon, struct json_stream *js) { for (size_t i = 0; i < tal_count(jcon->js_arr); i++) { if (js != jcon->js_arr[i]) continue; tal_arr_remove(&jcon->js_arr, i); return; } abort(); } /* jcon and cmd have separate lifetimes: we detach them on either destruction */ static void destroy_jcon(struct json_connection *jcon) { struct command *c; list_for_each(&jcon->commands, c, list) { log_debug(jcon->log, "Abandoning command %s", c->json_cmd->name); c->jcon = NULL; } /* Make sure this happens last! */ tal_free(jcon->log); } static struct command_result *json_help(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params); static const struct json_command help_command = { "help", "utility", json_help, "List available commands, or give verbose help on one {command}.", .verbose = "help [command]\n" "Without [command]:\n" " Outputs an array of objects with 'command' and 'description'\n" "With [command]:\n" " Give a single object containing 'verbose', which completely describes\n" " the command inputs and outputs." }; AUTODATA(json_command, &help_command); static struct command_result *json_stop(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct json_out *jout; const char *p; size_t len; if (!param(cmd, buffer, params, NULL)) return command_param_failed(); /* This can't have closed yet! */ cmd->ld->stop_conn = cmd->jcon->conn; log_unusual(cmd->ld->log, "JSON-RPC shutdown"); /* This is the one place where result is a literal string. */ jout = json_out_new(tmpctx); json_out_start(jout, NULL, '{'); json_out_addstr(jout, "jsonrpc", "2.0"); /* id may be a string or number, so copy direct. */ memcpy(json_out_member_direct(jout, "id", strlen(cmd->id)), cmd->id, strlen(cmd->id)); json_out_addstr(jout, "result", "Shutdown complete"); json_out_end(jout, '}'); json_out_finished(jout); /* Add two \n */ memcpy(json_out_direct(jout, 2), "\n\n", strlen("\n\n")); p = json_out_contents(jout, &len); cmd->ld->stop_response = tal_strndup(cmd->ld, p, len); /* Wake write loop in case it's not already. */ io_wake(cmd->jcon); return command_still_pending(cmd); } static const struct json_command stop_command = { "stop", "utility", json_stop, "Shut down the lightningd process" }; AUTODATA(json_command, &stop_command); #if DEVELOPER struct slowcmd { struct command *cmd; unsigned *msec; struct json_stream *js; }; static void slowcmd_finish(struct slowcmd *sc) { json_add_num(sc->js, "msec", *sc->msec); was_pending(command_success(sc->cmd, sc->js)); } static void slowcmd_start(struct slowcmd *sc) { sc->js = json_stream_success(sc->cmd); new_reltimer(sc->cmd->ld->timers, sc, time_from_msec(*sc->msec), slowcmd_finish, sc); } static struct command_result *json_dev(struct command *cmd UNUSED, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { const char *subcmd; subcmd = param_subcommand(cmd, buffer, params, "crash", "rhash", "slowcmd", NULL); if (!subcmd) return command_param_failed(); if (streq(subcmd, "crash")) { if (!param(cmd, buffer, params, p_req("subcommand", param_ignore, cmd), NULL)) return command_param_failed(); fatal("Crash at user request"); } else if (streq(subcmd, "slowcmd")) { struct slowcmd *sc = tal(cmd, struct slowcmd); sc->cmd = cmd; if (!param(cmd, buffer, params, p_req("subcommand", param_ignore, cmd), p_opt_def("msec", param_number, &sc->msec, 1000), NULL)) return command_param_failed(); new_reltimer(cmd->ld->timers, sc, time_from_msec(0), slowcmd_start, sc); return command_still_pending(cmd); } else { assert(streq(subcmd, "rhash")); struct json_stream *response; struct sha256 *secret; if (!param(cmd, buffer, params, p_req("subcommand", param_ignore, cmd), p_req("secret", param_sha256, &secret), NULL)) return command_param_failed(); /* Hash in place. */ sha256(secret, secret, sizeof(*secret)); response = json_stream_success(cmd); json_add_sha256(response, "rhash", secret); return command_success(cmd, response); } } static const struct json_command dev_command = { "dev", "developer", json_dev, "Developer command test multiplexer", .verbose = "dev rhash {secret}\n" " Show SHA256 of {secret}\n" "dev crash\n" " Crash lightningd by calling fatal()\n" "dev slowcmd {msec}\n" " Torture test for slow commands, optional {msec}\n" }; AUTODATA(json_command, &dev_command); #endif /* DEVELOPER */ static size_t num_cmdlist; static struct json_command **get_cmdlist(void) { static struct json_command **cmdlist; if (!cmdlist) cmdlist = autodata_get(json_command, &num_cmdlist); return cmdlist; } static void json_add_help_command(struct command *cmd, struct json_stream *response, struct json_command *json_command) { char *usage; usage = tal_fmt(cmd, "%s %s", json_command->name, strmap_get(&cmd->ld->jsonrpc->usagemap, json_command->name)); json_object_start(response, NULL); json_add_string(response, "command", usage); json_add_string(response, "category", json_command->category); json_add_string(response, "description", json_command->description); if (!json_command->verbose) { json_add_string(response, "verbose", "HELP! Please contribute" " a description for this" " json_command!"); } else { struct json_escape *esc; esc = json_escape(NULL, json_command->verbose); json_add_escaped_string(response, "verbose", take(esc)); } json_object_end(response); } static const struct json_command *find_command(struct json_command **commands, const char *buffer, const jsmntok_t *cmdtok) { for (size_t i = 0; i < tal_count(commands); i++) { if (json_tok_streq(buffer, cmdtok, commands[i]->name)) return commands[i]; } return NULL; } static int compare_commands_name(struct json_command *const *a, struct json_command *const *b, void *unused) { return strcmp((*a)->name, (*b)->name); } static struct command_result *json_help(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { struct json_stream *response; const jsmntok_t *cmdtok; struct json_command **commands; const struct json_command *one_cmd; if (!param(cmd, buffer, params, p_opt("command", param_tok, &cmdtok), NULL)) return command_param_failed(); commands = cmd->ld->jsonrpc->commands; if (cmdtok) { one_cmd = find_command(commands, buffer, cmdtok); if (!one_cmd) return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND, "Unknown command '%.*s'", cmdtok->end - cmdtok->start, buffer + cmdtok->start); } else one_cmd = NULL; asort(commands, tal_count(commands), compare_commands_name, NULL); response = json_stream_success(cmd); json_array_start(response, "help"); for (size_t i = 0; i < tal_count(commands); i++) { if (!one_cmd || one_cmd == commands[i]) json_add_help_command(cmd, response, commands[i]); } json_array_end(response); /* Tell cli this is simple enough to be formatted flat for humans */ json_add_string(response, "format-hint", "simple"); return command_success(cmd, response); } static const struct json_command *find_cmd(const struct jsonrpc *rpc, const char *buffer, const jsmntok_t *tok) { struct json_command **commands = rpc->commands; for (size_t i = 0; i < tal_count(commands); i++) if (json_tok_streq(buffer, tok, commands[i]->name)) return commands[i]; return NULL; } /* This can be called directly on shutdown, even with unfinished cmd */ static void destroy_command(struct command *cmd) { if (!cmd->jcon) { log_debug(cmd->ld->log, "Command returned result after jcon close"); return; } list_del_from(&cmd->jcon->commands, &cmd->list); } struct command_result *command_raw_complete(struct command *cmd, struct json_stream *result) { json_stream_close(result, cmd); /* If we have a jcon, it will free result for us. */ if (cmd->jcon) tal_steal(cmd->jcon, result); tal_free(cmd); return &complete; } struct command_result *command_success(struct command *cmd, struct json_stream *result) { assert(cmd); assert(cmd->json_stream == result); json_object_end(result); json_object_compat_end(result); return command_raw_complete(cmd, result); } struct command_result *command_failed(struct command *cmd, struct json_stream *result) { assert(cmd->json_stream == result); /* Have to close error */ json_object_end(result); json_object_compat_end(result); return command_raw_complete(cmd, result); } struct command_result *command_fail(struct command *cmd, int code, const char *fmt, ...) { const char *errmsg; struct json_stream *r; va_list ap; va_start(ap, fmt); errmsg = tal_vfmt(cmd, fmt, ap); va_end(ap); r = json_stream_fail_nodata(cmd, code, errmsg); return command_failed(cmd, r); } struct command_result *command_still_pending(struct command *cmd) { notleak_with_children(cmd); cmd->pending = true; /* If we've started writing, wake reader. */ if (cmd->json_stream) json_stream_flush(cmd->json_stream); return &pending; } static void json_command_malformed(struct json_connection *jcon, const char *id, const char *error) { /* NULL writer is OK here, since we close it immediately. */ struct json_stream *js = jcon_new_json_stream(jcon, jcon, NULL); json_object_start(js, NULL); json_add_string(js, "jsonrpc", "2.0"); json_add_literal(js, "id", id, strlen(id)); json_object_start(js, "error"); json_add_member(js, "code", false, "%d", JSONRPC2_INVALID_REQUEST); json_add_string(js, "message", error); json_object_end(js); json_object_compat_end(js); json_stream_close(js, NULL); } struct json_stream *json_stream_raw_for_cmd(struct command *cmd) { struct json_stream *js; /* If they still care about the result, attach it to them. */ if (cmd->jcon) js = jcon_new_json_stream(cmd, cmd->jcon, cmd); else js = new_json_stream(cmd, cmd, NULL); assert(!cmd->json_stream); cmd->json_stream = js; return js; } void json_stream_log_suppress_for_cmd(struct json_stream *js, const struct command *cmd) { const char *nm = cmd->json_cmd->name; const char *s = tal_fmt(tmpctx, "Suppressing logging of %s command", nm); log_io(cmd->jcon->log, LOG_IO_OUT, s, NULL, 0); json_stream_log_suppress(js, strdup(nm)); } static struct json_stream *json_start(struct command *cmd) { struct json_stream *js = json_stream_raw_for_cmd(cmd); json_object_start(js, NULL); json_add_string(js, "jsonrpc", "2.0"); json_add_literal(js, "id", cmd->id, strlen(cmd->id)); return js; } struct json_stream *json_stream_success(struct command *cmd) { struct json_stream *r = json_start(cmd); json_object_start(r, "result"); return r; } struct json_stream *json_stream_fail_nodata(struct command *cmd, int code, const char *errmsg) { struct json_stream *js = json_start(cmd); assert(code); json_object_start(js, "error"); json_add_member(js, "code", false, "%d", code); json_add_string(js, "message", errmsg); return js; } struct json_stream *json_stream_fail(struct command *cmd, int code, const char *errmsg) { struct json_stream *r = json_stream_fail_nodata(cmd, code, errmsg); json_object_start(r, "data"); return r; } static struct command_result *command_exec(struct json_connection *jcon, struct command *cmd, const char *buffer, const jsmntok_t *request, const jsmntok_t *params) { struct command_result *res; res = cmd->json_cmd->dispatch(cmd, buffer, request, params); assert(res == ¶m_failed || res == &complete || res == &pending || res == &unknown); /* If they didn't complete it, they must call command_still_pending. * If they completed it, it's freed already. */ if (res == &pending) assert(cmd->pending); list_for_each(&jcon->commands, cmd, list) assert(cmd->pending); return res; } /* A plugin hook to take over (fail/alter) RPC commands */ struct rpc_command_hook_payload { struct command *cmd; const char *buffer; const jsmntok_t *request; }; static void rpc_command_hook_serialize(struct rpc_command_hook_payload *p, struct json_stream *s) { json_object_start(s, "rpc_command"); json_add_tok(s, "rpc_command", p->request, p->buffer); json_object_end(s); } static void rpc_command_hook_callback(struct rpc_command_hook_payload *p, const char *buffer, const jsmntok_t *resulttok) { const jsmntok_t *tok, *method, *params, *custom_return, *tok_continue; struct json_stream *response; bool exec; params = json_get_member(p->buffer, p->request, "params"); /* If no plugin registered, just continue command execution. Same if * the registered plugin tells us to do so. */ if (buffer == NULL) return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer, p->request, params)); else { tok_continue = json_get_member(buffer, resulttok, "continue"); if (tok_continue && json_to_bool(buffer, tok_continue, &exec) && exec) return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer, p->request, params)); } /* If the registered plugin did not respond with continue, * it wants either to replace the request... */ tok = json_get_member(buffer, resulttok, "replace"); if (tok) { method = json_get_member(buffer, tok, "method"); params = json_get_member(buffer, tok, "params"); if (!method || !params) return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, "Bad response to 'rpc_command' hook: " "the 'replace' object must contain a " "'method' and a 'params' field.")); p->cmd->json_cmd = find_cmd(p->cmd->ld->jsonrpc, buffer, method); return was_pending(command_exec(p->cmd->jcon, p->cmd, buffer, method, params)); } /* ...or return a custom JSONRPC response. */ tok = json_get_member(buffer, resulttok, "return"); if (tok) { custom_return = json_get_member(buffer, tok, "result"); if (custom_return) { response = json_start(p->cmd); json_add_tok(response, "result", custom_return, buffer); json_object_compat_end(response); return was_pending(command_raw_complete(p->cmd, response)); } custom_return = json_get_member(buffer, tok, "error"); if (custom_return) { int code; const char *errmsg; if (!json_to_int(buffer, json_get_member(buffer, custom_return, "code"), &code)) return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, "Bad response to 'rpc_command' hook: " "'error' object does not contain a code.")); errmsg = json_strdup(tmpctx, buffer, json_get_member(buffer, custom_return, "message")); if (!errmsg) return was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, "Bad response to 'rpc_command' hook: " "'error' object does not contain a message.")); response = json_stream_fail_nodata(p->cmd, code, errmsg); return was_pending(command_failed(p->cmd, response)); } } was_pending(command_fail(p->cmd, JSONRPC2_INVALID_REQUEST, "Bad response to 'rpc_command' hook.")); } REGISTER_PLUGIN_HOOK(rpc_command, rpc_command_hook_callback, struct rpc_command_hook_payload *, rpc_command_hook_serialize, struct rpc_command_hook_payload *); static void call_rpc_command_hook(struct rpc_command_hook_payload *p) { plugin_hook_call_rpc_command(p->cmd->ld, p, p); } /* We return struct command_result so command_fail return value has a natural * sink; we don't actually use the result. */ static struct command_result * parse_request(struct json_connection *jcon, const jsmntok_t tok[]) { const jsmntok_t *method, *id, *params; struct command *c; struct rpc_command_hook_payload *rpc_hook; if (tok[0].type != JSMN_OBJECT) { json_command_malformed(jcon, "null", "Expected {} for json command"); return NULL; } method = json_get_member(jcon->buffer, tok, "method"); params = json_get_member(jcon->buffer, tok, "params"); id = json_get_member(jcon->buffer, tok, "id"); if (!id) { json_command_malformed(jcon, "null", "No id"); return NULL; } if (id->type != JSMN_STRING && id->type != JSMN_PRIMITIVE) { json_command_malformed(jcon, "null", "Expected string/primitive for id"); return NULL; } /* Allocate the command off of the `jsonrpc` object and not * the connection since the command may outlive `conn`. */ c = tal(jcon->ld->jsonrpc, struct command); c->jcon = jcon; c->ld = jcon->ld; c->pending = false; c->json_stream = NULL; c->id = tal_strndup(c, json_tok_full(jcon->buffer, id), json_tok_full_len(id)); c->mode = CMD_NORMAL; list_add_tail(&jcon->commands, &c->list); tal_add_destructor(c, destroy_command); if (!method || !params) { return command_fail(c, JSONRPC2_INVALID_REQUEST, method ? "No params" : "No method"); } if (method->type != JSMN_STRING) { return command_fail(c, JSONRPC2_INVALID_REQUEST, "Expected string for method"); } c->json_cmd = find_cmd(jcon->ld->jsonrpc, jcon->buffer, method); if (!c->json_cmd) { return command_fail( c, JSONRPC2_METHOD_NOT_FOUND, "Unknown command '%.*s'", method->end - method->start, jcon->buffer + method->start); } if (c->json_cmd->deprecated && !deprecated_apis) { return command_fail(c, JSONRPC2_METHOD_NOT_FOUND, "Command '%.*s' is deprecated", method->end - method->start, jcon->buffer + method->start); } rpc_hook = tal(c, struct rpc_command_hook_payload); rpc_hook->cmd = c; /* Duplicate since we might outlive the connection */ rpc_hook->buffer = tal_dup_arr(rpc_hook, char, jcon->buffer, tal_count(jcon->buffer), 0); rpc_hook->request = tal_dup_arr(rpc_hook, const jsmntok_t, tok, tal_count(tok), 0); /* Prevent a race between was_pending and still_pending */ new_reltimer(c->ld->timers, rpc_hook, time_from_msec(1), call_rpc_command_hook, rpc_hook); return command_still_pending(c); } /* Mutual recursion */ static struct io_plan *stream_out_complete(struct io_conn *conn, struct json_stream *js, struct json_connection *jcon); static struct io_plan *start_json_stream(struct io_conn *conn, struct json_connection *jcon) { /* If something has created an output buffer, start streaming. */ if (tal_count(jcon->js_arr)) return json_stream_output(jcon->js_arr[0], conn, stream_out_complete, jcon); /* Tell reader it can run next command. */ io_wake(conn); /* Once the stop_conn conn is drained, we can shut down. */ if (jcon->ld->stop_conn == conn) { /* Return us to toplevel lightningd.c */ io_break(jcon->ld); /* We never come back. */ return io_out_wait(conn, conn, io_never, conn); } return io_out_wait(conn, jcon, start_json_stream, jcon); } /* Command has completed writing, and we've written it all out to conn. */ static struct io_plan *stream_out_complete(struct io_conn *conn, struct json_stream *js, struct json_connection *jcon) { jcon_remove_json_stream(jcon, js); tal_free(js); /* Wait for more output. */ return start_json_stream(conn, jcon); } static struct io_plan *read_json(struct io_conn *conn, struct json_connection *jcon) { jsmntok_t *toks; bool valid; if (jcon->len_read) log_io(jcon->log, LOG_IO_IN, "", jcon->buffer + jcon->used, jcon->len_read); /* Resize larger if we're full. */ jcon->used += jcon->len_read; if (jcon->used == tal_count(jcon->buffer)) tal_resize(&jcon->buffer, jcon->used * 2); /* We wait for pending output to be consumed, to avoid DoS */ if (tal_count(jcon->js_arr) != 0) { jcon->len_read = 0; return io_wait(conn, conn, read_json, jcon); } toks = json_parse_input(jcon->buffer, jcon->buffer, jcon->used, &valid); if (!toks) { if (!valid) { log_unusual(jcon->log, "Invalid token in json input: '%.*s'", (int)jcon->used, jcon->buffer); json_command_malformed( jcon, "null", "Invalid token in json input"); return io_halfclose(conn); } /* We need more. */ goto read_more; } /* Empty buffer? (eg. just whitespace). */ if (tal_count(toks) == 1) { jcon->used = 0; goto read_more; } parse_request(jcon, toks); /* Remove first {}. */ memmove(jcon->buffer, jcon->buffer + toks[0].end, tal_count(jcon->buffer) - toks[0].end); jcon->used -= toks[0].end; /* If we have more to process, try again. FIXME: this still gets * first priority in io_loop, so can starve others. Hack would be * a (non-zero) timer, but better would be to have io_loop avoid * such livelock */ if (jcon->used) { tal_free(toks); jcon->len_read = 0; return io_always(conn, read_json, jcon); } read_more: tal_free(toks); return io_read_partial(conn, jcon->buffer + jcon->used, tal_count(jcon->buffer) - jcon->used, &jcon->len_read, read_json, jcon); } static struct io_plan *jcon_connected(struct io_conn *conn, struct lightningd *ld) { struct json_connection *jcon; /* We live as long as the connection, so we're not a leak. */ jcon = notleak(tal(conn, struct json_connection)); jcon->conn = conn; jcon->ld = ld; jcon->used = 0; jcon->buffer = tal_arr(jcon, char, 64); jcon->js_arr = tal_arr(jcon, struct json_stream *, 0); jcon->len_read = 0; list_head_init(&jcon->commands); /* We want to log on destruction, so we free this in destructor. */ jcon->log = new_log(ld->log_book, ld->log_book, "%sjcon fd %i:", log_prefix(ld->log), io_conn_fd(conn)); tal_add_destructor(jcon, destroy_jcon); /* Note that write_json and read_json alternate manually, by waking * each other. It would be simpler to not use a duplex io, and have * read_json parse one command, then io_wait() for command completion * and go to write_json. * * However, if we ever have notifications, this neat cmd-response * pattern would break down, so we use this trick. */ return io_duplex(conn, read_json(conn, jcon), start_json_stream(conn, jcon)); } static struct io_plan *incoming_jcon_connected(struct io_conn *conn, struct lightningd *ld) { /* Lifetime of JSON conn is limited to fd connect time. */ return jcon_connected(notleak(conn), ld); } static void destroy_json_command(struct json_command *command, struct jsonrpc *rpc) { strmap_del(&rpc->usagemap, command->name, NULL); for (size_t i = 0; i < tal_count(rpc->commands); i++) { if (rpc->commands[i] == command) { tal_arr_remove(&rpc->commands, i); return; } } abort(); } static bool command_add(struct jsonrpc *rpc, struct json_command *command) { size_t count = tal_count(rpc->commands); /* Check that we don't clobber a method */ for (size_t i = 0; i < count; i++) if (streq(rpc->commands[i]->name, command->name)) return false; tal_arr_expand(&rpc->commands, command); return true; } /* Built-in commands get called to construct usage string via param() */ static void setup_command_usage(struct lightningd *ld, struct json_command *command) { const struct command_result *res; struct command *dummy; /* Call it with minimal cmd, to fill out usagemap */ dummy = tal(tmpctx, struct command); dummy->mode = CMD_USAGE; dummy->ld = ld; dummy->json_cmd = command; res = command->dispatch(dummy, NULL, NULL, NULL); assert(res == ¶m_failed); assert(strmap_get(&ld->jsonrpc->usagemap, command->name)); } bool jsonrpc_command_add(struct jsonrpc *rpc, struct json_command *command, const char *usage TAKES) { if (!command_add(rpc, command)) return false; usage = tal_strdup(command, usage); strmap_add(&rpc->usagemap, command->name, usage); tal_add_destructor2(command, destroy_json_command, rpc); return true; } static bool jsonrpc_command_add_perm(struct lightningd *ld, struct jsonrpc *rpc, struct json_command *command) { if (!command_add(rpc, command)) return false; setup_command_usage(ld, command); return true; } static void destroy_jsonrpc(struct jsonrpc *jsonrpc) { strmap_clear(&jsonrpc->usagemap); } #if DEVELOPER static void memleak_help_jsonrpc(struct htable *memtable, struct jsonrpc *jsonrpc) { memleak_remove_strmap(memtable, &jsonrpc->usagemap); } #endif /* DEVELOPER */ void jsonrpc_setup(struct lightningd *ld) { struct json_command **commands = get_cmdlist(); ld->rpc_filename = default_rpcfile(ld); ld->jsonrpc = tal(ld, struct jsonrpc); strmap_init(&ld->jsonrpc->usagemap); ld->jsonrpc->commands = tal_arr(ld->jsonrpc, struct json_command *, 0); ld->jsonrpc->log = new_log(ld->jsonrpc, ld->log_book, "jsonrpc"); for (size_t i=0; ijsonrpc, commands[i])) fatal("Cannot add duplicate command %s", commands[i]->name); } ld->jsonrpc->rpc_listener = NULL; tal_add_destructor(ld->jsonrpc, destroy_jsonrpc); memleak_add_helper(ld->jsonrpc, memleak_help_jsonrpc); } bool command_usage_only(const struct command *cmd) { return cmd->mode == CMD_USAGE; } void command_set_usage(struct command *cmd, const char *usage TAKES) { usage = tal_strdup(cmd->ld, usage); if (!strmap_add(&cmd->ld->jsonrpc->usagemap, cmd->json_cmd->name, usage)) fatal("Two usages for command %s?", cmd->json_cmd->name); } bool command_check_only(const struct command *cmd) { return cmd->mode == CMD_CHECK; } void jsonrpc_listen(struct jsonrpc *jsonrpc, struct lightningd *ld) { struct sockaddr_un addr; int fd, old_umask; const char *rpc_filename = ld->rpc_filename; /* Should not initialize it twice. */ assert(!jsonrpc->rpc_listener); if (streq(rpc_filename, "/dev/tty")) { fd = open(rpc_filename, O_RDWR); if (fd == -1) err(1, "Opening %s", rpc_filename); /* Technically this is a leak, but there's only one */ notleak(io_new_conn(ld, fd, jcon_connected, ld)); return; } fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { errx(1, "domain socket creation failed"); } if (strlen(rpc_filename) + 1 > sizeof(addr.sun_path)) errx(1, "rpc filename '%s' too long", rpc_filename); strcpy(addr.sun_path, rpc_filename); addr.sun_family = AF_UNIX; /* Of course, this is racy! */ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) errx(1, "rpc filename '%s' in use", rpc_filename); unlink(rpc_filename); /* This file is only rw by us! */ old_umask = umask(0177); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))) err(1, "Binding rpc socket to '%s'", rpc_filename); umask(old_umask); if (listen(fd, 128) != 0) err(1, "Listening on '%s'", rpc_filename); jsonrpc->rpc_listener = io_new_listener( ld->rpc_filename, fd, incoming_jcon_connected, ld); log_debug(jsonrpc->log, "Listening on '%s'", ld->rpc_filename); } static struct command_result *param_command(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, const jsmntok_t **out) { cmd->json_cmd = find_cmd(cmd->jcon->ld->jsonrpc, buffer, tok); if (cmd->json_cmd) { *out = tok; return NULL; } return command_fail(cmd, JSONRPC2_METHOD_NOT_FOUND, "Unknown command '%.*s'", tok->end - tok->start, buffer + tok->start); } struct jsonrpc_notification *jsonrpc_notification_start(const tal_t *ctx, const char *method) { struct jsonrpc_notification *n = tal(ctx, struct jsonrpc_notification); n->method = tal_strdup(n, method); n->stream = new_json_stream(n, NULL, NULL); json_object_start(n->stream, NULL); json_add_string(n->stream, "jsonrpc", "2.0"); json_add_string(n->stream, "method", method); json_object_start(n->stream, "params"); return n; } void jsonrpc_notification_end(struct jsonrpc_notification *n) { json_object_end(n->stream); /* closes '.params' */ json_object_end(n->stream); /* closes '.' */ /* We guarantee to have \n\n at end of each response. */ json_stream_append(n->stream, "\n\n", strlen("\n\n")); } struct jsonrpc_request *jsonrpc_request_start_( const tal_t *ctx, const char *method, struct log *log, void (*response_cb)(const char *buffer, const jsmntok_t *toks, const jsmntok_t *idtok, void *), void *response_cb_arg) { struct jsonrpc_request *r = tal(ctx, struct jsonrpc_request); static u64 next_request_id = 0; r->id = next_request_id++; r->response_cb = response_cb; r->response_cb_arg = response_cb_arg; r->method = NULL; r->stream = new_json_stream(r, NULL, log); /* If no method is specified we don't prefill the JSON-RPC * request with the header. This serves as an escape hatch to * get a raw request, but get a valid request-id assigned. */ if (method != NULL) { r->method = tal_strdup(r, method); json_object_start(r->stream, NULL); json_add_string(r->stream, "jsonrpc", "2.0"); json_add_u64(r->stream, "id", r->id); json_add_string(r->stream, "method", method); json_object_start(r->stream, "params"); } return r; } void jsonrpc_request_end(struct jsonrpc_request *r) { json_object_end(r->stream); /* closes '.params' */ json_object_end(r->stream); /* closes '.' */ /* We guarantee to have \n\n at end of each response. */ json_stream_append(r->stream, "\n\n", strlen("\n\n")); } /* We add this destructor as a canary to detect cmd failing. */ static void destroy_command_canary(struct command *cmd, bool *failed) { *failed = true; } static struct command_result *json_check(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, const jsmntok_t *params) { jsmntok_t *mod_params; const jsmntok_t *name_tok; bool failed; struct json_stream *response; struct command_result *res; if (cmd->mode == CMD_USAGE) { mod_params = NULL; } else { mod_params = json_tok_copy(cmd, params); } if (!param(cmd, buffer, mod_params, p_req("command_to_check", param_command, &name_tok), p_opt_any(), NULL)) return command_param_failed(); /* Point name_tok to the name, not the value */ if (params->type == JSMN_OBJECT) name_tok--; json_tok_remove(&mod_params, mod_params, name_tok, 1); cmd->mode = CMD_CHECK; failed = false; tal_add_destructor2(cmd, destroy_command_canary, &failed); res = cmd->json_cmd->dispatch(cmd, buffer, mod_params, mod_params); /* CMD_CHECK always makes it "fail" parameter parsing. */ assert(res == ¶m_failed); if (failed) return res; response = json_stream_success(cmd); json_add_string(response, "command_to_check", cmd->json_cmd->name); return command_success(cmd, response); } static const struct json_command check_command = { "check", "utility", json_check, "Don't run {command_to_check}, just verify parameters.", .verbose = "check command_to_check [parameters...]\n" }; AUTODATA(json_command, &check_command);