Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If you keep writing weird runes, I'll be back... #5539

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ccan/README
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CCAN imported from http://ccodearchive.net.

CCAN version: init-2545-g7b11e744
CCAN version: init-2548-gab87e56b
36 changes: 20 additions & 16 deletions ccan/ccan/rune/coding.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ static void rune_altern_encode(const struct rune_altern *altern,
break;
esc[1] = p[len];
cb(esc, 2, arg);
p++;
p += len + 1;
}
}

Expand Down Expand Up @@ -184,7 +184,7 @@ static bool pull_char(const char **data, size_t *len, char *c)
return true;
}

static bool is_valid_cond(enum rune_condition cond)
bool rune_condition_is_valid(enum rune_condition cond)
{
switch (cond) {
case RUNE_COND_IF_MISSING:
Expand All @@ -203,31 +203,35 @@ static bool is_valid_cond(enum rune_condition cond)
return false;
}

size_t rune_altern_fieldname_len(const char *alternstr, size_t alternstrlen)
{
for (size_t i = 0; i < alternstrlen; i++) {
if (cispunct(alternstr[i]))
return i;
}
return alternstrlen;
}

/* Sets *more on success: true if another altern follows */
static struct rune_altern *rune_altern_decode(const tal_t *ctx,
const char **data, size_t *len,
bool *more)
{
struct rune_altern *alt = tal(ctx, struct rune_altern);
const char *strstart = *data;
char *value;
size_t strlen = 0;
size_t strlen;
char c;

/* Swallow field up to conditional */
for (;;) {
if (!pull_char(data, len, &c))
return tal_free(alt);
if (cispunct(c))
break;
strlen++;
}
/* Swallow field up to possible conditional */
strlen = rune_altern_fieldname_len(*data, *len);
alt->fieldname = tal_strndup(alt, *data, strlen);
*data += strlen;
*len -= strlen;

alt->fieldname = tal_strndup(alt, strstart, strlen);
if (!is_valid_cond(c)) {
pull_invalid(data, len);
/* Grab conditional */
if (!pull_char(data, len, &c) || !rune_condition_is_valid(c))
return tal_free(alt);
}

alt->condition = c;

/* Assign worst case. */
Expand Down
21 changes: 21 additions & 0 deletions ccan/ccan/rune/rune.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,25 @@ char *rune_to_string(const tal_t *ctx, const struct rune *rune);
struct rune_restr *rune_restr_from_string(const tal_t *ctx,
const char *str,
size_t len);

/**
* rune_condition_is_valid: is this a valid condition?
* @cond: potential condition character.
*
* Returns true if it's one of enum rune_condition.
*/
bool rune_condition_is_valid(enum rune_condition cond);

/**
* rune_altern_fieldname_len: how much of this string is condition?
* @alternstr: potential alternative string
* @alternstrlen: length
*
* This helps parsing your own runes.
*
* Returns the first possible condition (check with rune_condition_is_valid)
* or alternstrlen if none found.
*/
size_t rune_altern_fieldname_len(const char *alternstr, size_t alternstrlen);

#endif /* CCAN_RUNE_RUNE_H */
37 changes: 37 additions & 0 deletions ccan/ccan/rune/test/run-altern-escape.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <ccan/rune/rune.c>
#include <ccan/rune/coding.c>
#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/str/str.h>
#include <ccan/tap/tap.h>

int main(void)
{
static const u8 secret_zero[16];
struct rune *rune;
struct rune_restr *restr;
const tal_t *ctx = tal(NULL, char);

plan_tests(9);
restr = rune_restr_from_string(ctx, "desc=@tipjar\\|jb55@sendsats.lol",
strlen("desc=@tipjar\\|jb55@sendsats.lol"));
ok1(tal_count(restr->alterns) == 1);
ok1(restr->alterns[0]->condition == '=');
ok1(streq(restr->alterns[0]->fieldname, "desc"));
ok1(streq(restr->alterns[0]->value, "@tipjar|jb55@sendsats.lol"));

rune = rune_new(ctx, secret_zero, sizeof(secret_zero), NULL);
rune_add_restr(rune, take(restr));

/* Converting via base64 should not change it! */
rune = rune_from_base64(ctx, rune_to_base64(ctx, rune));
ok1(tal_count(rune->restrs) == 1);
restr = rune->restrs[0];
ok1(tal_count(restr->alterns) == 1);
ok1(restr->alterns[0]->condition == '=');
ok1(streq(restr->alterns[0]->fieldname, "desc"));
ok1(streq(restr->alterns[0]->value, "@tipjar|jb55@sendsats.lol"));

tal_free(ctx);
/* This exits depending on whether all tests passed */
return exit_status();
}
8 changes: 4 additions & 4 deletions ccan/ccan/rune/test/run.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ int main(void)
ok1(rune_is_derived_anyversion(rune1, rune2) == NULL);

restr = rune_restr_new(NULL);
for (size_t i = 4; parts[i]; i+=3) {
for (size_t j = 4; parts[j]; j+=3) {
struct rune_altern *alt;
alt = rune_altern_new(NULL,
parts[i],
parts[i+1][0],
parts[i+2]);
parts[j],
parts[j+1][0],
parts[j+2]);
rune_restr_add_altern(restr, take(alt));
}
rune_add_restr(rune1, take(restr));
Expand Down
2 changes: 1 addition & 1 deletion ccan/ccan/tal/tal.c
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ void *tal_dup_(const tal_t *ctx, const void *p, size_t size,
(void)taken(p);
return NULL;
}

if (!adjust_size(&nbytes, n)) {
if (taken(p))
tal_free(p);
Expand Down
4 changes: 2 additions & 2 deletions cln-rpc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ cln-rpc-wrongdir:
CLN_RPC_EXAMPLES := target/debug/examples/cln-rpc-getinfo
CLN_RPC_GENALL = cln-rpc/src/model.rs
CLN_RPC_SOURCES = $(shell find cln-rpc -name *.rs) ${CLN_RPC_GENALL}
JSON_SCHEMA = doc/schemas/*.schema.json
JSON_SCHEMAS = $(wildcard doc/schemas/*.request.json doc/schemas/*.schema.json)
DEFAULT_TARGETS += $(CLN_RPC_EXAMPLES) $(CLN_RPC_GENALL)

$(CLN_RPC_GENALL): $(JSON_SCHEMA)
$(CLN_RPC_GENALL): $(JSON_SCHEMAS)
PYTHONPATH=contrib/msggen python3 contrib/msggen/msggen/__main__.py

target/debug/examples/cln-rpc-getinfo: $(shell find cln-rpc -name *.rs)
Expand Down
25 changes: 12 additions & 13 deletions doc/lightning-commando-rune.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ If *rune* is supplied, the restrictions are simple appended to that

*restrictions* can be the string "readonly" (creates a rune which
allows most *get* and *list* commands, and the *summary* command), or
an array of restrictions, or a single resriction.
an array of restrictions.

Each restriction is a set of one or more alternatives, such as "method
is listpeers", or "method is listpeers OR time is before 2023".
Alternatives use a simple language to examine the command which is
Each restriction is an array of one or more alternatives, such as "method
is listpeers", or "method is listpeers OR time is before 2023". Alternatives use a simple language to examine the command which is
being run:

* time: the current UNIX time, e.g. "time<1656759180".
Expand All @@ -41,10 +40,10 @@ being run:
RESTRICTION FORMAT
------------------

Restrictions are one or more alternatives, separated by `|`. Each
Restrictions are one or more alternatives. Each
alternative is *name* *operator* *value*. The valid names are shown
above. If a value contains `|`, `&` or `\\`, it must be preceeded by
a `\\`.
above. Note that if a value contains `\\`, it must be preceeded by another `\\`
to form valid JSON:

* `=`: passes if equal ie. identical. e.g. `method=withdraw`
* `/`: not equals, e.g. `method/withdraw`
Expand Down Expand Up @@ -80,12 +79,12 @@ We can add restrictions to that rune, like so:

The "readonly" restriction is a short-cut for two restrictions:

1. `method^list|method^get|method=summary`: You may call list, get or summary.
2. `method/listdatastore`: But not listdatastore: that contains sensitive stuff!
1. `["method^list", "method^get", "method=summary"]`: You may call list, get or summary.
2. `["method/listdatastore"]`: But not listdatastore: that contains sensitive stuff!

We can do the same manually, like so:

$ lightning-cli commando-rune rune=KUhZzNlECC7pYsz3QVbF1TqjIUYi3oyESTI7n60hLMs9MA== restrictions='["method^list|method^get|method=summary","method/listdatastore"]'
$ lightning-cli commando-rune rune=KUhZzNlECC7pYsz3QVbF1TqjIUYi3oyESTI7n60hLMs9MA== restrictions='[["method^list", "method^get", "method=summary"],["method/listdatastore"]]'
{
"rune": "NbL7KkXcPQsVseJ9TdJNjJK2KsPjnt_q4cE_wvc873I9MCZtZXRob2RebGlzdHxtZXRob2ReZ2V0fG1ldGhvZD1zdW1tYXJ5Jm1ldGhvZC9saXN0ZGF0YXN0b3Jl",
"unique_id": "0"
Expand All @@ -95,15 +94,15 @@ Let's create a rune which lets a specific peer
(024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605)
run "listpeers" on themselves:

$ lightning-cli commando-rune restrictions='["id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605","method=listpeers","pnum=1","pnameid=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605|parr0=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605"]'
$ lightning-cli commando-rune restrictions='[["id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605"],["method=listpeers"],["pnum=1"],["pnameid=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605","parr0=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605"]]'
{
"rune": "FE8GHiGVvxcFqCQcClVRRiNE_XEeLYQzyG2jmqto4jM9MiZpZD0wMjRiOWExZmE4ZTAwNmYxZTM5MzdmNjVmNjZjNDA4ZTZkYThlMWNhNzI4ZWE0MzIyMmE3MzgxZGYxY2M0NDk2MDUmbWV0aG9kPWxpc3RwZWVycyZwbnVtPTEmcG5hbWVpZD0wMjRiOWExZmE4ZTAwNmYxZTM5MzdmNjVmNjZjNDA4ZTZkYThlMWNhNzI4ZWE0MzIyMmE3MzgxZGYxY2M0NDk2MDV8cGFycjA9MDI0YjlhMWZhOGUwMDZmMWUzOTM3ZjY1ZjY2YzQwOGU2ZGE4ZTFjYTcyOGVhNDMyMjJhNzM4MWRmMWNjNDQ5NjA1",
"unique_id": "2"
}

This allows `listpeers` with 1 argument (`pnum=1`), which is either by name (`pnameid`), or position (`parr0`). We could shorten this in several ways: either allowing only positional or named parameters, or by testing the start of the parameters only. Here's an example which only checks the first 9 bytes of the `listpeers` parameter:

$ lightning-cli commando-rune restrictions='["id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605","method=listpeers","pnum=1","pnameid^024b9a1fa8e006f1e393|parr0^024b9a1fa8e006f1e393"]'
$ lightning-cli commando-rune restrictions='[["id=024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605"],["method=listpeers"],["pnum=1"],["pnameid^024b9a1fa8e006f1e393", "parr0^024b9a1fa8e006f1e393"]'
{
"rune": "fTQnfL05coEbiBO8SS0cvQwCcPLxE9c02pZCC6HRVEY9MyZpZD0wMjRiOWExZmE4ZTAwNmYxZTM5MzdmNjVmNjZjNDA4ZTZkYThlMWNhNzI4ZWE0MzIyMmE3MzgxZGYxY2M0NDk2MDUmbWV0aG9kPWxpc3RwZWVycyZwbnVtPTEmcG5hbWVpZF4wMjRiOWExZmE4ZTAwNmYxZTM5M3xwYXJyMF4wMjRiOWExZmE4ZTAwNmYxZTM5Mw==",
"unique_id": "3"
Expand All @@ -114,7 +113,7 @@ it only be usable for 24 hours from now (`time<`), and that it can only
be used twice a minute (`rate=2`). `date +%s` can give us the current
time in seconds:

$ lightning-cli commando-rune rune=fTQnfL05coEbiBO8SS0cvQwCcPLxE9c02pZCC6HRVEY9MyZpZD0wMjRiOWExZmE4ZTAwNmYxZTM5MzdmNjVmNjZjNDA4ZTZkYThlMWNhNzI4ZWE0MzIyMmE3MzgxZGYxY2M0NDk2MDUmbWV0aG9kPWxpc3RwZWVycyZwbnVtPTEmcG5hbWVpZF4wMjRiOWExZmE4ZTAwNmYxZTM5M3xwYXJyMF4wMjRiOWExZmE4ZTAwNmYxZTM5Mw== restrictions='["time<'$(($(date +%s) + 24*60*60))'","rate=2"]'
$ lightning-cli commando-rune rune=fTQnfL05coEbiBO8SS0cvQwCcPLxE9c02pZCC6HRVEY9MyZpZD0wMjRiOWExZmE4ZTAwNmYxZTM5MzdmNjVmNjZjNDA4ZTZkYThlMWNhNzI4ZWE0MzIyMmE3MzgxZGYxY2M0NDk2MDUmbWV0aG9kPWxpc3RwZWVycyZwbnVtPTEmcG5hbWVpZF4wMjRiOWExZmE4ZTAwNmYxZTM5M3xwYXJyMF4wMjRiOWExZmE4ZTAwNmYxZTM5Mw== restrictions='[["time<'$(($(date +%s) + 24*60*60))'","rate=2"]]'
{
"rune": "tU-RLjMiDpY2U0o3W1oFowar36RFGpWloPbW9-RuZdo9MyZpZD0wMjRiOWExZmE4ZTAwNmYxZTM5MzdmNjVmNjZjNDA4ZTZkYThlMWNhNzI4ZWE0MzIyMmE3MzgxZGYxY2M0NDk2MDUmbWV0aG9kPWxpc3RwZWVycyZwbnVtPTEmcG5hbWVpZF4wMjRiOWExZmE4ZTAwNmYxZTM5M3xwYXJyMF4wMjRiOWExZmE4ZTAwNmYxZTM5MyZ0aW1lPDE2NTY5MjA1MzgmcmF0ZT0y",
"unique_id": "3"
Expand Down
10 changes: 8 additions & 2 deletions doc/schemas/commando-rune.request.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
"type": "array",
"description": "array of restrictions to add to rune",
"items": {
"type": "string"
"type": "array",
"items": {
"type": "string"
}
}
},
{
"type": "string",
"description": "single restrictions to add to rune, or readonly."
"enum": [
"readonly"
],
"description": "readonly string to indicate standard readonly restrictions."
}
]
}
Expand Down
77 changes: 68 additions & 9 deletions plugins/commando.c
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,68 @@ static struct rune_restr **readonly_restrictions(const tal_t *ctx)
return restrs;
}

static struct rune_altern *rune_altern_from_json(const tal_t *ctx,
const char *buffer,
const jsmntok_t *tok)
{
struct rune_altern *alt;
size_t condoff;
/* We still need to unescape here, for \\ -> \. JSON doesn't
* allow unnecessary \ */
const char *unescape;
struct json_escape *e = json_escape_string_(tmpctx,
buffer + tok->start,
tok->end - tok->start);
unescape = json_escape_unescape(tmpctx, e);
if (!unescape)
return NULL;

condoff = rune_altern_fieldname_len(unescape, strlen(unescape));
if (!rune_condition_is_valid(unescape[condoff]))
return NULL;

alt = tal(ctx, struct rune_altern);
alt->fieldname = tal_strndup(alt, unescape, condoff);
alt->condition = unescape[condoff];
alt->value = tal_strdup(alt, unescape + condoff + 1);
return alt;
}

static struct rune_restr *rune_restr_from_json(const tal_t *ctx,
const char *buffer,
const jsmntok_t *tok)
{
const jsmntok_t *t;
size_t i;
struct rune_restr *restr;

/* \| is not valid JSON, so they use \\|: undo it! */
if (deprecated_apis && tok->type == JSMN_STRING) {
const char *unescape;
struct json_escape *e = json_escape_string_(tmpctx,
buffer + tok->start,
tok->end - tok->start);
unescape = json_escape_unescape(tmpctx, e);
if (!unescape)
return NULL;
return rune_restr_from_string(ctx, unescape, strlen(unescape));
}

restr = tal(ctx, struct rune_restr);
/* FIXME: after deprecation removed, allow singletons again! */
if (tok->type != JSMN_ARRAY)
return NULL;

restr->alterns = tal_arr(restr, struct rune_altern *, tok->size);
json_for_each_arr(i, t, tok) {
restr->alterns[i] = rune_altern_from_json(restr->alterns,
buffer, t);
if (!restr->alterns[i])
return tal_free(restr);
}
return restr;
}

static struct command_result *param_restrictions(struct command *cmd,
const char *name,
const char *buffer,
Expand All @@ -776,21 +838,18 @@ static struct command_result *param_restrictions(struct command *cmd,

*restrs = tal_arr(cmd, struct rune_restr *, tok->size);
json_for_each_arr(i, t, tok) {
(*restrs)[i] = rune_restr_from_string(*restrs,
buffer + t->start,
t->end - t->start);
if (!(*restrs)[i])
(*restrs)[i] = rune_restr_from_json(*restrs, buffer, t);
if (!(*restrs)[i]) {
return command_fail_badparam(cmd, name, buffer, t,
"not a valid restriction");
"not a valid restriction (should be array)");
}
}
} else {
*restrs = tal_arr(cmd, struct rune_restr *, 1);
(*restrs)[0] = rune_restr_from_string(*restrs,
buffer + tok->start,
tok->end - tok->start);
(*restrs)[0] = rune_restr_from_json(*restrs, buffer, tok);
if (!(*restrs)[0])
return command_fail_badparam(cmd, name, buffer, tok,
"not a valid restriction");
"not a valid restriction (should be array)");
}
return NULL;
}
Expand Down
Loading