cgi-io: implement exec action
[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 now = time(NULL);
782 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
783
784 if (gethostname(hostname, sizeof(hostname) - 1))
785 sprintf(hostname, "OpenWrt");
786
787 printf("Status: 200 OK\r\n");
788 printf("Content-Type: application/x-targz\r\n");
789 printf("Content-Disposition: attachment; "
790 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
791
792 fflush(stdout);
793
794 do {
795 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
796 } while (len > 0);
797
798 waitpid(pid, &status, 0);
799
800 close(fds[0]);
801 close(fds[1]);
802
803 return 0;
804 }
805 }
806
807
808 static const char *
809 lookup_executable(const char *cmd)
810 {
811 size_t plen = 0, clen = strlen(cmd) + 1;
812 static char path[PATH_MAX];
813 char *search, *p;
814 struct stat s;
815
816 if (!stat(cmd, &s) && S_ISREG(s.st_mode))
817 return cmd;
818
819 search = getenv("PATH");
820
821 if (!search)
822 search = "/bin:/usr/bin:/sbin:/usr/sbin";
823
824 p = search;
825
826 do {
827 if (*p != ':' && *p != '\0')
828 continue;
829
830 plen = p - search;
831
832 if ((plen + clen) >= sizeof(path))
833 continue;
834
835 strncpy(path, search, plen);
836 sprintf(path + plen, "/%s", cmd);
837
838 if (!stat(path, &s) && S_ISREG(s.st_mode))
839 return path;
840
841 search = p + 1;
842 } while (*p++);
843
844 return NULL;
845 }
846
847 static char **
848 parse_command(const char *cmdline)
849 {
850 const char *p = cmdline, *s;
851 char **argv = NULL, *out;
852 size_t arglen = 0;
853 int argnum = 0;
854 bool esc;
855
856 while (isspace(*cmdline))
857 cmdline++;
858
859 for (p = cmdline, s = p, esc = false; p; p++) {
860 if (esc) {
861 esc = false;
862 }
863 else if (*p == '\\' && p[1] != 0) {
864 esc = true;
865 }
866 else if (isspace(*p) || *p == 0) {
867 if (p > s) {
868 argnum += 1;
869 arglen += sizeof(char *) + (p - s) + 1;
870 }
871
872 s = p + 1;
873 }
874
875 if (*p == 0)
876 break;
877 }
878
879 if (arglen == 0)
880 return NULL;
881
882 argv = calloc(1, arglen + sizeof(char *));
883
884 if (!argv)
885 return NULL;
886
887 out = (char *)argv + sizeof(char *) * (argnum + 1);
888 argv[0] = out;
889
890 for (p = cmdline, s = p, esc = false, argnum = 0; p; p++) {
891 if (esc) {
892 esc = false;
893 *out++ = *p;
894 }
895 else if (*p == '\\' && p[1] != 0) {
896 esc = true;
897 }
898 else if (isspace(*p) || *p == 0) {
899 if (p > s) {
900 *out++ = ' ';
901 argv[++argnum] = out;
902 }
903
904 s = p + 1;
905 }
906 else {
907 *out++ = *p;
908 }
909
910 if (*p == 0)
911 break;
912 }
913
914 argv[argnum] = NULL;
915 out[-1] = 0;
916
917 return argv;
918 }
919
920 static int
921 main_exec(int argc, char **argv)
922 {
923 char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL };
924 int i, devnull, status, fds[2];
925 bool allowed = false;
926 ssize_t len = 0;
927 const char *exe;
928 char *p, **args;
929 pid_t pid;
930
931 postdecode(fields, 4);
932
933 if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read"))
934 return failure(403, 0, "Exec permission denied");
935
936 for (p = fields[5]; p && *p; p++)
937 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p))
938 return failure(400, 0, "Invalid characters in filename");
939
940 for (p = fields[7]; p && *p; p++)
941 if (!isalnum(*p) && !strchr(" .;=/-", *p))
942 return failure(400, 0, "Invalid characters in mimetype");
943
944 args = fields[3] ? parse_command(fields[3]) : NULL;
945
946 if (!args)
947 return failure(400, 0, "Invalid command parameter");
948
949 /* First check if we find an ACL match for the whole cmdline ... */
950 allowed = session_access(fields[1], "file", args[0], "exec");
951
952 /* Now split the command vector... */
953 for (i = 1; args[i]; i++)
954 args[i][-1] = 0;
955
956 /* Find executable... */
957 exe = lookup_executable(args[0]);
958
959 if (!exe) {
960 free(args);
961 return failure(404, 0, "Executable not found");
962 }
963
964 /* If there was no ACL match, check for a match on the executable */
965 if (!allowed && !session_access(fields[1], "file", exe, "exec")) {
966 free(args);
967 return failure(403, 0, "Access to command denied by ACL");
968 }
969
970 if (pipe(fds)) {
971 free(args);
972 return failure(500, errno, "Failed to spawn pipe");
973 }
974
975 switch ((pid = fork()))
976 {
977 case -1:
978 free(args);
979 close(fds[0]);
980 close(fds[1]);
981 return failure(500, errno, "Failed to fork process");
982
983 case 0:
984 devnull = open("/dev/null", O_RDWR);
985
986 if (devnull > -1) {
987 dup2(devnull, 0);
988 dup2(devnull, 2);
989 close(devnull);
990 }
991 else {
992 close(0);
993 close(2);
994 }
995
996 dup2(fds[1], 1);
997 close(fds[0]);
998 close(fds[1]);
999
1000 if (chdir("/") < 0) {
1001 free(args);
1002 return failure(500, errno, "Failed chdir('/')");
1003 }
1004
1005 if (execv(exe, args) < 0) {
1006 free(args);
1007 return failure(500, errno, "Failed execv(...)");
1008 }
1009
1010 return -1;
1011
1012 default:
1013 printf("Status: 200 OK\r\n");
1014 printf("Content-Type: %s\r\n",
1015 fields[7] ? fields[7] : "application/octet-stream");
1016
1017 if (fields[5])
1018 printf("Content-Disposition: attachment; filename=\"%s\"\r\n",
1019 fields[5]);
1020
1021 printf("\r\n");
1022 fflush(stdout);
1023
1024 do {
1025 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE);
1026 } while (len > 0);
1027
1028 waitpid(pid, &status, 0);
1029
1030 close(fds[0]);
1031 close(fds[1]);
1032 free(args);
1033
1034 return 0;
1035 }
1036 }
1037
1038 int main(int argc, char **argv)
1039 {
1040 if (strstr(argv[0], "cgi-upload"))
1041 return main_upload(argc, argv);
1042 else if (strstr(argv[0], "cgi-download"))
1043 return main_download(argc, argv);
1044 else if (strstr(argv[0], "cgi-backup"))
1045 return main_backup(argc, argv);
1046 else if (strstr(argv[0], "cgi-exec"))
1047 return main_exec(argc, argv);
1048
1049 return -1;
1050 }