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