lede-packages-rs

git clone git://archive.git.mtrnord.blog/MTRNord/lede-packages-rs.git
Log | Files | Refs | README | LICENSE

main.c (11453B)


      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 #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", "cgi-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) - '0') : \
    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], "cgi-upload"))
    639 		return main_upload(argc, argv);
    640 	else if (strstr(argv[0], "cgi-backup"))
    641 		return main_backup(argc, argv);
    642 
    643 	return -1;
    644 }