57c17b803faff8d36f9b369d6dd5a8fe90bb4c0d
[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         rc = snprintf(target, PATH_MAX, "%s/%s/%s", root, desc->id, desc->ver);
402         if (rc < PATH_MAX)
403                 rc = 0;
404         else {
405                 ERROR("path too long");
406                 errno = EINVAL;
407                 rc = -1;
408         }
409         return rc;
410 }
411
412 static int move_widget_to(const char *destdir, int force)
413 {
414         return move_workdir(destdir, 1, force);
415 }
416
417 static int install_icon(const struct wgt_desc *desc)
418 {
419         char link[PATH_MAX];
420         char target[PATH_MAX];
421         int rc;
422
423         if (!desc->icons)
424                 return 0;
425
426         create_directory(FWK_ICON_DIR, 0755, 1);
427         rc = snprintf(link, sizeof link, "%s/%s", FWK_ICON_DIR, desc->idaver);
428         if (rc >= (int)sizeof link) {
429                 ERROR("link too long in install_icon");
430                 errno = EINVAL;
431                 return -1;
432         }
433
434         rc = snprintf(target, sizeof target, "%s/%s", workdir, desc->icons->src);
435         if (rc >= (int)sizeof target) {
436                 ERROR("target too long in install_icon");
437                 errno = EINVAL;
438                 return -1;
439         }
440
441         unlink(link);
442         rc = symlink(target, link);
443         if (rc)
444                 ERROR("can't create link %s -> %s", link, target);
445         return rc;
446 }
447
448 static int install_exec_flag(const struct wgt_desc *desc)
449 {
450         return for_all_content(desc, set_exec_flag);
451 }
452
453 static int install_file_properties(const struct wgt_desc *desc)
454 {
455         int rc, rc2;
456         struct wgt_desc_feature *feat;
457         struct wgt_desc_param *param;
458
459         rc = 0;
460         feat = desc->features;
461         while (feat) {
462                 if (!strcmp(feat->name, FWK_PREFIX"widget:file-properties")) {
463                         param = feat->params;
464                         while (param) {
465                                 if (!strcmp(param->value, "executable")) {
466                                         rc2 = fchmodat(workdirfd, param->name, 0755, 0);
467                                         if (rc2 < 0)
468                                                 ERROR("can't make executable the file %s: %m", param->name);
469                                 } else {
470                                         ERROR("unknown file property %s for %s", param->value, param->name);
471                                         errno = EINVAL;
472                                         rc2 = -1;
473                                 }
474                                 if (rc2 < 0 && !rc)
475                                         rc = rc2;
476                                 param = param->next;
477                         }
478                 }
479                 feat = feat->next;
480         }
481         return rc;
482 }
483
484 static int install_security(const struct wgt_desc *desc)
485 {
486         char path[PATH_MAX], *head;
487         const char *icon, *perm;
488         int rc;
489         unsigned int i, n, len, lic, lf;
490         struct filedesc *f;
491
492         rc = secmgr_init(desc->id);
493         if (rc)
494                 goto error;
495
496         rc = secmgr_path_public_read_only(workdir);
497         if (rc)
498                 goto error2;
499
500         /* instal the files */
501         head = stpcpy(path, workdir);
502         assert(head < path + sizeof path);
503         len = (unsigned)((path + sizeof path) - head);
504         if (!len) {
505                 ERROR("root path too long in install_security");
506                 errno = ENAMETOOLONG;
507                 goto error2;
508         }
509         len--;
510         *head++ = '/';
511         icon = desc->icons ? desc->icons->src : NULL;
512         lic = (unsigned)(icon ? strlen(icon) : 0);
513         n = file_count();
514         i = 0;
515         while(i < n) {
516                 f = file_of_index(i++);
517                 lf = (unsigned)strlen(f->name);
518                 if (lf >= len) {
519                         ERROR("path too long in install_security");
520                         errno = ENAMETOOLONG;
521                         goto error2;
522                 }
523                 strcpy(head, f->name);
524                 if (lf <= lic && icon && !memcmp(f->name, icon, lf) && (!f->name[lf] || f->name[lf] == '/'))
525                         rc = secmgr_path_public_read_only(path);
526                 else
527                         rc = secmgr_path_read_only(path);
528                 if (rc)
529                         goto error2;
530         }
531
532         /* install the permissions */
533         perm = first_usable_permission();
534         while(perm) {
535                 rc = secmgr_permit(perm);
536                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
537                 if (rc)
538                         goto error2;
539                 perm = next_usable_permission();
540         }
541
542         /* install default permissions */
543         n = (unsigned int)(sizeof default_permissions / sizeof *default_permissions);
544         for (i = 0 ; i < n ; i++) {
545                 perm = default_permissions[i];
546                 rc = secmgr_permit(perm);
547                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
548                 if (rc)
549                         goto error2;
550         }
551
552         rc = secmgr_install();
553         return rc;
554 error2:
555         secmgr_cancel();
556 error:
557         return -1;
558 }
559
560 /* install the widget of the file */
561 struct wgt_info *install_widget(const char *wgtfile, const char *root, int force)
562 {
563         struct wgt_info *ifo;
564         const struct wgt_desc *desc;
565         char installdir[PATH_MAX];
566         int err;
567         struct unitconf uconf;
568
569         NOTICE("-- INSTALLING widget %s to %s --", wgtfile, root);
570
571         /* workdir */
572         create_directory(root, 0755, 1);
573         if (make_workdir(root, "TMP", 0)) {
574                 ERROR("failed to create a working directory");
575                 goto error1;
576         }
577
578         if (zread(wgtfile, 0))
579                 goto error2;
580
581         if (check_all_signatures(DEFAULT_ALLOW_NO_SIGNATURE))
582                 goto error2;
583
584         ifo = wgt_info_createat(workdirfd, NULL, 1, 1, 1);
585         if (!ifo)
586                 goto error2;
587
588         reset_requested_permissions();
589         desc = wgt_info_desc(ifo);
590         if (check_widget(desc))
591                 goto error3;
592
593         if (get_target_directory(installdir, root, desc))
594                 goto error3;
595
596         if (access(installdir, F_OK) == 0) {
597                 if (!force) {
598                         ERROR("widget already installed");
599                         errno = EEXIST;
600                         goto error3;
601                 }
602                 if (uninstall_widget(desc->idaver, root))
603                         goto error3;
604         }
605
606         if (move_widget_to(installdir, force))
607                 goto error3;
608
609         if (install_icon(desc))
610                 goto error3;
611
612         if (install_security(desc))
613                 goto error4;
614
615         if (install_exec_flag(desc))
616                 goto error4;
617
618         if (install_file_properties(desc))
619                 goto error4;
620
621         uconf.installdir = installdir;
622         uconf.icondir = FWK_ICON_DIR;
623         uconf.new_afid = get_new_afid;
624         uconf.base_http_ports = HTTP_PORT_BASE;
625         if (unit_install(ifo, &uconf))
626                 goto error4;
627
628         file_reset();
629         return ifo;
630
631 error4:
632         /* TODO: cleanup */
633
634 error3:
635         wgt_info_unref(ifo);
636
637 error2:
638         err = errno;
639         remove_workdir();
640         errno = err;
641
642 error1:
643         file_reset();
644         return NULL;
645 }
646