2 * cgi-io - LuCI non-RPC helper
4 * Copyright (C) 2013 Jo-Philipp Wich <jo@mein.io>
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.
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.
31 #include <libubox/blobmsg.h>
33 #include "multipart_parser.h"
44 const char *parts[] = {
54 bool is_content_disposition;
69 static const struct blobmsg_policy ses_policy[__SES_MAX] = {
70 [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
74 static struct state st;
77 session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
79 struct blob_attr *tb[__SES_MAX];
80 bool *allow = (bool *)req->priv;
85 blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
88 *allow = blobmsg_get_bool(tb[SES_ACCESS]);
92 session_access(const char *sid, const char *obj, const char *func)
96 struct ubus_context *ctx;
97 static struct blob_buf req;
99 ctx = ubus_connect(NULL);
101 if (!ctx || ubus_lookup_id(ctx, "session", &id))
104 blob_buf_init(&req, 0);
105 blobmsg_add_string(&req, "ubus_rpc_session", sid);
106 blobmsg_add_string(&req, "scope", "cgi-io");
107 blobmsg_add_string(&req, "object", obj);
108 blobmsg_add_string(&req, "function", func);
110 ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
120 md5sum(const char *file)
129 switch ((pid = fork()))
144 if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL));
150 memset(md5, 0, sizeof(md5));
151 read(fds[0], md5, 32);
152 waitpid(pid, NULL, 0);
161 datadup(const void *in, size_t len)
163 char *out = malloc(len + 1);
168 memcpy(out, in, len);
184 (((x) <= '9') ? ((x) - '0') : \
185 (((x) <= 'F') ? ((x) - 'A' + 10) : \
188 for (c = p = buf; *p; c++)
192 if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
195 *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
216 postdecode(char **fields, int n_fields)
220 static char buf[1024];
221 int i, len, field, found = 0;
223 var = getenv("CONTENT_TYPE");
225 if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
228 memset(buf, 0, sizeof(buf));
230 if ((len = read(0, buf, sizeof(buf) - 1)) > 0)
232 for (p = buf, i = 0; i <= len; i++)
238 for (field = 0; field < (n_fields * 2); field += 2)
240 if (!strcmp(p, fields[field]))
242 fields[field + 1] = buf + i + 1;
247 else if (buf[i] == '&' || buf[i] == '\0')
251 if (found >= n_fields)
259 for (field = 0; field < (n_fields * 2); field += 2)
260 if (!urldecode(fields[field + 1]))
263 return (found >= n_fields);
267 response(bool success, const char *message)
272 printf("Status: 200 OK\r\n");
273 printf("Content-Type: text/plain\r\n\r\n{\n");
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);
284 printf("\t\"message\": \"%s\",\n", message);
286 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
298 failure(int e, const char *message)
300 printf("Status: 500 Internal Server failure\r\n");
301 printf("Content-Type: text/plain\r\n\r\n");
302 printf("%s", message);
305 printf(": %s", strerror(e));
320 return response(false, "No file data received");
323 if (lseek(st.tempfd, 0, SEEK_SET) < 0)
326 return response(false, "Failed to rewind temp file");
329 st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
334 return response(false, "Failed to open target file");
337 while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
339 if (write(st.filefd, buf, len) != len)
343 return response(false, "I/O failure while writing target file");
350 if (chmod(st.filename, st.filemode))
351 return response(false, "Failed to chmod target file");
357 header_field(multipart_parser *p, const char *data, size_t len)
359 st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
364 header_value(multipart_parser *p, const char *data, size_t len)
368 if (!st.is_content_disposition)
371 if (len < 10 || strncasecmp(data, "form-data", 9))
374 for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
376 if (len < 8 || strncasecmp(data, "name=\"", 6))
379 for (data += 6, len -= 6, i = 0; i <= len; i++)
381 if (*(data + i) != '"')
384 for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
385 if (!strncmp(data, parts[j], i))
395 data_begin_cb(multipart_parser *p)
397 char tmpname[24] = "/tmp/luci-upload.XXXXXX";
399 if (st.parttype == PART_FILEDATA)
402 return response(false, "File data without session");
405 return response(false, "File data without name");
407 st.tempfd = mkstemp(tmpname);
410 return response(false, "Failed to create temporary file");
419 data_cb(multipart_parser *p, const char *data, size_t len)
424 st.sessionid = datadup(data, len);
428 st.filename = datadup(data, len);
432 st.filemode = strtoul(data, NULL, 8);
436 if (write(st.tempfd, data, len) != len)
439 return response(false, "I/O failure while writing temporary file");
455 data_end_cb(multipart_parser *p)
457 if (st.parttype == PART_SESSIONID)
459 if (!session_access(st.sessionid, "upload", "write"))
462 return response(false, "Upload permission denied");
465 else if (st.parttype == PART_FILEDATA)
468 return response(false, "Internal program failure");
471 /* prepare directory */
472 for (ptr = st.filename; *ptr; ptr++)
478 if (mkdir(st.filename, 0755))
481 return response(false, "Failed to create destination directory");
492 return response(true, NULL);
495 st.parttype = PART_UNKNOWN;
499 static multipart_parser *
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
514 var = getenv("CONTENT_TYPE");
516 if (!var || strncmp(var, "multipart/form-data;", 20))
519 for (var += 20; *var && *var != '='; var++);
524 boundary = malloc(strlen(var) + 3);
529 strcpy(boundary, "--");
530 strcpy(boundary + 2, var);
536 p = multipart_parser_init(boundary, &s);
544 main_upload(int argc, char *argv[])
555 return response(false, "Invalid request");
558 while ((len = read(0, buf, sizeof(buf))) > 0)
560 rem = multipart_parser_execute(p, buf, len);
566 multipart_parser_free(p);
568 /* read remaining post data */
569 while ((len = read(0, buf, sizeof(buf))) > 0);
575 main_backup(int argc, char **argv)
582 char datestr[16] = { 0 };
583 char hostname[64] = { 0 };
584 char *fields[] = { "sessionid", NULL };
586 if (!postdecode(fields, 1) || !session_access(fields[1], "backup", "read"))
587 return failure(0, "Backup permission denied");
590 return failure(errno, "Failed to spawn pipe");
592 switch ((pid = fork()))
595 return failure(errno, "Failed to fork process");
607 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
608 "--create-backup", "-", NULL);
614 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
616 if (gethostname(hostname, sizeof(hostname) - 1))
617 sprintf(hostname, "OpenWrt");
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);
624 while ((len = read(fds[0], buf, sizeof(buf))) > 0)
625 fwrite(buf, len, 1, stdout);
627 waitpid(pid, NULL, 0);
636 int main(int argc, char **argv)
638 if (strstr(argv[0], "cgi-upload"))
639 return main_upload(argc, argv);
640 else if (strstr(argv[0], "cgi-backup"))
641 return main_backup(argc, argv);