From 7cf2cf4566cf67bc72056a1a0aacd4124e3a1299 Mon Sep 17 00:00:00 2001 From: Michael Pfeifroth Date: Fri, 7 Aug 2026 11:26:04 +0200 Subject: [PATCH] ubus: propagate _luci_bg=1 query flag as session/access notouch LuCI's Poll callbacks issue XHRs continuously (typically every few seconds) and each XHR goes through the /ubus/ endpoint. rpcd's session/access ubus method refreshes the session idle timer on every call, so the "sessiontime" idle timeout documented in /etc/config/rpcd is never reached and the session lives forever. Give LuCI a way to mark those background requests: when the request URL carries "_luci_bg=1" (or "&_luci_bg=1"), forward the hint to rpcd as the new "notouch" boolean on session/access. Two paths reach session/access: * uhttpd's internal permission check via uh_ubus_allowed(). Pass the hint into the request when the flag is set. * The JSON-RPC client can also invoke session/access directly (LuCI's per-page callSessionAccess() ACL probes go this route). Inject the hint into the RPC's own args in uh_ubus_send_request() when the caller did not already set it, so the semantics match regardless of how the browser reaches session/access. The injected "notouch" key is a plain blobmsg attribute that any other ubus object ignores via ordinary policy handling, so unrelated services are unaffected. Requests without the query parameter behave exactly as before, so behaviour is unchanged for callers that don't opt in. Depends on rpcd support for the "notouch" argument. Signed-off-by: Michael Pfeifroth --- ubus.c | 38 ++++++++++++++++++++++++++++++++------ uhttpd.h | 1 + 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/ubus.c b/ubus.c index 24f4bf4..14f077b 100644 --- a/ubus.c +++ b/ubus.c @@ -277,7 +277,7 @@ static void uh_ubus_allowed_cb(struct ubus_request *req, int type, struct blob_a *allow = blobmsg_get_bool(tb[SES_ACCESS]); } -static bool uh_ubus_allowed(const char *sid, const char *obj, const char *fun) +static bool uh_ubus_allowed(struct client *cl, const char *sid, const char *obj, const char *fun) { uint32_t id; bool allow = false; @@ -290,6 +290,8 @@ static bool uh_ubus_allowed(const char *sid, const char *obj, const char *fun) blobmsg_add_string(&req, "ubus_rpc_session", sid); blobmsg_add_string(&req, "object", obj); blobmsg_add_string(&req, "function", fun); + if (cl && cl->dispatch.ubus.notouch) + blobmsg_add_u8(&req, "notouch", 1); ubus_invoke(ctx, id, "access", req.head, uh_ubus_allowed_cb, &allow, conf.script_timeout * 500); @@ -379,7 +381,7 @@ static void uh_ubus_handle_get_subscribe(struct client *cl, const char *path) sid = uh_ubus_get_auth(cl->hdr.head); - if (!conf.ubus_noauth && !uh_ubus_allowed(sid, path, ":subscribe")) { + if (!conf.ubus_noauth && !uh_ubus_allowed(cl, sid, path, ":subscribe")) { uh_ubus_send_header(cl, 200, "OK", "application/json"); uh_ubus_posix_error(cl, EACCES); return; @@ -566,7 +568,7 @@ static void uh_ubus_send_request(struct client *cl, const char *sid, struct blob { struct dispatch *d = &cl->dispatch; struct dispatch_ubus *du = &d->ubus; - struct blob_attr *cur; + struct blob_attr *cur, *saw_notouch = NULL; static struct blob_buf req; int ret, rem; @@ -578,10 +580,17 @@ static void uh_ubus_send_request(struct client *cl, const char *sid, struct blob blobmsg_for_each_attr(cur, args, rem) { if (!strcmp(blobmsg_name(cur), "ubus_rpc_session")) return uh_ubus_json_rpc_error(cl, ERROR_PARAMS); + if (!strcmp(blobmsg_name(cur), "notouch")) + saw_notouch = cur; blobmsg_add_blob(&req, cur); } blobmsg_add_string(&req, "ubus_rpc_session", sid); + /* Propagate the LuCI background hint into the RPC args when the + * caller did not already set it. Harmless for objects that don't + * recognise the "notouch" key. */ + if (du->notouch && !saw_notouch) + blobmsg_add_u8(&req, "notouch", 1); blob_buf_init(&du->buf, 0); memset(&du->req, 0, sizeof(du->req)); @@ -800,7 +809,7 @@ static void uh_ubus_handle_request_object(struct client *cl, struct json_object goto error; } - if (!conf.ubus_noauth && !uh_ubus_allowed(data.sid, data.object, data.function)) { + if (!conf.ubus_noauth && !uh_ubus_allowed(cl, data.sid, data.object, data.function)) { err = ERROR_ACCESS; goto error; } @@ -887,7 +896,7 @@ static void uh_ubus_call(struct client *cl, const char *path, const char *sid) goto error; } - if (!conf.ubus_noauth && !uh_ubus_allowed(sid, path, data.method)) { + if (!conf.ubus_noauth && !uh_ubus_allowed(cl, sid, path, data.method)) { err = ERROR_ACCESS; goto error; } @@ -969,8 +978,25 @@ static void uh_ubus_handle_request(struct client *cl, char *url, struct path_inf return; } chr = strchr(du->url_path, '?'); - if (chr) + du->notouch = false; + if (chr) { + /* Check query string for _luci_bg=1 flag, used by LuCI to mark + * background poll requests that should not touch the rpcd + * session idle timer. */ + const char *q = chr + 1; + while (*q) { + if ((q[0] == '_') && !strncmp(q, "_luci_bg=1", 10) && + (q[10] == '\0' || q[10] == '&')) { + du->notouch = true; + break; + } + while (*q && *q != '&') + q++; + while (*q == '&') + q++; + } chr[0] = '\0'; + } du->legacy = false; d->free = uh_ubus_request_free; diff --git a/uhttpd.h b/uhttpd.h index 9a9fc3c..2a09175 100644 --- a/uhttpd.h +++ b/uhttpd.h @@ -258,6 +258,7 @@ struct dispatch_ubus { bool array; int array_idx; bool legacy; /* Got legacy request => use legacy reply */ + bool notouch; /* URL had ?_luci_bg=1 or &_luci_bg=1: pass notouch=1 to session/access */ struct ubus_subscriber sub; };