summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2019-09-13 07:23:25 +0200
committerJohn Crispin <john@phrozen.org>2019-09-13 13:05:09 +0200
commitab2a2b080d4143bfbbd8584a39999ef998905dd2 (patch)
tree7e559eb48db407cd44270bc9b5d60e6cf921434e /net
parentc8a86c8c8e1925eb7d84c52e702c4be5fc8ac76b (diff)
cgi-io: add download operation
Add a new `cgi-download` applet which allows to retrieve the contents of regular files or block devices. In order to initiate a transfer, a POST request in x-www-form-urlencoded format must be sent to the applet, with one field "sessionid" holding the login session and another field "path" containing the file path to download. Further optional fields are "filename" which - if present - will cause the download applet to set a Content-Dispostition header and "mimetype" which allows to let the applet respond with a specific type instead of the default "application/octet-stream". Below is an example for the required acl rules to grant download access to files or block devices: ubus call session grant '{ "ubus_rpc_session": "...", "scope": "cgi-io", "objects": [ [ "download", "read" ] ] }' ubus call session grant '{ "ubus_rpc_session": "...", "scope": "file", "objects": [ [ "/etc/config/*", "read" ], [ "/dev/mtdblock*", "read" ] ] }' Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'net')
-rw-r--r--net/cgi-io/Makefile3
-rw-r--r--net/cgi-io/src/main.c83
2 files changed, 85 insertions, 1 deletions
diff --git a/net/cgi-io/Makefile b/net/cgi-io/Makefile
index 4b2d664af..5ba695fae 100644
--- a/net/cgi-io/Makefile
+++ b/net/cgi-io/Makefile
@@ -8,7 +8,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=cgi-io
-PKG_RELEASE:=9
+PKG_RELEASE:=10
PKG_LICENSE:=GPL-2.0-or-later
@@ -38,6 +38,7 @@ 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
endef
diff --git a/net/cgi-io/src/main.c b/net/cgi-io/src/main.c
index 44a520576..aaba37d0d 100644
--- a/net/cgi-io/src/main.c
+++ b/net/cgi-io/src/main.c
@@ -26,6 +26,9 @@
#include <ctype.h>
#include <sys/stat.h>
#include <sys/wait.h>
+#include <sys/sendfile.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
#include <libubus.h>
#include <libubox/blobmsg.h>
@@ -646,6 +649,84 @@ main_upload(int argc, char *argv[])
}
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[4096];
+ 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(0, "Download permission denied");
+
+ if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
+ return failure(0, "Access to path denied by ACL");
+
+ if (stat(fields[3], &s))
+ return failure(errno, "Failed to stat requested path");
+
+ if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
+ return failure(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(0, "Invalid characters in filename");
+
+ for (p = fields[7]; p && *p; p++)
+ if (!isalnum(*p) && !strchr(" .;=/-", *p))
+ return failure(0, "Invalid characters in mimetype");
+
+ rfd = open(fields[3], O_RDONLY);
+
+ if (rfd < 0)
+ return failure(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;
@@ -718,6 +799,8 @@ 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);