1 /* 2 * luci-io - LuCI non-RPC helper 3 * 4 * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org> 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 #include <stdio.h> 20 #include <stdlib.h> 21 #include <stdbool.h> 22 #include <unistd.h> 23 #include <string.h> 24 #include <errno.h> 25 #include <fcntl.h> 26 #include <ctype.h> 27 #include <sys/stat.h> 28 #include <sys/wait.h> 29 30 #include <libubus.h> 31 #include <libubox/blobmsg.h> 32 33 #include "multipart_parser.h" 34 35 36 enum part { 37 PART_UNKNOWN, 38 PART_SESSIONID, 39 PART_FILENAME, 40 PART_FILEMODE, 41 PART_FILEDATA 42 }; 43 44 const char *parts[] = { 45 "(bug)", 46 "sessionid", 47 "filename", 48 "filemode", 49 "filedata", 50 }; 51 52 struct state 53 { 54 bool is_content_disposition; 55 enum part parttype; 56 char *sessionid; 57 char *filename; 58 bool filedata; 59 int filemode; 60 int filefd; 61 int tempfd; 62 }; 63 64 enum { 65 SES_ACCESS, 66 __SES_MAX, 67 }; 68 69 static const struct blobmsg_policy ses_policy[__SES_MAX] = { 70 [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL }, 71 }; 72 73 74 static struct state st; 75 76 static void 77 session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg) 78 { 79 struct blob_attr *tb[__SES_MAX]; 80 bool *allow = (bool *)req->priv; 81 82 if (!msg) 83 return; 84 85 blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg)); 86 87 if (tb[SES_ACCESS]) 88 *allow = blobmsg_get_bool(tb[SES_ACCESS]); 89 } 90 91 static bool 92 session_access(const char *sid, const char *obj, const char *func) 93 { 94 uint32_t id; 95 bool allow = false; 96 struct ubus_context *ctx; 97 static struct blob_buf req; 98 99 ctx = ubus_connect(NULL); 100 101 if (!ctx || ubus_lookup_id(ctx, "session", &id)) 102 goto out; 103 104 blob_buf_init(&req, 0); 105 blobmsg_add_string(&req, "ubus_rpc_session", sid); 106 blobmsg_add_string(&req, "scope", "luci-io"); 107 blobmsg_add_string(&req, "object", obj); 108 blobmsg_add_string(&req, "function", func); 109 110 ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500); 111 112 out: 113 if (ctx) 114 ubus_free(ctx); 115 116 return allow; 117 } 118 119 static char * 120 md5sum(const char *file) 121 { 122 pid_t pid; 123 int fds[2]; 124 static char md5[33]; 125 126 if (pipe(fds)) 127 return NULL; 128 129 switch ((pid = fork())) 130 { 131 case -1: 132 return NULL; 133 134 case 0: 135 uloop_done(); 136 137 dup2(fds[1], 1); 138 139 close(0); 140 close(2); 141 close(fds[0]); 142 close(fds[1]); 143 144 if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL)); 145 return NULL; 146 147 break; 148 149 default: 150 memset(md5, 0, sizeof(md5)); 151 read(fds[0], md5, 32); 152 waitpid(pid, NULL, 0); 153 close(fds[0]); 154 close(fds[1]); 155 } 156 157 return md5; 158 } 159 160 static char * 161 datadup(const void *in, size_t len) 162 { 163 char *out = malloc(len + 1); 164 165 if (!out) 166 return NULL; 167 168 memcpy(out, in, len); 169 170 *(out + len) = 0; 171 172 return out; 173 } 174 175 static bool 176 urldecode(char *buf) 177 { 178 char *c, *p; 179 180 if (!buf || !*buf) 181 return true; 182 183 #define hex(x) \ 184 (((x) <= '9') ? ((x) - '') : \ 185 (((x) <= 'F') ? ((x) - 'A' + 10) : \ 186 ((x) - 'a' + 10))) 187 188 for (c = p = buf; *p; c++) 189 { 190 if (*p == '%') 191 { 192 if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2))) 193 return false; 194 195 *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2))); 196 197 p += 3; 198 } 199 else if (*p == '+') 200 { 201 *c = ' '; 202 p++; 203 } 204 else 205 { 206 *c = *p++; 207 } 208 } 209 210 *c = 0; 211 212 return true; 213 } 214 215 static bool 216 postdecode(char **fields, int n_fields) 217 { 218 char *p; 219 const char *var; 220 static char buf[1024]; 221 int i, len, field, found = 0; 222 223 var = getenv("CONTENT_TYPE"); 224 225 if (!var || strncmp(var, "application/x-www-form-urlencoded", 33)) 226 return false; 227 228 memset(buf, 0, sizeof(buf)); 229 230 if ((len = read(0, buf, sizeof(buf) - 1)) > 0) 231 { 232 for (p = buf, i = 0; i <= len; i++) 233 { 234 if (buf[i] == '=') 235 { 236 buf[i] = 0; 237 238 for (field = 0; field < (n_fields * 2); field += 2) 239 { 240 if (!strcmp(p, fields[field])) 241 { 242 fields[field + 1] = buf + i + 1; 243 found++; 244 } 245 } 246 } 247 else if (buf[i] == '&' || buf[i] == '\0') 248 { 249 buf[i] = 0; 250 251 if (found >= n_fields) 252 break; 253 254 p = buf + i + 1; 255 } 256 } 257 } 258 259 for (field = 0; field < (n_fields * 2); field += 2) 260 if (!urldecode(fields[field + 1])) 261 return false; 262 263 return (found >= n_fields); 264 } 265 266 static int 267 response(bool success, const char *message) 268 { 269 char *md5; 270 struct stat s; 271 272 printf("Status: 200 OK\r\n"); 273 printf("Content-Type: text/plain\r\n\r\n{\n"); 274 275 if (success) 276 { 277 if (!stat(st.filename, &s) && (md5 = md5sum(st.filename)) != NULL) 278 printf("\t\"size\": %u,\n\t\"checksum\": \"%s\"\n", 279 (unsigned int)s.st_size, md5); 280 } 281 else 282 { 283 if (message) 284 printf("\t\"message\": \"%s\",\n", message); 285 286 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno)); 287 288 if (st.filefd > -1) 289 unlink(st.filename); 290 } 291 292 printf("}\n"); 293 294 return -1; 295 } 296 297 static int 298 failure(int e, const char *message) 299 { 300 printf("Status: 500 Internal Server failure\r\n"); 301 printf("Content-Type: text/plain\r\n\r\n"); 302 printf("%s", message); 303 304 if (e) 305 printf(": %s", strerror(e)); 306 307 return -1; 308 } 309 310 static int 311 filecopy(void) 312 { 313 int len; 314 char buf[4096]; 315 316 if (!st.filedata) 317 { 318 close(st.tempfd); 319 errno = EINVAL; 320 return response(false, "No file data received"); 321 } 322 323 if (lseek(st.tempfd, 0, SEEK_SET) < 0) 324 { 325 close(st.tempfd); 326 return response(false, "Failed to rewind temp file"); 327 } 328 329 st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); 330 331 if (st.filefd < 0) 332 { 333 close(st.tempfd); 334 return response(false, "Failed to open target file"); 335 } 336 337 while ((len = read(st.tempfd, buf, sizeof(buf))) > 0) 338 { 339 if (write(st.filefd, buf, len) != len) 340 { 341 close(st.tempfd); 342 close(st.filefd); 343 return response(false, "I/O failure while writing target file"); 344 } 345 } 346 347 close(st.tempfd); 348 close(st.filefd); 349 350 if (chmod(st.filename, st.filemode)) 351 return response(false, "Failed to chmod target file"); 352 353 return 0; 354 } 355 356 static int 357 header_field(multipart_parser *p, const char *data, size_t len) 358 { 359 st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len); 360 return 0; 361 } 362 363 static int 364 header_value(multipart_parser *p, const char *data, size_t len) 365 { 366 int i, j; 367 368 if (!st.is_content_disposition) 369 return 0; 370 371 if (len < 10 || strncasecmp(data, "form-data", 9)) 372 return 0; 373 374 for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--); 375 376 if (len < 8 || strncasecmp(data, "name=\"", 6)) 377 return 0; 378 379 for (data += 6, len -= 6, i = 0; i <= len; i++) 380 { 381 if (*(data + i) != '"') 382 continue; 383 384 for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++) 385 if (!strncmp(data, parts[j], i)) 386 st.parttype = j; 387 388 break; 389 } 390 391 return 0; 392 } 393 394 static int 395 data_begin_cb(multipart_parser *p) 396 { 397 char tmpname[24] = "/tmp/luci-upload.XXXXXX"; 398 399 if (st.parttype == PART_FILEDATA) 400 { 401 if (!st.sessionid) 402 return response(false, "File data without session"); 403 404 if (!st.filename) 405 return response(false, "File data without name"); 406 407 st.tempfd = mkstemp(tmpname); 408 409 if (st.tempfd < 0) 410 return response(false, "Failed to create temporary file"); 411 412 unlink(tmpname); 413 } 414 415 return 0; 416 } 417 418 static int 419 data_cb(multipart_parser *p, const char *data, size_t len) 420 { 421 switch (st.parttype) 422 { 423 case PART_SESSIONID: 424 st.sessionid = datadup(data, len); 425 break; 426 427 case PART_FILENAME: 428 st.filename = datadup(data, len); 429 break; 430 431 case PART_FILEMODE: 432 st.filemode = strtoul(data, NULL, 8); 433 break; 434 435 case PART_FILEDATA: 436 if (write(st.tempfd, data, len) != len) 437 { 438 close(st.tempfd); 439 return response(false, "I/O failure while writing temporary file"); 440 } 441 442 if (!st.filedata) 443 st.filedata = !!len; 444 445 break; 446 447 default: 448 break; 449 } 450 451 return 0; 452 } 453 454 static int 455 data_end_cb(multipart_parser *p) 456 { 457 if (st.parttype == PART_SESSIONID) 458 { 459 if (!session_access(st.sessionid, "upload", "write")) 460 { 461 errno = EPERM; 462 return response(false, "Upload permission denied"); 463 } 464 } 465 else if (st.parttype == PART_FILEDATA) 466 { 467 if (st.tempfd < 0) 468 return response(false, "Internal program failure"); 469 470 #if 0 471 /* prepare directory */ 472 for (ptr = st.filename; *ptr; ptr++) 473 { 474 if (*ptr == '/') 475 { 476 *ptr = 0; 477 478 if (mkdir(st.filename, 0755)) 479 { 480 unlink(st.tmpname); 481 return response(false, "Failed to create destination directory"); 482 } 483 484 *ptr = '/'; 485 } 486 } 487 #endif 488 489 if (filecopy()) 490 return -1; 491 492 return response(true, NULL); 493 } 494 495 st.parttype = PART_UNKNOWN; 496 return 0; 497 } 498 499 static multipart_parser * 500 init_parser(void) 501 { 502 char *boundary; 503 const char *var; 504 505 multipart_parser *p; 506 static multipart_parser_settings s = { 507 .on_part_data = data_cb, 508 .on_headers_complete = data_begin_cb, 509 .on_part_data_end = data_end_cb, 510 .on_header_field = header_field, 511 .on_header_value = header_value 512 }; 513 514 var = getenv("CONTENT_TYPE"); 515 516 if (!var || strncmp(var, "multipart/form-data;", 20)) 517 return NULL; 518 519 for (var += 20; *var && *var != '='; var++); 520 521 if (*var++ != '=') 522 return NULL; 523 524 boundary = malloc(strlen(var) + 3); 525 526 if (!boundary) 527 return NULL; 528 529 strcpy(boundary, "--"); 530 strcpy(boundary + 2, var); 531 532 st.tempfd = -1; 533 st.filefd = -1; 534 st.filemode = 0600; 535 536 p = multipart_parser_init(boundary, &s); 537 538 free(boundary); 539 540 return p; 541 } 542 543 static int 544 main_upload(int argc, char *argv[]) 545 { 546 int rem, len; 547 char buf[4096]; 548 multipart_parser *p; 549 550 p = init_parser(); 551 552 if (!p) 553 { 554 errno = EINVAL; 555 return response(false, "Invalid request"); 556 } 557 558 while ((len = read(0, buf, sizeof(buf))) > 0) 559 { 560 rem = multipart_parser_execute(p, buf, len); 561 562 if (rem < len) 563 break; 564 } 565 566 multipart_parser_free(p); 567 568 /* read remaining post data */ 569 while ((len = read(0, buf, sizeof(buf))) > 0); 570 571 return 0; 572 } 573 574 static int 575 main_backup(int argc, char **argv) 576 { 577 pid_t pid; 578 time_t now; 579 int len; 580 int fds[2]; 581 char buf[4096]; 582 char datestr[16] = { 0 }; 583 char hostname[64] = { 0 }; 584 char *fields[] = { "sessionid", NULL }; 585 586 if (!postdecode(fields, 1) || !session_access(fields[1], "backup", "read")) 587 return failure(0, "Backup permission denied"); 588 589 if (pipe(fds)) 590 return failure(errno, "Failed to spawn pipe"); 591 592 switch ((pid = fork())) 593 { 594 case -1: 595 return failure(errno, "Failed to fork process"); 596 597 case 0: 598 dup2(fds[1], 1); 599 600 close(0); 601 close(2); 602 close(fds[0]); 603 close(fds[1]); 604 605 chdir("/"); 606 607 execl("/sbin/sysupgrade", "/sbin/sysupgrade", 608 "--create-backup", "-", NULL); 609 610 return -1; 611 612 default: 613 now = time(NULL); 614 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now)); 615 616 if (gethostname(hostname, sizeof(hostname) - 1)) 617 sprintf(hostname, "OpenWrt"); 618 619 printf("Status: 200 OK\r\n"); 620 printf("Content-Type: application/x-targz\r\n"); 621 printf("Content-Disposition: attachment; " 622 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr); 623 624 while ((len = read(fds[0], buf, sizeof(buf))) > 0) 625 fwrite(buf, len, 1, stdout); 626 627 waitpid(pid, NULL, 0); 628 629 close(fds[0]); 630 close(fds[1]); 631 632 return 0; 633 } 634 } 635 636 int main(int argc, char **argv) 637 { 638 if (strstr(argv[0], "luci-upload")) 639 return main_upload(argc, argv); 640 else if (strstr(argv[0], "luci-backup")) 641 return main_backup(argc, argv); 642 643 return -1; 644 } 645
This page was automatically generated by LXR 0.3.1. • OpenWrt