cgi-io: add download operation
authorJo-Philipp Wich <jo@mein.io>
Fri, 13 Sep 2019 05:23:25 +0000 (07:23 +0200)
committerJohn Crispin <john@phrozen.org>
Fri, 13 Sep 2019 11:05:09 +0000 (13:05 +0200)
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>
net/cgi-io/Makefile
net/cgi-io/src/main.c

index 4b2d664af9638f08d35a274741923f0904f4a3fc..5ba695faed18ea8aa52e08e011a649beed081e8d 100644 (file)
@@ -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
 
index 44a520576028ef722829681528de17017474539a..aaba37d0d48662773d5d71b083369c096b134752 100644 (file)
@@ -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>
@@ -645,6 +648,84 @@ main_upload(int argc, char *argv[])
        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[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)
 {
@@ -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);