2 * cgi-io - LuCI non-RPC helper
4 * Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
29 #include <sys/sendfile.h>
30 #include <sys/ioctl.h>
34 #include <libubox/blobmsg.h>
36 #include "multipart_parser.h"
47 const char *parts
[] = {
57 bool is_content_disposition
;
72 static const struct blobmsg_policy ses_policy
[__SES_MAX
] = {
73 [SES_ACCESS
] = { .name
= "access", .type
= BLOBMSG_TYPE_BOOL
},
77 static struct state st
;
80 session_access_cb(struct ubus_request
*req
, int type
, struct blob_attr
*msg
)
82 struct blob_attr
*tb
[__SES_MAX
];
83 bool *allow
= (bool *)req
->priv
;
88 blobmsg_parse(ses_policy
, __SES_MAX
, tb
, blob_data(msg
), blob_len(msg
));
91 *allow
= blobmsg_get_bool(tb
[SES_ACCESS
]);
95 session_access(const char *sid
, const char *scope
, const char *obj
, const char *func
)
99 struct ubus_context
*ctx
;
100 static struct blob_buf req
;
102 ctx
= ubus_connect(NULL
);
104 if (!ctx
|| ubus_lookup_id(ctx
, "session", &id
))
107 blob_buf_init(&req
, 0);
108 blobmsg_add_string(&req
, "ubus_rpc_session", sid
);
109 blobmsg_add_string(&req
, "scope", scope
);
110 blobmsg_add_string(&req
, "object", obj
);
111 blobmsg_add_string(&req
, "function", func
);
113 ubus_invoke(ctx
, id
, "access", req
.head
, session_access_cb
, &allow
, 500);
123 checksum(const char *applet
, size_t sumlen
, const char *file
)
127 static char chksum
[65];
132 switch ((pid
= fork()))
147 if (execl("/bin/busybox", "/bin/busybox", applet
, file
, NULL
))
153 memset(chksum
, 0, sizeof(chksum
));
154 read(fds
[0], chksum
, sumlen
);
155 waitpid(pid
, NULL
, 0);
164 datadup(const void *in
, size_t len
)
166 char *out
= malloc(len
+ 1);
171 memcpy(out
, in
, len
);
187 (((x) <= '9') ? ((x) - '0') : \
188 (((x) <= 'F') ? ((x) - 'A' + 10) : \
191 for (c
= p
= buf
; *p
; c
++)
195 if (!isxdigit(*(p
+ 1)) || !isxdigit(*(p
+ 2)))
198 *c
= (char)(16 * hex(*(p
+ 1)) + hex(*(p
+ 2)));
219 postdecode(char **fields
, int n_fields
)
223 static char buf
[1024];
224 int i
, len
, field
, found
= 0;
226 var
= getenv("CONTENT_TYPE");
228 if (!var
|| strncmp(var
, "application/x-www-form-urlencoded", 33))
231 memset(buf
, 0, sizeof(buf
));
233 if ((len
= read(0, buf
, sizeof(buf
) - 1)) > 0)
235 for (p
= buf
, i
= 0; i
<= len
; i
++)
241 for (field
= 0; field
< (n_fields
* 2); field
+= 2)
243 if (!strcmp(p
, fields
[field
]))
245 fields
[field
+ 1] = buf
+ i
+ 1;
250 else if (buf
[i
] == '&' || buf
[i
] == '\0')
254 if (found
>= n_fields
)
262 for (field
= 0; field
< (n_fields
* 2); field
+= 2)
263 if (!urldecode(fields
[field
+ 1]))
266 return (found
>= n_fields
);
270 canonicalize_path(const char *path
, size_t len
)
272 char *canonpath
, *cp
;
275 if (path
== NULL
|| *path
== '\0')
278 canonpath
= datadup(path
, len
);
280 if (canonpath
== NULL
)
284 for (cp
= canonpath
, p
= path
, e
= path
+ len
; p
< e
; ) {
288 /* skip repeating / */
289 if ((p
+ 1 < e
) && (p
[1] == '/')) {
295 if ((p
+ 1 < e
) && (p
[1] == '.')) {
297 if ((p
+ 2 >= e
) || (p
[2] == '/')) {
302 /* collapse /x/../ */
303 if ((p
+ 2 < e
) && (p
[2] == '.') && ((p
+ 3 >= e
) || (p
[3] == '/'))) {
304 while ((cp
> canonpath
) && (*--cp
!= '/'))
316 /* remove trailing slash if not root / */
317 if ((cp
> canonpath
+ 1) && (cp
[-1] == '/'))
319 else if (cp
== canonpath
)
328 response(bool success
, const char *message
)
333 printf("Status: 200 OK\r\n");
334 printf("Content-Type: text/plain\r\n\r\n{\n");
338 if (!stat(st
.filename
, &s
))
339 printf("\t\"size\": %u,\n", (unsigned int)s
.st_size
);
341 printf("\t\"size\": null,\n");
343 chksum
= checksum("md5sum", 32, st
.filename
);
344 printf("\t\"checksum\": %s%s%s,\n",
346 chksum
? chksum
: "null",
349 chksum
= checksum("sha256sum", 64, st
.filename
);
350 printf("\t\"sha256sum\": %s%s%s\n",
352 chksum
? chksum
: "null",
358 printf("\t\"message\": \"%s\",\n", message
);
360 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno
, strerror(errno
));
372 failure(int code
, int e
, const char *message
)
374 printf("Status: %d %s\r\n", code
, message
);
375 printf("Content-Type: text/plain\r\n\r\n");
376 printf("%s", message
);
379 printf(": %s", strerror(e
));
396 return response(false, "No file data received");
399 if (lseek(st
.tempfd
, 0, SEEK_SET
) < 0)
402 return response(false, "Failed to rewind temp file");
405 st
.filefd
= open(st
.filename
, O_CREAT
| O_TRUNC
| O_WRONLY
, 0600);
410 return response(false, "Failed to open target file");
413 while ((len
= read(st
.tempfd
, buf
, sizeof(buf
))) > 0)
415 if (write(st
.filefd
, buf
, len
) != len
)
419 return response(false, "I/O failure while writing target file");
426 if (chmod(st
.filename
, st
.filemode
))
427 return response(false, "Failed to chmod target file");
433 header_field(multipart_parser
*p
, const char *data
, size_t len
)
435 st
.is_content_disposition
= !strncasecmp(data
, "Content-Disposition", len
);
440 header_value(multipart_parser
*p
, const char *data
, size_t len
)
444 if (!st
.is_content_disposition
)
447 if (len
< 10 || strncasecmp(data
, "form-data", 9))
450 for (data
+= 9, len
-= 9; *data
== ' ' || *data
== ';'; data
++, len
--);
452 if (len
< 8 || strncasecmp(data
, "name=\"", 6))
455 for (data
+= 6, len
-= 6, i
= 0; i
<= len
; i
++)
457 if (*(data
+ i
) != '"')
460 for (j
= 1; j
< sizeof(parts
) / sizeof(parts
[0]); j
++)
461 if (!strncmp(data
, parts
[j
], i
))
471 data_begin_cb(multipart_parser
*p
)
473 char tmpname
[24] = "/tmp/luci-upload.XXXXXX";
475 if (st
.parttype
== PART_FILEDATA
)
478 return response(false, "File data without session");
481 return response(false, "File data without name");
483 if (!session_access(st
.sessionid
, "file", st
.filename
, "write"))
484 return response(false, "Access to path denied by ACL");
486 st
.tempfd
= mkstemp(tmpname
);
489 return response(false, "Failed to create temporary file");
498 data_cb(multipart_parser
*p
, const char *data
, size_t len
)
503 st
.sessionid
= datadup(data
, len
);
507 st
.filename
= canonicalize_path(data
, len
);
511 st
.filemode
= strtoul(data
, NULL
, 8);
515 if (write(st
.tempfd
, data
, len
) != len
)
518 return response(false, "I/O failure while writing temporary file");
534 data_end_cb(multipart_parser
*p
)
536 if (st
.parttype
== PART_SESSIONID
)
538 if (!session_access(st
.sessionid
, "cgi-io", "upload", "write"))
541 return response(false, "Upload permission denied");
544 else if (st
.parttype
== PART_FILEDATA
)
547 return response(false, "Internal program failure");
550 /* prepare directory */
551 for (ptr
= st
.filename
; *ptr
; ptr
++)
557 if (mkdir(st
.filename
, 0755))
560 return response(false, "Failed to create destination directory");
571 return response(true, NULL
);
574 st
.parttype
= PART_UNKNOWN
;
578 static multipart_parser
*
585 static multipart_parser_settings s
= {
586 .on_part_data
= data_cb
,
587 .on_headers_complete
= data_begin_cb
,
588 .on_part_data_end
= data_end_cb
,
589 .on_header_field
= header_field
,
590 .on_header_value
= header_value
593 var
= getenv("CONTENT_TYPE");
595 if (!var
|| strncmp(var
, "multipart/form-data;", 20))
598 for (var
+= 20; *var
&& *var
!= '='; var
++);
603 boundary
= malloc(strlen(var
) + 3);
608 strcpy(boundary
, "--");
609 strcpy(boundary
+ 2, var
);
615 p
= multipart_parser_init(boundary
, &s
);
623 main_upload(int argc
, char *argv
[])
634 return response(false, "Invalid request");
637 while ((len
= read(0, buf
, sizeof(buf
))) > 0)
639 rem
= multipart_parser_execute(p
, buf
, len
);
645 multipart_parser_free(p
);
647 /* read remaining post data */
648 while ((len
= read(0, buf
, sizeof(buf
))) > 0);
654 main_download(int argc
, char **argv
)
656 char *fields
[] = { "sessionid", NULL
, "path", NULL
, "filename", NULL
, "mimetype", NULL
};
657 unsigned long long size
= 0;
663 postdecode(fields
, 4);
665 if (!fields
[1] || !session_access(fields
[1], "cgi-io", "download", "read"))
666 return failure(403, 0, "Download permission denied");
668 if (!fields
[3] || !session_access(fields
[1], "file", fields
[3], "read"))
669 return failure(403, 0, "Access to path denied by ACL");
671 if (stat(fields
[3], &s
))
672 return failure(404, errno
, "Failed to stat requested path");
674 if (!S_ISREG(s
.st_mode
) && !S_ISBLK(s
.st_mode
))
675 return failure(403, 0, "Requested path is not a regular file or block device");
677 for (p
= fields
[5]; p
&& *p
; p
++)
678 if (!isalnum(*p
) && !strchr(" ()<>@,;:[]?.=%", *p
))
679 return failure(400, 0, "Invalid characters in filename");
681 for (p
= fields
[7]; p
&& *p
; p
++)
682 if (!isalnum(*p
) && !strchr(" .;=/-", *p
))
683 return failure(400, 0, "Invalid characters in mimetype");
685 rfd
= open(fields
[3], O_RDONLY
);
688 return failure(500, errno
, "Failed to open requested path");
690 if (S_ISBLK(s
.st_mode
))
691 ioctl(rfd
, BLKGETSIZE64
, &size
);
693 size
= (unsigned long long)s
.st_size
;
695 printf("Status: 200 OK\r\n");
696 printf("Content-Type: %s\r\n", fields
[7] ? fields
[7] : "application/octet-stream");
699 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields
[5]);
701 printf("Content-Length: %llu\r\n\r\n", size
);
705 len
= sendfile(1, rfd
, NULL
, size
);
708 if (errno
== ENOSYS
|| errno
== EINVAL
) {
709 while ((len
= read(rfd
, buf
, sizeof(buf
))) > 0)
710 fwrite(buf
, len
, 1, stdout
);
716 if (errno
== EINTR
|| errno
== EAGAIN
)
732 main_backup(int argc
, char **argv
)
740 char datestr
[16] = { 0 };
741 char hostname
[64] = { 0 };
742 char *fields
[] = { "sessionid", NULL
};
744 if (!postdecode(fields
, 1) || !session_access(fields
[1], "cgi-io", "backup", "read"))
745 return failure(403, 0, "Backup permission denied");
748 return failure(500, errno
, "Failed to spawn pipe");
750 switch ((pid
= fork()))
753 return failure(500, errno
, "Failed to fork process");
765 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
766 "--create-backup", "-", NULL
);
771 fcntl(fds
[0], F_SETFL
, fcntl(fds
[0], F_GETFL
) | O_NONBLOCK
);
773 strftime(datestr
, sizeof(datestr
) - 1, "%Y-%m-%d", localtime(&now
));
775 if (gethostname(hostname
, sizeof(hostname
) - 1))
776 sprintf(hostname
, "OpenWrt");
778 printf("Status: 200 OK\r\n");
779 printf("Content-Type: application/x-targz\r\n");
780 printf("Content-Disposition: attachment; "
781 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname
, datestr
);
784 waitpid(pid
, &status
, 0);
786 while ((len
= read(fds
[0], buf
, sizeof(buf
))) > 0) {
787 fwrite(buf
, len
, 1, stdout
);
791 } while (!WIFEXITED(status
));
800 int main(int argc
, char **argv
)
802 if (strstr(argv
[0], "cgi-upload"))
803 return main_upload(argc
, argv
);
804 else if (strstr(argv
[0], "cgi-download"))
805 return main_download(argc
, argv
);
806 else if (strstr(argv
[0], "cgi-backup"))
807 return main_backup(argc
, argv
);