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