main: fix two one-byte overreads in header_value()
[project/cgi-io.git] / main.c
1 /*
2 * cgi-io - LuCI non-RPC helper
3 *
4 * Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
5 *
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.
9 *
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.
17 */
18
19 #define _GNU_SOURCE /* splice(), SPLICE_F_MORE */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <ctype.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 #include <sys/sendfile.h>
32 #include <sys/ioctl.h>
33 #include <linux/fs.h>
34
35 #include <libubus.h>
36 #include <libubox/blobmsg.h>
37
38 #include "util.h"
39 #include "multipart_parser.h"
40
41 #ifndef O_TMPFILE
42 #define O_TMPFILE (020000000 | O_DIRECTORY)
43 #endif
44
45 #define READ_BLOCK 4096
46
47 enum part {
48 PART_UNKNOWN,
49 PART_SESSIONID,
50 PART_FILENAME,
51 PART_FILEMODE,
52 PART_FILEDATA
53 };
54
55 const char *parts[] = {
56 "(bug)",
57 "sessionid",
58 "filename",
59 "filemode",
60 "filedata",
61 };
62
63 struct state
64 {
65 bool is_content_disposition;
66 enum part parttype;
67 char *sessionid;
68 char *filename;
69 bool filedata;
70 int filemode;
71 int filefd;
72 int tempfd;
73 };
74
75 static struct state st;
76
77 #ifndef UNIT_TESTING
78
79 enum {
80 SES_ACCESS,
81 __SES_MAX,
82 };
83
84 static const struct blobmsg_policy ses_policy[__SES_MAX] = {
85 [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
86 };
87
88 static void
89 session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
90 {
91 struct blob_attr *tb[__SES_MAX];
92 bool *allow = (bool *)req->priv;
93
94 if (!msg)
95 return;
96
97 blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
98
99 if (tb[SES_ACCESS])
100 *allow = blobmsg_get_bool(tb[SES_ACCESS]);
101 }
102 #endif
103
104 static bool
105 session_access(const char *sid, const char *scope, const char *obj, const char *func)
106 {
107 #ifdef UNIT_TESTING
108 return true;
109 #else
110 uint32_t id;
111 bool allow = false;
112 struct ubus_context *ctx;
113 static struct blob_buf req;
114
115 ctx = ubus_connect(NULL);
116
117 if (!ctx || !obj || ubus_lookup_id(ctx, "session", &id))
118 goto out;
119
120 blob_buf_init(&req, 0);
121 blobmsg_add_string(&req, "ubus_rpc_session", sid);
122 blobmsg_add_string(&req, "scope", scope);
123 blobmsg_add_string(&req, "object", obj);
124 blobmsg_add_string(&req, "function", func);
125
126 ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
127
128 out:
129 if (ctx)
130 ubus_free(ctx);
131
132 return allow;
133 #endif
134 }
135
136 static char *
137 checksum(const char *applet, size_t sumlen, const char *file)
138 {
139 pid_t pid;
140 int r;
141 int fds[2];
142 static char chksum[65];
143
144 if (pipe(fds))
145 return NULL;
146
147 switch ((pid = fork()))
148 {
149 case -1:
150 return NULL;
151
152 case 0:
153 uloop_done();
154
155 dup2(fds[1], 1);
156
157 close(0);
158 close(2);
159 close(fds[0]);
160 close(fds[1]);
161
162 if (execl("/bin/busybox", "/bin/busybox", applet, file, NULL))
163 return NULL;
164
165 break;
166
167 default:
168 memset(chksum, 0, sizeof(chksum));
169 r = read(fds[0], chksum, sumlen);
170
171 waitpid(pid, NULL, 0);
172 close(fds[0]);
173 close(fds[1]);
174
175 if (r < 0)
176 return NULL;
177 }
178
179 return chksum;
180 }
181
182 static int
183 response(bool success, const char *message)
184 {
185 char *chksum;
186 struct stat s;
187
188 printf("Status: 200 OK\r\n");
189 printf("Content-Type: text/plain\r\n\r\n{\n");
190
191 if (success)
192 {
193 if (!stat(st.filename, &s))
194 printf("\t\"size\": %u,\n", (unsigned int)s.st_size);
195 else
196 printf("\t\"size\": null,\n");
197
198 chksum = checksum("md5sum", 32, st.filename);
199 printf("\t\"checksum\": %s%s%s,\n",
200 chksum ? "\"" : "",
201 chksum ? chksum : "null",
202 chksum ? "\"" : "");
203
204 chksum = checksum("sha256sum", 64, st.filename);
205 printf("\t\"sha256sum\": %s%s%s\n",
206 chksum ? "\"" : "",
207 chksum ? chksum : "null",
208 chksum ? "\"" : "");
209 }
210 else
211 {
212 if (message)
213 printf("\t\"message\": \"%s\",\n", message);
214
215 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
216
217 if (st.filefd > -1 && st.filename)
218 unlink(st.filename);
219 }
220
221 printf("}\n");
222
223 return -1;
224 }
225
226 static int
227 failure(int code, int e, const char *message)
228 {
229 printf("Status: %d %s\r\n", code, message);
230 printf("Content-Type: text/plain\r\n\r\n");
231 printf("%s", message);
232
233 if (e)
234 printf(": %s", strerror(e));
235
236 printf("\n");
237
238 return -1;
239 }
240
241 static int
242 filecopy(void)
243 {
244 int len;
245 char buf[READ_BLOCK];
246
247 if (!st.filedata)
248 {
249 close(st.tempfd);
250 errno = EINVAL;
251 return response(false, "No file data received");
252 }
253
254 snprintf(buf, sizeof(buf), "/proc/self/fd/%d", st.tempfd);
255
256 if (unlink(st.filename) < 0 && errno != ENOENT)
257 {
258 close(st.tempfd);
259 return response(false, "Failed to unlink existing file");
260 }
261
262 if (linkat(AT_FDCWD, buf, AT_FDCWD, st.filename, AT_SYMLINK_FOLLOW) < 0)
263 {
264 if (lseek(st.tempfd, 0, SEEK_SET) < 0)
265 {
266 close(st.tempfd);
267 return response(false, "Failed to rewind temp file");
268 }
269
270 st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
271
272 if (st.filefd < 0)
273 {
274 close(st.tempfd);
275 return response(false, "Failed to open target file");
276 }
277
278 while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
279 {
280 if (write(st.filefd, buf, len) != len)
281 {
282 close(st.tempfd);
283 close(st.filefd);
284 return response(false, "I/O failure while writing target file");
285 }
286 }
287
288 close(st.filefd);
289 }
290
291 close(st.tempfd);
292
293 if (chmod(st.filename, st.filemode))
294 return response(false, "Failed to chmod target file");
295
296 return 0;
297 }
298
299 static int
300 header_field(multipart_parser *p, const char *data, size_t len)
301 {
302 st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
303 return 0;
304 }
305
306 static int
307 header_value(multipart_parser *p, const char *data, size_t len)
308 {
309 size_t i, j;
310
311 if (!st.is_content_disposition)
312 return 0;
313
314 if (len < 10 || strncasecmp(data, "form-data", 9))
315 return 0;
316
317 for (data += 9, len -= 9; len > 0 && (*data == ' ' || *data == ';'); data++, len--);
318
319 if (len < 8 || strncasecmp(data, "name=\"", 6))
320 return 0;
321
322 for (data += 6, len -= 6, i = 1; i < len; i++)
323 {
324 if (data[i] == '"')
325 {
326 for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
327 if (!strncmp(data, parts[j], i - 1))
328 st.parttype = j;
329
330 break;
331 }
332 }
333
334 return 0;
335 }
336
337 static int
338 data_begin_cb(multipart_parser *p)
339 {
340 if (st.parttype == PART_FILEDATA)
341 {
342 if (!st.sessionid)
343 return response(false, "File data without session");
344
345 if (!st.filename)
346 return response(false, "File data without name");
347
348 if (!session_access(st.sessionid, "file", st.filename, "write"))
349 return response(false, "Access to path denied by ACL");
350
351 st.tempfd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
352
353 if (st.tempfd < 0)
354 return response(false, "Failed to create temporary file");
355 }
356
357 return 0;
358 }
359
360 static int
361 data_cb(multipart_parser *p, const char *data, size_t len)
362 {
363 int wlen = len;
364
365 switch (st.parttype)
366 {
367 case PART_SESSIONID:
368 st.sessionid = datadup(data, len);
369 break;
370
371 case PART_FILENAME:
372 st.filename = canonicalize_path(data, len);
373 break;
374
375 case PART_FILEMODE:
376 st.filemode = strtoul(data, NULL, 8);
377 break;
378
379 case PART_FILEDATA:
380 if (write(st.tempfd, data, len) != wlen)
381 {
382 close(st.tempfd);
383 return response(false, "I/O failure while writing temporary file");
384 }
385
386 if (!st.filedata)
387 st.filedata = !!wlen;
388
389 break;
390
391 default:
392 break;
393 }
394
395 return 0;
396 }
397
398 static int
399 data_end_cb(multipart_parser *p)
400 {
401 if (st.parttype == PART_SESSIONID)
402 {
403 if (!session_access(st.sessionid, "cgi-io", "upload", "write"))
404 {
405 errno = EPERM;
406 return response(false, "Upload permission denied");
407 }
408 }
409 else if (st.parttype == PART_FILEDATA)
410 {
411 if (st.tempfd < 0)
412 return response(false, "Internal program failure");
413
414 #if 0
415 /* prepare directory */
416 for (ptr = st.filename; *ptr; ptr++)
417 {
418 if (*ptr == '/')
419 {
420 *ptr = 0;
421
422 if (mkdir(st.filename, 0755))
423 {
424 unlink(st.tmpname);
425 return response(false, "Failed to create destination directory");
426 }
427
428 *ptr = '/';
429 }
430 }
431 #endif
432
433 if (filecopy())
434 return -1;
435
436 return response(true, NULL);
437 }
438
439 st.parttype = PART_UNKNOWN;
440 return 0;
441 }
442
443 static multipart_parser *
444 init_parser(void)
445 {
446 char *boundary;
447 const char *var;
448
449 multipart_parser *p;
450 static multipart_parser_settings s = {
451 .on_part_data = data_cb,
452 .on_headers_complete = data_begin_cb,
453 .on_part_data_end = data_end_cb,
454 .on_header_field = header_field,
455 .on_header_value = header_value
456 };
457
458 var = getenv("CONTENT_TYPE");
459
460 if (!var || strncmp(var, "multipart/form-data;", 20))
461 return NULL;
462
463 for (var += 20; *var && *var != '='; var++);
464
465 if (*var++ != '=')
466 return NULL;
467
468 boundary = malloc(strlen(var) + 3);
469
470 if (!boundary)
471 return NULL;
472
473 strcpy(boundary, "--");
474 strcpy(boundary + 2, var);
475
476 st.tempfd = -1;
477 st.filefd = -1;
478 st.filemode = 0600;
479
480 p = multipart_parser_init(boundary, &s);
481
482 free(boundary);
483
484 return p;
485 }
486
487 static int
488 main_upload(int argc, char *argv[])
489 {
490 int rem, len;
491 bool done = false;
492 char buf[READ_BLOCK];
493 multipart_parser *p;
494
495 p = init_parser();
496
497 if (!p)
498 {
499 errno = EINVAL;
500 return response(false, "Invalid request");
501 }
502
503 while ((len = read(0, buf, sizeof(buf))) > 0)
504 {
505 if (!done) {
506 rem = multipart_parser_execute(p, buf, len);
507 done = (rem < len);
508 }
509 }
510
511 multipart_parser_free(p);
512
513 return 0;
514 }
515
516 static void
517 free_charp(char **ptr)
518 {
519 free(*ptr);
520 }
521
522 #define autochar __attribute__((__cleanup__(free_charp))) char
523
524 static int
525 main_download(int argc, char **argv)
526 {
527 char *fields[] = { "sessionid", NULL, "path", NULL, "filename", NULL, "mimetype", NULL };
528 unsigned long long size = 0;
529 char *p, buf[READ_BLOCK];
530 ssize_t len = 0;
531 struct stat s;
532 int rfd;
533
534 autochar *post = postdecode(fields, 4);
535 (void) post;
536
537 if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read"))
538 return failure(403, 0, "Download permission denied");
539
540 if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
541 return failure(403, 0, "Access to path denied by ACL");
542
543 if (stat(fields[3], &s))
544 return failure(404, errno, "Failed to stat requested path");
545
546 if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
547 return failure(403, 0, "Requested path is not a regular file or block device");
548
549 for (p = fields[5]; p && *p; p++)
550 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
551 return failure(400, 0, "Invalid characters in filename");
552
553 for (p = fields[7]; p && *p; p++)
554 if (!isalnum(*p) && !strchr(" .;=/-", *p))
555 return failure(400, 0, "Invalid characters in mimetype");
556
557 rfd = open(fields[3], O_RDONLY);
558
559 if (rfd < 0)
560 return failure(500, errno, "Failed to open requested path");
561
562 if (S_ISBLK(s.st_mode))
563 ioctl(rfd, BLKGETSIZE64, &size);
564 else
565 size = (unsigned long long)s.st_size;
566
567 printf("Status: 200 OK\r\n");
568 printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream");
569
570 if (fields[5])
571 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]);
572
573 if (size > 0) {
574 printf("Content-Length: %llu\r\n\r\n", size);
575 fflush(stdout);
576
577 while (size > 0) {
578 len = sendfile(1, rfd, NULL, size);
579
580 if (len == -1) {
581 if (errno == ENOSYS || errno == EINVAL) {
582 while ((len = read(rfd, buf, sizeof(buf))) > 0)
583 fwrite(buf, len, 1, stdout);
584
585 fflush(stdout);
586 break;
587 }
588
589 if (errno == EINTR || errno == EAGAIN)
590 continue;
591 }
592
593 if (len <= 0)
594 break;
595
596 size -= len;
597 }
598 }
599 else {
600 printf("\r\n");
601
602 while ((len = read(rfd, buf, sizeof(buf))) > 0)
603 fwrite(buf, len, 1, stdout);
604
605 fflush(stdout);
606 }
607
608 close(rfd);
609
610 return 0;
611 }
612
613 static int
614 main_backup(int argc, char **argv)
615 {
616 pid_t pid;
617 time_t now;
618 int r;
619 int len;
620 int status;
621 int fds[2];
622 char datestr[16] = { 0 };
623 char hostname[64] = { 0 };
624 char *fields[] = { "sessionid", NULL };
625
626 autochar *post = postdecode(fields, 1);
627 (void) post;
628
629 if (!fields[1] || !session_access(fields[1], "cgi-io", "backup", "read"))
630 return failure(403, 0, "Backup permission denied");
631
632 if (pipe(fds))
633 return failure(500, errno, "Failed to spawn pipe");
634
635 switch ((pid = fork()))
636 {
637 case -1:
638 return failure(500, errno, "Failed to fork process");
639
640 case 0:
641 dup2(fds[1], 1);
642
643 close(0);
644 close(2);
645 close(fds[0]);
646 close(fds[1]);
647
648 r = chdir("/");
649 if (r < 0)
650 return failure(500, errno, "Failed chdir('/')");
651
652 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
653 "--create-backup", "-", NULL);
654
655 return -1;
656
657 default:
658 close(fds[1]);
659
660 now = time(NULL);
661 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
662
663 if (gethostname(hostname, sizeof(hostname) - 1))
664 sprintf(hostname, "OpenWrt");
665
666 printf("Status: 200 OK\r\n");
667 printf("Content-Type: application/x-targz\r\n");
668 printf("Content-Disposition: attachment; "
669 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
670
671 fflush(stdout);
672
673 do {
674 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
675 } while (len > 0 || (len == -1 && errno == EINTR));
676
677 waitpid(pid, &status, 0);
678
679 close(fds[0]);
680
681 return 0;
682 }
683 }
684
685
686 static const char *
687 lookup_executable(const char *cmd)
688 {
689 size_t plen = 0, clen;
690 static char path[PATH_MAX];
691 char *search, *p;
692 struct stat s;
693
694 if (!cmd)
695 return NULL;
696
697 clen = strlen(cmd) + 1;
698
699 if (!stat(cmd, &s) && S_ISREG(s.st_mode))
700 return cmd;
701
702 search = getenv("PATH");
703
704 if (!search)
705 search = "/bin:/usr/bin:/sbin:/usr/sbin";
706
707 p = search;
708
709 do {
710 if (*p != ':' && *p != '\0')
711 continue;
712
713 plen = p - search;
714
715 if ((plen + clen) >= sizeof(path))
716 continue;
717
718 strncpy(path, search, plen);
719 sprintf(path + plen, "/%s", cmd);
720
721 if (!stat(path, &s) && S_ISREG(s.st_mode))
722 return path;
723
724 search = p + 1;
725 } while (*p++);
726
727 return NULL;
728 }
729
730 static int
731 main_exec(int argc, char **argv)
732 {
733 char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL };
734 int i, devnull, status, fds[2];
735 bool allowed = false;
736 ssize_t len = 0;
737 const char *exe;
738 char *p, **args;
739 pid_t pid;
740
741 autochar *post = postdecode(fields, 4);
742 (void) post;
743
744 if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read"))
745 return failure(403, 0, "Exec permission denied");
746
747 for (p = fields[5]; p && *p; p++)
748 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
749 return failure(400, 0, "Invalid characters in filename");
750
751 for (p = fields[7]; p && *p; p++)
752 if (!isalnum(*p) && !strchr(" .;=/-", *p))
753 return failure(400, 0, "Invalid characters in mimetype");
754
755 args = fields[3] ? parse_command(fields[3]) : NULL;
756
757 if (!args)
758 return failure(400, 0, "Invalid command parameter");
759
760 /* First check if we find an ACL match for the whole cmdline ... */
761 allowed = session_access(fields[1], "file", args[0], "exec");
762
763 /* Now split the command vector... */
764 for (i = 1; args[i]; i++)
765 args[i][-1] = 0;
766
767 /* Find executable... */
768 exe = lookup_executable(args[0]);
769
770 if (!exe) {
771 free(args);
772 return failure(404, 0, "Executable not found");
773 }
774
775 /* If there was no ACL match, check for a match on the executable */
776 if (!allowed && !session_access(fields[1], "file", exe, "exec")) {
777 free(args);
778 return failure(403, 0, "Access to command denied by ACL");
779 }
780
781 if (pipe(fds)) {
782 free(args);
783 return failure(500, errno, "Failed to spawn pipe");
784 }
785
786 switch ((pid = fork()))
787 {
788 case -1:
789 free(args);
790 close(fds[0]);
791 close(fds[1]);
792 return failure(500, errno, "Failed to fork process");
793
794 case 0:
795 devnull = open("/dev/null", O_RDWR);
796
797 if (devnull > -1) {
798 dup2(devnull, 0);
799 dup2(devnull, 2);
800 close(devnull);
801 }
802 else {
803 close(0);
804 close(2);
805 }
806
807 dup2(fds[1], 1);
808 close(fds[0]);
809 close(fds[1]);
810
811 if (chdir("/") < 0) {
812 free(args);
813 return failure(500, errno, "Failed chdir('/')");
814 }
815
816 if (execv(exe, args) < 0) {
817 free(args);
818 return failure(500, errno, "Failed execv(...)");
819 }
820
821 return -1;
822
823 default:
824 close(fds[1]);
825
826 printf("Status: 200 OK\r\n");
827 printf("Content-Type: %s\r\n",
828 fields[7] ? fields[7] : "application/octet-stream");
829
830 if (fields[5])
831 printf("Content-Disposition: attachment; filename=\"%s\"\r\n",
832 fields[5]);
833
834 printf("\r\n");
835 fflush(stdout);
836
837 do {
838 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
839 } while (len > 0 || (len == -1 && errno == EINTR));
840
841 waitpid(pid, &status, 0);
842
843 close(fds[0]);
844 free(args);
845
846 return 0;
847 }
848 }
849
850 int main(int argc, char **argv)
851 {
852 if (strstr(argv[0], "cgi-upload"))
853 return main_upload(argc, argv);
854 else if (strstr(argv[0], "cgi-download"))
855 return main_download(argc, argv);
856 else if (strstr(argv[0], "cgi-backup"))
857 return main_backup(argc, argv);
858 else if (strstr(argv[0], "cgi-exec"))
859 return main_exec(argc, argv);
860
861 return -1;
862 }