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