/* * rpcd - UBUS RPC server * * Copyright (C) 2013-2014 Jo-Philipp Wich * * 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 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; } static void rpc_plugin_json_array_to_blob(struct array_list *a, struct blob_buf *blob); static void rpc_plugin_json_object_to_blob(json_object *o, struct blob_buf *blob); static void rpc_plugin_json_element_to_blob(const char *name, json_object *val, struct blob_buf *blob) { void *c; int64_t n; switch (json_object_get_type(val)) { case json_type_object: c = blobmsg_open_table(blob, name); rpc_plugin_json_object_to_blob(val, blob); blobmsg_close_table(blob, c); break; case json_type_array: c = blobmsg_open_array(blob, name); rpc_plugin_json_array_to_blob(json_object_get_array(val), blob); blobmsg_close_array(blob, c); break; case json_type_string: blobmsg_add_string(blob, name, json_object_get_string(val)); break; case json_type_boolean: blobmsg_add_u8(blob, name, json_object_get_boolean(val)); break; case json_type_int: n = json_object_get_int64(val); if (n >= INT32_MIN && n <= INT32_MAX) blobmsg_add_u32(blob, name, n); else blobmsg_add_u64(blob, name, n); break; case json_type_double: blobmsg_add_double(blob, name, json_object_get_double(val)); break; case json_type_null: blobmsg_add_field(blob, BLOBMSG_TYPE_UNSPEC, name, NULL, 0); break; } } static void rpc_plugin_json_array_to_blob(struct array_list *a, struct blob_buf *blob) { int i, len; for (i = 0, len = array_list_length(a); i < len; i++) rpc_plugin_json_element_to_blob(NULL, array_list_get_idx(a, i), blob); } static void rpc_plugin_json_object_to_blob(json_object *o, struct blob_buf *blob) { json_object_object_foreach(o, key, val) rpc_plugin_json_element_to_blob(key, val, blob); } 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) { rpc_plugin_json_object_to_blob(c->obj, blob); rv = UBUS_STATUS_OK; } json_object_put(c->obj); } else { rv = UBUS_STATUS_NO_DATA; } } json_tokener_free(c->tok); free(c->input); 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, *mptr; c = calloc_a(sizeof(*c), &mptr, strlen(method) + 1); if (!c) goto fail; c->method = strcpy(mptr, method); c->input = blobmsg_format_json(msg, true); c->tok = json_tokener_new(); if (!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; rv = 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); if (rv == UBUS_STATUS_OK) return rv; fail: if (c) { 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 || blobmsg_type(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 = blobmsg_type(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) { free(obj); return NULL; } if (asprintf((char **)&obj_type->name, "rpcd-plugin-exec-%s", name) < 0) { free(obj); free(obj_type); return NULL; } 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 = { .session_access = rpc_session_access, .session_create_cb = rpc_session_create_cb, .session_destroy_cb = rpc_session_destroy_cb, .exec = rpc_exec, .exec_timeout = &rpc_exec_timeout, }; 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_LOCAL); if (!dlh) { fprintf(stderr, "Failed to load plugin %s: %s\n", path, dlerror()); 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; }