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