2 * luci-io - LuCI non-RPC helper
4 * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
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.
20 #define _XOPEN_SOURCE 700
34 #include <libubox/blobmsg.h>
41 #include "multipart_parser.h"
52 const char *parts[] = {
62 bool is_content_disposition;
77 static const struct blobmsg_policy ses_policy[__SES_MAX] = {
78 [SES_ACCESS] = { .name = "access", .type = BLOBMSG_TYPE_BOOL },
82 static struct state st;
85 session_access_cb(struct ubus_request *req, int type, struct blob_attr *msg)
87 struct blob_attr *tb[__SES_MAX];
88 bool *allow = (bool *)req->priv;
93 blobmsg_parse(ses_policy, __SES_MAX, tb, blob_data(msg), blob_len(msg));
96 *allow = blobmsg_get_bool(tb[SES_ACCESS]);
100 session_access(const char *sid, const char *obj, const char *func)
104 struct ubus_context *ctx;
105 static struct blob_buf req;
107 ctx = ubus_connect(NULL);
109 if (!ctx || ubus_lookup_id(ctx, "session", &id))
112 blob_buf_init(&req, 0);
113 blobmsg_add_string(&req, "sid", sid);
114 blobmsg_add_string(&req, "scope", "luci-io");
115 blobmsg_add_string(&req, "object", obj);
116 blobmsg_add_string(&req, "function", func);
118 ubus_invoke(ctx, id, "access", req.head, session_access_cb, &allow, 500);
128 md5sum(const char *file)
137 switch ((pid = fork()))
152 if (execl("/bin/busybox", "/bin/busybox", "md5sum", file, NULL));
158 memset(md5, 0, sizeof(md5));
159 read(fds[0], md5, 32);
160 waitpid(pid, NULL, 0);
169 datadup(const void *in, size_t len)
171 char *out = malloc(len + 1);
176 memcpy(out, in, len);
192 (((x) <= '9') ? ((x) - '0') : \
193 (((x) <= 'F') ? ((x) - 'A' + 10) : \
196 for (c = p = buf; *p; c++)
200 if (!isxdigit(*(p + 1)) || !isxdigit(*(p + 2)))
203 *c = (char)(16 * hex(*(p + 1)) + hex(*(p + 2)));
224 postdecode(char **fields, int n_fields)
228 static char buf[1024];
229 int i, len, field, found = 0;
231 var = getenv("CONTENT_TYPE");
233 if (!var || strncmp(var, "application/x-www-form-urlencoded", 33))
236 memset(buf, 0, sizeof(buf));
238 if ((len = read(0, buf, sizeof(buf) - 1)) > 0)
240 for (p = buf, i = 0; i <= len; i++)
246 for (field = 0; field < (n_fields * 2); field += 2)
248 if (!strcmp(p, fields[field]))
250 fields[field + 1] = buf + i + 1;
255 else if (buf[i] == '&' || buf[i] == '\0')
259 if (found >= n_fields)
267 for (field = 0; field < (n_fields * 2); field += 2)
268 if (!urldecode(fields[field + 1]))
271 return (found >= n_fields);
275 response(bool success, const char *message)
280 printf("Status: 200 OK\r\n");
281 printf("Content-Type: application/json\r\n\r\n{\n");
285 if (!stat(st.filename, &s) && (md5 = md5sum(st.filename)) != NULL)
286 printf("\t\"size\": %u,\n\t\"checksum\": \"%s\"\n",
287 (unsigned int)s.st_size, md5);
292 printf("\t\"message\": \"%s\",\n", message);
294 printf("\t\"failure\": [ %u, \"%s\" ]\n", errno, strerror(errno));
306 failure(int e, const char *message)
308 printf("Status: 500 Internal Server failure\r\n");
309 printf("Content-Type: text/plain\r\n\r\n");
313 printf(": %s", strerror(e));
328 return response(false, "No file data received");
331 if (lseek(st.tempfd, 0, SEEK_SET) < 0)
334 return response(false, "Failed to rewind temp file");
337 st.filefd = open(st.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
342 return response(false, "Failed to open target file");
345 while ((len = read(st.tempfd, buf, sizeof(buf))) > 0)
347 if (write(st.filefd, buf, len) != len)
351 return response(false, "I/O failure while writing target file");
358 if (chmod(st.filename, st.filemode))
359 return response(false, "Failed to chmod target file");
365 header_field(multipart_parser *p, const char *data, size_t len)
367 st.is_content_disposition = !strncasecmp(data, "Content-Disposition", len);
372 header_value(multipart_parser *p, const char *data, size_t len)
376 if (!st.is_content_disposition)
379 if (len < 10 || strncasecmp(data, "form-data", 9))
382 for (data += 9, len -= 9; *data == ' ' || *data == ';'; data++, len--);
384 if (len < 8 || strncasecmp(data, "name=\"", 6))
387 for (data += 6, len -= 6, i = 0; i <= len; i++)
389 if (*(data + i) != '"')
392 for (j = 1; j < sizeof(parts) / sizeof(parts[0]); j++)
393 if (!strncmp(data, parts[j], i))
403 data_begin_cb(multipart_parser *p)
405 char tmpname[24] = "/tmp/luci-upload.XXXXXX";
407 if (st.parttype == PART_FILEDATA)
410 return response(false, "File data without session");
413 return response(false, "File data without name");
415 st.tempfd = mkstemp(tmpname);
418 return response(false, "Failed to create temporary file");
427 data_cb(multipart_parser *p, const char *data, size_t len)
432 st.sessionid = datadup(data, len);
436 st.filename = datadup(data, len);
440 st.filemode = strtoul(data, NULL, 8);
444 if (write(st.tempfd, data, len) != len)
447 return response(false, "I/O failure while writing temporary file");
463 data_end_cb(multipart_parser *p)
465 if (st.parttype == PART_SESSIONID)
467 if (!session_access(st.sessionid, "upload", "write"))
470 return response(false, "Upload permission denied");
473 else if (st.parttype == PART_FILEDATA)
476 return response(false, "Internal program failure");
479 /* prepare directory */
480 for (ptr = st.filename; *ptr; ptr++)
486 if (mkdir(st.filename, 0755))
489 return response(false, "Failed to create destination directory");
500 return response(true, NULL);
503 st.parttype = PART_UNKNOWN;
507 static multipart_parser *
514 multipart_parser_settings s = {
515 .on_part_data = data_cb,
516 .on_headers_complete = data_begin_cb,
517 .on_part_data_end = data_end_cb,
518 .on_header_field = header_field,
519 .on_header_value = header_value
522 var = getenv("CONTENT_TYPE");
524 if (!var || strncmp(var, "multipart/form-data;", 20))
527 for (var += 20; *var && *var != '='; var++);
532 boundary = malloc(strlen(var) + 3);
537 strcpy(boundary, "--");
538 strcpy(boundary + 2, var);
544 p = multipart_parser_init(boundary, &s);
552 main_upload(int argc, char *argv[])
563 return response(false, "Invalid request");
566 while ((len = read(0, buf, sizeof(buf))) > 0)
568 rem = multipart_parser_execute(p, buf, len);
574 multipart_parser_free(p);
576 /* read remaining post data */
577 while ((len = read(0, buf, sizeof(buf))) > 0);
583 main_backup(int argc, char **argv)
590 char datestr[16] = { 0 };
591 char hostname[64] = { 0 };
592 char *fields[] = { "sessionid", NULL };
594 if (!postdecode(fields, 1) || !session_access(fields[1], "backup", "read"))
595 return failure(0, "Backup permission denied");
598 return failure(errno, "Failed to spawn pipe");
600 switch ((pid = fork()))
603 return failure(errno, "Failed to fork process");
615 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
616 "--create-backup", "-", NULL);
622 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
624 if (gethostname(hostname, sizeof(hostname) - 1))
625 sprintf(hostname, "OpenWrt");
627 printf("Status: 200 OK\r\n");
628 printf("Content-Type: application/x-targz\r\n");
629 printf("Content-Disposition: attachment; "
630 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
632 while ((len = read(fds[0], buf, sizeof(buf))) > 0)
633 fwrite(buf, len, 1, stdout);
635 waitpid(pid, NULL, 0);
645 main_login(int argc, char **argv)
647 char *hash, *fields[] = { "username", NULL, "password", NULL };
648 const char *sid = NULL;
650 if (postdecode(fields, 2))
653 struct spwd *sp = getspnam(fields[1]);
658 /* check whether a password is set */
659 if (sp->sp_pwdp && *sp->sp_pwdp &&
660 strcmp(sp->sp_pwdp, "!") && strcmp(sp->sp_pwdp, "x"))
662 hash = crypt(fields[3], sp->sp_pwdp);
664 if (strcmp(hash, sp->sp_pwdp))
668 struct passwd *pw = getpwnam(fields[1]);
673 /* check whether a password is set */
674 if (pw->pw_passwd && *pw->pw_passwd &&
675 strcmp(pw->pw_passwd, "!") && strcmp(pw->pw_passwd, "x"))
677 hash = crypt(fields[3], pw->pw_passwd);
679 if (strcmp(hash, pw->pw_passwd))
684 sid = setup_session(fields[1]);
689 printf("Status: 200 OK\r\n");
690 printf("Content-Type: application/json\r\n\r\n{\n");
691 printf("\t\"sessionid\": \"%s\"\n}\n", sid);
696 printf("Status: 200 OK\r\n");
697 printf("Content-Type: application/json\r\n\r\n{}\n");
701 int main(int argc, char **argv)
703 if (strstr(argv[0], "luci-upload"))
704 return main_upload(argc, argv);
705 else if (strstr(argv[0], "luci-backup"))
706 return main_backup(argc, argv);
707 else if (strstr(argv[0], "luci-login"))
708 return main_login(argc, argv);