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