Skip to content

Commit

Permalink
TEMP
Browse files Browse the repository at this point in the history
  • Loading branch information
darosior committed Sep 9, 2019
1 parent af0200f commit 5bcc8e7
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 17 deletions.
174 changes: 157 additions & 17 deletions lightningd/jsonrpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <lightningd/log.h>
#include <lightningd/memdump.h>
#include <lightningd/options.h>
#include <lightningd/plugin_hook.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/stat.h>
Expand Down Expand Up @@ -580,14 +581,157 @@ struct json_stream *json_stream_fail(struct command *cmd,
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;

//db_begin_transaction(jcon->ld->wallet->db);
res = cmd->json_cmd->dispatch(cmd, buffer, request, params);
//db_commit_transaction(jcon->ld->wallet->db);

assert(res == &param_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 sync 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)
{
const jsmntok_t *method, *params;

printf("\n\nReq in serialize: %.*s\n", p->request->end - p->request->start, p->buffer + p->request->start);

method = json_get_member(p->buffer, p->request, "method");
params = json_get_member(p->buffer, p->request, "params");

if (method)
printf("Method: %.*s\n", method->end - method->start, p->buffer + method->start);
if (params)
printf("Params: %.*s\n\n\n", params->end - params->start, p->buffer + params->start);

json_object_start(s, "rpc_command");
json_add_string(s, "method", tal_fmt(tmpctx, "%.*s",
method->end - method->start,
p->buffer + method->start));
json_add_string(s, "params", tal_fmt(tmpctx, "%.*s",
params->end - params->start,
p->buffer + params->start));
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, *new_method, *new_params, *custom_return, *method, *params;
struct json_stream *response;

printf("\n\nReq in callback: %.*s\n", p->request->end - p->request->start, p->buffer + p->request->start);

method = json_get_member(p->buffer, p->request, "method");
params = json_get_member(p->buffer, p->request, "params");

if (method)
printf("Method: %.*s\n", method->end - method->start, p->buffer + method->start);
if (params)
printf("Params: %.*s\n\n\n", params->end - params->start, p->buffer + params->start);

if (buffer) {
printf("Plugin response: %.*s\n\n", resulttok->end - resulttok->start, buffer + resulttok->start);
}
/* If no plugin registered, there is no restriction. */
if (buffer == NULL || json_tok_streq(buffer, resulttok, "continue")) {
return was_pending(command_exec(p->cmd->jcon, p->cmd, p->buffer,
method, 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) {
new_method = json_get_member(buffer, tok, "method");
new_params = json_get_member(buffer, tok, "params");
if (!new_method || !new_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, new_method);
return was_pending(command_exec(p->cmd->jcon, p->cmd, buffer,
new_method, new_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_stream_success(p->cmd);
json_add_member(response, "%.*s",
custom_return->end - custom_return->start,
buffer + custom_return->start);
return was_pending(command_success(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 *);

/* 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 command_result *res;
struct rpc_command_hook_payload *rpc_hook;

if (tok[0].type != JSMN_OBJECT) {
json_command_malformed(jcon, "null",
Expand Down Expand Up @@ -646,22 +790,18 @@ parse_request(struct json_connection *jcon, const jsmntok_t tok[])
jcon->buffer + method->start);
}

db_begin_transaction(jcon->ld->wallet->db);
res = c->json_cmd->dispatch(c, jcon->buffer, tok, params);
db_commit_transaction(jcon->ld->wallet->db);

assert(res == &param_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(c->pending);
list_for_each(&jcon->commands, c, list)
assert(c->pending);
return res;
rpc_hook = tal(c, struct rpc_command_hook_payload);
rpc_hook->cmd = c;
/* Duplicate since we might outlive the connection */
rpc_hook->buffer = tal_strdup(rpc_hook, jcon->buffer);
rpc_hook->request = tal_dup_arr(rpc_hook, const jsmntok_t, tok, tal_bytelen(tok), 0);
//rpc_hook->request = tal_steal(rpc_hook, tok);
//rpc_hook->request = tok;
printf("\n\nOriginal req in parse_request: %.*s\n\n", tok->end - tok->start, jcon->buffer + tok->start);
printf("\n\nReq in parse_request: %.*s\n\n", rpc_hook->request->end - rpc_hook->request->start, rpc_hook->buffer + rpc_hook->request->start);
plugin_hook_call_rpc_command(jcon->ld, rpc_hook, rpc_hook);

return command_still_pending(c);
}

/* Mutual recursion */
Expand Down
4 changes: 4 additions & 0 deletions lightningd/test/run-jsonrpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ struct command_result *param_tok(struct command *cmd UNNEEDED, const char *name
const char *buffer UNNEEDED, const jsmntok_t * tok UNNEEDED,
const jsmntok_t **out UNNEEDED)
{ fprintf(stderr, "param_tok called!\n"); abort(); }
/* Generated stub for plugin_hook_call_ */
void plugin_hook_call_(struct lightningd *ld UNNEEDED, const struct plugin_hook *hook UNNEEDED,
void *payload UNNEEDED, void *cb_arg UNNEEDED)
{ fprintf(stderr, "plugin_hook_call_ called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */

bool deprecated_apis;
Expand Down
25 changes: 25 additions & 0 deletions tests/plugins/rpc_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""
This plugin is used to test the `rpc_command` hook.
"""
import json
from lightning import Plugin

plugin = Plugin(dynamic=False)


@plugin.hook("rpc_command")
def on_rpc_command(plugin, rpc_command, **kwargs):
if rpc_command["method"] == "invoice":
plugin.log(str(rpc_command), 'warn')
rpc_command["params"] = json.loads(rpc_command["params"])
rpc_command["params"]["description"] = "A plugin modified this description"
plugin.log(str(rpc_command), 'warn')
return {"replace": rpc_command}
elif rpc_command["method"] == "sendpay":
# Don't allow this command to be executed
return {"return": {"error": {"code": -1, "message": "You cannot do this"}}}
return "continue"


plugin.run()
14 changes: 14 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,3 +673,17 @@ def test_plugin_deprecated_relpath(node_factory):
assert l1.daemon.is_in_log('DEPRECATED WARNING.*plugin={}'
.format(os.path.join(os.getcwd(),
'tests/plugins/millisatoshis.py')))


def test_rpc_command_hook(node_factory):
"""Test the `sensitive_command` hook"""
plugin = os.path.join(os.getcwd(), "tests/plugins/rpc_command.py")
l1 = node_factory.get_node(options={"plugin": plugin})

# Usage of "dev-listaddrs" has been restricted by the plugin
with pytest.raises(RpcError, match=r"You cannot do this"):
l1.rpc.call("sendpay")

invoice = l1.rpc.invoice(10**6, "test_side", "test_input")
decoded = l1.rpc.decodepay(invoice["bolt11"])
assert decoded["description"] == "A plugin modified this description"

0 comments on commit 5bcc8e7

Please sign in to comment.