X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=file.c;h=b1adcb7c431bc55a770dea3508bd4fcbacae5adc;hb=95f0973c340853007d7f2875ecc3541dc2459ae0;hp=fbc099c43a8f2d4f4703038c50ef056ef724ab2e;hpb=2cc4b998ec78fc8fb9e12a5e67e71c34b2511719;p=oweals%2Frpcd.git diff --git a/file.c b/file.c index fbc099c..b1adcb7 100644 --- a/file.c +++ b/file.c @@ -17,9 +17,12 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _GNU_SOURCE + #include #include #include +#include #include #include #include @@ -35,7 +38,7 @@ #include /* limit of sys & proc files */ -#define RPC_FILE_MIN_SIZE (128) +#define RPC_FILE_MIN_SIZE (4096) /* limit of regular files and command output data */ #define RPC_FILE_MAX_SIZE (4096 * 64) @@ -53,6 +56,8 @@ us.stream.notify_state = rpc_file_##name##_state_cb; \ ustream_fd_init(&us, fd); +static const struct rpc_daemon_ops *ops; + struct rpc_file_exec_context { struct ubus_context *context; struct ubus_request_data request; @@ -60,34 +65,37 @@ struct rpc_file_exec_context { struct uloop_process process; struct ustream_fd opipe; struct ustream_fd epipe; - int outlen; - char *out; - int errlen; - char *err; int stat; }; static struct blob_buf buf; +static char *canonpath; enum { RPC_F_R_PATH, + RPC_F_R_SESSION, __RPC_F_R_MAX, }; -static const struct blobmsg_policy rpc_file_r_policy[__RPC_F_R_MAX] = { - [RPC_F_R_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, +static const struct blobmsg_policy rpc_file_R_policy[__RPC_F_R_MAX] = { + [RPC_F_R_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [RPC_F_R_SESSION] = { .name = "ubus_rpc_session", + .type = BLOBMSG_TYPE_STRING }, }; enum { RPC_F_RB_PATH, RPC_F_RB_BASE64, + RPC_F_RB_SESSION, __RPC_F_RB_MAX, }; -static const struct blobmsg_policy rpc_file_rb_policy[__RPC_F_RB_MAX] = { - [RPC_F_RB_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, - [RPC_F_RB_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL }, +static const struct blobmsg_policy rpc_file_RB_policy[__RPC_F_RB_MAX] = { + [RPC_F_RB_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [RPC_F_RB_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL }, + [RPC_F_RB_SESSION] = { .name = "ubus_rpc_session", + .type = BLOBMSG_TYPE_STRING }, }; enum { @@ -96,28 +104,34 @@ enum { RPC_F_RW_APPEND, RPC_F_RW_MODE, RPC_F_RW_BASE64, + RPC_F_RW_SESSION, __RPC_F_RW_MAX, }; -static const struct blobmsg_policy rpc_file_rw_policy[__RPC_F_RW_MAX] = { - [RPC_F_RW_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, - [RPC_F_RW_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING }, - [RPC_F_RW_APPEND] = { .name = "append", .type = BLOBMSG_TYPE_BOOL }, - [RPC_F_RW_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 }, - [RPC_F_RW_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL }, +static const struct blobmsg_policy rpc_file_RW_policy[__RPC_F_RW_MAX] = { + [RPC_F_RW_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + [RPC_F_RW_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING }, + [RPC_F_RW_APPEND] = { .name = "append", .type = BLOBMSG_TYPE_BOOL }, + [RPC_F_RW_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 }, + [RPC_F_RW_BASE64] = { .name = "base64", .type = BLOBMSG_TYPE_BOOL }, + [RPC_F_RW_SESSION] = { .name = "ubus_rpc_session", + .type = BLOBMSG_TYPE_STRING }, }; enum { RPC_E_CMD, RPC_E_PARM, RPC_E_ENV, + RPC_E_SESSION, __RPC_E_MAX, }; static const struct blobmsg_policy rpc_exec_policy[__RPC_E_MAX] = { - [RPC_E_CMD] = { .name = "command", .type = BLOBMSG_TYPE_STRING }, - [RPC_E_PARM] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY }, - [RPC_E_ENV] = { .name = "env", .type = BLOBMSG_TYPE_TABLE }, + [RPC_E_CMD] = { .name = "command", .type = BLOBMSG_TYPE_STRING }, + [RPC_E_PARM] = { .name = "params", .type = BLOBMSG_TYPE_ARRAY }, + [RPC_E_ENV] = { .name = "env", .type = BLOBMSG_TYPE_TABLE }, + [RPC_E_SESSION] = { .name = "ubus_rpc_session", + .type = BLOBMSG_TYPE_STRING }, }; static const char *d_types[] = { @@ -154,33 +168,125 @@ rpc_errno_status(void) } } +static bool +rpc_file_access(const struct blob_attr *sid, + const char *path, const char *perm) +{ + if (!sid) + return true; + + return ops->session_access(blobmsg_data(sid), "file", path, perm); +} + +static char * +rpc_canonicalize_path(const char *path) +{ + char *cp; + const char *p; + + if (path == NULL || *path == '\0') + return NULL; + + if (canonpath != NULL) + free(canonpath); + + canonpath = strdup(path); + + if (canonpath == NULL) + return NULL; + + /* normalize */ + for (cp = canonpath, p = path; *p != '\0'; ) { + if (*p != '/') + goto next; + + /* skip repeating / */ + if (p[1] == '/') { + p++; + continue; + } + + /* /./ or /../ */ + if (p[1] == '.') { + /* skip /./ */ + if ((p[2] == '\0') || (p[2] == '/')) { + p += 2; + continue; + } + + /* collapse /x/../ */ + if ((p[2] == '.') && ((p[3] == '\0') || (p[3] == '/'))) { + while ((cp > canonpath) && (*--cp != '/')) + ; + + p += 3; + continue; + } + } + +next: + *cp++ = *p++; + } + + /* remove trailing slash if not root / */ + if ((cp > canonpath + 1) && (cp[-1] == '/')) + cp--; + else if (cp == canonpath) + *cp++ = '/'; + + *cp = '\0'; + + return canonpath; +} + static struct blob_attr ** -rpc_check_path(struct blob_attr *msg, char **path, struct stat *s) +__rpc_check_path(const struct blobmsg_policy *policy, size_t policy_len, + int policy_path_idx, int policy_sid_idx, const char *perm, + struct blob_attr *msg, char **path, struct stat *s) { - static struct blob_attr *tb[__RPC_F_R_MAX]; + static struct blob_attr *tb[__RPC_F_RW_MAX]; /* largest _MAX constant */ - blobmsg_parse(rpc_file_r_policy, __RPC_F_R_MAX, tb, blob_data(msg), blob_len(msg)); + blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg)); - if (!tb[RPC_F_R_PATH]) + if (!tb[policy_path_idx]) { errno = EINVAL; return NULL; } - *path = blobmsg_data(tb[RPC_F_R_PATH]); + *path = rpc_canonicalize_path(blobmsg_get_string(tb[policy_path_idx])); - if (stat(*path, s)) + if (*path == NULL) + { + errno = ENOMEM; + return NULL; + } + + if (!rpc_file_access(tb[policy_sid_idx], *path, perm)) + { + errno = EACCES; + return NULL; + } + + if (s != NULL && stat(*path, s) != 0) return NULL; return tb; } +#define rpc_check_path(msg, policy_selector, perm, path, s) \ + __rpc_check_path(rpc_file_ ## policy_selector ## _policy, \ + ARRAY_SIZE(rpc_file_ ## policy_selector ## _policy), \ + RPC_F_ ## policy_selector ## _PATH, \ + RPC_F_ ## policy_selector ## _SESSION, \ + perm, msg, path, s) + static int rpc_file_read(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { - static struct blob_attr *tb[__RPC_F_RB_MAX]; + struct blob_attr **tb; bool base64 = false; int fd, rv; ssize_t len; @@ -188,14 +294,9 @@ rpc_file_read(struct ubus_context *ctx, struct ubus_object *obj, struct stat s; char *wbuf; - blobmsg_parse(rpc_file_rb_policy, __RPC_F_RB_MAX, tb, blob_data(msg), blob_len(msg)); - - if (!tb[RPC_F_RB_PATH]) - return rpc_errno_status(); - - path = blobmsg_data(tb[RPC_F_RB_PATH]); + tb = rpc_check_path(msg, RB, "read", &path, &s); - if (stat(path, &s)) + if (tb == NULL) return rpc_errno_status(); if (s.st_size >= RPC_FILE_MAX_SIZE) @@ -266,17 +367,20 @@ rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { - struct blob_attr *tb[__RPC_F_RW_MAX]; + struct blob_attr **tb; int append = O_TRUNC; mode_t prev_mode, mode = 0666; int fd, rv = 0; + char *path = NULL; void *data = NULL; ssize_t data_len = 0; - blobmsg_parse(rpc_file_rw_policy, __RPC_F_RW_MAX, tb, - blob_data(msg), blob_len(msg)); + tb = rpc_check_path(msg, RW, "write", &path, NULL); + + if (tb == NULL) + return rpc_errno_status(); - if (!tb[RPC_F_RW_PATH] || !tb[RPC_F_RW_DATA]) + if (!tb[RPC_F_RW_DATA]) return UBUS_STATUS_INVALID_ARGUMENT; data = blobmsg_data(tb[RPC_F_RW_DATA]); @@ -289,7 +393,7 @@ rpc_file_write(struct ubus_context *ctx, struct ubus_object *obj, mode = blobmsg_get_u32(tb[RPC_F_RW_MODE]); prev_mode = umask(0); - fd = open(blobmsg_data(tb[RPC_F_RW_PATH]), O_CREAT | O_WRONLY | append, mode); + fd = open(path, O_CREAT | O_WRONLY | append, mode); umask(prev_mode); if (fd < 0) return rpc_errno_status(); @@ -324,14 +428,14 @@ static int rpc_file_md5(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) -{ +{ int rv, i; char *path; struct stat s; uint8_t md5[16]; char *wbuf; - if (!rpc_check_path(msg, &path, &s)) + if (!rpc_check_path(msg, R, "read", &path, &s)) return rpc_errno_status(); if (!S_ISREG(s.st_mode)) @@ -353,6 +457,31 @@ rpc_file_md5(struct ubus_context *ctx, struct ubus_object *obj, return UBUS_STATUS_OK; } +static void +_rpc_file_add_stat(struct stat *s) +{ + int type; + + type = S_ISREG(s->st_mode) ? DT_REG : + S_ISDIR(s->st_mode) ? DT_DIR : + S_ISCHR(s->st_mode) ? DT_CHR : + S_ISBLK(s->st_mode) ? DT_BLK : + S_ISFIFO(s->st_mode) ? DT_FIFO : + S_ISLNK(s->st_mode) ? DT_LNK : + S_ISSOCK(s->st_mode) ? DT_SOCK : + DT_UNKNOWN; + + blobmsg_add_string(&buf, "type", d_types[type]); + blobmsg_add_u32(&buf, "size", s->st_size); + blobmsg_add_u32(&buf, "mode", s->st_mode); + blobmsg_add_u32(&buf, "atime", s->st_atime); + blobmsg_add_u32(&buf, "mtime", s->st_mtime); + blobmsg_add_u32(&buf, "ctime", s->st_ctime); + blobmsg_add_u32(&buf, "inode", s->st_ino); + blobmsg_add_u32(&buf, "uid", s->st_uid); + blobmsg_add_u32(&buf, "gid", s->st_gid); +} + static int rpc_file_list(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, @@ -360,11 +489,11 @@ rpc_file_list(struct ubus_context *ctx, struct ubus_object *obj, { DIR *fd; void *c, *d; - char *path; struct stat s; struct dirent *e; + char *path, *entrypath; - if (!rpc_check_path(msg, &path, &s)) + if (!rpc_check_path(msg, R, "list", &path, NULL)) return rpc_errno_status(); if ((fd = opendir(path)) == NULL) @@ -378,10 +507,18 @@ rpc_file_list(struct ubus_context *ctx, struct ubus_object *obj, if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue; - d = blobmsg_open_table(&buf, NULL); - blobmsg_add_string(&buf, "name", e->d_name); - blobmsg_add_string(&buf, "type", d_types[e->d_type]); - blobmsg_close_table(&buf, d); + if (asprintf(&entrypath, "%s/%s", path, e->d_name) < 0) + continue; + + if (!stat(entrypath, &s)) + { + d = blobmsg_open_table(&buf, NULL); + blobmsg_add_string(&buf, "name", e->d_name); + _rpc_file_add_stat(&s); + blobmsg_close_table(&buf, d); + } + + free(entrypath); } closedir(fd); @@ -398,34 +535,16 @@ rpc_file_stat(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { - int type; char *path; struct stat s; - if (!rpc_check_path(msg, &path, &s)) + if (!rpc_check_path(msg, R, "list", &path, &s)) return rpc_errno_status(); blob_buf_init(&buf, 0); - type = S_ISREG(s.st_mode) ? DT_REG : - S_ISDIR(s.st_mode) ? DT_DIR : - S_ISCHR(s.st_mode) ? DT_CHR : - S_ISBLK(s.st_mode) ? DT_BLK : - S_ISFIFO(s.st_mode) ? DT_FIFO : - S_ISLNK(s.st_mode) ? DT_LNK : - S_ISSOCK(s.st_mode) ? DT_SOCK : - DT_UNKNOWN; - blobmsg_add_string(&buf, "path", path); - blobmsg_add_string(&buf, "type", d_types[type]); - blobmsg_add_u32(&buf, "size", s.st_size); - blobmsg_add_u32(&buf, "mode", s.st_mode); - blobmsg_add_u32(&buf, "atime", s.st_atime); - blobmsg_add_u32(&buf, "mtime", s.st_mtime); - blobmsg_add_u32(&buf, "ctime", s.st_ctime); - blobmsg_add_u32(&buf, "inode", s.st_ino); - blobmsg_add_u32(&buf, "uid", s.st_uid); - blobmsg_add_u32(&buf, "gid", s.st_gid); + _rpc_file_add_stat(&s); ubus_send_reply(ctx, req, buf.head); blob_buf_free(&buf); @@ -433,6 +552,75 @@ rpc_file_stat(struct ubus_context *ctx, struct ubus_object *obj, return 0; } +static int +rpc_file_remove_recursive(const char *path); + +static int +rpc_file_remove_recursive(const char *path) +{ + DIR *fd; + int err = 0; + struct stat s; + struct dirent *e; + char *entrypath; + + if ((fd = opendir(path)) == NULL) + return rpc_errno_status(); + + for (e = readdir(fd); e != NULL && err == 0; e = readdir(fd)) + { + if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) + continue; + + if (asprintf(&entrypath, "%s/%s", path, e->d_name) >= 0) + { + if (!lstat(entrypath, &s)) + { + if (S_ISDIR(s.st_mode)) + err = rpc_file_remove_recursive(entrypath); + else if (unlink(entrypath)) + err = rpc_errno_status(); + } + + free(entrypath); + } + else + { + err = UBUS_STATUS_UNKNOWN_ERROR; + } + } + + closedir(fd); + + if (!err && rmdir(path)) + return rpc_errno_status(); + + return err; +} + +static int +rpc_file_remove(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct stat s; + char *path = NULL; + + if (!rpc_check_path(msg, R, "write", &path, NULL)) + return rpc_errno_status(); + + if (lstat(path, &s)) + return rpc_errno_status(); + + if (S_ISDIR(s.st_mode)) + return rpc_file_remove_recursive(path); + + if (unlink(path)) + return rpc_errno_status(); + + return 0; +} + static const char * rpc_file_exec_lookup(const char *cmd) { @@ -591,21 +779,29 @@ rpc_file_exec_epipe_state_cb(struct ustream *s) rpc_file_exec_reply(c, UBUS_STATUS_OK); } +static void +rpc_fdclose(int fd) +{ + if (fd > 2) + close(fd); +} + static int -rpc_file_exec_run(const char *cmd, +rpc_file_exec_run(const char *cmd, const struct blob_attr *sid, const struct blob_attr *arg, const struct blob_attr *env, struct ubus_context *ctx, struct ubus_request_data *req) { pid_t pid; + int devnull; int opipe[2]; int epipe[2]; int rem; struct blob_attr *cur; - char arglen; - char **args; + uint8_t arglen; + char *executable, **args, **tmp; struct rpc_file_exec_context *c; @@ -614,6 +810,14 @@ rpc_file_exec_run(const char *cmd, if (!cmd) return UBUS_STATUS_NOT_FOUND; + executable = rpc_canonicalize_path(cmd); + + if (executable == NULL) + return UBUS_STATUS_UNKNOWN_ERROR; + + if (!rpc_file_access(sid, executable, "exec")) + return UBUS_STATUS_PERMISSION_DENIED; + c = malloc(sizeof(*c)); if (!c) @@ -630,14 +834,20 @@ rpc_file_exec_run(const char *cmd, case 0: uloop_done(); + devnull = open("/dev/null", O_RDWR); + + if (devnull == -1) + return UBUS_STATUS_UNKNOWN_ERROR; + + dup2(devnull, 0); dup2(opipe[1], 1); dup2(epipe[1], 2); - close(0); - close(opipe[0]); - close(opipe[1]); - close(epipe[0]); - close(epipe[1]); + rpc_fdclose(devnull); + rpc_fdclose(opipe[0]); + rpc_fdclose(opipe[1]); + rpc_fdclose(epipe[0]); + rpc_fdclose(epipe[1]); arglen = 2; args = malloc(sizeof(char *) * arglen); @@ -645,7 +855,7 @@ rpc_file_exec_run(const char *cmd, if (!args) return UBUS_STATUS_UNKNOWN_ERROR; - args[0] = (char *)cmd; + args[0] = (char *)executable; args[1] = NULL; if (arg) @@ -655,11 +865,22 @@ rpc_file_exec_run(const char *cmd, if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) continue; + if (arglen == 255) + { + free(args); + return UBUS_STATUS_INVALID_ARGUMENT; + } + arglen++; + tmp = realloc(args, sizeof(char *) * arglen); - if (!(args = realloc(args, sizeof(char *) * arglen))) + if (!tmp) + { + free(args); return UBUS_STATUS_UNKNOWN_ERROR; + } + args = tmp; args[arglen-2] = blobmsg_data(cur); args[arglen-1] = NULL; } @@ -676,7 +897,7 @@ rpc_file_exec_run(const char *cmd, } } - if (execv(cmd, args)) + if (execv(executable, args)) return rpc_errno_status(); default: @@ -690,7 +911,7 @@ rpc_file_exec_run(const char *cmd, uloop_process_add(&c->process); c->timeout.cb = rpc_file_exec_timeout_cb; - uloop_timeout_set(&c->timeout, exec_timeout); + uloop_timeout_set(&c->timeout, *ops->exec_timeout); close(opipe[1]); close(epipe[1]); @@ -715,8 +936,8 @@ rpc_file_exec(struct ubus_context *ctx, struct ubus_object *obj, if (!tb[RPC_E_CMD]) return UBUS_STATUS_INVALID_ARGUMENT; - return rpc_file_exec_run(blobmsg_data(tb[RPC_E_CMD]), - tb[RPC_E_PARM], tb[RPC_E_ENV], ctx, req); + return rpc_file_exec_run(blobmsg_data(tb[RPC_E_CMD]), tb[RPC_E_SESSION], + tb[RPC_E_PARM], tb[RPC_E_ENV], ctx, req); } @@ -724,12 +945,13 @@ static int rpc_file_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx) { static const struct ubus_method file_methods[] = { - UBUS_METHOD("read", rpc_file_read, rpc_file_rb_policy), - UBUS_METHOD("write", rpc_file_write, rpc_file_rw_policy), - UBUS_METHOD("list", rpc_file_list, rpc_file_r_policy), - UBUS_METHOD("stat", rpc_file_stat, rpc_file_r_policy), - UBUS_METHOD("md5", rpc_file_md5, rpc_file_r_policy), - UBUS_METHOD("exec", rpc_file_exec, rpc_exec_policy), + UBUS_METHOD("read", rpc_file_read, rpc_file_RB_policy), + UBUS_METHOD("write", rpc_file_write, rpc_file_RW_policy), + UBUS_METHOD("list", rpc_file_list, rpc_file_R_policy), + UBUS_METHOD("stat", rpc_file_stat, rpc_file_R_policy), + UBUS_METHOD("md5", rpc_file_md5, rpc_file_R_policy), + UBUS_METHOD("remove", rpc_file_remove, rpc_file_R_policy), + UBUS_METHOD("exec", rpc_file_exec, rpc_exec_policy), }; static struct ubus_object_type file_type = @@ -742,6 +964,8 @@ rpc_file_api_init(const struct rpc_daemon_ops *o, struct ubus_context *ctx) .n_methods = ARRAY_SIZE(file_methods), }; + ops = o; + return ubus_add_object(ctx, &obj); }