io: whitespace cleanup
[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 }