49172f6181a3b66d9a0207802374f7e89fc8794d
[src/app-framework-main.git] / src / afm-launch.c
1 /*
2  Copyright 2015 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 <unistd.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <limits.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <fcntl.h>
28 #include <assert.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #include <poll.h>
32 #include <signal.h>
33
34 extern char **environ;
35
36 #include "verbose.h"
37 #include "afm-launch-mode.h"
38 #include "afm-launch.h"
39 #include "secmgr-wrap.h"
40
41 #define DEFAULT_TYPE "text/html"
42
43 struct type_list {
44         struct type_list *next;
45         char type[1];
46 };
47
48 struct exec_vector {
49         int has_readyfd;
50         const char **args;
51 };
52
53 struct desc_list {
54         struct desc_list *next;
55         enum afm_launch_mode mode;
56         struct type_list *types;
57         struct exec_vector execs[2];
58 };
59
60 struct launchparam {
61         int port;
62         int readyfd;
63         char **uri;
64         const char *secret;
65         const char *datadir;
66         struct exec_vector *execs;
67 };
68
69 struct confread {
70         const char *filepath;
71         FILE *file;
72         int lineno;
73         int index;
74         int length;
75         char buffer[4096];
76 };
77
78 struct desc_list *launchers = NULL;
79
80 static gid_t groupid = 0;
81
82 static const char separators[] = " \t\n";
83 static const char readystr[] = "READY=1";
84 static const int ready_timeout = 1500;
85
86 static void dump_launchers()
87 {
88         int j, k;
89         struct desc_list *desc;
90         struct type_list *type;
91
92         for (desc = launchers ; desc != NULL ; desc = desc->next) {
93                 printf("mode %s\n", name_of_launch_mode(desc->mode));
94                 for (type = desc->types ; type != NULL ; type = type->next)
95                         printf("%s\n", type->type);
96                 for ( j = 0 ; j < 2 ; j++)
97                         if (desc->execs[j].args != NULL) {
98                                 for (k = 0 ; desc->execs[j].args[k] != NULL ; k++)
99                                         printf("  %s", desc->execs[j].args[k]);
100                                 printf("\n");
101                         }
102                 printf("\n");
103         }
104 }
105
106 static int next_token(struct confread *cread)
107 {
108         int idx = cread->index + cread->length;
109         cread->index = idx + strspn(&cread->buffer[idx], separators);
110         cread->length = strcspn(&cread->buffer[cread->index], separators);
111         return cread->length;
112 }
113
114 static int read_line(struct confread *cread)
115 {
116         while (fgets(cread->buffer, sizeof cread->buffer, cread->file) != NULL) {
117                 cread->lineno++;
118                 cread->index = strspn(cread->buffer, separators);
119                 if (cread->buffer[cread->index] && cread->buffer[cread->index] != '#') {
120                         cread->length = strcspn(&cread->buffer[cread->index], separators);
121                         assert(cread->length > 0);
122                         return cread->length;
123                 }
124         }
125         if (ferror(cread->file)) {
126                 ERROR("%s:%d: error while reading, %m", cread->filepath, cread->lineno);
127                 return -1;
128         }
129         return 0;
130 }
131
132 static const char **read_vector(struct confread *cread)
133 {
134         int index0, length0;
135         const char **vector;
136         char *args;
137         int count, length;
138
139         /* record origin */
140         index0 = cread->index;
141         length0 = cread->length;
142
143         /* count */
144         count = 0;
145         length = 0;
146         while(cread->length) {
147                 count++;
148                 length += cread->length;
149                 next_token(cread);
150         }
151
152         /* allocates */
153         cread->index = index0;
154         cread->length = length0;
155         vector = malloc(length + count + (count + 1) * sizeof(char*));
156         if (vector == NULL)
157                 return NULL;
158
159         /* copies */
160         args = (char*)(vector + count + 1);
161         count = 0;
162         while(cread->length) {
163                 vector[count++] = args;
164                 memcpy(args, &cread->buffer[cread->index], cread->length);
165                 args += cread->length;
166                 *args++ = 0;
167                 next_token(cread);
168         }
169         vector[count] = NULL;
170         cread->index = index0;
171         cread->length = length0;
172         return vector;
173 }
174
175 static struct type_list *read_type(struct confread *cread)
176 {
177         int index, length;
178         struct type_list *result;
179
180         /* record index and length */
181         index = cread->index;
182         length = cread->length;
183
184         /* check no extra characters */
185         if (next_token(cread)) {
186                 ERROR("%s:%d: extra characters found after type %.*s",
187                         cread->filepath, cread->lineno, length, &cread->buffer[index]);
188                 errno = EINVAL;
189                 return NULL;
190         }
191
192         /* allocate structure */
193         result = malloc(sizeof(struct type_list) + length);
194         if (result == NULL) {
195                 ERROR("%s:%d: out of memory", cread->filepath, cread->lineno);
196                 errno = ENOMEM;
197                 return NULL;
198         }
199
200         /* fill the structure */
201         memcpy(result->type, &cread->buffer[index], length);
202         result->type[length] = 0;
203         return result;
204 }
205
206 static enum afm_launch_mode read_mode(struct confread *cread)
207 {
208         int index, length;
209         enum afm_launch_mode result;
210
211         assert(cread->index == 0);
212         assert(!strncmp(&cread->buffer[cread->index], "mode", 4));
213
214         /* get the next token: the mode string */
215         if (!next_token(cread)) {
216                 ERROR("%s:%d: no mode value set", cread->filepath, cread->lineno);
217                 errno = EINVAL;
218                 return invalid_launch_mode;
219         }
220
221         /* record index and length */
222         index = cread->index;
223         length = cread->length;
224
225         /* check no extra characters */
226         if (next_token(cread)) {
227                 ERROR("%s:%d: extra characters found after mode %.*s",
228                         cread->filepath, cread->lineno, length, &cread->buffer[index]);
229                 errno = EINVAL;
230                 return invalid_launch_mode;
231         }
232
233         /* get the mode */
234         cread->buffer[index + length] = 0;
235         result = launch_mode_of_string(&cread->buffer[index]);
236         if (result == invalid_launch_mode) {
237                 ERROR("%s:%d: invalid mode value %s",
238                         cread->filepath, cread->lineno, &cread->buffer[index]);
239                 errno = EINVAL;
240         }
241         return result;
242 }
243
244 static void free_type_list(struct type_list *types)
245 {
246         while (types != NULL) {
247                 struct type_list *next = types->next;
248                 free(types);
249                 types = next;
250         }
251 }
252
253 static int read_launchers(struct confread *cread)
254 {
255         int rc, has_readyfd;
256         struct type_list *types, *lt;
257         struct desc_list *desc;
258         enum afm_launch_mode mode;
259         const char **vector;
260
261         /* reads the file */
262         lt = NULL;
263         types = NULL;
264         desc = NULL;
265         mode = invalid_launch_mode;
266         rc = read_line(cread);
267         while (rc > 0) {
268                 if (cread->index == 0) {
269                         if (cread->length == 4
270                         && !memcmp(&cread->buffer[cread->index], "mode", 4)) {
271                                 /* check if allowed */
272                                 if (types != NULL) {
273                                         ERROR("%s:%d: mode found before launch vector",
274                                                 cread->filepath, cread->lineno);
275                                         errno = EINVAL;
276                                         free_type_list(types);
277                                         return -1;
278                                 }
279
280                                 /* read the mode */
281                                 mode = read_mode(cread);
282                                 if (mode == invalid_launch_mode)
283                                         return -1;
284                         } else {
285                                 if (mode == invalid_launch_mode) {
286                                         ERROR("%s:%d: mode not found before type",
287                                                         cread->filepath, cread->lineno);
288                                         errno = EINVAL;
289                                         assert(types == NULL);
290                                         return -1;
291                                 }
292                                 /* read a type */
293                                 lt = read_type(cread);
294                                 if (lt == NULL) {
295                                         free_type_list(types);
296                                         return -1;
297                                 }
298                                 lt->next = types;
299                                 types = lt;
300                         }
301                         desc = NULL;
302                 } else if (types == NULL && desc == NULL) {
303                         if (lt == NULL)
304                                 ERROR("%s:%d: untyped launch vector found",
305                                         cread->filepath, cread->lineno);
306                         else
307                                 ERROR("%s:%d: extra launch vector found (2 max)",
308                                         cread->filepath, cread->lineno);
309                         errno = EINVAL;
310                         return -1;
311                 } else {
312                         has_readyfd = NULL != strstr(&cread->buffer[cread->index], "%R");
313                         vector = read_vector(cread);
314                         if (vector == NULL) {
315                                 ERROR("%s:%d: out of memory",
316                                         cread->filepath, cread->lineno);
317                                 free_type_list(types);
318                                 errno = ENOMEM;
319                                 return -1;
320                         }
321                         if (types) {
322                                 assert(desc == NULL);
323                                 desc = malloc(sizeof * desc);
324                                 if (desc == NULL) {
325                                         ERROR("%s:%d: out of memory",
326                                                 cread->filepath, cread->lineno);
327                                         free_type_list(types);
328                                         errno = ENOMEM;
329                                         return -1;
330                                 }
331                                 desc->next = launchers;
332                                 desc->mode = mode;
333                                 desc->types = types;
334                                 desc->execs[0].has_readyfd = has_readyfd;
335                                 desc->execs[0].args = vector;
336                                 desc->execs[1].has_readyfd = 0;
337                                 desc->execs[1].args = NULL;
338                                 types = NULL;
339                                 launchers = desc;
340                         } else {
341                                 desc->execs[1].has_readyfd = has_readyfd;
342                                 desc->execs[1].args = vector;
343                                 desc = NULL;
344                         }
345                 }
346                 rc = read_line(cread);
347         }
348         if (types != NULL) {
349                 ERROR("%s:%d: end of file found before launch vector",
350                         cread->filepath, cread->lineno);
351                 free_type_list(types);
352                 errno = EINVAL;
353                 return -1;
354         }
355         return rc;
356 }
357
358 static int read_configuration_file(const char *filepath)
359 {
360         int rc;
361         struct confread cread;
362
363         /* opens the configuration file */
364         cread.file = fopen(filepath, "r");
365         if (cread.file == NULL) {
366                 /* error */
367                 ERROR("can't read file %s: %m", filepath);
368                 rc = -1;
369         } else {
370                 /* reads it */
371                 cread.filepath = filepath;
372                 cread.lineno = 0;
373                 rc = read_launchers(&cread);
374                 fclose(cread.file);
375         }
376         return rc;
377 }
378
379 /*
380 %% %
381 %a appid                        desc->appid
382 %c content                      desc->content
383 %D datadir                      params->datadir
384 %H height                       desc->height
385 %h homedir                      desc->home
386 %I icondir                      FWK_ICON_DIR
387 %m mime-type                    desc->type
388 %n name                         desc->name
389 %p plugins                      desc->plugins
390 %P port                         params->port
391 %r rootdir                      desc->path
392 %R readyfd                      params->readyfd
393 %S secret                       params->secret
394 %t tag (smack label)            desc->tag
395 %W width                        desc->width
396 */
397
398 union arguments {
399         char *scalar;
400         char **vector;
401 };
402
403 static union arguments instantiate_arguments(
404         const char * const     *args,
405         struct afm_launch_desc *desc,
406         struct launchparam     *params,
407         int                     wants_vector
408 )
409 {
410         const char * const *iter;
411         const char *p, *v;
412         char *data, port[20], width[20], height[20], readyfd[20], mini[3], c, sep;
413         int n, s;
414         union arguments result;
415
416         /* init */
417         sep = wants_vector ? 0 : ' ';
418         mini[0] = '%';
419         mini[2] = 0;
420
421         /* loop that either compute the size and build the result */
422         result.vector = NULL;
423         result.scalar = NULL;
424         data = NULL;
425         n = s = 0;
426         for (;;) {
427                 iter = args;
428                 n = 0;
429                 while (*iter) {
430                         p = *iter++;
431                         if (data && !sep)
432                                 result.vector[n] = data;
433                         n++;
434                         while((c = *p++) != 0) {
435                                 if (c != '%') {
436                                         if (data)
437                                                 *data++ = c;
438                                         else
439                                                 s++;
440                                 } else {
441                                         c = *p++;
442                                         switch (c) {
443                                         case 'a': v = desc->appid; break;
444                                         case 'c': v = desc->content; break;
445                                         case 'D': v = params->datadir; break;
446                                         case 'H':
447                                                 if(!data)
448                                                         sprintf(height, "%d", desc->height);
449                                                 v = height;
450                                                 break;
451                                         case 'h': v = desc->home; break;
452                                         case 'I': v = FWK_ICON_DIR; break;
453                                         case 'm': v = desc->type; break;
454                                         case 'n': v = desc->name; break;
455                                         case 'P':
456                                                 if(!data)
457                                                         sprintf(port, "%d", params->port);
458                                                 v = port;
459                                                 break;
460                                         case 'p': v = "" /*desc->plugins*/; break;
461                                         case 'R':
462                                                 if(!data)
463                                                         sprintf(readyfd, "%d", params->readyfd);
464                                                 v = readyfd;
465                                                 break;
466                                         case 'r': v = desc->path; break;
467                                         case 'S': v = params->secret; break;
468                                         case 't': v = desc->tag; break;
469                                         case 'W':
470                                                 if(!data)
471                                                         sprintf(width, "%d", desc->width);
472                                                 v = width;
473                                                 break;
474                                         case '%':
475                                                 c = 0;
476                                         default:
477                                                 mini[1] = c;
478                                                 v = mini;
479                                                 break;
480                                         }
481                                         if (data)
482                                                 data = stpcpy(data, v);
483                                         else
484                                                 s += strlen(v);
485                                 }
486                         }
487                         if (data)
488                                 *data++ = sep;
489                         else
490                                 s++;
491                 }
492                 if (sep) {
493                         assert(!wants_vector);
494                         if (data) {
495                                 *--data = 0;
496                                 return result;
497                         }
498                         /* allocation */
499                         result.scalar = malloc(s);
500                         if (result.scalar == NULL) {
501                                 errno = ENOMEM;
502                                 return result;
503                         }
504                         data = result.scalar;
505                 } else {
506                         assert(wants_vector);
507                         if (data) {
508                                 result.vector[n] = NULL;
509                                 return result;
510                         }
511                         /* allocation */
512                         result.vector = malloc((n+1)*sizeof(char*) + s);
513                         if (result.vector == NULL) {
514                                 errno = ENOMEM;
515                                 return result;
516                         }
517                         data = (char*)(&result.vector[n + 1]);
518                 }
519         }
520 }
521
522 static pid_t launch(
523         struct afm_launch_desc *desc,
524         struct launchparam     *params,
525         struct exec_vector     *exec,
526         pid_t                   progrp
527 )
528 {
529         int rc;
530         char **args;
531         pid_t pid;
532         int rpipe[2];
533         struct pollfd pfd;
534
535         /* prepare the pipes */
536         rc = pipe(rpipe);
537         if (rc < 0) {
538                 ERROR("error while calling pipe2: %m");
539                 return -1;
540         }
541
542         /* instanciate the arguments */
543         params->readyfd = rpipe[1];
544         args = instantiate_arguments(exec->args, desc, params, 1).vector;
545         if (args == NULL) {
546                 close(rpipe[0]);
547                 close(rpipe[1]);
548                 ERROR("out of memory in master");
549                 errno = ENOMEM;
550                 return -1;
551         }
552
553         /* fork the master child */
554         pid = fork();
555         if (pid < 0) {
556
557                 /********* can't fork ************/
558
559                 close(rpipe[0]);
560                 close(rpipe[1]);
561                 free(args);
562                 ERROR("master fork failed: %m");
563                 return -1;
564         }
565         if (pid) {
566
567                 /********* in the parent process ************/
568
569                 close(rpipe[1]);
570                 free(args);
571                 pfd.fd = rpipe[0];
572                 pfd.events = POLLIN;
573                 poll(&pfd, 1, ready_timeout);
574                 close(rpipe[0]);
575                 return pid;
576         }
577
578         /********* in the child process ************/
579
580         close(rpipe[0]);
581
582         /* avoid set-gid effect */
583         setresgid(groupid, groupid, groupid);
584
585         /* enter the process group */
586         rc = setpgid(0, progrp);
587         if (rc) {
588                 ERROR("setpgid failed");
589                 _exit(1);
590         }
591
592         /* enter security mode */
593         rc = secmgr_prepare_exec(desc->tag);
594         if (rc < 0) {
595                 ERROR("call to secmgr_prepare_exec failed: %m");
596                 _exit(1);
597         }
598
599         /* enter the datadirectory */
600         rc = mkdir(params->datadir, 0755);
601         if (rc && errno != EEXIST) {
602                 ERROR("creation of datadir %s failed: %m", params->datadir);
603                 _exit(1);
604         }
605         rc = chdir(params->datadir);
606         if (rc) {
607                 ERROR("can't enter the datadir %s: %m", params->datadir);
608                 _exit(1);
609         }
610
611         /* signal if needed */
612         if (!exec->has_readyfd) {
613                 write(rpipe[1], readystr, sizeof(readystr) - 1);
614                 close(rpipe[1]);
615         }
616
617         /* executes the process */
618         rc = execve(args[0], args, environ);
619         ERROR("failed to exec master %s: %m", args[0]);
620         _exit(1);
621         return -1;
622 }
623
624 static int launch_local(
625         struct afm_launch_desc *desc,
626         pid_t                   children[2],
627         struct launchparam     *params
628 )
629 {
630         children[0] = launch(desc, params, &params->execs[0], 0);
631         if (children[0] <= 0)
632                 return -1;
633
634         if (params->execs[1].args == NULL)
635                 return 0;
636
637         children[1] = launch(desc, params, &params->execs[1], children[0]);
638         if (children[1] > 0)
639                 return 0;
640
641         killpg(children[0], SIGKILL);
642         return -1;
643 }
644
645 static int launch_remote(
646         struct afm_launch_desc *desc,
647         pid_t                   children[2],
648         struct launchparam     *params
649 )
650 {
651         char *uri;
652
653         /* instanciate the uri */
654         if (params->execs[1].args == NULL)
655                 uri = NULL;
656         else
657                 uri = instantiate_arguments(params->execs[1].args, desc, params, 0).scalar;
658         if (uri == NULL) {
659                 ERROR("out of memory for remote uri");
660                 errno = ENOMEM;
661                 return -1;
662         }
663
664         /* launch the command */
665         children[0] = launch(desc, params, &params->execs[0], 0);
666         if (children[0] <= 0) {
667                 free(uri);
668                 return -1;
669         }
670
671         *params->uri = uri;
672         return 0;
673 }
674
675 static void mksecret(char buffer[9])
676 {
677         snprintf(buffer, 9, "%08lX", (0xffffffff & random()));
678 }
679
680 static int mkport()
681 {
682         static int port_ring = 12345;
683         int port = port_ring;
684         if (port < 12345 || port > 15432)
685                 port = 12345;
686         port_ring = port + 1;
687         return port;
688 }
689
690 static struct desc_list *search_launcher(const char *type, enum afm_launch_mode mode)
691 {
692         struct desc_list *dl;
693         struct type_list *tl;
694
695         for (dl = launchers ; dl ; dl = dl->next)
696                 if (dl->mode == mode)
697                         for (tl = dl->types ; tl != NULL ; tl = tl->next)
698                                 if (!strcmp(tl->type, type))
699                                         return dl;
700         return NULL;
701 }
702
703 int afm_launch(struct afm_launch_desc *desc, pid_t children[2], char **uri)
704 {
705         int rc;
706         char datadir[PATH_MAX];
707         char secret[9];
708         struct launchparam params;
709         const char *type;
710         struct desc_list *dl;
711
712         /* should be init */
713         assert(groupid != 0);
714         assert(launch_mode_is_valid(desc->mode));
715         assert(desc->mode == mode_local || uri != NULL);
716         assert(uri == NULL || *uri == NULL);
717
718         /* init */
719         children[0] = 0;
720         children[1] = 0;
721
722         /* what launcher ? */
723         type = desc->type != NULL && *desc->type ? desc->type : DEFAULT_TYPE;
724         dl = search_launcher(type, desc->mode);
725         if (dl == NULL) {
726                 ERROR("type %s not found for mode %s!", type, name_of_launch_mode(desc->mode));
727                 errno = ENOENT;
728                 return -1;
729         }
730
731         /* prepare paths */
732         rc = snprintf(datadir, sizeof datadir, "%s/%s", desc->home, desc->tag);
733         if (rc < 0 || rc >= sizeof datadir) {
734                 ERROR("overflow for datadir");
735                 errno = EINVAL;
736                 return -1;
737         }
738
739         /* make the secret and port */
740         mksecret(secret);
741         params.uri = uri;
742         params.port = mkport();
743         params.secret = secret;
744         params.datadir = datadir;
745         params.execs = dl->execs;
746
747         switch (desc->mode) {
748         case mode_local:
749                 return launch_local(desc, children, &params);
750         case mode_remote:
751                 return launch_remote(desc, children, &params);
752         default:
753                 assert(0);
754                 return -1;
755         }
756 }
757
758 int afm_launch_initialize()
759 {
760         int rc;
761         gid_t r, e, s;
762
763         getresgid(&r, &e, &s);
764         if (s && s != e)
765                 groupid = s;
766         else
767                 groupid = -1;
768
769         rc = read_configuration_file(FWK_LAUNCH_CONF);
770         /* dump_launchers(); */
771         return rc;
772 }
773