7760edaf4f853c31327b3b9518ac87e9db35ac93
[feed/packages.git] / net / cgi-io / src / main.c
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 status;
581 int fds[2];
582 char buf[4096];
583 char datestr[16] = { 0 };
584 char hostname[64] = { 0 };
585 char *fields[] = { "sessionid", NULL };
586
587 if (!postdecode(fields, 1) || !session_access(fields[1], "backup", "read"))
588 return failure(0, "Backup permission denied");
589
590 if (pipe(fds))
591 return failure(errno, "Failed to spawn pipe");
592
593 switch ((pid = fork()))
594 {
595 case -1:
596 return failure(errno, "Failed to fork process");
597
598 case 0:
599 dup2(fds[1], 1);
600
601 close(0);
602 close(2);
603 close(fds[0]);
604 close(fds[1]);
605
606 chdir("/");
607
608 execl("/sbin/sysupgrade", "/sbin/sysupgrade",
609 "--create-backup", "-", NULL);
610
611 return -1;
612
613 default:
614 fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);
615 now = time(NULL);
616 strftime(datestr, sizeof(datestr) - 1, "%Y-%m-%d", localtime(&now));
617
618 if (gethostname(hostname, sizeof(hostname) - 1))
619 sprintf(hostname, "OpenWrt");
620
621 printf("Status: 200 OK\r\n");
622 printf("Content-Type: application/x-targz\r\n");
623 printf("Content-Disposition: attachment; "
624 "filename=\"backup-%s-%s.tar.gz\"\r\n\r\n", hostname, datestr);
625
626 do {
627 waitpid(pid, &status, 0);
628
629 while ((len = read(fds[0], buf, sizeof(buf))) > 0) {
630 fwrite(buf, len, 1, stdout);
631 fflush(stdout);
632 }
633
634 } while (!WIFEXITED(status));
635
636 close(fds[0]);
637 close(fds[1]);
638
639 return 0;
640 }
641 }
642
643 int main(int argc, char **argv)
644 {
645 if (strstr(argv[0], "cgi-upload"))
646 return main_upload(argc, argv);
647 else if (strstr(argv[0], "cgi-backup"))
648 return main_backup(argc, argv);
649
650 return -1;
651 }