Fix possible NULL dereference
[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 "multipart_parser.h"
39
40 #ifndef O_TMPFILE
41 #define O_TMPFILE (020000000 | O_DIRECTORY)
42 #endif
43
44 #define READ_BLOCK 4096
45 #define POST_LIMIT 131072
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 enum {
76 SES_ACCESS,
77 __SES_MAX,
78 };
79
80 static const struct blobmsg_policy ses_policy[__SES_MAX] = {
81 [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
82 };
83
84
85 static struct state st;
86
87 static void
88 session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
89 {
90 struct blob_attr *tb[__SES_MAX];
91 bool *allow = (bool *)req->priv;
92
93 if (!msg)
94 return;
95
96 blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
97
98 if (tb[SES_ACCESS])
99 *allow = blobmsg_get_bool(tb[SES_ACCESS]);
100 }
101
102 static bool
103 session_access(const char *sid, const char *scope, const char *obj, const char *func)
104 {
105 uint32_t id;
106 bool allow = false;
107 struct ubus_context *ctx;
108 static struct blob_buf req;
109
110 ctx = ubus_connect(NULL);
111
112 if (!ctx || !obj || ubus_lookup_id(ctx, "session", &id))
113 goto out;
114
115 blob_buf_init(&req, 0);
116 blobmsg_add_string(&req, "ubus_rpc_session", sid);
117 blobmsg_add_string(&req, "scope", scope);
118 blobmsg_add_string(&req, "object", obj);
119 blobmsg_add_string(&req, "function", func);
120
121 ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
122
123 out:
124 if (ctx)
125 ubus_free(ctx);
126
127 return allow;
128 }
129
130 static char *
131 checksum(const char *applet, size_t sumlen, const char *file)
132 {
133 pid_t pid;
134 int r;
135 int fds[2];
136 static char chksum[65];
137
138 if (pipe(fds))
139 return NULL;
140
141 switch ((pid = fork()))
142 {
143 case -1:
144 return NULL;
145
146 case 0:
147 uloop_done();
148
149 dup2(fds[1], 1);
150
151 close(0);
152 close(2);
153 close(fds[0]);
154 close(fds[1]);
155
156 if (execl("/bin/busybox", "/bin/busybox", applet, file, NULL))
157 return NULL;
158
159 break;
160
161 default:
162 memset(chksum, 0, sizeof(chksum));
163 r = read(fds[0], chksum, sumlen);
164
165 waitpid(pid, NULL, 0);
166 close(fds[0]);
167 close(fds[1]);
168
169 if (r < 0)
170 return NULL;
171 }
172
173 return chksum;
174 }
175
176 static char *
177 datadup(const void *in, size_t len)
178 {
179 char *out = malloc(len + 1);
180
181 if (!out)
182 return NULL;
183
184 memcpy(out, in, len);
185
186 *(out + len) = 0;
187
188 return out;
189 }
190
191 static bool
192 urldecode(char *buf)
193 {
194 char *c, *p;
195
196 if (!buf || !*buf)
197 return true;
198
199 #define hex(x) \
200 (((x) <= '9') ? ((x) - '0') : \
201 (((x) <= 'F') ? ((x) - 'A' + 10) : \
202 ((x) - 'a' + 10)))
203
204 for (c = p = buf; *p; c++)
205 {
206 if (*p == '%')
207 {
208 if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
209 return false;
210
211 *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
212
213 p += 3;
214 }
215 else if (*p == '+')
216 {
217 *c = ' ';
218 p++;
219 }
220 else
221 {
222 *c = *p++;
223 }
224 }
225
226 *c = 0;
227
228 return true;
229 }
230
231 static char *
232 postdecode(char **fields, int n_fields)
233 {
234 const char *var;
235 char *p, *postbuf;
236 int i, field, found = 0;
237 ssize_t len = 0, rlen = 0, content_length = 0;
238
239 var = getenv("CONTENT_TYPE");
240
241 if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
242 return NULL;
243
244 var = getenv("CONTENT_LENGTH");
245
246 if (!var)
247 return NULL;
248
249 content_length = strtol(var, &p, 10);
250
251 if (p == var || content_length <= 0 || content_length >= POST_LIMIT)
252 return NULL;
253
254 postbuf = calloc(1, content_length + 1);
255
256 if (postbuf == NULL)
257 return NULL;
258
259 for (len = 0; len < content_length; )
260 {
261 rlen = read(0, postbuf + len, content_length - len);
262
263 if (rlen <= 0)
264 break;
265
266 len += rlen;
267 }
268
269 if (len < content_length)
270 {
271 free(postbuf);
272 return NULL;
273 }
274
275 for (p = postbuf, i = 0; i <= len; i++)
276 {
277 if (postbuf[i] == '=')
278 {
279 postbuf[i] = 0;
280
281 for (field = 0; field < (n_fields * 2); field += 2)
282 {
283 if (!strcmp(p, fields[field]))
284 {
285 fields[field + 1] = postbuf + i + 1;
286 found++;
287 }
288 }
289 }
290 else if (postbuf[i] == '&' || postbuf[i] == '\0')
291 {
292 postbuf[i] = 0;
293
294 if (found >= n_fields)
295 break;
296
297 p = postbuf + i + 1;
298 }
299 }
300
301 for (field = 0; field < (n_fields * 2); field += 2)
302 {
303 if (!urldecode(fields[field + 1]))
304 {
305 free(postbuf);
306 return NULL;
307 }
308 }
309
310 return postbuf;
311 }
312
313 static char *
314 canonicalize_path(const char *path, size_t len)
315 {
316 char *canonpath, *cp;
317 const char *p, *e;
318
319 if (path == NULL || *path == '\0')
320 return NULL;
321
322 canonpath = datadup(path, len);
323
324 if (canonpath == NULL)
325 return NULL;
326
327 /* normalize */
328 for (cp = canonpath, p = path, e = path + len; p < e; ) {
329 if (*p != '/')
330 goto next;
331
332 /* skip repeating / */
333 if ((p + 1 < e) && (p[1] == '/')) {
334 p++;
335 continue;
336 }
337
338 /* /./ or /../ */
339 if ((p + 1 < e) && (p[1] == '.')) {
340 /* skip /./ */
341 if ((p + 2 >= e) || (p[2] == '/')) {
342 p += 2;
343 continue;
344 }
345
346 /* collapse /x/../ */
347 if ((p + 2 < e) && (p[2] == '.') && ((p + 3 >= e) || (p[3] == '/'))) {
348 while ((cp > canonpath) && (*--cp != '/'))
349 ;
350
351 p += 3;
352 continue;
353 }
354 }
355
356 next:
357 *cp++ = *p++;
358 }
359
360 /* remove trailing slash if not root / */
361 if ((cp > canonpath + 1) && (cp[-1] == '/'))
362 cp--;
363 else if (cp == canonpath)
364 *cp++ = '/';
365
366 *cp = '\0';
367
368 return canonpath;
369 }
370
371 static int
372 response(bool success, const char *message)
373 {
374 char *chksum;
375 struct stat s;
376
377 printf("Status: 200 OK\r\n");
378 printf("Content-Type: text/plain\r\n\r\n{\n");
379
380 if (success)
381 {
382 if (!stat(st.filename, &s))
383 printf("\t\"size\": %u,\n", (unsigned int)s.st_size);
384 else
385 printf("\t\"size\": null,\n");
386
387 chksum = checksum("md5sum", 32, st.filename);
388 printf("\t\"checksum\": %s%s%s,\n",
389 chksum ? "\"" : "",
390 chksum ? chksum : "null",
391 chksum ? "\"" : "");
392
393 chksum = checksum("sha256sum", 64, st.filename);
394 printf("\t\"sha256sum\": %s%s%s\n",
395 chksum ? "\"" : "",
396 chksum ? chksum : "null",
397 chksum ? "\"" : "");
398 }
399 else
400 {
401 if (message)
402 printf("\t\"message\": \"%s\",\n", message);
403
404 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
405
406 if (st.filefd > -1 && st.filename)
407 unlink(st.filename);
408 }
409
410 printf("}\n");
411
412 return -1;
413 }
414
415 static int
416 failure(int code, int e, const char *message)
417 {
418 printf("Status: %d %s\r\n", code, message);
419 printf("Content-Type: text/plain\r\n\r\n");
420 printf("%s", message);
421
422 if (e)
423 printf(": %s", strerror(e));
424
425 printf("\n");
426
427 return -1;
428 }
429
430 static int
431 filecopy(void)
432 {
433 int len;
434 char buf[READ_BLOCK];
435
436 if (!st.filedata)
437 {
438 close(st.tempfd);
439 errno = EINVAL;
440 return response(false, "No file data received");
441 }
442
443 snprintf(buf, sizeof(buf), "/proc/self/fd/%d", st.tempfd);
444
445 if (unlink(st.filename) < 0 && errno != ENOENT)
446 {
447 close(st.tempfd);
448 return response(false, "Failed to unlink existing file");
449 }
450
451 if (linkat(AT_FDCWD, buf, AT_FDCWD, st.filename, AT_SYMLINK_FOLLOW) < 0)
452 {
453 if (lseek(st.tempfd, 0, SEEK_SET) < 0)
454 {
455 close(st.tempfd);
456 return response(false, "Failed to rewind temp file");
457 }
458
459 st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
460
461 if (st.filefd < 0)
462 {
463 close(st.tempfd);
464 return response(false, "Failed to open target file");
465 }
466
467 while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
468 {
469 if (write(st.filefd, buf, len) != len)
470 {
471 close(st.tempfd);
472 close(st.filefd);
473 return response(false, "I/O failure while writing target file");
474 }
475 }
476
477 close(st.filefd);
478 }
479
480 close(st.tempfd);
481
482 if (chmod(st.filename, st.filemode))
483 return response(false, "Failed to chmod target file");
484
485 return 0;
486 }
487
488 static int
489 header_field(multipart_parser *p, const char *data, size_t len)
490 {
491 st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
492 return 0;
493 }
494
495 static int
496 header_value(multipart_parser *p, const char *data, size_t len)
497 {
498 size_t i, j;
499
500 if (!st.is_content_disposition)
501 return 0;
502
503 if (len < 10 || strncasecmp(data, "form-data", 9))
504 return 0;
505
506 for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
507
508 if (len < 8 || strncasecmp(data, "name=\"", 6))
509 return 0;
510
511 for (data += 6, len -= 6, i = 0; i <= len; i++)
512 {
513 if (*(data + i) != '"')
514 continue;
515
516 for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
517 if (!strncmp(data, parts[j], i))
518 st.parttype = j;
519
520 break;
521 }
522
523 return 0;
524 }
525
526 static int
527 data_begin_cb(multipart_parser *p)
528 {
529 if (st.parttype == PART_FILEDATA)
530 {
531 if (!st.sessionid)
532 return response(false, "File data without session");
533
534 if (!st.filename)
535 return response(false, "File data without name");
536
537 if (!session_access(st.sessionid, "file", st.filename, "write"))
538 return response(false, "Access to path denied by ACL");
539
540 st.tempfd = open("/tmp", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
541
542 if (st.tempfd < 0)
543 return response(false, "Failed to create temporary file");
544 }
545
546 return 0;
547 }
548
549 static int
550 data_cb(multipart_parser *p, const char *data, size_t len)
551 {
552 int wlen = len;
553
554 switch (st.parttype)
555 {
556 case PART_SESSIONID:
557 st.sessionid = datadup(data, len);
558 break;
559
560 case PART_FILENAME:
561 st.filename = canonicalize_path(data, len);
562 break;
563
564 case PART_FILEMODE:
565 st.filemode = strtoul(data, NULL, 8);
566 break;
567
568 case PART_FILEDATA:
569 if (write(st.tempfd, data, len) != wlen)
570 {
571 close(st.tempfd);
572 return response(false, "I/O failure while writing temporary file");
573 }
574
575 if (!st.filedata)
576 st.filedata = !!wlen;
577
578 break;
579
580 default:
581 break;
582 }
583
584 return 0;
585 }
586
587 static int
588 data_end_cb(multipart_parser *p)
589 {
590 if (st.parttype == PART_SESSIONID)
591 {
592 if (!session_access(st.sessionid, "cgi-io", "upload", "write"))
593 {
594 errno = EPERM;
595 return response(false, "Upload permission denied");
596 }
597 }
598 else if (st.parttype == PART_FILEDATA)
599 {
600 if (st.tempfd < 0)
601 return response(false, "Internal program failure");
602
603 #if 0
604 /* prepare directory */
605 for (ptr = st.filename; *ptr; ptr++)
606 {
607 if (*ptr == '/')
608 {
609 *ptr = 0;
610
611 if (mkdir(st.filename, 0755))
612 {
613 unlink(st.tmpname);
614 return response(false, "Failed to create destination directory");
615 }
616
617 *ptr = '/';
618 }
619 }
620 #endif
621
622 if (filecopy())
623 return -1;
624
625 return response(true, NULL);
626 }
627
628 st.parttype = PART_UNKNOWN;
629 return 0;
630 }
631
632 static multipart_parser *
633 init_parser(void)
634 {
635 char *boundary;
636 const char *var;
637
638 multipart_parser *p;
639 static multipart_parser_settings s = {
640 .on_part_data = data_cb,
641 .on_headers_complete = data_begin_cb,
642 .on_part_data_end = data_end_cb,
643 .on_header_field = header_field,
644 .on_header_value = header_value
645 };
646
647 var = getenv("CONTENT_TYPE");
648
649 if (!var || strncmp(var, "multipart/form-data;", 20))
650 return NULL;
651
652 for (var += 20; *var && *var != '='; var++);
653
654 if (*var++ != '=')
655 return NULL;
656
657 boundary = malloc(strlen(var) + 3);
658
659 if (!boundary)
660 return NULL;
661
662 strcpy(boundary, "--");
663 strcpy(boundary + 2, var);
664
665 st.tempfd = -1;
666 st.filefd = -1;
667 st.filemode = 0600;
668
669 p = multipart_parser_init(boundary, &s);
670
671 free(boundary);
672
673 return p;
674 }
675
676 static int
677 main_upload(int argc, char *argv[])
678 {
679 int rem, len;
680 bool done = false;
681 char buf[READ_BLOCK];
682 multipart_parser *p;
683
684 p = init_parser();
685
686 if (!p)
687 {
688 errno = EINVAL;
689 return response(false, "Invalid request");
690 }
691
692 while ((len = read(0, buf, sizeof(buf))) > 0)
693 {
694 if (!done) {
695 rem = multipart_parser_execute(p, buf, len);
696 done = (rem < len);
697 }
698 }
699
700 multipart_parser_free(p);
701
702 return 0;
703 }
704
705 static void
706 free_charp(char **ptr)
707 {
708 free(*ptr);
709 }
710
711 #define autochar __attribute__((__cleanup__(free_charp))) char
712
713 static int
714 main_download(int argc, char **argv)
715 {
716 char *fields[] = { "sessionid", NULL, "path", NULL, "filename", NULL, "mimetype", NULL };
717 unsigned long long size = 0;
718 char *p, buf[READ_BLOCK];
719 ssize_t len = 0;
720 struct stat s;
721 int rfd;
722
723 autochar *post = postdecode(fields, 4);
724
725 if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read"))
726 return failure(403, 0, "Download permission denied");
727
728 if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
729 return failure(403, 0, "Access to path denied by ACL");
730
731 if (stat(fields[3], &s))
732 return failure(404, errno, "Failed to stat requested path");
733
734 if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
735 return failure(403, 0, "Requested path is not a regular file or block device");
736
737 for (p = fields[5]; p && *p; p++)
738 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
739 return failure(400, 0, "Invalid characters in filename");
740
741 for (p = fields[7]; p && *p; p++)
742 if (!isalnum(*p) && !strchr(" .;=/-", *p))
743 return failure(400, 0, "Invalid characters in mimetype");
744
745 rfd = open(fields[3], O_RDONLY);
746
747 if (rfd < 0)
748 return failure(500, errno, "Failed to open requested path");
749
750 if (S_ISBLK(s.st_mode))
751 ioctl(rfd, BLKGETSIZE64, &size);
752 else
753 size = (unsigned long long)s.st_size;
754
755 printf("Status: 200 OK\r\n");
756 printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream");
757
758 if (fields[5])
759 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]);
760
761 if (size > 0) {
762 printf("Content-Length: %llu\r\n\r\n", size);
763 fflush(stdout);
764
765 while (size > 0) {
766 len = sendfile(1, rfd, NULL, size);
767
768 if (len == -1) {
769 if (errno == ENOSYS || errno == EINVAL) {
770 while ((len = read(rfd, buf, sizeof(buf))) > 0)
771 fwrite(buf, len, 1, stdout);
772
773 fflush(stdout);
774 break;
775 }
776
777 if (errno == EINTR || errno == EAGAIN)
778 continue;
779 }
780
781 if (len <= 0)
782 break;
783
784 size -= len;
785 }
786 }
787 else {
788 printf("\r\n");
789
790 while ((len = read(rfd, buf, sizeof(buf))) > 0)
791 fwrite(buf, len, 1, stdout);
792
793 fflush(stdout);
794 }
795
796 close(rfd);
797
798 return 0;
799 }
800
801 static int
802 main_backup(int argc, char **argv)
803 {
804 pid_t pid;
805 time_t now;
806 int r;
807 int len;
808 int status;
809 int fds[2];
810 char datestr[16] = { 0 };
811 char hostname[64] = { 0 };
812 char *fields[] = { "sessionid", NULL };
813
814 autochar *post = postdecode(fields, 1);
815
816 if (!fields[1] || !session_access(fields[1], "cgi-io", "backup", "read"))
817 return failure(403, 0, "Backup permission denied");
818
819 if (pipe(fds))
820 return failure(500, errno, "Failed to spawn pipe");
821
822 switch ((pid = fork()))
823 {
824 case -1:
825 return failure(500, errno, "Failed to fork process");
826
827 case 0:
828 dup2(fds[1], 1);
829
830 close(0);
831 close(2);
832 close(fds[0]);
833 close(fds[1]);
834
835 r = chdir("/");
836 if (r < 0)
837 return failure(500, errno, "Failed chdir('/')");
838
839 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
840 "--create-backup", "-", NULL);
841
842 return -1;
843
844 default:
845 close(fds[1]);
846
847 now = time(NULL);
848 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
849
850 if (gethostname(hostname, sizeof(hostname) - 1))
851 sprintf(hostname, "OpenWrt");
852
853 printf("Status: 200 OK\r\n");
854 printf("Content-Type: application/x-targz\r\n");
855 printf("Content-Disposition: attachment; "
856 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
857
858 fflush(stdout);
859
860 do {
861 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
862 } while (len > 0);
863
864 waitpid(pid, &status, 0);
865
866 close(fds[0]);
867
868 return 0;
869 }
870 }
871
872
873 static const char *
874 lookup_executable(const char *cmd)
875 {
876 size_t plen = 0, clen;
877 static char path[PATH_MAX];
878 char *search, *p;
879 struct stat s;
880
881 if (!cmd)
882 return NULL;
883
884 clen = strlen(cmd) + 1;
885
886 if (!stat(cmd, &s) && S_ISREG(s.st_mode))
887 return cmd;
888
889 search = getenv("PATH");
890
891 if (!search)
892 search = "/bin:/usr/bin:/sbin:/usr/sbin";
893
894 p = search;
895
896 do {
897 if (*p != ':' && *p != '\0')
898 continue;
899
900 plen = p - search;
901
902 if ((plen + clen) >= sizeof(path))
903 continue;
904
905 strncpy(path, search, plen);
906 sprintf(path + plen, "/%s", cmd);
907
908 if (!stat(path, &s) && S_ISREG(s.st_mode))
909 return path;
910
911 search = p + 1;
912 } while (*p++);
913
914 return NULL;
915 }
916
917 static char **
918 parse_command(const char *cmdline)
919 {
920 const char *p = cmdline, *s;
921 char **argv = NULL, *out;
922 size_t arglen = 0;
923 int argnum = 0;
924 bool esc;
925
926 while (isspace(*cmdline))
927 cmdline++;
928
929 for (p = cmdline, s = p, esc = false; p; p++) {
930 if (esc) {
931 esc = false;
932 }
933 else if (*p == '\\' && p[1] != 0) {
934 esc = true;
935 }
936 else if (isspace(*p) || *p == 0) {
937 if (p > s) {
938 argnum += 1;
939 arglen += sizeof(char *) + (p - s) + 1;
940 }
941
942 s = p + 1;
943 }
944
945 if (*p == 0)
946 break;
947 }
948
949 if (arglen == 0)
950 return NULL;
951
952 argv = calloc(1, arglen + sizeof(char *));
953
954 if (!argv)
955 return NULL;
956
957 out = (char *)argv + sizeof(char *) * (argnum + 1);
958 argv[0] = out;
959
960 for (p = cmdline, s = p, esc = false, argnum = 0; p; p++) {
961 if (esc) {
962 esc = false;
963 *out++ = *p;
964 }
965 else if (*p == '\\' && p[1] != 0) {
966 esc = true;
967 }
968 else if (isspace(*p) || *p == 0) {
969 if (p > s) {
970 *out++ = ' ';
971 argv[++argnum] = out;
972 }
973
974 s = p + 1;
975 }
976 else {
977 *out++ = *p;
978 }
979
980 if (*p == 0)
981 break;
982 }
983
984 argv[argnum] = NULL;
985 out[-1] = 0;
986
987 return argv;
988 }
989
990 static int
991 main_exec(int argc, char **argv)
992 {
993 char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL };
994 int i, devnull, status, fds[2];
995 bool allowed = false;
996 ssize_t len = 0;
997 const char *exe;
998 char *p, **args;
999 pid_t pid;
1000
1001 autochar *post = postdecode(fields, 4);
1002
1003 if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read"))
1004 return failure(403, 0, "Exec permission denied");
1005
1006 for (p = fields[5]; p && *p; p++)
1007 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
1008 return failure(400, 0, "Invalid characters in filename");
1009
1010 for (p = fields[7]; p && *p; p++)
1011 if (!isalnum(*p) && !strchr(" .;=/-", *p))
1012 return failure(400, 0, "Invalid characters in mimetype");
1013
1014 args = fields[3] ? parse_command(fields[3]) : NULL;
1015
1016 if (!args)
1017 return failure(400, 0, "Invalid command parameter");
1018
1019 /* First check if we find an ACL match for the whole cmdline ... */
1020 allowed = session_access(fields[1], "file", args[0], "exec");
1021
1022 /* Now split the command vector... */
1023 for (i = 1; args[i]; i++)
1024 args[i][-1] = 0;
1025
1026 /* Find executable... */
1027 exe = lookup_executable(args[0]);
1028
1029 if (!exe) {
1030 free(args);
1031 return failure(404, 0, "Executable not found");
1032 }
1033
1034 /* If there was no ACL match, check for a match on the executable */
1035 if (!allowed && !session_access(fields[1], "file", exe, "exec")) {
1036 free(args);
1037 return failure(403, 0, "Access to command denied by ACL");
1038 }
1039
1040 if (pipe(fds)) {
1041 free(args);
1042 return failure(500, errno, "Failed to spawn pipe");
1043 }
1044
1045 switch ((pid = fork()))
1046 {
1047 case -1:
1048 free(args);
1049 close(fds[0]);
1050 close(fds[1]);
1051 return failure(500, errno, "Failed to fork process");
1052
1053 case 0:
1054 devnull = open("/dev/null", O_RDWR);
1055
1056 if (devnull > -1) {
1057 dup2(devnull, 0);
1058 dup2(devnull, 2);
1059 close(devnull);
1060 }
1061 else {
1062 close(0);
1063 close(2);
1064 }
1065
1066 dup2(fds[1], 1);
1067 close(fds[0]);
1068 close(fds[1]);
1069
1070 if (chdir("/") < 0) {
1071 free(args);
1072 return failure(500, errno, "Failed chdir('/')");
1073 }
1074
1075 if (execv(exe, args) < 0) {
1076 free(args);
1077 return failure(500, errno, "Failed execv(...)");
1078 }
1079
1080 return -1;
1081
1082 default:
1083 close(fds[1]);
1084
1085 printf("Status: 200 OK\r\n");
1086 printf("Content-Type: %s\r\n",
1087 fields[7] ? fields[7] : "application/octet-stream");
1088
1089 if (fields[5])
1090 printf("Content-Disposition: attachment; filename=\"%s\"\r\n",
1091 fields[5]);
1092
1093 printf("\r\n");
1094 fflush(stdout);
1095
1096 do {
1097 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
1098 } while (len > 0);
1099
1100 waitpid(pid, &status, 0);
1101
1102 close(fds[0]);
1103 free(args);
1104
1105 return 0;
1106 }
1107 }
1108
1109 int main(int argc, char **argv)
1110 {
1111 if (strstr(argv[0], "cgi-upload"))
1112 return main_upload(argc, argv);
1113 else if (strstr(argv[0], "cgi-download"))
1114 return main_download(argc, argv);
1115 else if (strstr(argv[0], "cgi-backup"))
1116 return main_backup(argc, argv);
1117 else if (strstr(argv[0], "cgi-exec"))
1118 return main_exec(argc, argv);
1119
1120 return -1;
1121 }