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
);