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