Fix labelling of files of widgets
[src/app-framework-main.git] / src / wgtpkg-install.c
1 /*
2  Copyright (C) 2015-2020 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19 #define _GNU_SOURCE
20
21 #include <limits.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <assert.h>
26 #include <unistd.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <stdint.h>
30 #include <fcntl.h>
31 #include <sys/stat.h>
32
33 #include "verbose.h"
34 #include "wgt.h"
35 #include "wgt-info.h"
36 #include "wgt-strings.h"
37 #include "wgtpkg-files.h"
38 #include "wgtpkg-workdir.h"
39 #include "wgtpkg-zip.h"
40 #include "wgtpkg-permissions.h"
41 #include "wgtpkg-digsig.h"
42 #include "wgtpkg-install.h"
43 #include "wgtpkg-uninstall.h"
44 #include "secmgr-wrap.h"
45 #include "utils-dir.h"
46 #include "wgtpkg-unit.h"
47 #include "utils-systemd.h"
48 #include "utils-file.h"
49
50 static const char* exec_type_strings[] = {
51         "application/x-executable",
52         "application/vnd.agl.native"
53 };
54
55 static const char key_afm_prefix[] = "X-AFM-";
56 static const char key_afid[] = "ID";
57
58 #define HTTP_PORT_BASE          30000
59
60 #define AFID_MIN                1
61 #define AFID_MAX                1999
62 #define AFID_IS_VALID(afid)     (AFID_MIN <= (afid) && (afid) <= AFID_MAX)
63 #define AFID_COUNT              (AFID_MAX - AFID_MIN + 1)
64 #define AFID_ACNT               ((AFID_COUNT + 31) >> 5)
65 #define AFID_ASFT(afid)         (((afid) - AFID_MIN) & 31)
66 #define AFID_AIDX(afid)         (((afid) - AFID_MIN) >> 5)
67 #define AFID_TEST(array,afid)   ((((array)[AFID_AIDX(afid)]) >> AFID_ASFT(afid)) & 1)
68 #define AFID_SET(array,afid)    (((array)[AFID_AIDX(afid)]) |= (((uint32_t)1) << AFID_ASFT(afid)))
69
70 static uint32_t *afids_array = NULL;
71
72 static const char *default_permissions[] = {
73         "urn:AGL:token:valid"
74 };
75
76 /*
77  * normalize unit files: remove comments, remove heading blanks,
78  * make single lines
79  */
80 static void normalize_unit_file(char *content)
81 {
82         char *read, *write, c;
83
84         read = write = content;
85         c = *read++;
86         while (c) {
87                 switch (c) {
88                 case '\n':
89                 case ' ':
90                 case '\t':
91                         c = *read++;
92                         break;
93                 case '#':
94                 case ';':
95                         do { c = *read++; } while(c && c != '\n');
96                         break;
97                 default:
98                         *write++ = c;
99                         do { *write++ = c = *read++; } while(c && c != '\n');
100                         if (write - content >= 2 && write[-2] == '\\')
101                                 (--write)[-1] = ' ';
102                         break;
103                 }
104         }
105         *write = c;
106 }
107
108 static int get_afid_cb(void *closure, const char *name, const char *path, int isuser)
109 {
110         char *iter;
111         char *content;
112         size_t length;
113         int rc, p;
114
115         /* reads the file */
116         rc = getfile(path, &content, &length);
117         if (rc < 0)
118                 return rc;
119
120         /* normalize the unit file */
121         normalize_unit_file(content);
122
123         /* process the file */
124         iter = strstr(content, key_afm_prefix);
125         while (iter) {
126                 iter += sizeof key_afm_prefix - 1;
127                 if (*iter == '-')
128                         iter++;
129                 if (!strncmp(iter, key_afid, sizeof key_afid - 1)) {
130                         iter += sizeof key_afid - 1;
131                         while(*iter && *iter != '=' && *iter != '\n')
132                                 iter++;
133                         if (*iter == '=') {
134                                 while(*++iter == ' ');
135                                 p = atoi(iter);
136                                 if (AFID_IS_VALID(p))
137                                         AFID_SET((uint32_t*)closure, p);
138                         }
139                 }
140                 iter = strstr(iter, key_afm_prefix);
141         }
142         free(content);
143         return 0;
144 }
145
146 static int update_afids(uint32_t *afids)
147 {
148         int rc;
149
150         memset(afids, 0, AFID_ACNT * sizeof(uint32_t));
151         rc = systemd_unit_list(0, get_afid_cb, afids);
152         if (rc >= 0)
153                 rc = systemd_unit_list(1, get_afid_cb, afids);
154         if (rc < 0)
155                 ERROR("troubles while updating afids");
156         return rc;
157 }
158
159 static int first_free_afid(uint32_t *afids)
160 {
161         int afid;
162
163         afid = AFID_MIN;
164         while (afid <= AFID_MAX && !~afids[AFID_AIDX(afid)])
165                 afid += 32;
166         while (afid <= AFID_MAX && AFID_TEST(afids, afid))
167                 afid++;
168         if (afid > AFID_MAX) {
169                 ERROR("Can't compute a valid afid");
170                 errno = EADDRNOTAVAIL;
171                 afid = -1;
172         }
173         return afid;
174 }
175
176 static int get_new_afid()
177 {
178         int afid;
179
180         /* ensure existing afid bitmap */
181         if (afids_array == NULL) {
182                 afids_array = malloc(AFID_ACNT * sizeof(uint32_t));
183                 if (afids_array == NULL || update_afids(afids_array) < 0)
184                         return -1;
185         }
186
187         /* allocates the afid */
188         afid = first_free_afid(afids_array);
189         if (afid < 0 && errno == EADDRNOTAVAIL) {
190                 /* no more ids, try to rescan */
191                 memset(afids_array, 0, AFID_ACNT * sizeof(uint32_t));
192                 if (update_afids(afids_array) >= 0)
193                         afid = first_free_afid(afids_array);
194         }
195         if (afid >= 0)
196                 AFID_SET(afids_array, afid);
197
198         return afid;
199 }
200
201 static int check_defined(const void *data, const char *name)
202 {
203         if (data)
204                 return 0;
205         ERROR("widget has no defined '%s' (temporary constraints)", name);
206         errno = EINVAL;
207         return -1;
208 }
209
210 static int check_valid_string(const char *value, const char *name)
211 {
212         int pos;
213         char c;
214
215         if (check_defined(value, name))
216                 return -1;
217         pos = 0;
218         c = value[pos];
219         if (c == 0) {
220                 ERROR("empty string forbidden in '%s' (temporary constraints)", name);
221                 errno = EINVAL;
222                 return -1;
223         }
224         do {
225                 if (!isalnum(c) && !strchr(".-_", c)) {
226                         ERROR("forbidden char %c in '%s' -> '%s' (temporary constraints)", c, name, value);
227                         errno = EINVAL;
228                         return -1;
229                 }
230                 c = value[++pos];
231         } while(c);
232         return 0;
233 }
234
235 static int check_temporary_constraints(const struct wgt_desc *desc)
236 {
237         int result;
238
239         result  = check_valid_string(desc->id, "id");
240         result |= check_valid_string(desc->version, "version");
241         result |= check_valid_string(desc->ver, "ver");
242         result |= check_defined(desc->content_src, "content");
243         if (desc->icons)
244                 result |= check_defined(desc->icons->src, "icon.src");
245         if (result)
246                 return result;
247
248         if (desc->icons && desc->icons->next) {
249                 ERROR("widget has more than one icon defined (temporary constraints)");
250                 errno = EINVAL;
251                 result = -1;
252         }
253         return 0;
254 }
255
256 static int set_required_permissions(struct wgt_desc_param *params, int required)
257 {
258         int optional;
259
260         while (params) {
261                 /* check if target */
262                 if (!strcmp(params->name, string_sharp_target)) {
263                         /* do nothing when #target */
264                 } else {
265                         /* check the value */
266                         if (!strcmp(params->value, string_required))
267                                 optional = !required;
268                         else if (!strcmp(params->value, string_optional))
269                                 optional = 1;
270                         else {
271                                 ERROR("unexpected parameter value: %s found for %s", params->value, params->name);
272                                 errno = EPERM;
273                                 return -1;
274                         }
275                         /* set the permission */
276                         if (request_permission(params->name)) {
277                                 DEBUG("granted permission: %s", params->name);
278                         } else if (optional) {
279                                 INFO("optional permission ungranted: %s", params->name);
280                         } else {
281                                 ERROR("ungranted permission required: %s", params->name);
282                                 errno = EPERM;
283                                 return -1;
284                         }
285                 }
286                 params = params->next;
287         }
288         return 0;
289 }
290
291 static int check_permissions(const struct wgt_desc *desc)
292 {
293         int result;
294         const struct wgt_desc_feature *feature;
295
296         result = 0;
297         feature = desc->features;
298         while(result >= 0 && feature) {
299                 if (!strcmp(feature->name, feature_required_permission))
300                         result = set_required_permissions(feature->params, feature->required);
301                 feature = feature->next;
302         }
303         return result;
304 }
305
306 static int for_all_content(const struct wgt_desc *desc, int (*action)(const char *src, const char *type))
307 {
308         int rc, rc2;
309         struct wgt_desc_feature *feat;
310         const char *src, *type;
311
312         rc = action(desc->content_src, desc->content_type);
313         feat = desc->features;
314         while (feat) {
315                 if (!strcmp(feat->name, FWK_PREFIX"widget:provided-unit")) {
316                         src = wgt_info_param(feat, "content.src");
317                         type = wgt_info_param(feat, "content.type");
318                         rc2 = action(src, type);
319                         if (rc >= 0 && rc2 < 0)
320                                 rc = rc2;
321                 }
322                 feat = feat->next;
323         }
324         return rc;
325 }
326
327 static int set_exec_flag(const char *src, const char *type)
328 {
329         int i, rc;
330
331         if (src && type) {
332                 i = sizeof exec_type_strings / sizeof *exec_type_strings;
333                 while (i) {
334                         if (!strcasecmp(type, exec_type_strings[--i])) {
335                                 rc = fchmodat(workdirfd, src, 0755, 0);
336                                 if (rc < 0)
337                                         ERROR("can't make executable the file %s", src);
338                                 return rc;
339                         }
340                 }
341         }
342         return 0;
343 }
344
345 static int check_one_content(const char *src, const char *type)
346 {
347         int rc;
348         struct stat s;
349         int fhtdocs, serr;
350
351         if (!src) {
352                 ERROR("a content src is missing");
353                 errno = EINVAL;
354                 rc = -1;
355         } else {
356                 /* TODO: when dealing with HTML and languages, the check should
357                  * include i18n path search of widgets */
358                 rc = fstatat(workdirfd, src, &s, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
359                 if (rc < 0) {
360                         serr = errno;
361                         fhtdocs = openat(workdirfd, "htdocs", O_DIRECTORY|O_PATH);
362                         if (fhtdocs >= 0) {
363                                 rc = fstatat(fhtdocs, src, &s, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
364                                 serr = errno;
365                                 close(fhtdocs);
366                         }
367                         errno = serr;
368                 }
369                 if (rc < 0)
370                         ERROR("can't get info on content %s: %m", src);
371                 else if (!S_ISREG(s.st_mode)) {
372                         ERROR("content %s isn't a regular file", src);
373                         errno = EINVAL;
374                         rc = -1;
375                 }
376         }
377         return rc;
378 }
379
380 static int check_content(const struct wgt_desc *desc)
381 {
382         return for_all_content(desc, check_one_content);
383 }
384
385 static int check_widget(const struct wgt_desc *desc)
386 {
387         int result;
388
389         result = check_temporary_constraints(desc);
390         if (result >= 0)
391                 result = check_permissions(desc);
392         if (result >= 0)
393                 result = check_content(desc);
394         return result;
395 }
396
397 static int get_target_directory(char target[PATH_MAX], const char *root, const struct wgt_desc *desc)
398 {
399         int rc;
400
401 #if DISTINCT_VERSIONS
402         rc = snprintf(target, PATH_MAX, "%s/%s/%s", root, desc->id, desc->ver);
403 #else
404         rc = snprintf(target, PATH_MAX, "%s/%s", root, desc->id);
405 #endif
406         if (rc < PATH_MAX)
407                 rc = 0;
408         else {
409                 ERROR("path too long");
410                 errno = EINVAL;
411                 rc = -1;
412         }
413         return rc;
414 }
415
416 static int move_widget_to(const char *destdir, int force)
417 {
418         return move_workdir(destdir, 1, force);
419 }
420
421 static int install_icon(const struct wgt_desc *desc)
422 {
423         char link[PATH_MAX];
424         char target[PATH_MAX];
425         int rc;
426
427         if (!desc->icons)
428                 return 0;
429
430         create_directory(FWK_ICON_DIR, 0755, 1);
431         rc = snprintf(link, sizeof link, "%s/%s", FWK_ICON_DIR, desc->idaver);
432         if (rc >= (int)sizeof link) {
433                 ERROR("link too long in install_icon");
434                 errno = EINVAL;
435                 return -1;
436         }
437
438         rc = snprintf(target, sizeof target, "%s/%s", workdir, desc->icons->src);
439         if (rc >= (int)sizeof target) {
440                 ERROR("target too long in install_icon");
441                 errno = EINVAL;
442                 return -1;
443         }
444
445         unlink(link);
446         rc = symlink(target, link);
447         if (rc)
448                 ERROR("can't create link %s -> %s", link, target);
449         return rc;
450 }
451
452 static int install_exec_flag(const struct wgt_desc *desc)
453 {
454         return for_all_content(desc, set_exec_flag);
455 }
456
457 static int install_file_properties(const struct wgt_desc *desc)
458 {
459         int rc, rc2;
460         struct wgt_desc_feature *feat;
461         struct wgt_desc_param *param;
462
463         rc = 0;
464         feat = desc->features;
465         while (feat) {
466                 if (!strcmp(feat->name, FWK_PREFIX"widget:file-properties")) {
467                         param = feat->params;
468                         while (param) {
469                                 if (!strcmp(param->value, "executable")) {
470                                         rc2 = fchmodat(workdirfd, param->name, 0755, 0);
471                                         if (rc2 < 0)
472                                                 ERROR("can't make executable the file %s: %m", param->name);
473                                 } else {
474                                         ERROR("unknown file property %s for %s", param->value, param->name);
475                                         errno = EINVAL;
476                                         rc2 = -1;
477                                 }
478                                 if (rc2 < 0 && !rc)
479                                         rc = rc2;
480                                 param = param->next;
481                         }
482                 }
483                 feat = feat->next;
484         }
485         return rc;
486 }
487
488 static int is_path_public(const char *path, const struct wgt_desc *desc)
489 {
490         const struct wgt_desc_icon *icon;
491         const struct wgt_desc_feature *feat;
492         const struct wgt_desc_param *param;
493         size_t len;
494
495         /* icons are public */
496         icon = desc->icons;
497         while (icon != NULL) {
498                 len = strlen(icon->src);
499                 if (!memcmp(path, icon->src, len) && (path[len] == 0 || path[len] == '/'))
500                         return 1;
501                 icon = icon->next;
502         }
503
504         /* provided bindings are public */
505         feat = desc->features;
506         while (feat != NULL) {
507                 if (strcasecmp(feat->name, "urn:AGL:widget:provided-binding") == 0
508                  || strcasecmp(feat->name, "urn:AGL:widget:public-files") == 0) {
509                         param = feat->params;
510                         while(param != NULL) {
511                                 if (strcmp(param->value, path) == 0)
512                                         return 1;
513                                 param = param->next;
514                         }
515                 }
516                 feat = feat->next;
517         }
518
519         /* otherwise no */
520         return 0;
521 }
522
523 static int install_security(const struct wgt_desc *desc)
524 {
525         char path[PATH_MAX], *head;
526         const char *perm;
527         int rc, public;
528         unsigned int i, n, len, lf, j;
529         struct filedesc *f;
530         struct pathent {
531                 struct pathent *next;
532                 unsigned int len;
533                 int public;
534                 char name[];
535         } *pe0, *pe2, *ppe;
536
537         pe0 = NULL;
538         rc = secmgr_init(desc->id);
539         if (rc)
540                 goto error;
541
542         /* instal the files */
543         head = stpcpy(path, workdir);
544         assert(head < path + sizeof path);
545         len = (unsigned)((path + sizeof path) - head);
546         if (!len) {
547                 ERROR("root path too long in install_security");
548                 errno = ENAMETOOLONG;
549                 goto error2;
550         }
551         len--;
552         *head++ = '/';
553
554         /* build root entry */
555         pe0 = malloc(1 + sizeof *pe0);
556         if (pe0 == NULL)
557                 goto error2;
558         pe0->next = NULL;
559         pe0->len = 0;
560         pe0->public = 0;
561         pe0->name[0] = 0;
562
563         /* build list of entries */
564         n = file_count();
565         for (i = 0 ; i < n ; i++) {
566                 f = file_of_index(i);
567                 public = is_path_public(f->name, desc);
568                 pe0->public |= public;
569                 lf = j = 0;
570                 while(f->name[j] == '/')
571                         j++;
572                 while (f->name[j] != 0) {
573                         /* copy next entry of the path */
574                         while(f->name[j] && f->name[j] != '/') {
575                                 if (lf + 1 >= len) {
576                                         ERROR("path too long in install_security");
577                                         errno = ENAMETOOLONG;
578                                         goto error2;
579                                 }
580                                 head[lf++] = f->name[j++];
581                         }
582                         head[lf] = 0;
583
584                         /* search if it already exists */
585                         ppe = pe0;
586                         pe2 = pe0->next;
587                         while (pe2 != NULL && pe2->len < lf) {
588                                 ppe = pe2;
589                                 pe2 = pe2->next;
590                         }
591                         while (pe2 != NULL && pe2->len == lf && strcmp(head, pe2->name)) {
592                                 ppe = pe2;
593                                 pe2 = pe2->next;
594                         }
595
596                         if (pe2 != NULL && pe2->len == lf)
597                                 /* existing, update public status */
598                                 pe2->public |= public;
599                         else {
600                                 /* not existing, create it */
601                                 pe2 = malloc(lf + 1 + sizeof *pe2);
602                                 if (pe2 == NULL)
603                                         goto error2;
604                                 pe2->next = ppe->next;
605                                 pe2->len = lf;
606                                 pe2->public = public;
607                                 memcpy(pe2->name, head, 1 + lf);
608                                 ppe->next = pe2;
609                         }
610
611                         /* prepare next path entry */
612                         head[lf++] = '/';       
613                         while(f->name[j] == '/')
614                                 j++;
615                 }
616         }
617
618         /* set the path entries */
619         for (pe2 = pe0 ; pe2 != NULL ; pe2 = pe2->next) {
620                 strcpy(head, pe2->name);
621                 if (pe2->public)
622                         rc = secmgr_path_public_read_only(path);
623                 else
624                         rc = secmgr_path_private(path);
625                 if (rc)
626                         goto error2;
627         }
628
629         /* install the permissions */
630         perm = first_usable_permission();
631         while(perm) {
632                 rc = secmgr_permit(perm);
633                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
634                 if (rc)
635                         goto error2;
636                 perm = next_usable_permission();
637         }
638
639         /* install default permissions */
640         n = (unsigned int)(sizeof default_permissions / sizeof *default_permissions);
641         for (i = 0 ; i < n ; i++) {
642                 perm = default_permissions[i];
643                 rc = secmgr_permit(perm);
644                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
645                 if (rc)
646                         goto error2;
647         }
648
649         rc = secmgr_install();
650         goto end;
651 error2:
652         secmgr_cancel();
653 error:
654         rc = -1;
655 end:
656         /* free memory of path entries */
657         while (pe0 != NULL) {
658                 ppe = pe0;
659                 pe0 = pe0->next;
660                 free(ppe);
661         }
662         return rc;
663 }
664
665 /* install the widget of the file */
666 struct wgt_info *install_widget(const char *wgtfile, const char *root, int force)
667 {
668         struct wgt_info *ifo;
669         const struct wgt_desc *desc;
670         char installdir[PATH_MAX];
671         int err, rc;
672         struct unitconf uconf;
673
674         NOTICE("-- INSTALLING widget %s to %s --", wgtfile, root);
675
676         /* workdir */
677         create_directory(root, 0755, 1);
678         if (make_workdir(root, "TMP", 0)) {
679                 ERROR("failed to create a working directory");
680                 goto error1;
681         }
682
683         if (zread(wgtfile, 0))
684                 goto error2;
685
686 #if defined(ALLOW_NO_SIGNATURE)
687         rc = check_all_signatures(1);
688 #else
689         rc = check_all_signatures(0);
690 #endif
691         if (rc)
692                 goto error2;
693
694         ifo = wgt_info_createat(workdirfd, NULL, 1, 1, 1);
695         if (!ifo)
696                 goto error2;
697
698         reset_requested_permissions();
699         desc = wgt_info_desc(ifo);
700         if (check_widget(desc))
701                 goto error3;
702
703         if (get_target_directory(installdir, root, desc))
704                 goto error3;
705
706         if (access(installdir, F_OK) == 0) {
707                 if (!force) {
708                         ERROR("widget already installed");
709                         errno = EEXIST;
710                         goto error3;
711                 }
712                 if (uninstall_widget(desc->idaver, root))
713                         goto error3;
714         }
715
716         if (move_widget_to(installdir, force))
717                 goto error3;
718
719         if (install_icon(desc))
720                 goto error3;
721
722         if (install_security(desc))
723                 goto error4;
724
725         if (install_exec_flag(desc))
726                 goto error4;
727
728         if (install_file_properties(desc))
729                 goto error4;
730
731         uconf.installdir = installdir;
732         uconf.icondir = FWK_ICON_DIR;
733         uconf.new_afid = get_new_afid;
734         uconf.base_http_ports = HTTP_PORT_BASE;
735         if (unit_install(ifo, &uconf))
736                 goto error4;
737
738         file_reset();
739         return ifo;
740
741 error4:
742         /* TODO: cleanup */
743
744 error3:
745         wgt_info_unref(ifo);
746
747 error2:
748         err = errno;
749         remove_workdir();
750         errno = err;
751
752 error1:
753         file_reset();
754         return NULL;
755 }
756