Enforce numeric application IDs
[src/app-framework-main.git] / src / wgtpkg-install.c
1 /*
2  Copyright (C) 2015-2019 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)
190                 AFID_SET(afids_array, afid);
191
192         return afid;
193 }
194
195 static int check_defined(const void *data, const char *name)
196 {
197         if (data)
198                 return 0;
199         ERROR("widget has no defined '%s' (temporary constraints)", name);
200         errno = EINVAL;
201         return -1;
202 }
203
204 static int check_valid_string(const char *value, const char *name)
205 {
206         int pos;
207         char c;
208
209         if (check_defined(value, name))
210                 return -1;
211         pos = 0;
212         c = value[pos];
213         if (c == 0) {
214                 ERROR("empty string forbidden in '%s' (temporary constraints)", name);
215                 errno = EINVAL;
216                 return -1;
217         }
218         do {
219                 if (!isalnum(c) && !strchr(".-_", c)) {
220                         ERROR("forbidden char %c in '%s' -> '%s' (temporary constraints)", c, name, value);
221                         errno = EINVAL;
222                         return -1;
223                 }
224                 c = value[++pos];
225         } while(c);
226         return 0;
227 }
228
229 static int check_temporary_constraints(const struct wgt_desc *desc)
230 {
231         int result;
232
233         result  = check_valid_string(desc->id, "id");
234         result |= check_valid_string(desc->version, "version");
235         result |= check_valid_string(desc->ver, "ver");
236         result |= check_defined(desc->content_src, "content");
237         if (desc->icons)
238                 result |= check_defined(desc->icons->src, "icon.src");
239         if (result)
240                 return result;
241
242         if (desc->icons && desc->icons->next) {
243                 ERROR("widget has more than one icon defined (temporary constraints)");
244                 errno = EINVAL;
245                 result = -1;
246         }
247         return 0;
248 }
249
250 static int set_required_permissions(struct wgt_desc_param *params, int required)
251 {
252         int optional;
253
254         while (params) {
255                 /* check if target */
256                 if (!strcmp(params->name, string_sharp_target)) {
257                         /* do nothing when #target */
258                 } else {
259                         /* check the value */
260                         if (!strcmp(params->value, string_required))
261                                 optional = !required;
262                         else if (!strcmp(params->value, string_optional))
263                                 optional = 1;
264                         else {
265                                 ERROR("unexpected parameter value: %s found for %s", params->value, params->name);
266                                 errno = EPERM;
267                                 return -1;
268                         }
269                         /* set the permission */
270                         if (request_permission(params->name)) {
271                                 DEBUG("granted permission: %s", params->name);
272                         } else if (optional) {
273                                 INFO("optional permission ungranted: %s", params->name);
274                         } else {
275                                 ERROR("ungranted permission required: %s", params->name);
276                                 errno = EPERM;
277                                 return -1;
278                         }
279                 }
280                 params = params->next;
281         }
282         return 0;
283 }
284
285 static int check_permissions(const struct wgt_desc *desc)
286 {
287         int result;
288         const struct wgt_desc_feature *feature;
289
290         result = 0;
291         feature = desc->features;
292         while(result >= 0 && feature) {
293                 if (!strcmp(feature->name, feature_required_permission))
294                         result = set_required_permissions(feature->params, feature->required);
295                 feature = feature->next;
296         }
297         return result;
298 }
299
300 static int for_all_content(const struct wgt_desc *desc, int (*action)(const char *src, const char *type))
301 {
302         int rc, rc2;
303         struct wgt_desc_feature *feat;
304         const char *src, *type;
305
306         rc = action(desc->content_src, desc->content_type);
307         feat = desc->features;
308         while (feat) {
309                 if (!strcmp(feat->name, FWK_PREFIX"widget:provided-unit")) {
310                         src = wgt_info_param(feat, "content.src");
311                         type = wgt_info_param(feat, "content.type");
312                         rc2 = action(src, type);
313                         if (rc >= 0 && rc2 < 0)
314                                 rc = rc2;
315                 }
316                 feat = feat->next;
317         }
318         return rc;
319 }
320
321 static int set_exec_flag(const char *src, const char *type)
322 {
323         int i, rc;
324
325         if (src && type) {
326                 i = sizeof exec_type_strings / sizeof *exec_type_strings;
327                 while (i) {
328                         if (!strcasecmp(type, exec_type_strings[--i])) {
329                                 rc = fchmodat(workdirfd, src, 0755, 0);
330                                 if (rc < 0)
331                                         ERROR("can't make executable the file %s", src);
332                                 return rc;
333                         }
334                 }
335         }
336         return 0;
337 }
338
339 static int check_one_content(const char *src, const char *type)
340 {
341         int rc;
342         struct stat s;
343         int fhtdocs, serr;
344
345         if (!src) {
346                 ERROR("a content src is missing");
347                 errno = EINVAL;
348                 rc = -1;
349         } else {
350                 /* TODO: when dealing with HTML and languages, the check should
351                  * include i18n path search of widgets */
352                 rc = fstatat(workdirfd, src, &s, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
353                 if (rc < 0) {
354                         serr = errno;
355                         fhtdocs = openat(workdirfd, "htdocs", O_DIRECTORY|O_PATH);
356                         if (fhtdocs >= 0) {
357                                 rc = fstatat(fhtdocs, src, &s, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
358                                 serr = errno;
359                                 close(fhtdocs);
360                         }
361                         errno = serr;
362                 }
363                 if (rc < 0)
364                         ERROR("can't get info on content %s: %m", src);
365                 else if (!S_ISREG(s.st_mode)) {
366                         ERROR("content %s isn't a regular file", src);
367                         errno = EINVAL;
368                         rc = -1;
369                 }
370         }
371         return rc;
372 }
373
374 static int check_content(const struct wgt_desc *desc)
375 {
376         return for_all_content(desc, check_one_content);
377 }
378
379 static int check_widget(const struct wgt_desc *desc)
380 {
381         int result;
382
383         result = check_temporary_constraints(desc);
384         if (result >= 0)
385                 result = check_permissions(desc);
386         if (result >= 0)
387                 result = check_content(desc);
388         return result;
389 }
390
391 static int get_target_directory(char target[PATH_MAX], const char *root, const struct wgt_desc *desc)
392 {
393         int rc;
394
395         rc = snprintf(target, PATH_MAX, "%s/%s/%s", root, desc->id, desc->ver);
396         if (rc < PATH_MAX)
397                 rc = 0;
398         else {
399                 ERROR("path too long");
400                 errno = EINVAL;
401                 rc = -1;
402         }
403         return rc;
404 }
405
406 static int move_widget_to(const char *destdir, int force)
407 {
408         return move_workdir(destdir, 1, force);
409 }
410
411 static int install_icon(const struct wgt_desc *desc)
412 {
413         char link[PATH_MAX];
414         char target[PATH_MAX];
415         int rc;
416
417         if (!desc->icons)
418                 return 0;
419
420         create_directory(FWK_ICON_DIR, 0755, 1);
421         rc = snprintf(link, sizeof link, "%s/%s", FWK_ICON_DIR, desc->idaver);
422         if (rc >= (int)sizeof link) {
423                 ERROR("link too long in install_icon");
424                 errno = EINVAL;
425                 return -1;
426         }
427
428         rc = snprintf(target, sizeof target, "%s/%s", workdir, desc->icons->src);
429         if (rc >= (int)sizeof target) {
430                 ERROR("target too long in install_icon");
431                 errno = EINVAL;
432                 return -1;
433         }
434
435         unlink(link);
436         rc = symlink(target, link);
437         if (rc)
438                 ERROR("can't create link %s -> %s", link, target);
439         return rc;
440 }
441
442 static int install_exec_flag(const struct wgt_desc *desc)
443 {
444         return for_all_content(desc, set_exec_flag);
445 }
446
447 static int install_file_properties(const struct wgt_desc *desc)
448 {
449         int rc, rc2;
450         struct wgt_desc_feature *feat;
451         struct wgt_desc_param *param;
452
453         rc = 0;
454         feat = desc->features;
455         while (feat) {
456                 if (!strcmp(feat->name, FWK_PREFIX"widget:file-properties")) {
457                         param = feat->params;
458                         while (param) {
459                                 if (!strcmp(param->value, "executable")) {
460                                         rc2 = fchmodat(workdirfd, param->name, 0755, 0);
461                                         if (rc2 < 0)
462                                                 ERROR("can't make executable the file %s: %m", param->name);
463                                 } else {
464                                         ERROR("unknown file property %s for %s", param->value, param->name);
465                                         errno = EINVAL;
466                                         rc2 = -1;
467                                 }
468                                 if (rc2 < 0 && !rc)
469                                         rc = rc2;
470                                 param = param->next;
471                         }
472                 }
473                 feat = feat->next;
474         }
475         return rc;
476 }
477
478 static int install_security(const struct wgt_desc *desc)
479 {
480         char path[PATH_MAX], *head;
481         const char *icon, *perm;
482         int rc;
483         unsigned int i, n, len, lic, lf;
484         struct filedesc *f;
485
486         rc = secmgr_init(desc->id);
487         if (rc)
488                 goto error;
489
490         rc = secmgr_path_public_read_only(workdir);
491         if (rc)
492                 goto error2;
493
494         /* instal the files */
495         head = stpcpy(path, workdir);
496         assert(head < path + sizeof path);
497         len = (unsigned)((path + sizeof path) - head);
498         if (!len) {
499                 ERROR("root path too long in install_security");
500                 errno = ENAMETOOLONG;
501                 goto error2;
502         }
503         len--;
504         *head++ = '/';
505         icon = desc->icons ? desc->icons->src : NULL;
506         lic = (unsigned)(icon ? strlen(icon) : 0);
507         n = file_count();
508         i = 0;
509         while(i < n) {
510                 f = file_of_index(i++);
511                 lf = (unsigned)strlen(f->name);
512                 if (lf >= len) {
513                         ERROR("path too long in install_security");
514                         errno = ENAMETOOLONG;
515                         goto error2;
516                 }
517                 strcpy(head, f->name);
518                 if (lf <= lic && icon && !memcmp(f->name, icon, lf) && (!f->name[lf] || f->name[lf] == '/'))
519                         rc = secmgr_path_public_read_only(path);
520                 else
521                         rc = secmgr_path_read_only(path);
522                 if (rc)
523                         goto error2;
524         }
525
526         /* install the permissions */
527         perm = first_usable_permission();
528         while(perm) {
529                 rc = secmgr_permit(perm);
530                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
531                 if (rc)
532                         goto error2;
533                 perm = next_usable_permission();
534         }
535
536         /* install default permissions */
537         n = (unsigned int)(sizeof default_permissions / sizeof *default_permissions);
538         for (i = 0 ; i < n ; i++) {
539                 perm = default_permissions[i];
540                 rc = secmgr_permit(perm);
541                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
542                 if (rc)
543                         goto error2;
544         }
545
546         rc = secmgr_install();
547         return rc;
548 error2:
549         secmgr_cancel();
550 error:
551         return -1;
552 }
553
554 /* install the widget of the file */
555 struct wgt_info *install_widget(const char *wgtfile, const char *root, int force)
556 {
557         struct wgt_info *ifo;
558         const struct wgt_desc *desc;
559         char installdir[PATH_MAX];
560         int err;
561         struct unitconf uconf;
562
563         NOTICE("-- INSTALLING widget %s to %s --", wgtfile, root);
564
565         /* workdir */
566         create_directory(root, 0755, 1);
567         if (make_workdir(root, "TMP", 0)) {
568                 ERROR("failed to create a working directory");
569                 goto error1;
570         }
571
572         if (zread(wgtfile, 0))
573                 goto error2;
574
575         if (check_all_signatures(DEFAULT_ALLOW_NO_SIGNATURE))
576                 goto error2;
577
578         ifo = wgt_info_createat(workdirfd, NULL, 1, 1, 1);
579         if (!ifo)
580                 goto error2;
581
582         reset_requested_permissions();
583         desc = wgt_info_desc(ifo);
584         if (check_widget(desc))
585                 goto error3;
586
587         if (get_target_directory(installdir, root, desc))
588                 goto error3;
589
590         if (access(installdir, F_OK) == 0) {
591                 if (!force) {
592                         ERROR("widget already installed");
593                         errno = EEXIST;
594                         goto error3;
595                 }
596                 if (uninstall_widget(desc->idaver, root))
597                         goto error3;
598         }
599
600         if (move_widget_to(installdir, force))
601                 goto error3;
602
603         if (install_icon(desc))
604                 goto error3;
605
606         if (install_security(desc))
607                 goto error4;
608
609         if (install_exec_flag(desc))
610                 goto error4;
611
612         if (install_file_properties(desc))
613                 goto error4;
614
615         uconf.installdir = installdir;
616         uconf.icondir = FWK_ICON_DIR;
617         uconf.new_afid = get_new_afid;
618         uconf.base_http_ports = HTTP_PORT_BASE;
619         if (unit_install(ifo, &uconf))
620                 goto error4;
621
622         file_reset();
623         return ifo;
624
625 error4:
626         /* TODO: cleanup */
627
628 error3:
629         wgt_info_unref(ifo);
630
631 error2:
632         err = errno;
633         remove_workdir();
634         errno = err;
635
636 error1:
637         file_reset();
638         return NULL;
639 }
640