470 lines
8.6 KiB
C
470 lines
8.6 KiB
C
/*
|
|
* luci-rpcd - LuCI UBUS RPC server
|
|
*
|
|
* Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "plugin.h"
|
|
|
|
static struct blob_buf buf;
|
|
|
|
struct rpc_plugin_lookup_context {
|
|
uint32_t id;
|
|
char *name;
|
|
bool found;
|
|
};
|
|
|
|
static void
|
|
rpc_plugin_lookup_plugin_cb(struct ubus_context *ctx,
|
|
struct ubus_object_data *obj, void *priv)
|
|
{
|
|
struct rpc_plugin_lookup_context *c = priv;
|
|
|
|
if (c->id == obj->id)
|
|
{
|
|
c->found = true;
|
|
sprintf(c->name, "%s", obj->path);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
rpc_plugin_lookup_plugin(struct ubus_context *ctx, struct ubus_object *obj,
|
|
char *strptr)
|
|
{
|
|
struct rpc_plugin_lookup_context c = { .id = obj->id, .name = strptr };
|
|
|
|
if (ubus_lookup(ctx, NULL, rpc_plugin_lookup_plugin_cb, &c))
|
|
return false;
|
|
|
|
return c.found;
|
|
}
|
|
|
|
struct call_context {
|
|
char path[PATH_MAX];
|
|
const char *argv[4];
|
|
char *method;
|
|
char *input;
|
|
json_tokener *tok;
|
|
json_object *obj;
|
|
bool input_done;
|
|
bool output_done;
|
|
};
|
|
|
|
static int
|
|
rpc_plugin_call_stdin_cb(struct ustream *s, void *priv)
|
|
{
|
|
struct call_context *c = priv;
|
|
|
|
if (!c->input_done)
|
|
{
|
|
ustream_write(s, c->input, strlen(c->input), false);
|
|
c->input_done = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
rpc_plugin_call_stdout_cb(struct blob_buf *blob, char *buf, int len, void *priv)
|
|
{
|
|
struct call_context *c = priv;
|
|
|
|
if (!c->output_done)
|
|
{
|
|
c->obj = json_tokener_parse_ex(c->tok, buf, len);
|
|
|
|
if (json_tokener_get_error(c->tok) != json_tokener_continue)
|
|
c->output_done = true;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
rpc_plugin_call_stderr_cb(struct blob_buf *blob, char *buf, int len, void *priv)
|
|
{
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
rpc_plugin_call_finish_cb(struct blob_buf *blob, int stat, void *priv)
|
|
{
|
|
struct call_context *c = priv;
|
|
int rv = UBUS_STATUS_INVALID_ARGUMENT;
|
|
|
|
if (json_tokener_get_error(c->tok) == json_tokener_success)
|
|
{
|
|
if (c->obj)
|
|
{
|
|
if (json_object_get_type(c->obj) == json_type_object ||
|
|
json_object_get_type(c->obj) == json_type_array)
|
|
{
|
|
blobmsg_add_json_element(blob, NULL, c->obj);
|
|
rv = UBUS_STATUS_OK;
|
|
}
|
|
|
|
json_object_put(c->obj);
|
|
}
|
|
else
|
|
{
|
|
rv = UBUS_STATUS_NO_DATA;
|
|
}
|
|
}
|
|
|
|
json_tokener_free(c->tok);
|
|
|
|
free(c->input);
|
|
free(c->method);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
rpc_plugin_call(struct ubus_context *ctx, struct ubus_object *obj,
|
|
struct ubus_request_data *req, const char *method,
|
|
struct blob_attr *msg)
|
|
{
|
|
int rv = UBUS_STATUS_UNKNOWN_ERROR;
|
|
struct call_context *c;
|
|
char *plugin;
|
|
|
|
c = calloc(1, sizeof(*c));
|
|
|
|
if (!c)
|
|
goto fail;
|
|
|
|
c->method = strdup(method);
|
|
c->input = blobmsg_format_json(msg, true);
|
|
c->tok = json_tokener_new();
|
|
|
|
if (!c->method || !c->input || !c->tok)
|
|
goto fail;
|
|
|
|
plugin = c->path + sprintf(c->path, "%s/", RPC_PLUGIN_DIRECTORY);
|
|
|
|
if (!rpc_plugin_lookup_plugin(ctx, obj, plugin))
|
|
{
|
|
rv = UBUS_STATUS_NOT_FOUND;
|
|
goto fail;
|
|
}
|
|
|
|
c->argv[0] = c->path;
|
|
c->argv[1] = "call";
|
|
c->argv[2] = c->method;
|
|
|
|
return rpc_exec(c->argv, rpc_plugin_call_stdin_cb,
|
|
rpc_plugin_call_stdout_cb, rpc_plugin_call_stderr_cb,
|
|
rpc_plugin_call_finish_cb, c, ctx, req);
|
|
|
|
fail:
|
|
if (c)
|
|
{
|
|
if (c->method)
|
|
free(c->method);
|
|
|
|
if (c->input)
|
|
free(c->input);
|
|
|
|
if (c->tok)
|
|
json_tokener_free(c->tok);
|
|
|
|
free(c);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static bool
|
|
rpc_plugin_parse_signature(struct blob_attr *sig, struct ubus_method *method)
|
|
{
|
|
int rem, n_attr;
|
|
enum blobmsg_type type;
|
|
struct blob_attr *attr;
|
|
struct blobmsg_policy *policy = NULL;
|
|
|
|
if (!sig || blob_id(sig) != BLOBMSG_TYPE_TABLE)
|
|
return false;
|
|
|
|
n_attr = 0;
|
|
|
|
blobmsg_for_each_attr(attr, sig, rem)
|
|
n_attr++;
|
|
|
|
if (n_attr)
|
|
{
|
|
policy = calloc(n_attr, sizeof(*policy));
|
|
|
|
if (!policy)
|
|
return false;
|
|
|
|
n_attr = 0;
|
|
|
|
blobmsg_for_each_attr(attr, sig, rem)
|
|
{
|
|
type = blob_id(attr);
|
|
|
|
if (type == BLOBMSG_TYPE_INT32)
|
|
{
|
|
switch (blobmsg_get_u32(attr))
|
|
{
|
|
case 8:
|
|
type = BLOBMSG_TYPE_INT8;
|
|
break;
|
|
|
|
case 16:
|
|
type = BLOBMSG_TYPE_INT16;
|
|
break;
|
|
|
|
case 64:
|
|
type = BLOBMSG_TYPE_INT64;
|
|
break;
|
|
|
|
default:
|
|
type = BLOBMSG_TYPE_INT32;
|
|
break;
|
|
}
|
|
}
|
|
|
|
policy[n_attr].name = strdup(blobmsg_name(attr));
|
|
policy[n_attr].type = type;
|
|
|
|
n_attr++;
|
|
}
|
|
}
|
|
|
|
method->name = strdup(blobmsg_name(sig));
|
|
method->handler = rpc_plugin_call;
|
|
method->policy = policy;
|
|
method->n_policy = n_attr;
|
|
|
|
return true;
|
|
}
|
|
|
|
static struct ubus_object *
|
|
rpc_plugin_parse_exec(const char *name, int fd)
|
|
{
|
|
int len, rem, n_method;
|
|
struct blob_attr *cur;
|
|
struct ubus_method *methods;
|
|
struct ubus_object_type *obj_type;
|
|
struct ubus_object *obj;
|
|
char outbuf[1024];
|
|
|
|
json_tokener *tok;
|
|
json_object *jsobj;
|
|
|
|
blob_buf_init(&buf, 0);
|
|
|
|
tok = json_tokener_new();
|
|
|
|
if (!tok)
|
|
return NULL;
|
|
|
|
while ((len = read(fd, outbuf, sizeof(outbuf))) > 0)
|
|
{
|
|
jsobj = json_tokener_parse_ex(tok, outbuf, len);
|
|
|
|
if (json_tokener_get_error(tok) == json_tokener_continue)
|
|
continue;
|
|
|
|
if (json_tokener_get_error(tok) != json_tokener_success)
|
|
break;
|
|
|
|
if (jsobj)
|
|
{
|
|
if (json_object_get_type(jsobj) == json_type_object)
|
|
blobmsg_add_object(&buf, jsobj);
|
|
|
|
json_object_put(jsobj);
|
|
break;
|
|
}
|
|
}
|
|
|
|
json_tokener_free(tok);
|
|
|
|
n_method = 0;
|
|
|
|
blob_for_each_attr(cur, buf.head, rem)
|
|
n_method++;
|
|
|
|
if (!n_method)
|
|
return NULL;
|
|
|
|
methods = calloc(n_method, sizeof(*methods));
|
|
|
|
if (!methods)
|
|
return NULL;
|
|
|
|
n_method = 0;
|
|
|
|
blob_for_each_attr(cur, buf.head, rem)
|
|
{
|
|
if (!rpc_plugin_parse_signature(cur, &methods[n_method]))
|
|
continue;
|
|
|
|
n_method++;
|
|
}
|
|
|
|
obj = calloc(1, sizeof(*obj));
|
|
|
|
if (!obj)
|
|
return NULL;
|
|
|
|
obj_type = calloc(1, sizeof(*obj_type));
|
|
|
|
if (!obj_type)
|
|
return NULL;
|
|
|
|
asprintf((char **)&obj_type->name, "luci-rpc-plugin-%s", name);
|
|
obj_type->methods = methods;
|
|
obj_type->n_methods = n_method;
|
|
|
|
obj->name = strdup(name);
|
|
obj->type = obj_type;
|
|
obj->methods = methods;
|
|
obj->n_methods = n_method;
|
|
|
|
return obj;
|
|
}
|
|
|
|
static int
|
|
rpc_plugin_register_exec(struct ubus_context *ctx, const char *path)
|
|
{
|
|
pid_t pid;
|
|
int rv = UBUS_STATUS_NO_DATA, fd, fds[2];
|
|
const char *name;
|
|
struct ubus_object *plugin;
|
|
|
|
name = strrchr(path, '/');
|
|
|
|
if (!name)
|
|
return UBUS_STATUS_INVALID_ARGUMENT;
|
|
|
|
if (pipe(fds))
|
|
return UBUS_STATUS_UNKNOWN_ERROR;
|
|
|
|
switch ((pid = fork()))
|
|
{
|
|
case -1:
|
|
return UBUS_STATUS_UNKNOWN_ERROR;
|
|
|
|
case 0:
|
|
fd = open("/dev/null", O_RDWR);
|
|
|
|
if (fd > -1)
|
|
{
|
|
dup2(fd, 0);
|
|
dup2(fd, 2);
|
|
|
|
if (fd > 2)
|
|
close(fd);
|
|
}
|
|
|
|
dup2(fds[1], 1);
|
|
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
|
|
if (execl(path, path, "list", NULL))
|
|
return UBUS_STATUS_UNKNOWN_ERROR;
|
|
|
|
default:
|
|
plugin = rpc_plugin_parse_exec(name + 1, fds[0]);
|
|
|
|
if (!plugin)
|
|
goto out;
|
|
|
|
rv = ubus_add_object(ctx, plugin);
|
|
|
|
out:
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
waitpid(pid, NULL, 0);
|
|
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
|
|
static LIST_HEAD(plugins);
|
|
|
|
static const struct rpc_daemon_ops ops = {
|
|
.access = rpc_session_access,
|
|
.exec = rpc_exec,
|
|
};
|
|
|
|
static int
|
|
rpc_plugin_register_library(struct ubus_context *ctx, const char *path)
|
|
{
|
|
struct rpc_plugin *p;
|
|
void *dlh;
|
|
|
|
dlh = dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
|
|
|
|
if (!dlh)
|
|
return UBUS_STATUS_UNKNOWN_ERROR;
|
|
|
|
p = dlsym(dlh, "rpc_plugin");
|
|
|
|
if (!p)
|
|
return UBUS_STATUS_NOT_FOUND;
|
|
|
|
list_add(&p->list, &plugins);
|
|
|
|
return p->init(&ops, ctx);
|
|
}
|
|
|
|
int rpc_plugin_api_init(struct ubus_context *ctx)
|
|
{
|
|
DIR *d;
|
|
int rv = 0;
|
|
struct stat s;
|
|
struct dirent *e;
|
|
char path[PATH_MAX];
|
|
|
|
if ((d = opendir(RPC_PLUGIN_DIRECTORY)) != NULL)
|
|
{
|
|
while ((e = readdir(d)) != NULL)
|
|
{
|
|
snprintf(path, sizeof(path) - 1,
|
|
RPC_PLUGIN_DIRECTORY "/%s", e->d_name);
|
|
|
|
if (stat(path, &s) || !S_ISREG(s.st_mode) || !(s.st_mode & S_IXUSR))
|
|
continue;
|
|
|
|
rv |= rpc_plugin_register_exec(ctx, path);
|
|
}
|
|
|
|
closedir(d);
|
|
}
|
|
|
|
if ((d = opendir(RPC_LIBRARY_DIRECTORY)) != NULL)
|
|
{
|
|
while ((e = readdir(d)) != NULL)
|
|
{
|
|
snprintf(path, sizeof(path) - 1,
|
|
RPC_LIBRARY_DIRECTORY "/%s", e->d_name);
|
|
|
|
if (stat(path, &s) || !S_ISREG(s.st_mode))
|
|
continue;
|
|
|
|
rv |= rpc_plugin_register_library(ctx, path);
|
|
}
|
|
|
|
closedir(d);
|
|
}
|
|
|
|
return rv;
|
|
}
|