0122edab8b8ea1060c7408b25ab516c3350206c9
[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_http_port[] = "http-port";
57
58 #define HTTP_PORT_MIN                   31000
59 #define HTTP_PORT_MAX                   32759
60 #define HTTP_PORT_IS_VALID(port)        (HTTP_PORT_MIN <= (port) && (port) <= HTTP_PORT_MAX)
61 #define HTTP_PORT_COUNT                 (HTTP_PORT_MAX - HTTP_PORT_MIN + 1)
62 #define HTTP_PORT_ACNT                  ((HTTP_PORT_COUNT + 31) >> 5)
63 #define HTTP_PORT_ASFT(port)            (((port) - HTTP_PORT_MIN) & 31)
64 #define HTTP_PORT_AIDX(port)            (((port) - HTTP_PORT_MIN) >> 5)
65 #define HTTP_PORT_TEST(array,port)      ((((array)[HTTP_PORT_AIDX(port)]) >> HTTP_PORT_ASFT(port)) & 1)
66 #define HTTP_PORT_SET(array,port)       (((array)[HTTP_PORT_AIDX(port)]) |= (((uint32_t)1) << HTTP_PORT_ASFT(port)))
67
68 static uint32_t *port_bits = NULL;
69
70 static const char *default_permissions[] = {
71         "urn:AGL:token:valid"
72 };
73
74 /*
75  * normalize unit files: remove comments, remove heading blanks,
76  * make single lines
77  */
78 static void normalize_unit_file(char *content)
79 {
80         char *read, *write, c;
81
82         read = write = content;
83         c = *read++;
84         while (c) {
85                 switch (c) {
86                 case '\n':
87                 case ' ':
88                 case '\t':
89                         c = *read++;
90                         break;
91                 case '#':
92                 case ';':
93                         do { c = *read++; } while(c && c != '\n');
94                         break;
95                 default:
96                         *write++ = c;
97                         do { *write++ = c = *read++; } while(c && c != '\n');
98                         if (write - content >= 2 && write[-2] == '\\')
99                                 (--write)[-1] = ' ';
100                         break;
101                 }
102         }
103         *write = c;
104 }
105
106 static int get_port_cb(void *closure, const char *name, const char *path, int isuser)
107 {
108         char *iter;
109         char *content;
110         size_t length;
111         int rc, p;
112
113         /* reads the file */
114         rc = getfile(path, &content, &length);
115         if (rc < 0)
116                 return rc;
117
118         /* normalize the unit file */
119         normalize_unit_file(content);
120
121         /* process the file */
122         iter = strstr(content, key_afm_prefix);
123         while (iter) {
124                 iter += sizeof key_afm_prefix - 1;
125                 if (*iter == '-')
126                         iter++;
127                 if (!strncmp(iter, key_http_port, sizeof key_http_port - 1)) {
128                         iter += sizeof key_http_port - 1;
129                         while(*iter && *iter != '=' && *iter != '\n')
130                                 iter++;
131                         if (*iter == '=') {
132                                 while(*++iter == ' ');
133                                 p = atoi(iter);
134                                 if (HTTP_PORT_IS_VALID(p))
135                                         HTTP_PORT_SET((uint32_t*)closure, p);
136                         }
137                 }
138                 iter = strstr(iter, key_afm_prefix);
139         }
140         free(content);
141         return 0;
142 }
143
144 static int update_portbits(uint32_t *portbits)
145 {
146         int rc;
147
148         memset(portbits, 0, HTTP_PORT_ACNT * sizeof(uint32_t));
149         rc = systemd_unit_list(0, get_port_cb, portbits);
150         if (rc >= 0)
151                 rc = systemd_unit_list(1, get_port_cb, portbits);
152         if (rc < 0)
153                 ERROR("troubles while updating ports");
154         return rc;
155 }
156
157 static int first_free_port(uint32_t *portbits)
158 {
159         int port;
160
161         port = HTTP_PORT_MIN;
162         while (port <= HTTP_PORT_MAX && !~portbits[HTTP_PORT_AIDX(port)])
163                 port += 32;
164         while (port <= HTTP_PORT_MAX && HTTP_PORT_TEST(portbits, port))
165                 port++;
166         if (port > HTTP_PORT_MAX) {
167                 ERROR("Can't compute a valid port");
168                 errno = EADDRNOTAVAIL;
169                 port = -1;
170         }
171         return port;
172 }
173
174 static int get_port()
175 {
176         int port;
177
178         /* ensure existing port bitmap */
179         if (port_bits == NULL) {
180                 port_bits = malloc(HTTP_PORT_ACNT * sizeof(uint32_t));
181                 if (port_bits == NULL || update_portbits(port_bits) < 0)
182                         return -1;
183         }
184
185         /* allocates the port */
186         port = first_free_port(port_bits);
187         if (port >= 0)
188                 HTTP_PORT_SET(port_bits, port);
189
190         return port;
191 }
192
193 static int check_defined(const void *data, const char *name)
194 {
195         if (data)
196                 return 0;
197         ERROR("widget has no defined '%s' (temporary constraints)", name);
198         errno = EINVAL;
199         return -1;
200 }
201
202 static int check_valid_string(const char *value, const char *name)
203 {
204         int pos;
205         char c;
206
207         if (check_defined(value, name))
208                 return -1;
209         pos = 0;
210         c = value[pos];
211         if (c == 0) {
212                 ERROR("empty string forbidden in '%s' (temporary constraints)", name);
213                 errno = EINVAL;
214                 return -1;
215         }
216         do {
217                 if (!isalnum(c) && !strchr(".-_", c)) {
218                         ERROR("forbidden char %c in '%s' -> '%s' (temporary constraints)", c, name, value);
219                         errno = EINVAL;
220                         return -1;
221                 }
222                 c = value[++pos];
223         } while(c);
224         return 0;
225 }
226
227 static int check_temporary_constraints(const struct wgt_desc *desc)
228 {
229         int result;
230
231         result  = check_valid_string(desc->id, "id");
232         result |= check_valid_string(desc->version, "version");
233         result |= check_valid_string(desc->ver, "ver");
234         result |= check_defined(desc->content_src, "content");
235         if (desc->icons)
236                 result |= check_defined(desc->icons->src, "icon.src");
237         if (result)
238                 return result;
239
240         if (desc->icons && desc->icons->next) {
241                 ERROR("widget has more than one icon defined (temporary constraints)");
242                 errno = EINVAL;
243                 result = -1;
244         }
245         return 0;
246 }
247
248 static int set_required_permissions(struct wgt_desc_param *params, int required)
249 {
250         int optional;
251
252         while (params) {
253                 /* check if target */
254                 if (!strcmp(params->name, string_sharp_target)) {
255                         /* do nothing when #target */
256                 } else {
257                         /* check the value */
258                         if (!strcmp(params->value, string_required))
259                                 optional = !required;
260                         else if (!strcmp(params->value, string_optional))
261                                 optional = 1;
262                         else {
263                                 ERROR("unexpected parameter value: %s found for %s", params->value, params->name);
264                                 errno = EPERM;
265                                 return -1;
266                         }
267                         /* set the permission */
268                         if (request_permission(params->name)) {
269                                 DEBUG("granted permission: %s", params->name);
270                         } else if (optional) {
271                                 INFO("optional permission ungranted: %s", params->name);
272                         } else {
273                                 ERROR("ungranted permission required: %s", params->name);
274                                 errno = EPERM;
275                                 return -1;
276                         }
277                 }
278                 params = params->next;
279         }
280         return 0;
281 }
282
283 static int check_permissions(const struct wgt_desc *desc)
284 {
285         int result;
286         const struct wgt_desc_feature *feature;
287
288         result = 0;
289         feature = desc->features;
290         while(result >= 0 && feature) {
291                 if (!strcmp(feature->name, feature_required_permission))
292                         result = set_required_permissions(feature->params, feature->required);
293                 feature = feature->next;
294         }
295         return result;
296 }
297
298 static int for_all_content(const struct wgt_desc *desc, int (*action)(const char *src, const char *type))
299 {
300         int rc, rc2;
301         struct wgt_desc_feature *feat;
302         const char *src, *type;
303
304         rc = action(desc->content_src, desc->content_type);
305         feat = desc->features;
306         while (feat) {
307                 if (!strcmp(feat->name, FWK_PREFIX"widget:provided-unit")) {
308                         src = wgt_info_param(feat, "content.src");
309                         type = wgt_info_param(feat, "content.type");
310                         rc2 = action(src, type);
311                         if (rc >= 0 && rc2 < 0)
312                                 rc = rc2;
313                 }
314                 feat = feat->next;
315         }
316         return rc;
317 }
318
319 static int set_exec_flag(const char *src, const char *type)
320 {
321         int i, rc;
322
323         if (src && type) {
324                 i = sizeof exec_type_strings / sizeof *exec_type_strings;
325                 while (i) {
326                         if (!strcasecmp(type, exec_type_strings[--i])) {
327                                 rc = fchmodat(workdirfd, src, 0755, 0);
328                                 if (rc < 0)
329                                         ERROR("can't make executable the file %s", src);
330                                 return rc;
331                         }
332                 }
333         }
334         return 0;
335 }
336
337 static int check_one_content(const char *src, const char *type)
338 {
339         int rc;
340         struct stat s;
341         int fhtdocs, serr;
342
343         if (!src) {
344                 ERROR("a content src is missing");
345                 errno = EINVAL;
346                 rc = -1;
347         } else {
348                 /* TODO: when dealing with HTML and languages, the check should
349                  * include i18n path search of widgets */
350                 rc = fstatat(workdirfd, src, &s, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
351                 if (rc < 0) {
352                         serr = errno;
353                         fhtdocs = openat(workdirfd, "htdocs", O_DIRECTORY|O_PATH);
354                         if (fhtdocs >= 0) {
355                                 rc = fstatat(fhtdocs, src, &s, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW);
356                                 serr = errno;
357                                 close(fhtdocs);
358                         }
359                         errno = serr;
360                 }
361                 if (rc < 0)
362                         ERROR("can't get info on content %s: %m", src);
363                 else if (!S_ISREG(s.st_mode)) {
364                         ERROR("content %s isn't a regular file", src);
365                         errno = EINVAL;
366                         rc = -1;
367                 }
368         }
369         return rc;
370 }
371
372 static int check_content(const struct wgt_desc *desc)
373 {
374         return for_all_content(desc, check_one_content);
375 }
376
377 static int check_widget(const struct wgt_desc *desc)
378 {
379         int result;
380
381         result = check_temporary_constraints(desc);
382         if (result >= 0)
383                 result = check_permissions(desc);
384         if (result >= 0)
385                 result = check_content(desc);
386         return result;
387 }
388
389 static int get_target_directory(char target[PATH_MAX], const char *root, const struct wgt_desc *desc)
390 {
391         int rc;
392
393         rc = snprintf(target, PATH_MAX, "%s/%s/%s", root, desc->id, desc->ver);
394         if (rc < PATH_MAX)
395                 rc = 0;
396         else {
397                 ERROR("path too long");
398                 errno = EINVAL;
399                 rc = -1;
400         }
401         return rc;
402 }
403
404 static int move_widget_to(const char *destdir, int force)
405 {
406         return move_workdir(destdir, 1, force);
407 }
408
409 static int install_icon(const struct wgt_desc *desc)
410 {
411         char link[PATH_MAX];
412         char target[PATH_MAX];
413         int rc;
414
415         if (!desc->icons)
416                 return 0;
417
418         create_directory(FWK_ICON_DIR, 0755, 1);
419         rc = snprintf(link, sizeof link, "%s/%s", FWK_ICON_DIR, desc->idaver);
420         if (rc >= (int)sizeof link) {
421                 ERROR("link too long in install_icon");
422                 errno = EINVAL;
423                 return -1;
424         }
425
426         rc = snprintf(target, sizeof target, "%s/%s", workdir, desc->icons->src);
427         if (rc >= (int)sizeof target) {
428                 ERROR("target too long in install_icon");
429                 errno = EINVAL;
430                 return -1;
431         }
432
433         unlink(link);
434         rc = symlink(target, link);
435         if (rc)
436                 ERROR("can't create link %s -> %s", link, target);
437         return rc;
438 }
439
440 static int install_exec_flag(const struct wgt_desc *desc)
441 {
442         return for_all_content(desc, set_exec_flag);
443 }
444
445 static int install_file_properties(const struct wgt_desc *desc)
446 {
447         int rc, rc2;
448         struct wgt_desc_feature *feat;
449         struct wgt_desc_param *param;
450
451         rc = 0;
452         feat = desc->features;
453         while (feat) {
454                 if (!strcmp(feat->name, FWK_PREFIX"widget:file-properties")) {
455                         param = feat->params;
456                         while (param) {
457                                 if (!strcmp(param->value, "executable")) {
458                                         rc2 = fchmodat(workdirfd, param->name, 0755, 0);
459                                         if (rc2 < 0)
460                                                 ERROR("can't make executable the file %s: %m", param->name);
461                                 } else {
462                                         ERROR("unknown file property %s for %s", param->value, param->name);
463                                         errno = EINVAL;
464                                         rc2 = -1;
465                                 }
466                                 if (rc2 < 0 && !rc)
467                                         rc = rc2;
468                                 param = param->next;
469                         }
470                 }
471                 feat = feat->next;
472         }
473         return rc;
474 }
475
476 static int install_security(const struct wgt_desc *desc)
477 {
478         char path[PATH_MAX], *head;
479         const char *icon, *perm;
480         int rc;
481         unsigned int i, n, len, lic, lf;
482         struct filedesc *f;
483
484         rc = secmgr_init(desc->id);
485         if (rc)
486                 goto error;
487
488         rc = secmgr_path_public_read_only(workdir);
489         if (rc)
490                 goto error2;
491
492         /* instal the files */
493         head = stpcpy(path, workdir);
494         assert(head < path + sizeof path);
495         len = (unsigned)((path + sizeof path) - head);
496         if (!len) {
497                 ERROR("root path too long in install_security");
498                 errno = ENAMETOOLONG;
499                 goto error2;
500         }
501         len--;
502         *head++ = '/';
503         icon = desc->icons ? desc->icons->src : NULL;
504         lic = (unsigned)(icon ? strlen(icon) : 0);
505         n = file_count();
506         i = 0;
507         while(i < n) {
508                 f = file_of_index(i++);
509                 lf = (unsigned)strlen(f->name);
510                 if (lf >= len) {
511                         ERROR("path too long in install_security");
512                         errno = ENAMETOOLONG;
513                         goto error2;
514                 }
515                 strcpy(head, f->name);
516                 if (lf <= lic && icon && !memcmp(f->name, icon, lf) && (!f->name[lf] || f->name[lf] == '/'))
517                         rc = secmgr_path_public_read_only(path);
518                 else
519                         rc = secmgr_path_read_only(path);
520                 if (rc)
521                         goto error2;
522         }
523
524         /* install the permissions */
525         perm = first_usable_permission();
526         while(perm) {
527                 rc = secmgr_permit(perm);
528                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
529                 if (rc)
530                         goto error2;
531                 perm = next_usable_permission();
532         }
533
534         /* install default permissions */
535         n = (unsigned int)(sizeof default_permissions / sizeof *default_permissions);
536         for (i = 0 ; i < n ; i++) {
537                 perm = default_permissions[i];
538                 rc = secmgr_permit(perm);
539                 INFO("permitting %s %s", perm, rc ? "FAILED!" : "success");
540                 if (rc)
541                         goto error2;
542         }
543
544         rc = secmgr_install();
545         return rc;
546 error2:
547         secmgr_cancel();
548 error:
549         return -1;
550 }
551
552 /* install the widget of the file */
553 struct wgt_info *install_widget(const char *wgtfile, const char *root, int force)
554 {
555         struct wgt_info *ifo;
556         const struct wgt_desc *desc;
557         char installdir[PATH_MAX];
558         int err;
559         struct unitconf uconf;
560
561         NOTICE("-- INSTALLING widget %s to %s --", wgtfile, root);
562
563         /* workdir */
564         create_directory(root, 0755, 1);
565         if (make_workdir(root, "TMP", 0)) {
566                 ERROR("failed to create a working directory");
567                 goto error1;
568         }
569
570         if (zread(wgtfile, 0))
571                 goto error2;
572
573         if (check_all_signatures(DEFAULT_ALLOW_NO_SIGNATURE))
574                 goto error2;
575
576         ifo = wgt_info_createat(workdirfd, NULL, 1, 1, 1);
577         if (!ifo)
578                 goto error2;
579
580         reset_requested_permissions();
581         desc = wgt_info_desc(ifo);
582         if (check_widget(desc))
583                 goto error3;
584
585         if (get_target_directory(installdir, root, desc))
586                 goto error3;
587
588         if (access(installdir, F_OK) == 0) {
589                 if (!force) {
590                         ERROR("widget already installed");
591                         errno = EEXIST;
592                         goto error3;
593                 }
594                 if (uninstall_widget(desc->idaver, root))
595                         goto error3;
596         }
597
598         if (move_widget_to(installdir, force))
599                 goto error3;
600
601         if (install_icon(desc))
602                 goto error3;
603
604         if (install_security(desc))
605                 goto error4;
606
607         if (install_exec_flag(desc))
608                 goto error4;
609
610         if (install_file_properties(desc))
611                 goto error4;
612
613         uconf.installdir = installdir;
614         uconf.icondir = FWK_ICON_DIR;
615         uconf.port = get_port;
616         if (unit_install(ifo, &uconf))
617                 goto error4;
618
619         file_reset();
620         return ifo;
621
622 error4:
623         /* TODO: cleanup */
624
625 error3:
626         wgt_info_unref(ifo);
627
628 error2:
629         err = errno;
630         remove_workdir();
631         errno = err;
632
633 error1:
634         file_reset();
635         return NULL;
636 }
637