Fix clang compiler errors
[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 (void) post;
725
726 if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read"))
727 return failure(403, 0, "Download permission denied");
728
729 if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
730 return failure(403, 0, "Access to path denied by ACL");
731
732 if (stat(fields[3], &s))
733 return failure(404, errno, "Failed to stat requested path");
734
735 if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
736 return failure(403, 0, "Requested path is not a regular file or block device");
737
738 for (p = fields[5]; p && *p; p++)
739 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
740 return failure(400, 0, "Invalid characters in filename");
741
742 for (p = fields[7]; p && *p; p++)
743 if (!isalnum(*p) && !strchr(" .;=/-", *p))
744 return failure(400, 0, "Invalid characters in mimetype");
745
746 rfd = open(fields[3], O_RDONLY);
747
748 if (rfd < 0)
749 return failure(500, errno, "Failed to open requested path");
750
751 if (S_ISBLK(s.st_mode))
752 ioctl(rfd, BLKGETSIZE64, &size);
753 else
754 size = (unsigned long long)s.st_size;
755
756 printf("Status: 200 OK\r\n");
757 printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream");
758
759 if (fields[5])
760 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]);
761
762 if (size > 0) {
763 printf("Content-Length: %llu\r\n\r\n", size);
764 fflush(stdout);
765
766 while (size > 0) {
767 len = sendfile(1, rfd, NULL, size);
768
769 if (len == -1) {
770 if (errno == ENOSYS || errno == EINVAL) {
771 while ((len = read(rfd, buf, sizeof(buf))) > 0)
772 fwrite(buf, len, 1, stdout);
773
774 fflush(stdout);
775 break;
776 }
777
778 if (errno == EINTR || errno == EAGAIN)
779 continue;
780 }
781
782 if (len <= 0)
783 break;
784
785 size -= len;
786 }
787 }
788 else {
789 printf("\r\n");
790
791 while ((len = read(rfd, buf, sizeof(buf))) > 0)
792 fwrite(buf, len, 1, stdout);
793
794 fflush(stdout);
795 }
796
797 close(rfd);
798
799 return 0;
800 }
801
802 static int
803 main_backup(int argc, char **argv)
804 {
805 pid_t pid;
806 time_t now;
807 int r;
808 int len;
809 int status;
810 int fds[2];
811 char datestr[16] = { 0 };
812 char hostname[64] = { 0 };
813 char *fields[] = { "sessionid", NULL };
814
815 autochar *post = postdecode(fields, 1);
816 (void) post;
817
818 if (!fields[1] || !session_access(fields[1], "cgi-io", "backup", "read"))
819 return failure(403, 0, "Backup permission denied");
820
821 if (pipe(fds))
822 return failure(500, errno, "Failed to spawn pipe");
823
824 switch ((pid = fork()))
825 {
826 case -1:
827 return failure(500, errno, "Failed to fork process");
828
829 case 0:
830 dup2(fds[1], 1);
831
832 close(0);
833 close(2);
834 close(fds[0]);
835 close(fds[1]);
836
837 r = chdir("/");
838 if (r < 0)
839 return failure(500, errno, "Failed chdir('/')");
840
841 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
842 "--create-backup", "-", NULL);
843
844 return -1;
845
846 default:
847 close(fds[1]);
848
849 now = time(NULL);
850 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
851
852 if (gethostname(hostname, sizeof(hostname) - 1))
853 sprintf(hostname, "OpenWrt");
854
855 printf("Status: 200 OK\r\n");
856 printf("Content-Type: application/x-targz\r\n");
857 printf("Content-Disposition: attachment; "
858 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
859
860 fflush(stdout);
861
862 do {
863 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
864 } while (len > 0);
865
866 waitpid(pid, &status, 0);
867
868 close(fds[0]);
869
870 return 0;
871 }
872 }
873
874
875 static const char *
876 lookup_executable(const char *cmd)
877 {
878 size_t plen = 0, clen;
879 static char path[PATH_MAX];
880 char *search, *p;
881 struct stat s;
882
883 if (!cmd)
884 return NULL;
885
886 clen = strlen(cmd) + 1;
887
888 if (!stat(cmd, &s) && S_ISREG(s.st_mode))
889 return cmd;
890
891 search = getenv("PATH");
892
893 if (!search)
894 search = "/bin:/usr/bin:/sbin:/usr/sbin";
895
896 p = search;
897
898 do {
899 if (*p != ':' && *p != '\0')
900 continue;
901
902 plen = p - search;
903
904 if ((plen + clen) >= sizeof(path))
905 continue;
906
907 strncpy(path, search, plen);
908 sprintf(path + plen, "/%s", cmd);
909
910 if (!stat(path, &s) && S_ISREG(s.st_mode))
911 return path;
912
913 search = p + 1;
914 } while (*p++);
915
916 return NULL;
917 }
918
919 static char **
920 parse_command(const char *cmdline)
921 {
922 const char *p = cmdline, *s;
923 char **argv = NULL, *out;
924 size_t arglen = 0;
925 int argnum = 0;
926 bool esc;
927
928 while (isspace(*cmdline))
929 cmdline++;
930
931 for (p = cmdline, s = p, esc = false; p; p++) {
932 if (esc) {
933 esc = false;
934 }
935 else if (*p == '\\' && p[1] != 0) {
936 esc = true;
937 }
938 else if (isspace(*p) || *p == 0) {
939 if (p > s) {
940 argnum += 1;
941 arglen += sizeof(char *) + (p - s) + 1;
942 }
943
944 s = p + 1;
945 }
946
947 if (*p == 0)
948 break;
949 }
950
951 if (arglen == 0)
952 return NULL;
953
954 argv = calloc(1, arglen + sizeof(char *));
955
956 if (!argv)
957 return NULL;
958
959 out = (char *)argv + sizeof(char *) * (argnum + 1);
960 argv[0] = out;
961
962 for (p = cmdline, s = p, esc = false, argnum = 0; p; p++) {
963 if (esc) {
964 esc = false;
965 *out++ = *p;
966 }
967 else if (*p == '\\' && p[1] != 0) {
968 esc = true;
969 }
970 else if (isspace(*p) || *p == 0) {
971 if (p > s) {
972 *out++ = ' ';
973 argv[++argnum] = out;
974 }
975
976 s = p + 1;
977 }
978 else {
979 *out++ = *p;
980 }
981
982 if (*p == 0)
983 break;
984 }
985
986 argv[argnum] = NULL;
987 out[-1] = 0;
988
989 return argv;
990 }
991
992 static int
993 main_exec(int argc, char **argv)
994 {
995 char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL };
996 int i, devnull, status, fds[2];
997 bool allowed = false;
998 ssize_t len = 0;
999 const char *exe;
1000 char *p, **args;
1001 pid_t pid;
1002
1003 autochar *post = postdecode(fields, 4);
1004 (void) post;
1005
1006 if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read"))
1007 return failure(403, 0, "Exec permission denied");
1008
1009 for (p = fields[5]; p && *p; p++)
1010 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
1011 return failure(400, 0, "Invalid characters in filename");
1012
1013 for (p = fields[7]; p && *p; p++)
1014 if (!isalnum(*p) && !strchr(" .;=/-", *p))
1015 return failure(400, 0, "Invalid characters in mimetype");
1016
1017 args = fields[3] ? parse_command(fields[3]) : NULL;
1018
1019 if (!args)
1020 return failure(400, 0, "Invalid command parameter");
1021
1022 /* First check if we find an ACL match for the whole cmdline ... */
1023 allowed = session_access(fields[1], "file", args[0], "exec");
1024
1025 /* Now split the command vector... */
1026 for (i = 1; args[i]; i++)
1027 args[i][-1] = 0;
1028
1029 /* Find executable... */
1030 exe = lookup_executable(args[0]);
1031
1032 if (!exe) {
1033 free(args);
1034 return failure(404, 0, "Executable not found");
1035 }
1036
1037 /* If there was no ACL match, check for a match on the executable */
1038 if (!allowed && !session_access(fields[1], "file", exe, "exec")) {
1039 free(args);
1040 return failure(403, 0, "Access to command denied by ACL");
1041 }
1042
1043 if (pipe(fds)) {
1044 free(args);
1045 return failure(500, errno, "Failed to spawn pipe");
1046 }
1047
1048 switch ((pid = fork()))
1049 {
1050 case -1:
1051 free(args);
1052 close(fds[0]);
1053 close(fds[1]);
1054 return failure(500, errno, "Failed to fork process");
1055
1056 case 0:
1057 devnull = open("/dev/null", O_RDWR);
1058
1059 if (devnull > -1) {
1060 dup2(devnull, 0);
1061 dup2(devnull, 2);
1062 close(devnull);
1063 }
1064 else {
1065 close(0);
1066 close(2);
1067 }
1068
1069 dup2(fds[1], 1);
1070 close(fds[0]);
1071 close(fds[1]);
1072
1073 if (chdir("/") < 0) {
1074 free(args);
1075 return failure(500, errno, "Failed chdir('/')");
1076 }
1077
1078 if (execv(exe, args) < 0) {
1079 free(args);
1080 return failure(500, errno, "Failed execv(...)");
1081 }
1082
1083 return -1;
1084
1085 default:
1086 close(fds[1]);
1087
1088 printf("Status: 200 OK\r\n");
1089 printf("Content-Type: %s\r\n",
1090 fields[7] ? fields[7] : "application/octet-stream");
1091
1092 if (fields[5])
1093 printf("Content-Disposition: attachment; filename=\"%s\"\r\n",
1094 fields[5]);
1095
1096 printf("\r\n");
1097 fflush(stdout);
1098
1099 do {
1100 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
1101 } while (len > 0);
1102
1103 waitpid(pid, &status, 0);
1104
1105 close(fds[0]);
1106 free(args);
1107
1108 return 0;
1109 }
1110 }
1111
1112 int main(int argc, char **argv)
1113 {
1114 if (strstr(argv[0], "cgi-upload"))
1115 return main_upload(argc, argv);
1116 else if (strstr(argv[0], "cgi-download"))
1117 return main_download(argc, argv);
1118 else if (strstr(argv[0], "cgi-backup"))
1119 return main_backup(argc, argv);
1120 else if (strstr(argv[0], "cgi-exec"))
1121 return main_exec(argc, argv);
1122
1123 return -1;
1124 }