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