discover/discover-server: Restrict clients based on uid
[petitboot] / discover / syslinux-parser.c
1 #if defined(HAVE_CONFIG_H)
2 #include "config.h"
3 #endif
4
5 #include <assert.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <i18n/i18n.h>
9 #include <libgen.h>
10
11 #include "log/log.h"
12 #include "list/list.h"
13 #include "file/file.h"
14 #include "talloc/talloc.h"
15 #include "types/types.h"
16 #include "parser-conf.h"
17 #include "parser-utils.h"
18 #include "resource.h"
19 #include "paths.h"
20
21 struct syslinux_boot_option {
22         char *label;
23         char *image;
24         char *append;
25         char *initrd;
26         struct list_item list;
27 };
28
29 /* by spec 16 is allowed */
30 #define INCLUDE_NEST_LIMIT 16
31
32 struct syslinux_options {
33         struct list processed_options;
34         struct syslinux_boot_option *current_option;
35         int include_nest_level;
36         char *cfg_dir;
37 };
38
39 static const char *const syslinux_conf_files[] = {
40         "/boot/syslinux/syslinux.cfg",
41         "/syslinux/syslinux.cfg",
42         "/syslinux.cfg",
43         "/BOOT/SYSLINUX/SYSLINUX.CFG",
44         "/SYSLINUX/SYSLINUX.CFG",
45         "/SYSLINUX.CFG",
46         NULL
47 };
48
49 static const char *const syslinux_kernel_unsupported_extensions[] = {
50         ".0", /* eventually support PXE here? */
51         ".bin",
52         ".bs",
53         ".bss",
54         ".c32",
55         ".cbt",
56         ".com",
57         ".img",
58         NULL
59 };
60
61 static const char *const syslinux_ignored_names[] = {
62         "config",
63         "sysapend",
64         "localboot",
65         "ui",
66         "prompt",
67         "noescape",
68         "nocomplete",
69         "allowoptions",
70         "timeout",
71         "totaltimeout",
72         "ontimeout",
73         "onerror",
74         "serial",
75         "nohalt",
76         "console",
77         "font",
78         "kbdmap",
79         "say",
80         "display",
81         "f1",
82         "f2",
83         "f3",
84         "f4",
85         "f5",
86         "f6",
87         "f7",
88         "f8",
89         "f9",
90         "f10",
91         "f11",
92         "f12",
93         NULL
94 };
95
96 static const char *const syslinux_unsupported_boot_names[] = {
97         "boot",
98         "bss",
99         "pxe",
100         "fdimage",
101         "comboot",
102         "com32",
103         NULL
104 };
105
106 static struct conf_global_option syslinux_global_options[] = {
107         { .name = "default" },
108         { .name = "implicit" },
109         { .name = "append" },
110         { .name = NULL }
111 };
112
113
114 static void finish_boot_option(struct syslinux_options *state,
115                                bool free_if_unused)
116 {
117         /*
118          * in the normal this function signals a new image block which means
119          * move the current block to the list of processed items
120          * the special case is a label before an image block which we need to
121          * know whether to keep it for further processing or junk it
122          */
123         if (state->current_option) {
124                 if (state->current_option->image) {
125                         list_add(&state->processed_options,
126                                  &state->current_option->list);
127                         state->current_option = NULL;
128                 } else if (free_if_unused) {
129                         talloc_free(state->current_option);
130                         state->current_option = NULL;
131                 }
132         }
133 }
134
135 static bool start_new_option(struct syslinux_options *state)
136 {
137         bool ret = false;
138
139         finish_boot_option(state, false);
140         if (!state->current_option)
141                 state->current_option = talloc_zero(state, struct syslinux_boot_option);
142
143         if (state->current_option)
144                 ret = true;
145
146         return ret;
147 }
148
149 static void syslinux_process_pair(struct conf_context *conf, const char *name, char *value)
150 {
151         struct syslinux_options *state = conf->parser_info;
152         char *buf, *pos, *path;
153         int len, rc;
154
155         /* ignore bare values */
156         if (!name)
157                 return;
158
159         if (conf_param_in_list(syslinux_ignored_names, name))
160                 return;
161
162         /* a new boot entry needs to terminate any prior one */
163         if (conf_param_in_list(syslinux_unsupported_boot_names, name)) {
164                 finish_boot_option(state, true);
165                 return;
166         }
167
168         if (streq(name, "label")) {
169                 finish_boot_option(state, true);
170                 state->current_option = talloc_zero(state,
171                                             struct syslinux_boot_option);
172                 if (state->current_option)
173                         state->current_option->label = talloc_strdup(state, value);
174                 return;
175         }
176
177         if (streq(name, "linux")) {
178
179                 if (start_new_option(state)) {
180                         state->current_option->image = talloc_strdup(state, value);
181                         if (!state->current_option->image) {
182                                 talloc_free(state->current_option);
183                                 state->current_option = NULL;
184                         }
185                 }
186
187                 return;
188         }
189
190         if (streq(name, "kernel")) {
191
192                 if (start_new_option(state)) {
193                 /*
194                  * by spec a linux image can not have any of these
195                  * extensions, it can have no extension or anything not
196                  * in this list
197                  */
198                         pos = strrchr(value, '.');
199                         if (!pos ||
200                         !conf_param_in_list(syslinux_kernel_unsupported_extensions, pos)) {
201                                 state->current_option->image = talloc_strdup(state, value);
202                                 if (!state->current_option->image) {
203                                         talloc_free(state->current_option);
204                                         state->current_option = NULL;
205                                 }
206                         } else  /* clean up any possible trailing label */
207                                 finish_boot_option(state, true);
208                 }
209                 return;
210         }
211
212
213
214         /* APPEND can be global and/or local so need special handling */
215         if (streq(name, "append")) {
216                 if (state->current_option) {
217                         /* by spec only take last if multiple APPENDs */
218                         if (state->current_option->append)
219                                 talloc_free(state->current_option->append);
220                         state->current_option->append = talloc_strdup(state, value);
221                         if (!state->current_option->append) {
222                                 talloc_free(state->current_option);
223                                 state->current_option = NULL;
224                         }
225                 } else {
226                         finish_boot_option(state, true);
227                         conf_set_global_option(conf, name, value);
228                 }
229                 return;
230         }
231
232         /* now the general globals */
233         if (conf_set_global_option(conf, name, value)) {
234                 finish_boot_option(state, true);
235                 return;
236         }
237
238         if (streq(name, "initrd")) {
239                 if (state->current_option) {
240                         state->current_option->initrd = talloc_strdup(state, value);
241                         if (!state->current_option->initrd) {
242                                 talloc_free(state->current_option);
243                                 state->current_option = NULL;
244                         }
245                 }
246                 return;
247         }
248
249         if (streq(name, "include")) {
250                 if (state->include_nest_level < INCLUDE_NEST_LIMIT) {
251                         state->include_nest_level++;
252
253                         /* if absolute in as-is */
254                         if (value[0] == '/')
255                                 path = talloc_strdup(state, value);
256                         else /* otherwise relative to the root config file */
257                                 path = join_paths(state, state->cfg_dir, value);
258
259                         rc = parser_request_file(conf->dc, conf->dc->device, path, &buf, &len);
260                         if (!rc) {
261                                 conf_parse_buf(conf, buf, len);
262
263                                 device_handler_status_dev_info(conf->dc->handler, conf->dc->device,
264                                 _("Parsed nested syslinux configuration from %s"), value);
265                                 talloc_free(buf);
266                         } else {
267                                 device_handler_status_dev_info(conf->dc->handler, conf->dc->device,
268                                 _("Failed to parse nested syslinux configuration from %s"), value);
269                         }
270
271                         talloc_free(path);
272
273                         state->include_nest_level--;
274                 } else {
275                         device_handler_status_dev_err(conf->dc->handler, conf->dc->device,
276                                 _("Nested syslinux INCLUDE exceeds limit...ignored"));
277                 }
278                 return;
279         }
280
281         pb_debug("%s: unknown name: %s\n", __func__, name);
282 }
283
284 static void syslinux_finalize(struct conf_context *conf)
285 {
286         struct syslinux_options *state = conf->parser_info;
287         struct syslinux_boot_option *syslinux_opt, *tmp;
288         struct discover_context *dc = conf->dc;
289         struct discover_boot_option *d_opt;
290         bool implicit_image = true;
291         char *args_sigfile_default;
292         const char *global_default;
293         const char *global_append;
294         struct boot_option *opt;
295         const char *image;
296         const char *label;
297
298         /* clean up any lingering boot entries */
299         finish_boot_option(state, true);
300
301         global_append  = conf_get_global_option(conf, "append");
302         global_default = conf_get_global_option(conf, "default");
303
304         /*
305          * by spec '0' means disable
306          * note we set the default to '1' (which is by spec) in syslinux_parse
307          */
308         if (conf_get_global_option(conf, "implicit"), "0")
309                 implicit_image = false;
310
311         list_for_each_entry(&state->processed_options, syslinux_opt, list) {
312                 /* need a valid image */
313                 if (!syslinux_opt->image)
314                         continue;
315
316                 image = syslinux_opt->image;
317                 label = syslinux_opt->label;
318
319                 /* if implicit is disabled we must have a label */
320                 if (!label && !implicit_image)
321                         continue;
322
323                 d_opt = discover_boot_option_create(dc, dc->device);
324                 if (!d_opt)
325                         continue;
326                 if (!d_opt->option)
327                         goto fail;
328
329                 opt = d_opt->option;
330
331                 if (syslinux_opt->append) {
332                         /* '-' can signal do not use global APPEND */
333                         if (!strcmp(syslinux_opt->append, "-"))
334                                 opt->boot_args = talloc_strdup(opt, "");
335                         else if (global_append)
336                                 opt->boot_args = talloc_asprintf(opt, "%s %s",
337                                                                  global_append,
338                                                                  syslinux_opt->append);
339                         else
340                                 opt->boot_args = talloc_strdup(opt, syslinux_opt->append);
341                 } else if (global_append)
342                         opt->boot_args = talloc_strdup(opt, global_append);
343
344                 if (!opt->boot_args)
345                         goto fail;
346
347                 opt->id = talloc_asprintf(opt, "%s#%s", dc->device->device->id, image);
348
349                 if (!opt->id)
350                         goto fail;
351
352                 if (label) {
353                         opt->name = talloc_strdup(opt, label);
354                         if (!strcmp(label, global_default))
355                                 opt->is_default = true;
356
357                         opt->description = talloc_asprintf(opt, "(%s) %s", label, image);
358                 } else {
359                         opt->name = talloc_strdup(opt, image);
360                         opt->description = talloc_strdup(opt, image);
361                 }
362
363                 if (!opt->name)
364                         goto fail;
365
366                 d_opt->boot_image = create_devpath_resource(d_opt, dc->device, image);
367
368                 if(!d_opt->boot_image)
369                         goto fail;
370
371                 if (syslinux_opt->initrd) {
372                         d_opt->initrd = create_devpath_resource(d_opt, dc->device,
373                                                                 syslinux_opt->initrd);
374                         opt->description = talloc_asprintf_append(opt->description, 
375                                                                   " initrd=%s",
376                                                                   syslinux_opt->initrd);
377
378                         if (!d_opt->initrd)
379                                 goto fail;
380                 }
381
382                 opt->description = talloc_asprintf_append(opt->description,
383                                                           " args=%s",
384                                                           opt->boot_args);
385
386                 if (!opt->description)
387                         goto fail;
388
389                 args_sigfile_default = talloc_asprintf(d_opt, "%s.cmdline.sig",
390                                                        image);
391
392                 if (!args_sigfile_default)
393                         goto fail;
394
395                 d_opt->args_sig_file = create_devpath_resource(d_opt, dc->device,
396                                                                args_sigfile_default);
397
398                 if (!d_opt->args_sig_file)
399                         goto fail;
400
401                 talloc_free(args_sigfile_default);
402
403                 conf_strip_str(opt->boot_args);
404                 conf_strip_str(opt->description);
405
406                 discover_context_add_boot_option(dc, d_opt);
407                 d_opt = NULL;
408                 continue;
409
410 fail:
411                 talloc_free(d_opt);
412         }
413
414         list_for_each_entry_safe(&state->processed_options, syslinux_opt, tmp, list)
415                 talloc_free(syslinux_opt);
416         list_init(&state->processed_options);
417 }
418
419 static int syslinux_parse(struct discover_context *dc)
420 {
421         struct syslinux_options *state;
422         const char * const *filename;
423         struct conf_context *conf;
424         struct list *found_list;
425
426         /* Support block device boot only at present */
427         if (dc->event)
428                 return -1;
429
430         conf = talloc_zero(dc, struct conf_context);
431         if (!conf)
432                 return -1;
433
434         found_list = talloc(conf, struct list);
435         if (!found_list)
436                 return -1;
437         list_init(found_list);
438
439         conf->dc = dc;
440         conf->global_options = syslinux_global_options,
441         conf_init_global_options(conf);
442         conf->get_pair = conf_get_pair_space;
443         conf->process_pair = syslinux_process_pair;
444
445         conf->parser_info = state = talloc_zero(conf, struct syslinux_options);
446         list_init(&state->processed_options);
447
448         /*
449          * set the global defaults
450          * by spec 'default' defaults to 'linux' and
451          * 'implicit' defaults to '1'
452          */
453         conf_set_global_option(conf, "default", "linux");
454         conf_set_global_option(conf, "implicit", "1");
455
456         for (filename = syslinux_conf_files; *filename; filename++) {
457                 char *cfg_dir;
458                 int len, rc;
459                 char *buf;
460
461                 /*
462                  * guard against duplicate entries in case-insensitive
463                  * filesystems, mainly vfat boot partitions
464                  */
465                 if (!parser_is_unique(dc, dc->device, *filename, found_list))
466                         continue;
467
468                 rc = parser_request_file(dc, dc->device, *filename, &buf, &len);
469                 if (rc)
470                         continue;
471
472                 /*
473                  * save location of root config file for possible
474                  * INCLUDE directives later
475                  *
476                  * dirname can overwrite so need local copy to work on
477                  */
478                 cfg_dir = talloc_strdup(conf, *filename);
479                 state->cfg_dir = talloc_strdup(state, dirname(cfg_dir));
480                 talloc_free(cfg_dir);
481
482                 conf_parse_buf(conf, buf, len);
483                 device_handler_status_dev_info(dc->handler, dc->device,
484                                 _("Parsed syslinux configuration from %s"),
485                                 *filename);
486                 talloc_free(buf);
487
488                 syslinux_finalize(conf);
489         }
490
491         talloc_free(conf);
492         return 0;
493 }
494
495 static struct parser syslinux_parser = {
496         .name                   = "syslinux",
497         .parse                  = syslinux_parse,
498         .resolve_resource       = resolve_devpath_resource,
499 };
500
501 register_parser(syslinux_parser);