From 84ed0f639b0237bc2d6bd3df0c72c7344638cfc3 Mon Sep 17 00:00:00 2001 From: RISCi_ATOM Date: Thu, 26 Dec 2019 12:07:11 -0500 Subject: [PATCH] Add cgi-io --- net/cgi-io/Makefile | 46 ++ net/cgi-io/src/CMakeLists.txt | 25 + net/cgi-io/src/main.c | 1052 +++++++++++++++++++++++++++++ net/cgi-io/src/multipart_parser.c | 309 +++++++++ net/cgi-io/src/multipart_parser.h | 48 ++ 5 files changed, 1480 insertions(+) create mode 100644 net/cgi-io/Makefile create mode 100644 net/cgi-io/src/CMakeLists.txt create mode 100644 net/cgi-io/src/main.c create mode 100644 net/cgi-io/src/multipart_parser.c create mode 100644 net/cgi-io/src/multipart_parser.h diff --git a/net/cgi-io/Makefile b/net/cgi-io/Makefile new file mode 100644 index 0000000..27bdf73 --- /dev/null +++ b/net/cgi-io/Makefile @@ -0,0 +1,46 @@ +# +# Copyright (C) 2015 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=cgi-io +PKG_RELEASE:=16 + +PKG_LICENSE:=GPL-2.0-or-later + +PKG_MAINTAINER:=John Crispin + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/cgi-io + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + DEPENDS:=+libubox +libubus + TITLE:=CGI utility for handling up/downloading of files +endef + +define Package/cgi-io/description + This package contains an cgi utility that is useful for up/downloading files +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Package/cgi-io/install + $(INSTALL_DIR) $(1)/usr/libexec $(1)/www/cgi-bin/ + $(INSTALL_BIN) $(PKG_BUILD_DIR)/cgi-io $(1)/usr/libexec + $(LN) ../../usr/libexec/cgi-io $(1)/www/cgi-bin/cgi-upload + $(LN) ../../usr/libexec/cgi-io $(1)/www/cgi-bin/cgi-download + $(LN) ../../usr/libexec/cgi-io $(1)/www/cgi-bin/cgi-backup + $(LN) ../../usr/libexec/cgi-io $(1)/www/cgi-bin/cgi-exec +endef + +$(eval $(call BuildPackage,cgi-io)) diff --git a/net/cgi-io/src/CMakeLists.txt b/net/cgi-io/src/CMakeLists.txt new file mode 100644 index 0000000..c7c9d40 --- /dev/null +++ b/net/cgi-io/src/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(cgi-io C) + +INCLUDE(CheckFunctionExists) + +FIND_PATH(ubus_include_dir libubus.h) +FIND_LIBRARY(ubox NAMES ubox) +FIND_LIBRARY(ubus NAMES ubus) +INCLUDE_DIRECTORIES(${ubus_include_dir}) + +ADD_DEFINITIONS(-Os -Wall -Werror -Wextra --std=gnu99 -g3) +ADD_DEFINITIONS(-Wno-unused-parameter -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +ADD_EXECUTABLE(cgi-io main.c multipart_parser.c) +TARGET_LINK_LIBRARIES(cgi-io ${ubox} ${ubus}) + +INSTALL(TARGETS cgi-io RUNTIME DESTINATION sbin) diff --git a/net/cgi-io/src/main.c b/net/cgi-io/src/main.c new file mode 100644 index 0000000..7cf8d7b --- /dev/null +++ b/net/cgi-io/src/main.c @@ -0,0 +1,1052 @@ +/* + * cgi-io - LuCI non-RPC helper + * + * Copyright (C) 2013 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. + */ + +#define _GNU_SOURCE /* splice(), SPLICE_F_MORE */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "multipart_parser.h" + +#define READ_BLOCK 4096 + +enum part { + PART_UNKNOWN, + PART_SESSIONID, + PART_FILENAME, + PART_FILEMODE, + PART_FILEDATA +}; + +const char *parts[] = { + "(bug)", + "sessionid", + "filename", + "filemode", + "filedata", +}; + +struct state +{ + bool is_content_disposition; + enum part parttype; + char *sessionid; + char *filename; + bool filedata; + int filemode; + int filefd; + int tempfd; +}; + +enum { + SES_ACCESS, + __SES_MAX, +}; + +static const struct blobmsg_policy ses_policy[__SES_MAX] = { + [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL }, +}; + + +static struct state st; + +static void +session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg) +{ + struct blob_attr *tb[__SES_MAX]; + bool *allow = (bool *)req->priv; + + if (!msg) + return; + + blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg)); + + if (tb[SES_ACCESS]) + *allow = blobmsg_get_bool(tb[SES_ACCESS]); +} + +static bool +session_access(const char *sid, const char *scope, const char *obj, const char *func) +{ + uint32_t id; + bool allow = false; + struct ubus_context *ctx; + static struct blob_buf req; + + ctx = ubus_connect(NULL); + + if (!ctx || ubus_lookup_id(ctx, "session", &id)) + goto out; + + blob_buf_init(&req, 0); + blobmsg_add_string(&req, "ubus_rpc_session", sid); + blobmsg_add_string(&req, "scope", scope); + blobmsg_add_string(&req, "object", obj); + blobmsg_add_string(&req, "function", func); + + ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500); + +out: + if (ctx) + ubus_free(ctx); + + return allow; +} + +static char * +checksum(const char *applet, size_t sumlen, const char *file) +{ + pid_t pid; + int r; + int fds[2]; + static char chksum[65]; + + if (pipe(fds)) + return NULL; + + switch ((pid = fork())) + { + case -1: + return NULL; + + case 0: + uloop_done(); + + dup2(fds[1], 1); + + close(0); + close(2); + close(fds[0]); + close(fds[1]); + + if (execl("/bin/busybox", "/bin/busybox", applet, file, NULL)) + return NULL; + + break; + + default: + memset(chksum, 0, sizeof(chksum)); + r = read(fds[0], chksum, sumlen); + + waitpid(pid, NULL, 0); + close(fds[0]); + close(fds[1]); + + if (r < 0) + return NULL; + } + + return chksum; +} + +static char * +datadup(const void *in, size_t len) +{ + char *out = malloc(len + 1); + + if (!out) + return NULL; + + memcpy(out, in, len); + + *(out + len) = 0; + + return out; +} + +static bool +urldecode(char *buf) +{ + char *c, *p; + + if (!buf || !*buf) + return true; + +#define hex(x) \ + (((x) <= '9') ? ((x) - '0') : \ + (((x) <= 'F') ? ((x) - 'A' + 10) : \ + ((x) - 'a' + 10))) + + for (c = p = buf; *p; c++) + { + if (*p == '%') + { + if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2))) + return false; + + *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2))); + + p += 3; + } + else if (*p == '+') + { + *c = ' '; + p++; + } + else + { + *c = *p++; + } + } + + *c = 0; + + return true; +} + +static bool +postdecode(char **fields, int n_fields) +{ + char *p; + const char *var; + static char buf[1024]; + int i, len, field, found = 0; + + var = getenv("CONTENT_TYPE"); + + if (!var || strncmp(var, "application/x-www-form-urlencoded", 33)) + return false; + + memset(buf, 0, sizeof(buf)); + + if ((len = read(0, buf, sizeof(buf) - 1)) > 0) + { + for (p = buf, i = 0; i <= len; i++) + { + if (buf[i] == '=') + { + buf[i] = 0; + + for (field = 0; field < (n_fields * 2); field += 2) + { + if (!strcmp(p, fields[field])) + { + fields[field + 1] = buf + i + 1; + found++; + } + } + } + else if (buf[i] == '&' || buf[i] == '\0') + { + buf[i] = 0; + + if (found >= n_fields) + break; + + p = buf + i + 1; + } + } + } + + for (field = 0; field < (n_fields * 2); field += 2) + if (!urldecode(fields[field + 1])) + return false; + + return (found >= n_fields); +} + +static char * +canonicalize_path(const char *path, size_t len) +{ + char *canonpath, *cp; + const char *p, *e; + + if (path == NULL || *path == '\0') + return NULL; + + canonpath = datadup(path, len); + + if (canonpath == NULL) + return NULL; + + /* normalize */ + for (cp = canonpath, p = path, e = path + len; p < e; ) { + if (*p != '/') + goto next; + + /* skip repeating / */ + if ((p + 1 < e) && (p[1] == '/')) { + p++; + continue; + } + + /* /./ or /../ */ + if ((p + 1 < e) && (p[1] == '.')) { + /* skip /./ */ + if ((p + 2 >= e) || (p[2] == '/')) { + p += 2; + continue; + } + + /* collapse /x/../ */ + if ((p + 2 < e) && (p[2] == '.') && ((p + 3 >= e) || (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 int +response(bool success, const char *message) +{ + char *chksum; + struct stat s; + + printf("Status: 200 OK\r\n"); + printf("Content-Type: text/plain\r\n\r\n{\n"); + + if (success) + { + if (!stat(st.filename, &s)) + printf("\t\"size\": %u,\n", (unsigned int)s.st_size); + else + printf("\t\"size\": null,\n"); + + chksum = checksum("md5sum", 32, st.filename); + printf("\t\"checksum\": %s%s%s,\n", + chksum ? "\"" : "", + chksum ? chksum : "null", + chksum ? "\"" : ""); + + chksum = checksum("sha256sum", 64, st.filename); + printf("\t\"sha256sum\": %s%s%s\n", + chksum ? "\"" : "", + chksum ? chksum : "null", + chksum ? "\"" : ""); + } + else + { + if (message) + printf("\t\"message\": \"%s\",\n", message); + + printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno)); + + if (st.filefd > -1) + unlink(st.filename); + } + + printf("}\n"); + + return -1; +} + +static int +failure(int code, int e, const char *message) +{ + printf("Status: %d %s\r\n", code, message); + printf("Content-Type: text/plain\r\n\r\n"); + printf("%s", message); + + if (e) + printf(": %s", strerror(e)); + + printf("\n"); + + return -1; +} + +static int +filecopy(void) +{ + int len; + char buf[READ_BLOCK]; + + if (!st.filedata) + { + close(st.tempfd); + errno = EINVAL; + return response(false, "No file data received"); + } + + if (lseek(st.tempfd, 0, SEEK_SET) < 0) + { + close(st.tempfd); + return response(false, "Failed to rewind temp file"); + } + + st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + + if (st.filefd < 0) + { + close(st.tempfd); + return response(false, "Failed to open target file"); + } + + while ((len = read(st.tempfd, buf, sizeof(buf))) > 0) + { + if (write(st.filefd, buf, len) != len) + { + close(st.tempfd); + close(st.filefd); + return response(false, "I/O failure while writing target file"); + } + } + + close(st.tempfd); + close(st.filefd); + + if (chmod(st.filename, st.filemode)) + return response(false, "Failed to chmod target file"); + + return 0; +} + +static int +header_field(multipart_parser *p, const char *data, size_t len) +{ + st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len); + return 0; +} + +static int +header_value(multipart_parser *p, const char *data, size_t len) +{ + size_t i, j; + + if (!st.is_content_disposition) + return 0; + + if (len < 10 || strncasecmp(data, "form-data", 9)) + return 0; + + for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--); + + if (len < 8 || strncasecmp(data, "name=\"", 6)) + return 0; + + for (data += 6, len -= 6, i = 0; i <= len; i++) + { + if (*(data + i) != '"') + continue; + + for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++) + if (!strncmp(data, parts[j], i)) + st.parttype = j; + + break; + } + + return 0; +} + +static int +data_begin_cb(multipart_parser *p) +{ + char tmpname[24] = "/tmp/luci-upload.XXXXXX"; + + if (st.parttype == PART_FILEDATA) + { + if (!st.sessionid) + return response(false, "File data without session"); + + if (!st.filename) + return response(false, "File data without name"); + + if (!session_access(st.sessionid, "file", st.filename, "write")) + return response(false, "Access to path denied by ACL"); + + st.tempfd = mkstemp(tmpname); + + if (st.tempfd < 0) + return response(false, "Failed to create temporary file"); + + unlink(tmpname); + } + + return 0; +} + +static int +data_cb(multipart_parser *p, const char *data, size_t len) +{ + int wlen = len; + + switch (st.parttype) + { + case PART_SESSIONID: + st.sessionid = datadup(data, len); + break; + + case PART_FILENAME: + st.filename = canonicalize_path(data, len); + break; + + case PART_FILEMODE: + st.filemode = strtoul(data, NULL, 8); + break; + + case PART_FILEDATA: + if (write(st.tempfd, data, len) != wlen) + { + close(st.tempfd); + return response(false, "I/O failure while writing temporary file"); + } + + if (!st.filedata) + st.filedata = !!wlen; + + break; + + default: + break; + } + + return 0; +} + +static int +data_end_cb(multipart_parser *p) +{ + if (st.parttype == PART_SESSIONID) + { + if (!session_access(st.sessionid, "cgi-io", "upload", "write")) + { + errno = EPERM; + return response(false, "Upload permission denied"); + } + } + else if (st.parttype == PART_FILEDATA) + { + if (st.tempfd < 0) + return response(false, "Internal program failure"); + +#if 0 + /* prepare directory */ + for (ptr = st.filename; *ptr; ptr++) + { + if (*ptr == '/') + { + *ptr = 0; + + if (mkdir(st.filename, 0755)) + { + unlink(st.tmpname); + return response(false, "Failed to create destination directory"); + } + + *ptr = '/'; + } + } +#endif + + if (filecopy()) + return -1; + + return response(true, NULL); + } + + st.parttype = PART_UNKNOWN; + return 0; +} + +static multipart_parser * +init_parser(void) +{ + char *boundary; + const char *var; + + multipart_parser *p; + static multipart_parser_settings s = { + .on_part_data = data_cb, + .on_headers_complete = data_begin_cb, + .on_part_data_end = data_end_cb, + .on_header_field = header_field, + .on_header_value = header_value + }; + + var = getenv("CONTENT_TYPE"); + + if (!var || strncmp(var, "multipart/form-data;", 20)) + return NULL; + + for (var += 20; *var && *var != '='; var++); + + if (*var++ != '=') + return NULL; + + boundary = malloc(strlen(var) + 3); + + if (!boundary) + return NULL; + + strcpy(boundary, "--"); + strcpy(boundary + 2, var); + + st.tempfd = -1; + st.filefd = -1; + st.filemode = 0600; + + p = multipart_parser_init(boundary, &s); + + free(boundary); + + return p; +} + +static int +main_upload(int argc, char *argv[]) +{ + int rem, len; + bool done = false; + char buf[READ_BLOCK]; + multipart_parser *p; + + p = init_parser(); + + if (!p) + { + errno = EINVAL; + return response(false, "Invalid request"); + } + + while ((len = read(0, buf, sizeof(buf))) > 0) + { + if (!done) { + rem = multipart_parser_execute(p, buf, len); + done = (rem < len); + } + } + + multipart_parser_free(p); + + return 0; +} + +static int +main_download(int argc, char **argv) +{ + char *fields[] = { "sessionid", NULL, "path", NULL, "filename", NULL, "mimetype", NULL }; + unsigned long long size = 0; + char *p, buf[READ_BLOCK]; + ssize_t len = 0; + struct stat s; + int rfd; + + postdecode(fields, 4); + + if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read")) + return failure(403, 0, "Download permission denied"); + + if (!fields[3] || !session_access(fields[1], "file", fields[3], "read")) + return failure(403, 0, "Access to path denied by ACL"); + + if (stat(fields[3], &s)) + return failure(404, errno, "Failed to stat requested path"); + + if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode)) + return failure(403, 0, "Requested path is not a regular file or block device"); + + for (p = fields[5]; p && *p; p++) + if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p)) + return failure(400, 0, "Invalid characters in filename"); + + for (p = fields[7]; p && *p; p++) + if (!isalnum(*p) && !strchr(" .;=/-", *p)) + return failure(400, 0, "Invalid characters in mimetype"); + + rfd = open(fields[3], O_RDONLY); + + if (rfd < 0) + return failure(500, errno, "Failed to open requested path"); + + if (S_ISBLK(s.st_mode)) + ioctl(rfd, BLKGETSIZE64, &size); + else + size = (unsigned long long)s.st_size; + + printf("Status: 200 OK\r\n"); + printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream"); + + if (fields[5]) + printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]); + + printf("Content-Length: %llu\r\n\r\n", size); + fflush(stdout); + + while (size > 0) { + len = sendfile(1, rfd, NULL, size); + + if (len == -1) { + if (errno == ENOSYS || errno == EINVAL) { + while ((len = read(rfd, buf, sizeof(buf))) > 0) + fwrite(buf, len, 1, stdout); + + fflush(stdout); + break; + } + + if (errno == EINTR || errno == EAGAIN) + continue; + } + + if (len <= 0) + break; + + size -= len; + } + + close(rfd); + + return 0; +} + +static int +main_backup(int argc, char **argv) +{ + pid_t pid; + time_t now; + int r; + int len; + int status; + int fds[2]; + char datestr[16] = { 0 }; + char hostname[64] = { 0 }; + char *fields[] = { "sessionid", NULL }; + + if (!postdecode(fields, 1) || !session_access(fields[1], "cgi-io", "backup", "read")) + return failure(403, 0, "Backup permission denied"); + + if (pipe(fds)) + return failure(500, errno, "Failed to spawn pipe"); + + switch ((pid = fork())) + { + case -1: + return failure(500, errno, "Failed to fork process"); + + case 0: + dup2(fds[1], 1); + + close(0); + close(2); + close(fds[0]); + close(fds[1]); + + r = chdir("/"); + if (r < 0) + return failure(500, errno, "Failed chdir('/')"); + + execl("/sbin/sysupgrade", "/sbin/sysupgrade", + "--create-backup", "-", NULL); + + return -1; + + default: + close(fds[1]); + + now = time(NULL); + strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now)); + + if (gethostname(hostname, sizeof(hostname) - 1)) + sprintf(hostname, "OpenWrt"); + + printf("Status: 200 OK\r\n"); + printf("Content-Type: application/x-targz\r\n"); + printf("Content-Disposition: attachment; " + "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr); + + fflush(stdout); + + do { + len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE); + } while (len > 0); + + waitpid(pid, &status, 0); + + close(fds[0]); + + return 0; + } +} + + +static const char * +lookup_executable(const char *cmd) +{ + size_t plen = 0, clen = strlen(cmd) + 1; + static char path[PATH_MAX]; + char *search, *p; + struct stat s; + + if (!stat(cmd, &s) && S_ISREG(s.st_mode)) + return cmd; + + search = getenv("PATH"); + + if (!search) + search = "/bin:/usr/bin:/sbin:/usr/sbin"; + + p = search; + + do { + if (*p != ':' && *p != '\0') + continue; + + plen = p - search; + + if ((plen + clen) >= sizeof(path)) + continue; + + strncpy(path, search, plen); + sprintf(path + plen, "/%s", cmd); + + if (!stat(path, &s) && S_ISREG(s.st_mode)) + return path; + + search = p + 1; + } while (*p++); + + return NULL; +} + +static char ** +parse_command(const char *cmdline) +{ + const char *p = cmdline, *s; + char **argv = NULL, *out; + size_t arglen = 0; + int argnum = 0; + bool esc; + + while (isspace(*cmdline)) + cmdline++; + + for (p = cmdline, s = p, esc = false; p; p++) { + if (esc) { + esc = false; + } + else if (*p == '\\' && p[1] != 0) { + esc = true; + } + else if (isspace(*p) || *p == 0) { + if (p > s) { + argnum += 1; + arglen += sizeof(char *) + (p - s) + 1; + } + + s = p + 1; + } + + if (*p == 0) + break; + } + + if (arglen == 0) + return NULL; + + argv = calloc(1, arglen + sizeof(char *)); + + if (!argv) + return NULL; + + out = (char *)argv + sizeof(char *) * (argnum + 1); + argv[0] = out; + + for (p = cmdline, s = p, esc = false, argnum = 0; p; p++) { + if (esc) { + esc = false; + *out++ = *p; + } + else if (*p == '\\' && p[1] != 0) { + esc = true; + } + else if (isspace(*p) || *p == 0) { + if (p > s) { + *out++ = ' '; + argv[++argnum] = out; + } + + s = p + 1; + } + else { + *out++ = *p; + } + + if (*p == 0) + break; + } + + argv[argnum] = NULL; + out[-1] = 0; + + return argv; +} + +static int +main_exec(int argc, char **argv) +{ + char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL }; + int i, devnull, status, fds[2]; + bool allowed = false; + ssize_t len = 0; + const char *exe; + char *p, **args; + pid_t pid; + + postdecode(fields, 4); + + if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read")) + return failure(403, 0, "Exec permission denied"); + + for (p = fields[5]; p && *p; p++) + if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p)) + return failure(400, 0, "Invalid characters in filename"); + + for (p = fields[7]; p && *p; p++) + if (!isalnum(*p) && !strchr(" .;=/-", *p)) + return failure(400, 0, "Invalid characters in mimetype"); + + args = fields[3] ? parse_command(fields[3]) : NULL; + + if (!args) + return failure(400, 0, "Invalid command parameter"); + + /* First check if we find an ACL match for the whole cmdline ... */ + allowed = session_access(fields[1], "file", args[0], "exec"); + + /* Now split the command vector... */ + for (i = 1; args[i]; i++) + args[i][-1] = 0; + + /* Find executable... */ + exe = lookup_executable(args[0]); + + if (!exe) { + free(args); + return failure(404, 0, "Executable not found"); + } + + /* If there was no ACL match, check for a match on the executable */ + if (!allowed && !session_access(fields[1], "file", exe, "exec")) { + free(args); + return failure(403, 0, "Access to command denied by ACL"); + } + + if (pipe(fds)) { + free(args); + return failure(500, errno, "Failed to spawn pipe"); + } + + switch ((pid = fork())) + { + case -1: + free(args); + close(fds[0]); + close(fds[1]); + return failure(500, errno, "Failed to fork process"); + + case 0: + devnull = open("/dev/null", O_RDWR); + + if (devnull > -1) { + dup2(devnull, 0); + dup2(devnull, 2); + close(devnull); + } + else { + close(0); + close(2); + } + + dup2(fds[1], 1); + close(fds[0]); + close(fds[1]); + + if (chdir("/") < 0) { + free(args); + return failure(500, errno, "Failed chdir('/')"); + } + + if (execv(exe, args) < 0) { + free(args); + return failure(500, errno, "Failed execv(...)"); + } + + return -1; + + default: + close(fds[1]); + + printf("Status: 200 OK\r\n"); + printf("Content-Type: %s\r\n", + fields[7] ? fields[7] : "application/octet-stream"); + + if (fields[5]) + printf("Content-Disposition: attachment; filename=\"%s\"\r\n", + fields[5]); + + printf("\r\n"); + fflush(stdout); + + do { + len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE); + } while (len > 0); + + waitpid(pid, &status, 0); + + close(fds[0]); + free(args); + + return 0; + } +} + +int main(int argc, char **argv) +{ + if (strstr(argv[0], "cgi-upload")) + return main_upload(argc, argv); + else if (strstr(argv[0], "cgi-download")) + return main_download(argc, argv); + else if (strstr(argv[0], "cgi-backup")) + return main_backup(argc, argv); + else if (strstr(argv[0], "cgi-exec")) + return main_exec(argc, argv); + + return -1; +} diff --git a/net/cgi-io/src/multipart_parser.c b/net/cgi-io/src/multipart_parser.c new file mode 100644 index 0000000..ee82c82 --- /dev/null +++ b/net/cgi-io/src/multipart_parser.c @@ -0,0 +1,309 @@ +/* Based on node-formidable by Felix Geisendörfer + * Igor Afonov - afonov@gmail.com - 2012 + * MIT License - http://www.opensource.org/licenses/mit-license.php + */ + +#include "multipart_parser.h" + +#include +#include +#include + +static void multipart_log(const char * format, ...) +{ +#ifdef DEBUG_MULTIPART + va_list args; + va_start(args, format); + + fprintf(stderr, "[HTTP_MULTIPART_PARSER] %s:%d: ", __FILE__, __LINE__); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); +#endif +} + +#define NOTIFY_CB(FOR) \ +do { \ + if (p->settings->on_##FOR) { \ + if (p->settings->on_##FOR(p) != 0) { \ + return i; \ + } \ + } \ +} while (0) + +#define EMIT_DATA_CB(FOR, ptr, len) \ +do { \ + if (p->settings->on_##FOR) { \ + if (p->settings->on_##FOR(p, ptr, len) != 0) { \ + return i; \ + } \ + } \ +} while (0) + + +#define LF 10 +#define CR 13 + +struct multipart_parser { + void * data; + + size_t index; + size_t boundary_length; + + unsigned char state; + + const multipart_parser_settings* settings; + + char* lookbehind; + char multipart_boundary[1]; +}; + +enum state { + s_uninitialized = 1, + s_start, + s_start_boundary, + s_header_field_start, + s_header_field, + s_headers_almost_done, + s_header_value_start, + s_header_value, + s_header_value_almost_done, + s_part_data_start, + s_part_data, + s_part_data_almost_boundary, + s_part_data_boundary, + s_part_data_almost_end, + s_part_data_end, + s_part_data_final_hyphen, + s_end +}; + +multipart_parser* multipart_parser_init + (const char *boundary, const multipart_parser_settings* settings) { + + multipart_parser* p = malloc(sizeof(multipart_parser) + + strlen(boundary) + + strlen(boundary) + 9); + + strcpy(p->multipart_boundary, boundary); + p->boundary_length = strlen(boundary); + + p->lookbehind = (p->multipart_boundary + p->boundary_length + 1); + + p->index = 0; + p->state = s_start; + p->settings = settings; + + return p; +} + +void multipart_parser_free(multipart_parser* p) { + free(p); +} + +void multipart_parser_set_data(multipart_parser *p, void *data) { + p->data = data; +} + +void *multipart_parser_get_data(multipart_parser *p) { + return p->data; +} + +size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len) { + size_t i = 0; + size_t mark = 0; + char c, cl; + int is_last = 0; + + while(i < len) { + c = buf[i]; + is_last = (i == (len - 1)); + switch (p->state) { + case s_start: + multipart_log("s_start"); + p->index = 0; + p->state = s_start_boundary; + + /* fallthrough */ + case s_start_boundary: + multipart_log("s_start_boundary"); + if (p->index == p->boundary_length) { + if (c != CR) { + return i; + } + p->index++; + break; + } else if (p->index == (p->boundary_length + 1)) { + if (c != LF) { + return i; + } + p->index = 0; + NOTIFY_CB(part_data_begin); + p->state = s_header_field_start; + break; + } + if (c != p->multipart_boundary[p->index]) { + return i; + } + p->index++; + break; + + case s_header_field_start: + multipart_log("s_header_field_start"); + mark = i; + p->state = s_header_field; + + /* fallthrough */ + case s_header_field: + multipart_log("s_header_field"); + if (c == CR) { + p->state = s_headers_almost_done; + break; + } + + if (c == '-') { + break; + } + + if (c == ':') { + EMIT_DATA_CB(header_field, buf + mark, i - mark); + p->state = s_header_value_start; + break; + } + + cl = tolower(c); + if (cl < 'a' || cl > 'z') { + multipart_log("invalid character in header name"); + return i; + } + if (is_last) + EMIT_DATA_CB(header_field, buf + mark, (i - mark) + 1); + break; + + case s_headers_almost_done: + multipart_log("s_headers_almost_done"); + if (c != LF) { + return i; + } + + p->state = s_part_data_start; + break; + + case s_header_value_start: + multipart_log("s_header_value_start"); + if (c == ' ') { + break; + } + + mark = i; + p->state = s_header_value; + + /* fallthrough */ + case s_header_value: + multipart_log("s_header_value"); + if (c == CR) { + EMIT_DATA_CB(header_value, buf + mark, i - mark); + p->state = s_header_value_almost_done; + } + if (is_last) + EMIT_DATA_CB(header_value, buf + mark, (i - mark) + 1); + break; + + case s_header_value_almost_done: + multipart_log("s_header_value_almost_done"); + if (c != LF) { + return i; + } + p->state = s_header_field_start; + break; + + case s_part_data_start: + multipart_log("s_part_data_start"); + NOTIFY_CB(headers_complete); + mark = i; + p->state = s_part_data; + + /* fallthrough */ + case s_part_data: + multipart_log("s_part_data"); + if (c == CR) { + EMIT_DATA_CB(part_data, buf + mark, i - mark); + mark = i; + p->state = s_part_data_almost_boundary; + p->lookbehind[0] = CR; + break; + } + if (is_last) + EMIT_DATA_CB(part_data, buf + mark, (i - mark) + 1); + break; + + case s_part_data_almost_boundary: + multipart_log("s_part_data_almost_boundary"); + if (c == LF) { + p->state = s_part_data_boundary; + p->lookbehind[1] = LF; + p->index = 0; + break; + } + EMIT_DATA_CB(part_data, p->lookbehind, 1); + p->state = s_part_data; + mark = i --; + break; + + case s_part_data_boundary: + multipart_log("s_part_data_boundary"); + if (p->multipart_boundary[p->index] != c) { + EMIT_DATA_CB(part_data, p->lookbehind, 2 + p->index); + p->state = s_part_data; + mark = i --; + break; + } + p->lookbehind[2 + p->index] = c; + if ((++ p->index) == p->boundary_length) { + NOTIFY_CB(part_data_end); + p->state = s_part_data_almost_end; + } + break; + + case s_part_data_almost_end: + multipart_log("s_part_data_almost_end"); + if (c == '-') { + p->state = s_part_data_final_hyphen; + break; + } + if (c == CR) { + p->state = s_part_data_end; + break; + } + return i; + + case s_part_data_final_hyphen: + multipart_log("s_part_data_final_hyphen"); + if (c == '-') { + NOTIFY_CB(body_end); + p->state = s_end; + break; + } + return i; + + case s_part_data_end: + multipart_log("s_part_data_end"); + if (c == LF) { + p->state = s_header_field_start; + NOTIFY_CB(part_data_begin); + break; + } + return i; + + case s_end: + multipart_log("s_end: %02X", (int) c); + break; + + default: + multipart_log("Multipart parser unrecoverable error"); + return 0; + } + ++ i; + } + + return len; +} diff --git a/net/cgi-io/src/multipart_parser.h b/net/cgi-io/src/multipart_parser.h new file mode 100644 index 0000000..87e67f4 --- /dev/null +++ b/net/cgi-io/src/multipart_parser.h @@ -0,0 +1,48 @@ +/* Based on node-formidable by Felix Geisendörfer + * Igor Afonov - afonov@gmail.com - 2012 + * MIT License - http://www.opensource.org/licenses/mit-license.php + */ +#ifndef _multipart_parser_h +#define _multipart_parser_h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +typedef struct multipart_parser multipart_parser; +typedef struct multipart_parser_settings multipart_parser_settings; +typedef struct multipart_parser_state multipart_parser_state; + +typedef int (*multipart_data_cb) (multipart_parser*, const char *at, size_t length); +typedef int (*multipart_notify_cb) (multipart_parser*); + +struct multipart_parser_settings { + multipart_data_cb on_header_field; + multipart_data_cb on_header_value; + multipart_data_cb on_part_data; + + multipart_notify_cb on_part_data_begin; + multipart_notify_cb on_headers_complete; + multipart_notify_cb on_part_data_end; + multipart_notify_cb on_body_end; +}; + +multipart_parser* multipart_parser_init + (const char *boundary, const multipart_parser_settings* settings); + +void multipart_parser_free(multipart_parser* p); + +size_t multipart_parser_execute(multipart_parser* p, const char *buf, size_t len); + +void multipart_parser_set_data(multipart_parser* p, void* data); +void * multipart_parser_get_data(multipart_parser* p); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif -- 2.25.1