Merge pull request #9992 from flyn-org/openldap
[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
41 enum part {
42 PART_UNKNOWN,
43 PART_SESSIONID,
44 PART_FILENAME,
45 PART_FILEMODE,
46 PART_FILEDATA
47 };
48
49 const char *parts[] = {
50 "(bug)",
51 "sessionid",
52 "filename",
53 "filemode",
54 "filedata",
55 };
56
57 struct state
58 {
59 bool is_content_disposition;
60 enum part parttype;
61 char *sessionid;
62 char *filename;
63 bool filedata;
64 int filemode;
65 int filefd;
66 int tempfd;
67 };
68
69 enum {
70 SES_ACCESS,
71 __SES_MAX,
72 };
73
74 static const struct blobmsg_policy ses_policy[__SES_MAX] = {
75 [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
76 };
77
78
79 static struct state st;
80
81 static void
82 session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
83 {
84 struct blob_attr *tb[__SES_MAX];
85 bool *allow = (bool *)req->priv;
86
87 if (!msg)
88 return;
89
90 blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
91
92 if (tb[SES_ACCESS])
93 *allow = blobmsg_get_bool(tb[SES_ACCESS]);
94 }
95
96 static bool
97 session_access(const char *sid, const char *scope, const char *obj, const char *func)
98 {
99 uint32_t id;
100 bool allow = false;
101 struct ubus_context *ctx;
102 static struct blob_buf req;
103
104 ctx = ubus_connect(NULL);
105
106 if (!ctx || ubus_lookup_id(ctx, "session", &id))
107 goto out;
108
109 blob_buf_init(&req, 0);
110 blobmsg_add_string(&req, "ubus_rpc_session", sid);
111 blobmsg_add_string(&req, "scope", scope);
112 blobmsg_add_string(&req, "object", obj);
113 blobmsg_add_string(&req, "function", func);
114
115 ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
116
117 out:
118 if (ctx)
119 ubus_free(ctx);
120
121 return allow;
122 }
123
124 static char *
125 checksum(const char *applet, size_t sumlen, const char *file)
126 {
127 pid_t pid;
128 int fds[2];
129 static char chksum[65];
130
131 if (pipe(fds))
132 return NULL;
133
134 switch ((pid = fork()))
135 {
136 case -1:
137 return NULL;
138
139 case 0:
140 uloop_done();
141
142 dup2(fds[1], 1);
143
144 close(0);
145 close(2);
146 close(fds[0]);
147 close(fds[1]);
148
149 if (execl("/bin/busybox", "/bin/busybox", applet, file, NULL))
150 return NULL;
151
152 break;
153
154 default:
155 memset(chksum, 0, sizeof(chksum));
156 read(fds[0], chksum, sumlen);
157 waitpid(pid, NULL, 0);
158 close(fds[0]);
159 close(fds[1]);
160 }
161
162 return chksum;
163 }
164
165 static char *
166 datadup(const void *in, size_t len)
167 {
168 char *out = malloc(len + 1);
169
170 if (!out)
171 return NULL;
172
173 memcpy(out, in, len);
174
175 *(out + len) = 0;
176
177 return out;
178 }
179
180 static bool
181 urldecode(char *buf)
182 {
183 char *c, *p;
184
185 if (!buf || !*buf)
186 return true;
187
188 #define hex(x) \
189 (((x) <= '9') ? ((x) - '0') : \
190 (((x) <= 'F') ? ((x) - 'A' + 10) : \
191 ((x) - 'a' + 10)))
192
193 for (c = p = buf; *p; c++)
194 {
195 if (*p == '%')
196 {
197 if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
198 return false;
199
200 *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
201
202 p += 3;
203 }
204 else if (*p == '+')
205 {
206 *c = ' ';
207 p++;
208 }
209 else
210 {
211 *c = *p++;
212 }
213 }
214
215 *c = 0;
216
217 return true;
218 }
219
220 static bool
221 postdecode(char **fields, int n_fields)
222 {
223 char *p;
224 const char *var;
225 static char buf[1024];
226 int i, len, field, found = 0;
227
228 var = getenv("CONTENT_TYPE");
229
230 if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
231 return false;
232
233 memset(buf, 0, sizeof(buf));
234
235 if ((len = read(0, buf, sizeof(buf) - 1)) > 0)
236 {
237 for (p = buf, i = 0; i <= len; i++)
238 {
239 if (buf[i] == '=')
240 {
241 buf[i] = 0;
242
243 for (field = 0; field < (n_fields * 2); field += 2)
244 {
245 if (!strcmp(p, fields[field]))
246 {
247 fields[field + 1] = buf + i + 1;
248 found++;
249 }
250 }
251 }
252 else if (buf[i] == '&' || buf[i] == '\0')
253 {
254 buf[i] = 0;
255
256 if (found >= n_fields)
257 break;
258
259 p = buf + i + 1;
260 }
261 }
262 }
263
264 for (field = 0; field < (n_fields * 2); field += 2)
265 if (!urldecode(fields[field + 1]))
266 return false;
267
268 return (found >= n_fields);
269 }
270
271 static char *
272 canonicalize_path(const char *path, size_t len)
273 {
274 char *canonpath, *cp;
275 const char *p, *e;
276
277 if (path == NULL || *path == '\0')
278 return NULL;
279
280 canonpath = datadup(path, len);
281
282 if (canonpath == NULL)
283 return NULL;
284
285 /* normalize */
286 for (cp = canonpath, p = path, e = path + len; p < e; ) {
287 if (*p != '/')
288 goto next;
289
290 /* skip repeating / */
291 if ((p + 1 < e) && (p[1] == '/')) {
292 p++;
293 continue;
294 }
295
296 /* /./ or /../ */
297 if ((p + 1 < e) && (p[1] == '.')) {
298 /* skip /./ */
299 if ((p + 2 >= e) || (p[2] == '/')) {
300 p += 2;
301 continue;
302 }
303
304 /* collapse /x/../ */
305 if ((p + 2 < e) && (p[2] == '.') && ((p + 3 >= e) || (p[3] == '/'))) {
306 while ((cp > canonpath) && (*--cp != '/'))
307 ;
308
309 p += 3;
310 continue;
311 }
312 }
313
314 next:
315 *cp++ = *p++;
316 }
317
318 /* remove trailing slash if not root / */
319 if ((cp > canonpath + 1) && (cp[-1] == '/'))
320 cp--;
321 else if (cp == canonpath)
322 *cp++ = '/';
323
324 *cp = '\0';
325
326 return canonpath;
327 }
328
329 static int
330 response(bool success, const char *message)
331 {
332 char *chksum;
333 struct stat s;
334
335 printf("Status: 200 OK\r\n");
336 printf("Content-Type: text/plain\r\n\r\n{\n");
337
338 if (success)
339 {
340 if (!stat(st.filename, &s))
341 printf("\t\"size\": %u,\n", (unsigned int)s.st_size);
342 else
343 printf("\t\"size\": null,\n");
344
345 chksum = checksum("md5sum", 32, st.filename);
346 printf("\t\"checksum\": %s%s%s,\n",
347 chksum ? "\"" : "",
348 chksum ? chksum : "null",
349 chksum ? "\"" : "");
350
351 chksum = checksum("sha256sum", 64, st.filename);
352 printf("\t\"sha256sum\": %s%s%s\n",
353 chksum ? "\"" : "",
354 chksum ? chksum : "null",
355 chksum ? "\"" : "");
356 }
357 else
358 {
359 if (message)
360 printf("\t\"message\": \"%s\",\n", message);
361
362 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
363
364 if (st.filefd > -1)
365 unlink(st.filename);
366 }
367
368 printf("}\n");
369
370 return -1;
371 }
372
373 static int
374 failure(int code, int e, const char *message)
375 {
376 printf("Status: %d %s\r\n", code, message);
377 printf("Content-Type: text/plain\r\n\r\n");
378 printf("%s", message);
379
380 if (e)
381 printf(": %s", strerror(e));
382
383 printf("\n");
384
385 return -1;
386 }
387
388 static int
389 filecopy(void)
390 {
391 int len;
392 char buf[4096];
393
394 if (!st.filedata)
395 {
396 close(st.tempfd);
397 errno = EINVAL;
398 return response(false, "No file data received");
399 }
400
401 if (lseek(st.tempfd, 0, SEEK_SET) < 0)
402 {
403 close(st.tempfd);
404 return response(false, "Failed to rewind temp file");
405 }
406
407 st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
408
409 if (st.filefd < 0)
410 {
411 close(st.tempfd);
412 return response(false, "Failed to open target file");
413 }
414
415 while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
416 {
417 if (write(st.filefd, buf, len) != len)
418 {
419 close(st.tempfd);
420 close(st.filefd);
421 return response(false, "I/O failure while writing target file");
422 }
423 }
424
425 close(st.tempfd);
426 close(st.filefd);
427
428 if (chmod(st.filename, st.filemode))
429 return response(false, "Failed to chmod target file");
430
431 return 0;
432 }
433
434 static int
435 header_field(multipart_parser *p, const char *data, size_t len)
436 {
437 st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
438 return 0;
439 }
440
441 static int
442 header_value(multipart_parser *p, const char *data, size_t len)
443 {
444 int i, j;
445
446 if (!st.is_content_disposition)
447 return 0;
448
449 if (len < 10 || strncasecmp(data, "form-data", 9))
450 return 0;
451
452 for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
453
454 if (len < 8 || strncasecmp(data, "name=\"", 6))
455 return 0;
456
457 for (data += 6, len -= 6, i = 0; i <= len; i++)
458 {
459 if (*(data + i) != '"')
460 continue;
461
462 for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
463 if (!strncmp(data, parts[j], i))
464 st.parttype = j;
465
466 break;
467 }
468
469 return 0;
470 }
471
472 static int
473 data_begin_cb(multipart_parser *p)
474 {
475 char tmpname[24] = "/tmp/luci-upload.XXXXXX";
476
477 if (st.parttype == PART_FILEDATA)
478 {
479 if (!st.sessionid)
480 return response(false, "File data without session");
481
482 if (!st.filename)
483 return response(false, "File data without name");
484
485 if (!session_access(st.sessionid, "file", st.filename, "write"))
486 return response(false, "Access to path denied by ACL");
487
488 st.tempfd = mkstemp(tmpname);
489
490 if (st.tempfd < 0)
491 return response(false, "Failed to create temporary file");
492
493 unlink(tmpname);
494 }
495
496 return 0;
497 }
498
499 static int
500 data_cb(multipart_parser *p, const char *data, size_t len)
501 {
502 switch (st.parttype)
503 {
504 case PART_SESSIONID:
505 st.sessionid = datadup(data, len);
506 break;
507
508 case PART_FILENAME:
509 st.filename = canonicalize_path(data, len);
510 break;
511
512 case PART_FILEMODE:
513 st.filemode = strtoul(data, NULL, 8);
514 break;
515
516 case PART_FILEDATA:
517 if (write(st.tempfd, data, len) != len)
518 {
519 close(st.tempfd);
520 return response(false, "I/O failure while writing temporary file");
521 }
522
523 if (!st.filedata)
524 st.filedata = !!len;
525
526 break;
527
528 default:
529 break;
530 }
531
532 return 0;
533 }
534
535 static int
536 data_end_cb(multipart_parser *p)
537 {
538 if (st.parttype == PART_SESSIONID)
539 {
540 if (!session_access(st.sessionid, "cgi-io", "upload", "write"))
541 {
542 errno = EPERM;
543 return response(false, "Upload permission denied");
544 }
545 }
546 else if (st.parttype == PART_FILEDATA)
547 {
548 if (st.tempfd < 0)
549 return response(false, "Internal program failure");
550
551 #if 0
552 /* prepare directory */
553 for (ptr = st.filename; *ptr; ptr++)
554 {
555 if (*ptr == '/')
556 {
557 *ptr = 0;
558
559 if (mkdir(st.filename, 0755))
560 {
561 unlink(st.tmpname);
562 return response(false, "Failed to create destination directory");
563 }
564
565 *ptr = '/';
566 }
567 }
568 #endif
569
570 if (filecopy())
571 return -1;
572
573 return response(true, NULL);
574 }
575
576 st.parttype = PART_UNKNOWN;
577 return 0;
578 }
579
580 static multipart_parser *
581 init_parser(void)
582 {
583 char *boundary;
584 const char *var;
585
586 multipart_parser *p;
587 static multipart_parser_settings s = {
588 .on_part_data = data_cb,
589 .on_headers_complete = data_begin_cb,
590 .on_part_data_end = data_end_cb,
591 .on_header_field = header_field,
592 .on_header_value = header_value
593 };
594
595 var = getenv("CONTENT_TYPE");
596
597 if (!var || strncmp(var, "multipart/form-data;", 20))
598 return NULL;
599
600 for (var += 20; *var && *var != '='; var++);
601
602 if (*var++ != '=')
603 return NULL;
604
605 boundary = malloc(strlen(var) + 3);
606
607 if (!boundary)
608 return NULL;
609
610 strcpy(boundary, "--");
611 strcpy(boundary + 2, var);
612
613 st.tempfd = -1;
614 st.filefd = -1;
615 st.filemode = 0600;
616
617 p = multipart_parser_init(boundary, &s);
618
619 free(boundary);
620
621 return p;
622 }
623
624 static int
625 main_upload(int argc, char *argv[])
626 {
627 int rem, len;
628 char buf[4096];
629 multipart_parser *p;
630
631 p = init_parser();
632
633 if (!p)
634 {
635 errno = EINVAL;
636 return response(false, "Invalid request");
637 }
638
639 while ((len = read(0, buf, sizeof(buf))) > 0)
640 {
641 rem = multipart_parser_execute(p, buf, len);
642
643 if (rem < len)
644 break;
645 }
646
647 multipart_parser_free(p);
648
649 /* read remaining post data */
650 while ((len = read(0, buf, sizeof(buf))) > 0);
651
652 return 0;
653 }
654
655 static int
656 main_download(int argc, char **argv)
657 {
658 char *fields[] = { "sessionid", NULL, "path", NULL, "filename", NULL, "mimetype", NULL };
659 unsigned long long size = 0;
660 char *p, buf[4096];
661 ssize_t len = 0;
662 struct stat s;
663 int rfd;
664
665 postdecode(fields, 4);
666
667 if (!fields[1] || !session_access(fields[1], "cgi-io", "download", "read"))
668 return failure(403, 0, "Download permission denied");
669
670 if (!fields[3] || !session_access(fields[1], "file", fields[3], "read"))
671 return failure(403, 0, "Access to path denied by ACL");
672
673 if (stat(fields[3], &s))
674 return failure(404, errno, "Failed to stat requested path");
675
676 if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode))
677 return failure(403, 0, "Requested path is not a regular file or block device");
678
679 for (p = fields[5]; p && *p; p++)
680 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%", *p))
681 return failure(400, 0, "Invalid characters in filename");
682
683 for (p = fields[7]; p && *p; p++)
684 if (!isalnum(*p) && !strchr(" .;=/-", *p))
685 return failure(400, 0, "Invalid characters in mimetype");
686
687 rfd = open(fields[3], O_RDONLY);
688
689 if (rfd < 0)
690 return failure(500, errno, "Failed to open requested path");
691
692 if (S_ISBLK(s.st_mode))
693 ioctl(rfd, BLKGETSIZE64, &size);
694 else
695 size = (unsigned long long)s.st_size;
696
697 printf("Status: 200 OK\r\n");
698 printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream");
699
700 if (fields[5])
701 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]);
702
703 printf("Content-Length: %llu\r\n\r\n", size);
704 fflush(stdout);
705
706 while (size > 0) {
707 len = sendfile(1, rfd, NULL, size);
708
709 if (len == -1) {
710 if (errno == ENOSYS || errno == EINVAL) {
711 while ((len = read(rfd, buf, sizeof(buf))) > 0)
712 fwrite(buf, len, 1, stdout);
713
714 fflush(stdout);
715 break;
716 }
717
718 if (errno == EINTR || errno == EAGAIN)
719 continue;
720 }
721
722 if (len <= 0)
723 break;
724
725 size -= len;
726 }
727
728 close(rfd);
729
730 return 0;
731 }
732
733 static int
734 main_backup(int argc, char **argv)
735 {
736 pid_t pid;
737 time_t now;
738 int len;
739 int status;
740 int fds[2];
741 char datestr[16] = { 0 };
742 char hostname[64] = { 0 };
743 char *fields[] = { "sessionid", NULL };
744
745 if (!postdecode(fields, 1) || !session_access(fields[1], "cgi-io", "backup", "read"))
746 return failure(403, 0, "Backup permission denied");
747
748 if (pipe(fds))
749 return failure(500, errno, "Failed to spawn pipe");
750
751 switch ((pid = fork()))
752 {
753 case -1:
754 return failure(500, errno, "Failed to fork process");
755
756 case 0:
757 dup2(fds[1], 1);
758
759 close(0);
760 close(2);
761 close(fds[0]);
762 close(fds[1]);
763
764 chdir("/");
765
766 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
767 "--create-backup", "-", NULL);
768
769 return -1;
770
771 default:
772 now = time(NULL);
773 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
774
775 if (gethostname(hostname, sizeof(hostname) - 1))
776 sprintf(hostname, "OpenWrt");
777
778 printf("Status: 200 OK\r\n");
779 printf("Content-Type: application/x-targz\r\n");
780 printf("Content-Disposition: attachment; "
781 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
782
783 fflush(stdout);
784
785 do {
786 len = splice(fds[0], NULL, 1, NULL, 4096, SPLICE_F_MORE);
787 } while (len > 0);
788
789 waitpid(pid, &status, 0);
790
791 close(fds[0]);
792 close(fds[1]);
793
794 return 0;
795 }
796 }
797
798 int main(int argc, char **argv)
799 {
800 if (strstr(argv[0], "cgi-upload"))
801 return main_upload(argc, argv);
802 else if (strstr(argv[0], "cgi-download"))
803 return main_download(argc, argv);
804 else if (strstr(argv[0], "cgi-backup"))
805 return main_backup(argc, argv);
806
807 return -1;
808 }