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 /* 541 * Canonicalize the requested path before checking it against the session 542 * ACL and opening it. The rpcd ACL matcher uses fnmatch() without 543 * FNM_PATHNAME, so a "../" suffix would otherwise match an allowed glob 544 * prefix during the ACL check while open() resolves the embedded ".." 545 * and reaches a file outside of that prefix. 546 */ 547 autochar *path = fields[3] ? canonicalize_path(fields[3], strlen(fields[3])) : NULL; 548 549 if (!path || !session_access(fields[1], "file", path, "read")) 550 return failure(403, 0, "Access to path denied by ACL"); 551 552 if (stat(path, &s)) 553 return failure(404, errno, "Failed to stat requested path"); 554 555 if (!S_ISREG(s.st_mode) && !S_ISBLK(s.st_mode)) 556 return failure(403, 0, "Requested path is not a regular file or block device"); 557 558 for (p = fields[5]; p && *p; p++) 559 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p)) 560 return failure(400, 0, "Invalid characters in filename"); 561 562 for (p = fields[7]; p && *p; p++) 563 if (!isalnum(*p) && !strchr(" .;=/-", *p)) 564 return failure(400, 0, "Invalid characters in mimetype"); 565 566 rfd = open(path, O_RDONLY); 567 568 if (rfd < 0) 569 return failure(500, errno, "Failed to open requested path"); 570 571 if (S_ISBLK(s.st_mode)) 572 ioctl(rfd, BLKGETSIZE64, &size); 573 else 574 size = (unsigned long long)s.st_size; 575 576 printf("Status: 200 OK\r\n"); 577 printf("Content-Type: %s\r\n", fields[7] ? fields[7] : "application/octet-stream"); 578 579 if (fields[5]) 580 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", fields[5]); 581 582 if (size > 0) { 583 printf("Content-Length: %llu\r\n\r\n", size); 584 fflush(stdout); 585 586 while (size > 0) { 587 len = sendfile(1, rfd, NULL, size); 588 589 if (len == -1) { 590 if (errno == ENOSYS || errno == EINVAL) { 591 while ((len = read(rfd, buf, sizeof(buf))) > 0) 592 fwrite(buf, len, 1, stdout); 593 594 fflush(stdout); 595 break; 596 } 597 598 if (errno == EINTR || errno == EAGAIN) 599 continue; 600 } 601 602 if (len <= 0) 603 break; 604 605 size -= len; 606 } 607 } 608 else { 609 printf("\r\n"); 610 611 while ((len = read(rfd, buf, sizeof(buf))) > 0) 612 fwrite(buf, len, 1, stdout); 613 614 fflush(stdout); 615 } 616 617 close(rfd); 618 619 return 0; 620 } 621 622 static int 623 main_backup(int argc, char **argv) 624 { 625 pid_t pid; 626 time_t now; 627 int r; 628 int len; 629 int status; 630 int fds[2]; 631 char datestr[16] = { 0 }; 632 char hostname[64] = { 0 }; 633 char *fields[] = { "sessionid", NULL }; 634 635 autochar *post = postdecode(fields, 1); 636 (void) post; 637 638 if (!fields[1] || !session_access(fields[1], "cgi-io", "backup", "read")) 639 return failure(403, 0, "Backup permission denied"); 640 641 if (pipe(fds)) 642 return failure(500, errno, "Failed to spawn pipe"); 643 644 switch ((pid = fork())) 645 { 646 case -1: 647 return failure(500, errno, "Failed to fork process"); 648 649 case 0: 650 dup2(fds[1], 1); 651 652 close(0); 653 close(2); 654 close(fds[0]); 655 close(fds[1]); 656 657 r = chdir("/"); 658 if (r < 0) 659 return failure(500, errno, "Failed chdir('/')"); 660 661 execl("/sbin/sysupgrade", "/sbin/sysupgrade", 662 "--create-backup", "-", NULL); 663 664 return -1; 665 666 default: 667 close(fds[1]); 668 669 now = time(NULL); 670 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now)); 671 672 if (gethostname(hostname, sizeof(hostname) - 1)) 673 sprintf(hostname, "OpenWrt"); 674 675 printf("Status: 200 OK\r\n"); 676 printf("Content-Type: application/x-targz\r\n"); 677 printf("Content-Disposition: attachment; " 678 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr); 679 680 fflush(stdout); 681 682 do { 683 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE); 684 } while (len > 0 || (len == -1 && errno == EINTR)); 685 686 waitpid(pid, &status, 0); 687 688 close(fds[0]); 689 690 return 0; 691 } 692 } 693 694 695 static const char * 696 lookup_executable(const char *cmd) 697 { 698 size_t plen = 0, clen; 699 static char path[PATH_MAX]; 700 char *search, *p; 701 struct stat s; 702 703 if (!cmd) 704 return NULL; 705 706 clen = strlen(cmd) + 1; 707 708 if (!stat(cmd, &s) && S_ISREG(s.st_mode)) 709 return cmd; 710 711 search = getenv("PATH"); 712 713 if (!search) 714 search = "/bin:/usr/bin:/sbin:/usr/sbin"; 715 716 p = search; 717 718 do { 719 if (*p != ':' && *p != '\0') 720 continue; 721 722 plen = p - search; 723 724 if ((plen + clen) >= sizeof(path)) 725 continue; 726 727 strncpy(path, search, plen); 728 sprintf(path + plen, "/%s", cmd); 729 730 if (!stat(path, &s) && S_ISREG(s.st_mode)) 731 return path; 732 733 search = p + 1; 734 } while (*p++); 735 736 return NULL; 737 } 738 739 static int 740 main_exec(int argc, char **argv) 741 { 742 char *fields[] = { "sessionid", NULL, "command", NULL, "filename", NULL, "mimetype", NULL, "stderr", NULL }; 743 int i, devnull, status, fds[2]; 744 bool allowed = false, redir_stderr = false; 745 ssize_t len = 0; 746 size_t exelen; 747 const char *exe; 748 char *p, **args; 749 autochar *canon = NULL; 750 pid_t pid; 751 752 autochar *post = postdecode(fields, 5); 753 (void) post; 754 755 if (!fields[1] || !session_access(fields[1], "cgi-io", "exec", "read")) 756 return failure(403, 0, "Exec permission denied"); 757 758 for (p = fields[5]; p && *p; p++) 759 if (!isalnum(*p) && !strchr(" ()<>@,;:[]?.=%-", *p)) 760 return failure(400, 0, "Invalid characters in filename"); 761 762 for (p = fields[7]; p && *p; p++) 763 if (!isalnum(*p) && !strchr(" .;=/-", *p)) 764 return failure(400, 0, "Invalid characters in mimetype"); 765 766 p = fields[9]; 767 if (p && p[0] == '1' && p[1] == '\0') 768 redir_stderr = true; 769 770 args = fields[3] ? parse_command(fields[3]) : NULL; 771 772 if (!args) 773 return failure(400, 0, "Invalid command parameter"); 774 775 /* 776 * Canonicalize the executable path before checking it against the 777 * session ACL and executing it. As the rpcd ACL matcher uses fnmatch() 778 * without FNM_PATHNAME, a "../" sequence in the leading token would 779 * otherwise let an allowed glob prefix match while execv() resolves to a 780 * binary outside of that prefix. The leading token is everything up to 781 * the first argument separator. 782 */ 783 exelen = args[1] ? (size_t)(args[1] - args[0] - 1) : strlen(args[0]); 784 canon = canonicalize_path(args[0], exelen); 785 786 if (!canon) { 787 free(args); 788 return failure(400, 0, "Invalid command parameter"); 789 } 790 791 /* First check if we find an ACL match for the whole cmdline ... */ 792 { 793 char *tail = args[0] + exelen; 794 autochar *cmdline = malloc(strlen(canon) + strlen(tail) + 1); 795 796 if (!cmdline) { 797 free(args); 798 return failure(500, errno, "Out of memory"); 799 } 800 801 strcpy(cmdline, canon); 802 strcat(cmdline, tail); 803 804 allowed = session_access(fields[1], "file", cmdline, "exec"); 805 } 806 807 /* Now split the command vector... */ 808 for (i = 1; args[i]; i++) 809 args[i][-1] = 0; 810 811 /* Find executable... */ 812 exe = lookup_executable(canon); 813 814 if (!exe) { 815 free(args); 816 return failure(404, 0, "Executable not found"); 817 } 818 819 /* If there was no ACL match, check for a match on the executable */ 820 if (!allowed && !session_access(fields[1], "file", exe, "exec")) { 821 free(args); 822 return failure(403, 0, "Access to command denied by ACL"); 823 } 824 825 if (pipe(fds)) { 826 free(args); 827 return failure(500, errno, "Failed to spawn pipe"); 828 } 829 830 switch ((pid = fork())) 831 { 832 case -1: 833 free(args); 834 close(fds[0]); 835 close(fds[1]); 836 return failure(500, errno, "Failed to fork process"); 837 838 case 0: 839 devnull = open("/dev/null", O_RDWR); 840 841 if (devnull > -1) { 842 dup2(devnull, 0); 843 if (!redir_stderr) 844 dup2(devnull, 2); 845 close(devnull); 846 } 847 else { 848 close(0); 849 close(2); 850 } 851 852 dup2(fds[1], 1); 853 if (redir_stderr) 854 dup2(fds[1], 2); 855 close(fds[0]); 856 close(fds[1]); 857 858 if (chdir("/") < 0) { 859 free(args); 860 return failure(500, errno, "Failed chdir('/')"); 861 } 862 863 if (execv(exe, args) < 0) { 864 free(args); 865 return failure(500, errno, "Failed execv(...)"); 866 } 867 868 return -1; 869 870 default: 871 close(fds[1]); 872 873 printf("Status: 200 OK\r\n"); 874 printf("Content-Type: %s\r\n", 875 fields[7] ? fields[7] : "application/octet-stream"); 876 877 if (fields[5]) 878 printf("Content-Disposition: attachment; filename=\"%s\"\r\n", 879 fields[5]); 880 881 printf("\r\n"); 882 fflush(stdout); 883 884 do { 885 len = splice(fds[0], NULL, 1, NULL, READ_BLOCK, SPLICE_F_MORE); 886 } while (len > 0 || (len == -1 && errno == EINTR)); 887 888 waitpid(pid, &status, 0); 889 890 close(fds[0]); 891 free(args); 892 893 return 0; 894 } 895 } 896 897 int main(int argc, char **argv) 898 { 899 if (strstr(argv[0], "cgi-upload")) 900 return main_upload(argc, argv); 901 else if (strstr(argv[0], "cgi-download")) 902 return main_download(argc, argv); 903 else if (strstr(argv[0], "cgi-backup")) 904 return main_backup(argc, argv); 905 else if (strstr(argv[0], "cgi-exec")) 906 return main_exec(argc, argv); 907 908 return -1; 909 } 910
This page was automatically generated by LXR 0.3.1. • OpenWrt