confparse.c revision 1.1.1.2 1 1.1 christos /* $NetBSD: confparse.c,v 1.1.1.2 2022/04/03 01:08:42 christos Exp $ */
2 1.1 christos
3 1.1 christos /*
4 1.1.1.2 christos * Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
5 1.1 christos *
6 1.1 christos * Permission to use, copy, modify, and distribute this software for any
7 1.1 christos * purpose with or without fee is hereby granted, provided that the above
8 1.1 christos * copyright notice and this permission notice appear in all copies.
9 1.1 christos *
10 1.1 christos * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
11 1.1 christos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 1.1 christos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
13 1.1 christos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 1.1 christos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 1.1 christos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16 1.1 christos * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 1.1 christos *
18 1.1 christos * Internet Systems Consortium, Inc.
19 1.1.1.2 christos * PO Box 360
20 1.1.1.2 christos * Newmarket, NH 03857 USA
21 1.1 christos * <info (at) isc.org>
22 1.1 christos * https://www.isc.org/
23 1.1 christos *
24 1.1 christos */
25 1.1 christos
26 1.1 christos #include <sys/cdefs.h>
27 1.1 christos __RCSID("$NetBSD: confparse.c,v 1.1.1.2 2022/04/03 01:08:42 christos Exp $");
28 1.1 christos
29 1.1 christos /* From server/confpars.c */
30 1.1 christos
31 1.1 christos #include "keama.h"
32 1.1 christos
33 1.1 christos #include <sys/errno.h>
34 1.1 christos #include <arpa/inet.h>
35 1.1 christos #include <assert.h>
36 1.1 christos #include <ctype.h>
37 1.1 christos #include <fcntl.h>
38 1.1 christos #include <time.h>
39 1.1 christos #include <stdlib.h>
40 1.1 christos #include <string.h>
41 1.1 christos
42 1.1 christos /* Print failover stuff once */
43 1.1 christos isc_boolean_t failover_once = ISC_TRUE;
44 1.1 christos
45 1.1 christos /* To manage host-reservation-identifiers */
46 1.1 christos isc_boolean_t use_client_id = ISC_FALSE;
47 1.1 christos isc_boolean_t use_flex_id = ISC_FALSE;
48 1.1 christos isc_boolean_t use_hw_address = ISC_FALSE;
49 1.1 christos
50 1.1 christos /* option and relays used for flexible host identifier */
51 1.1 christos const struct option *host_id_option = NULL;
52 1.1 christos int host_id_relays = 0;
53 1.1 christos
54 1.1 christos /* Simple or complex config */
55 1.1 christos unsigned subnet_counter = 0;
56 1.1 christos
57 1.1 christos /* For subclass name generation */
58 1.1 christos unsigned subclass_counter = 0;
59 1.1 christos
60 1.1 christos /* To map reservations to declared subnets */
61 1.1 christos struct subnet {
62 1.1 christos struct element *subnet;
63 1.1 christos struct element *share;
64 1.1 christos struct string *addr;
65 1.1 christos struct string *mask;
66 1.1 christos TAILQ_ENTRY(subnet) next;
67 1.1 christos };
68 1.1 christos
69 1.1 christos TAILQ_HEAD(subnets, subnet) known_subnets;
70 1.1 christos
71 1.1 christos /* To map pools to subnets inside a shared-network */
72 1.1 christos struct range {
73 1.1 christos struct element *pool;
74 1.1 christos struct element *share;
75 1.1 christos struct string *low;
76 1.1 christos TAILQ_ENTRY(range) next;
77 1.1 christos };
78 1.1 christos
79 1.1 christos TAILQ_HEAD(ranges, range) known_ranges;
80 1.1 christos
81 1.1 christos static void post_process_lifetimes(struct parse *);
82 1.1 christos static size_t post_process_reservations(struct parse *);
83 1.1 christos static void post_process_classes(struct parse *);
84 1.1 christos static void post_process_generated_classes(struct parse *);
85 1.1 christos static void check_depend(struct element *, struct element *);
86 1.1 christos static void post_process_option_definitions(struct parse *);
87 1.1 christos static void add_host_reservation_identifiers(struct parse *, const char *);
88 1.1 christos static void add_host_id_option(struct parse *, const struct option *, int);
89 1.1 christos static void subclass_inherit(struct parse *, struct element *,
90 1.1 christos struct element *);
91 1.1 christos static void add_match_class(struct parse *, struct element *,
92 1.1 christos struct element *);
93 1.1 christos static void option_data_derive(struct parse *, struct handle *,
94 1.1 christos struct handle *);
95 1.1 christos static void derive_classes(struct parse *, struct handle *, struct handle *);
96 1.1 christos static isc_boolean_t is_hexa_only(const char *, unsigned len);
97 1.1 christos static void new_network_interface(struct parse *, struct element *);
98 1.1 christos static struct string *addrmask(const struct string *, const struct string *);
99 1.1 christos static struct element *find_match(struct parse *, struct element *,
100 1.1 christos isc_boolean_t *);
101 1.1 christos static struct element *find_location(struct element *, struct range *);
102 1.1 christos static int get_prefix_length(const char *, const char *);
103 1.1 christos static struct element *get_class(struct parse *, struct element *);
104 1.1 christos static void concat_classes(struct parse *, struct element *, struct element *);
105 1.1 christos static void generate_class(struct parse *, struct element *, struct element *,
106 1.1 christos struct element *);
107 1.1 christos
108 1.1 christos static struct string *CLASS_ALL;
109 1.1 christos static struct string *CLASS_KNOWN;
110 1.1 christos
111 1.1 christos /* Add head config file comments to the DHCP server map */
112 1.1 christos
113 1.1 christos size_t
114 1.1 christos conf_file_parse(struct parse *cfile)
115 1.1 christos {
116 1.1 christos struct element *top;
117 1.1 christos struct element *dhcp;
118 1.1 christos size_t issues;
119 1.1 christos
120 1.1 christos TAILQ_INIT(&known_subnets);
121 1.1 christos TAILQ_INIT(&known_ranges);
122 1.1 christos CLASS_ALL = makeString(-1, "ALL");
123 1.1 christos CLASS_KNOWN = makeString(-1, "KNOWN");
124 1.1 christos
125 1.1 christos top = createMap();
126 1.1 christos top->kind = TOPLEVEL;
127 1.1 christos TAILQ_CONCAT(&top->comments, &cfile->comments);
128 1.1 christos
129 1.1 christos dhcp = createMap();
130 1.1 christos dhcp->kind = ROOT_GROUP;
131 1.1 christos (void) peek_token(NULL, NULL, cfile);
132 1.1 christos TAILQ_CONCAT(&dhcp->comments, &cfile->comments);
133 1.1 christos stackPush(cfile, dhcp);
134 1.1 christos assert(cfile->stack_top == 1);
135 1.1 christos cfile->stack[0] = top;
136 1.1 christos
137 1.1 christos if (local_family == AF_INET)
138 1.1 christos mapSet(top, dhcp, "Dhcp4");
139 1.1 christos else if (local_family == AF_INET6)
140 1.1 christos mapSet(top, dhcp, "Dhcp6");
141 1.1 christos else
142 1.1 christos parse_error(cfile, "address family is not set");
143 1.1 christos
144 1.1 christos issues = conf_file_subparse(cfile, ROOT_GROUP);
145 1.1 christos
146 1.1 christos /* Add a warning when interfaces-config is not present */
147 1.1 christos if (subnet_counter > 0) {
148 1.1 christos struct element *ifconf;
149 1.1 christos
150 1.1 christos ifconf = mapGet(cfile->stack[1], "interfaces-config");
151 1.1 christos if (ifconf == NULL) {
152 1.1 christos struct comment *comment;
153 1.1 christos
154 1.1 christos comment = createComment("/// This configuration "
155 1.1 christos "declares some subnets but "
156 1.1 christos "has no interfaces-config");
157 1.1 christos TAILQ_INSERT_TAIL(&cfile->stack[1]->comments, comment);
158 1.1 christos comment = createComment("/// Reference Kea #245");
159 1.1 christos TAILQ_INSERT_TAIL(&cfile->stack[1]->comments, comment);
160 1.1 christos }
161 1.1 christos }
162 1.1 christos
163 1.1 christos post_process_lifetimes(cfile);
164 1.1 christos if (!global_hr)
165 1.1 christos issues += post_process_reservations(cfile);
166 1.1 christos post_process_classes(cfile);
167 1.1 christos post_process_generated_classes(cfile);
168 1.1 christos post_process_option_definitions(cfile);
169 1.1 christos
170 1.1 christos return issues;
171 1.1 christos }
172 1.1 christos
173 1.1 christos /* Lifetime post-processing */
174 1.1 christos static void
175 1.1 christos post_process_lifetimes(struct parse *cfile)
176 1.1 christos {
177 1.1 christos struct element *entry;
178 1.1 christos
179 1.1 christos entry = mapGet(cfile->stack[1], "valid-lifetime");
180 1.1 christos if ((entry == NULL) && use_isc_lifetimes) {
181 1.1 christos struct comment *comment;
182 1.1 christos
183 1.1 christos /* DEFAULT_DEFAULT_LEASE_TIME is 43200 */
184 1.1 christos entry = createInt(43200);
185 1.1 christos comment = createComment("/// Use ISC DHCP default lifetime");
186 1.1 christos TAILQ_INSERT_TAIL(&entry->comments, comment);
187 1.1 christos mapSet(cfile->stack[1], entry, "valid-lifetime");
188 1.1 christos }
189 1.1 christos
190 1.1 christos entry = mapGet(cfile->stack[1], "min-valid-lifetime");
191 1.1 christos if ((entry == NULL) && use_isc_lifetimes) {
192 1.1 christos struct comment *comment;
193 1.1 christos
194 1.1 christos /* DEFAULT_MIN_LEASE_TIME is 300 */
195 1.1 christos entry = createInt(300);
196 1.1 christos comment = createComment("/// Use ISC DHCP min lifetime");
197 1.1 christos TAILQ_INSERT_TAIL(&entry->comments, comment);
198 1.1 christos mapSet(cfile->stack[1], entry, "min-valid-lifetime");
199 1.1 christos }
200 1.1 christos
201 1.1 christos entry = mapGet(cfile->stack[1], "max-valid-lifetime");
202 1.1 christos if ((entry == NULL) && use_isc_lifetimes) {
203 1.1 christos struct comment *comment;
204 1.1 christos
205 1.1 christos /* DEFAULT_MAX_LEASE_TIME is 86400 */
206 1.1 christos entry = createInt(86400);
207 1.1 christos comment = createComment("/// Use ISC DHCP max lifetime");
208 1.1 christos TAILQ_INSERT_TAIL(&entry->comments, comment);
209 1.1 christos mapSet(cfile->stack[1], entry, "max-valid-lifetime");
210 1.1 christos }
211 1.1 christos
212 1.1 christos /* Done for DHCPv4 */
213 1.1 christos if (local_family == AF_INET)
214 1.1 christos return;
215 1.1 christos
216 1.1 christos /* There is no builtin default for preferred-lifetime,
217 1.1 christos nor min/max values in ISC DHCP. */
218 1.1 christos }
219 1.1 christos
220 1.1 christos /* Reservation post-processing */
221 1.1 christos
222 1.1 christos static size_t
223 1.1 christos post_process_reservations(struct parse *cfile)
224 1.1 christos {
225 1.1 christos struct element *hosts;
226 1.1 christos struct element *orphans;
227 1.1 christos struct element *host;
228 1.1 christos struct element *where;
229 1.1 christos struct element *dest;
230 1.1 christos isc_boolean_t used_heuristic;
231 1.1 christos size_t issues;
232 1.1 christos
233 1.1 christos issues = 0;
234 1.1 christos hosts = mapGet(cfile->stack[1], "reservations");
235 1.1 christos if ((hosts == NULL) || global_hr)
236 1.1 christos return issues;
237 1.1 christos mapRemove(cfile->stack[1], "reservations");
238 1.1 christos orphans = createList();
239 1.1 christos orphans->kind = HOST_DECL;
240 1.1 christos while (listSize(hosts) > 0) {
241 1.1 christos host = listGet(hosts, 0);
242 1.1 christos listRemove(hosts, 0);
243 1.1 christos used_heuristic = ISC_FALSE;
244 1.1 christos where = find_match(cfile, host, &used_heuristic);
245 1.1 christos if (where == cfile->stack[1])
246 1.1 christos dest = orphans;
247 1.1 christos else
248 1.1 christos dest = mapGet(where, "reservations");
249 1.1 christos if (dest == NULL) {
250 1.1 christos dest = createList();
251 1.1 christos dest->kind = HOST_DECL;
252 1.1 christos mapSet(where, dest, "reservations");
253 1.1 christos }
254 1.1 christos listPush(dest, host);
255 1.1 christos }
256 1.1 christos if (listSize(orphans) > 0) {
257 1.1 christos struct comment *comment;
258 1.1 christos
259 1.1 christos comment = createComment("/// Orphan reservations");
260 1.1 christos TAILQ_INSERT_TAIL(&orphans->comments, comment);
261 1.1 christos comment = createComment("/// Kea reservations are per subnet");
262 1.1 christos TAILQ_INSERT_TAIL(&orphans->comments, comment);
263 1.1 christos comment = createComment("/// Reference Kea #231");
264 1.1 christos TAILQ_INSERT_TAIL(&orphans->comments, comment);
265 1.1 christos orphans->skip = ISC_TRUE;
266 1.1 christos issues++;
267 1.1 christos mapSet(cfile->stack[1], orphans, "reservations");
268 1.1 christos }
269 1.1 christos return issues;
270 1.1 christos }
271 1.1 christos
272 1.1 christos /* Cleanup classes */
273 1.1 christos
274 1.1 christos static void
275 1.1 christos post_process_classes(struct parse *cfile)
276 1.1 christos {
277 1.1 christos struct element *classes;
278 1.1 christos struct element *class;
279 1.1 christos struct element *name;
280 1.1 christos struct element *entry;
281 1.1 christos struct element *reduced;
282 1.1 christos struct string *msg;
283 1.1 christos struct comment *comment;
284 1.1 christos isc_boolean_t lose;
285 1.1 christos size_t i;
286 1.1 christos
287 1.1 christos classes = mapGet(cfile->stack[1], "client-classes");
288 1.1 christos if ((classes == NULL) || (listSize(classes) == 0))
289 1.1 christos return;
290 1.1 christos for (i = 0; i < listSize(classes); i++) {
291 1.1 christos class = listGet(classes, i);
292 1.1 christos if ((class == NULL) || (class->type != ELEMENT_MAP))
293 1.1 christos parse_error(cfile, "null global class at %i",
294 1.1 christos (unsigned)i);
295 1.1 christos name = mapGet(class, "name");
296 1.1 christos if ((name == NULL) || (name->type != ELEMENT_STRING))
297 1.1 christos parse_error(cfile, "global class at %u "
298 1.1 christos "without a name", (unsigned)i);
299 1.1 christos if (!mapContains(class, "super"))
300 1.1 christos goto cleanup_superclass;
301 1.1 christos
302 1.1 christos /* cleanup subclass */
303 1.1 christos mapRemove(class,"super");
304 1.1 christos entry = mapGet(class, "string");
305 1.1 christos if (entry != NULL) {
306 1.1 christos if (entry->type != ELEMENT_STRING)
307 1.1 christos parse_error(cfile, "subclass %s has "
308 1.1 christos "a bad string selector",
309 1.1 christos stringValue(name)->content);
310 1.1 christos msg = makeString(-1, "/// subclass selector ");
311 1.1 christos appendString(msg, "'");
312 1.1 christos concatString(msg, stringValue(entry));
313 1.1 christos appendString(msg, "'");
314 1.1 christos comment = createComment(msg->content);
315 1.1 christos TAILQ_INSERT_TAIL(&class->comments, comment);
316 1.1 christos mapRemove(class, "string");
317 1.1 christos continue;
318 1.1 christos }
319 1.1 christos entry = mapGet(class, "binary");
320 1.1 christos if (entry == NULL)
321 1.1 christos parse_error(cfile, "subclass %s has no selector",
322 1.1 christos stringValue(name)->content);
323 1.1 christos msg = makeString(-1, "/// subclass selector 0x");
324 1.1 christos concatString(msg, stringValue(entry));
325 1.1 christos comment = createComment(msg->content);
326 1.1 christos TAILQ_INSERT_TAIL(&class->comments, comment);
327 1.1 christos mapRemove(class, "binary");
328 1.1 christos
329 1.1 christos cleanup_superclass:
330 1.1 christos /* cleanup superclass */
331 1.1 christos entry = mapGet(class, "spawning");
332 1.1 christos if (entry == NULL)
333 1.1 christos goto cleanup_class;
334 1.1 christos if (entry->type != ELEMENT_BOOLEAN)
335 1.1 christos parse_error(cfile, "superclass %s has bad "
336 1.1 christos "spawning flag",
337 1.1 christos stringValue(name)->content);
338 1.1 christos if (boolValue(entry)) {
339 1.1 christos msg = makeString(-1, "/// Spawning classes "
340 1.1 christos "are not supported by Kea");
341 1.1 christos comment = createComment(msg->content);
342 1.1 christos TAILQ_INSERT_TAIL(&class->comments, comment);
343 1.1 christos msg = makeString(-1, "/// Reference Kea #248");
344 1.1 christos comment = createComment(msg->content);
345 1.1 christos TAILQ_INSERT_TAIL(&class->comments, comment);
346 1.1 christos msg = makeString(-1, "/// spawn with: ");
347 1.1 christos } else
348 1.1 christos msg = makeString(-1, "/// match: ");
349 1.1 christos entry = mapGet(class, "submatch");
350 1.1 christos
351 1.1 christos if (entry == NULL)
352 1.1 christos parse_error(cfile, "superclass %s has no submatch",
353 1.1 christos stringValue(name)->content);
354 1.1 christos lose = ISC_FALSE;
355 1.1 christos appendString(msg, print_data_expression(entry, &lose));
356 1.1 christos if (!lose) {
357 1.1 christos comment = createComment(msg->content);
358 1.1 christos TAILQ_INSERT_TAIL(&class->comments, comment);
359 1.1 christos mapRemove(class, "spawning");
360 1.1 christos mapRemove(class, "submatch");
361 1.1 christos }
362 1.1 christos
363 1.1 christos cleanup_class:
364 1.1 christos /* cleanup class */
365 1.1 christos entry = mapGet(class, "match-if");
366 1.1 christos if (entry == NULL)
367 1.1 christos continue;
368 1.1 christos reduced = mapGet(class, "test");
369 1.1 christos lose = ISC_FALSE;
370 1.1 christos if (reduced != NULL)
371 1.1 christos msg = makeString(-1, "/// from: match if ");
372 1.1 christos else
373 1.1 christos msg = makeString(-1, "/// match if ");
374 1.1 christos appendString(msg, print_boolean_expression(entry, &lose));
375 1.1 christos if (!lose) {
376 1.1 christos comment = createComment(msg->content);
377 1.1 christos if (reduced != NULL) {
378 1.1 christos TAILQ_INSERT_TAIL(&reduced->comments, comment);
379 1.1 christos mapRemove(class, "match-if");
380 1.1 christos continue;
381 1.1 christos }
382 1.1 christos TAILQ_INSERT_TAIL(&entry->comments, comment);
383 1.1 christos }
384 1.1 christos }
385 1.1 christos }
386 1.1 christos
387 1.1 christos /* Move generated client classes to the end of client class list */
388 1.1 christos
389 1.1 christos static void
390 1.1 christos post_process_generated_classes(struct parse *cfile)
391 1.1 christos {
392 1.1 christos struct element *generated;
393 1.1 christos struct element *classes;
394 1.1 christos struct element *class;
395 1.1 christos
396 1.1 christos generated = mapGet(cfile->stack[1], "generated-classes");
397 1.1 christos if (generated == NULL)
398 1.1 christos return;
399 1.1 christos mapRemove(cfile->stack[1], "generated-classes");
400 1.1 christos if (listSize(generated) == 0)
401 1.1 christos return;
402 1.1 christos classes = mapGet(cfile->stack[1], "client-classes");
403 1.1 christos if (classes == NULL) {
404 1.1 christos classes = createList();
405 1.1 christos mapSet(cfile->stack[1], classes, "client-classes");
406 1.1 christos }
407 1.1 christos
408 1.1 christos while (listSize(generated) > 0) {
409 1.1 christos class = listGet(generated, 0);
410 1.1 christos listRemove(generated, 0);
411 1.1 christos check_depend(class, classes);
412 1.1 christos listPush(classes, class);
413 1.1 christos }
414 1.1 christos }
415 1.1 christos
416 1.1 christos static void
417 1.1 christos check_depend(struct element *class, struct element *classes)
418 1.1 christos {
419 1.1 christos struct element *list;
420 1.1 christos
421 1.1 christos if (!mapContains(class, "depend"))
422 1.1 christos return;
423 1.1 christos list = mapGet(class, "depend");
424 1.1 christos mapRemove(class, "depend");
425 1.1 christos while (listSize(list) > 0) {
426 1.1 christos struct element *depend;
427 1.1 christos struct string *dname;
428 1.1 christos struct string *msg;
429 1.1 christos struct comment *comment;
430 1.1 christos isc_boolean_t found;
431 1.1 christos size_t i;
432 1.1 christos
433 1.1 christos depend = listGet(list, 0);
434 1.1 christos listRemove(list, 0);
435 1.1 christos assert(depend != NULL);
436 1.1 christos assert(depend->type == ELEMENT_STRING);
437 1.1 christos dname = stringValue(depend);
438 1.1 christos if (eqString(dname, CLASS_ALL) ||
439 1.1 christos eqString(dname, CLASS_KNOWN))
440 1.1 christos continue;
441 1.1 christos found = ISC_FALSE;
442 1.1 christos for (i = 0; i < listSize(classes); i++) {
443 1.1 christos struct element *item;
444 1.1 christos struct element *name;
445 1.1 christos
446 1.1 christos item = listGet(classes, i);
447 1.1 christos assert(item != NULL);
448 1.1 christos assert(item->type == ELEMENT_MAP);
449 1.1 christos name = mapGet(item, "name");
450 1.1 christos if (name == NULL)
451 1.1 christos continue;
452 1.1 christos assert(name->type == ELEMENT_STRING);
453 1.1 christos if (eqString(stringValue(name), dname)) {
454 1.1 christos found = ISC_TRUE;
455 1.1 christos break;
456 1.1 christos }
457 1.1 christos }
458 1.1 christos if (found)
459 1.1 christos continue;
460 1.1 christos msg = makeString(-1, "/// Depend on missing '");
461 1.1 christos concatString(msg, dname);
462 1.1 christos appendString(msg, "' class");
463 1.1 christos comment = createComment(msg->content);
464 1.1 christos TAILQ_INSERT_TAIL(&class->comments, comment);
465 1.1 christos class->skip = ISC_TRUE;
466 1.1 christos }
467 1.1 christos }
468 1.1 christos
469 1.1 christos static void
470 1.1 christos post_process_option_definitions(struct parse *cfile)
471 1.1 christos {
472 1.1 christos struct element *opt_def;
473 1.1 christos struct element *def, *ndef;
474 1.1 christos
475 1.1 christos opt_def = mapGet(cfile->stack[1], "option-def");
476 1.1 christos if (opt_def == NULL)
477 1.1 christos return;
478 1.1 christos TAILQ_FOREACH_SAFE(def, &opt_def->value.list_value, ndef) {
479 1.1 christos if (mapContains(def, "no-export"))
480 1.1 christos TAILQ_REMOVE(&opt_def->value.list_value, def);
481 1.1 christos }
482 1.1 christos }
483 1.1 christos
484 1.1 christos void
485 1.1 christos read_conf_file(struct parse *parent, const char *filename, int group_type)
486 1.1 christos {
487 1.1 christos int file;
488 1.1 christos struct parse *cfile;
489 1.1 christos struct string *msg;
490 1.1 christos struct comment *comment;
491 1.1 christos size_t amount = parent->stack_size * sizeof(struct element *);
492 1.1 christos
493 1.1 christos if ((file = open (filename, O_RDONLY)) < 0)
494 1.1 christos
495 1.1 christos parse_error(parent, "Can't open %s: %s",
496 1.1 christos filename, strerror(errno));
497 1.1 christos
498 1.1 christos cfile = new_parse(file, NULL, 0, filename, 0);
499 1.1 christos if (cfile == NULL)
500 1.1 christos parse_error(parent, "Can't create new parse structure");
501 1.1 christos
502 1.1 christos cfile->stack = (struct element **)malloc(amount);
503 1.1 christos if (cfile->stack == NULL)
504 1.1 christos parse_error(parent, "Can't create new element stack");
505 1.1 christos memcpy(cfile->stack, parent->stack, amount);
506 1.1 christos cfile->stack_size = parent->stack_size;
507 1.1 christos cfile->stack_top = parent->stack_top;
508 1.1 christos cfile->issue_counter = parent->issue_counter;
509 1.1 christos
510 1.1 christos msg = makeString(-1, "/// Begin file ");
511 1.1 christos concatString(msg, makeString(-1, filename));
512 1.1 christos comment = createComment(msg->content);
513 1.1 christos TAILQ_INSERT_TAIL(&cfile->comments, comment);
514 1.1 christos
515 1.1 christos conf_file_subparse(cfile, group_type);
516 1.1 christos
517 1.1 christos amount = cfile->stack_size * sizeof(struct element *);
518 1.1 christos if (cfile->stack_size > parent->stack_size) {
519 1.1 christos parent->stack =
520 1.1 christos (struct element **)realloc(parent->stack, amount);
521 1.1 christos if (parent->stack == NULL)
522 1.1 christos parse_error(cfile, "can't resize element stack");
523 1.1 christos }
524 1.1 christos memcpy(parent->stack, cfile->stack, amount);
525 1.1 christos parent->stack_size = cfile->stack_size;
526 1.1 christos parent->stack_top = cfile->stack_top;
527 1.1 christos parent->issue_counter = cfile->issue_counter;
528 1.1 christos msg = makeString(-1, "/// End file ");
529 1.1 christos concatString(msg, makeString(-1, filename));
530 1.1 christos comment= createComment(msg->content);
531 1.1 christos TAILQ_INSERT_TAIL(&parent->comments, comment);
532 1.1 christos end_parse(cfile);
533 1.1 christos }
534 1.1 christos
535 1.1 christos /* conf-file :== parameters declarations END_OF_FILE
536 1.1 christos parameters :== <nil> | parameter | parameters parameter
537 1.1 christos declarations :== <nil> | declaration | declarations declaration */
538 1.1 christos
539 1.1 christos size_t
540 1.1 christos conf_file_subparse(struct parse *cfile, int type)
541 1.1 christos {
542 1.1 christos const char *val;
543 1.1 christos enum dhcp_token token;
544 1.1 christos isc_boolean_t declaration = ISC_FALSE;
545 1.1 christos
546 1.1 christos for (;;) {
547 1.1 christos token = peek_token(&val, NULL, cfile);
548 1.1 christos if (token == END_OF_FILE)
549 1.1 christos break;
550 1.1 christos declaration = parse_statement(cfile, type, declaration);
551 1.1 christos }
552 1.1 christos skip_token(&val, NULL, cfile);
553 1.1 christos
554 1.1 christos return cfile->issue_counter;
555 1.1 christos }
556 1.1 christos
557 1.1 christos /* statement :== parameter | declaration | PERCENT directive
558 1.1 christos
559 1.1 christos parameter :== DEFAULT_LEASE_TIME lease_time
560 1.1 christos | MAX_LEASE_TIME lease_time
561 1.1 christos | DYNAMIC_BOOTP_LEASE_CUTOFF date
562 1.1 christos | DYNAMIC_BOOTP_LEASE_LENGTH lease_time
563 1.1 christos | BOOT_UNKNOWN_CLIENTS boolean
564 1.1 christos | ONE_LEASE_PER_CLIENT boolean
565 1.1 christos | GET_LEASE_HOSTNAMES boolean
566 1.1 christos | USE_HOST_DECL_NAME boolean
567 1.1 christos | NEXT_SERVER ip-addr-or-hostname SEMI
568 1.1 christos | option_parameter
569 1.1 christos | SERVER-IDENTIFIER ip-addr-or-hostname SEMI
570 1.1 christos | FILENAME string-parameter
571 1.1 christos | SERVER_NAME string-parameter
572 1.1 christos | hardware-parameter
573 1.1 christos | fixed-address-parameter
574 1.1 christos | ALLOW allow-deny-keyword
575 1.1 christos | DENY allow-deny-keyword
576 1.1 christos | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean
577 1.1 christos | AUTHORITATIVE
578 1.1 christos | NOT AUTHORITATIVE
579 1.1 christos
580 1.1 christos declaration :== host-declaration
581 1.1 christos | group-declaration
582 1.1 christos | shared-network-declaration
583 1.1 christos | subnet-declaration
584 1.1 christos | VENDOR_CLASS class-declaration
585 1.1 christos | USER_CLASS class-declaration
586 1.1 christos | RANGE address-range-declaration */
587 1.1 christos
588 1.1 christos isc_boolean_t
589 1.1 christos parse_statement(struct parse *cfile, int type, isc_boolean_t declaration)
590 1.1 christos {
591 1.1 christos enum dhcp_token token;
592 1.1 christos const char *val;
593 1.1 christos struct element *hardware;
594 1.1 christos struct element *cache;
595 1.1 christos struct element *et;
596 1.1 christos isc_boolean_t lose;
597 1.1 christos isc_boolean_t known;
598 1.1 christos isc_boolean_t authoritative;
599 1.1 christos struct option *option;
600 1.1 christos size_t host_decl = 0;
601 1.1 christos size_t subnet = 0;
602 1.1 christos size_t i;
603 1.1 christos
604 1.1 christos token = peek_token(&val, NULL, cfile);
605 1.1 christos
606 1.1 christos switch (token) {
607 1.1 christos case INCLUDE:
608 1.1 christos skip_token(&val, NULL, cfile);
609 1.1 christos token = next_token(&val, NULL, cfile);
610 1.1 christos if (token != STRING)
611 1.1 christos parse_error(cfile, "filename string expected.");
612 1.1 christos read_conf_file(cfile, val, type);
613 1.1 christos parse_semi(cfile);
614 1.1 christos return 1;
615 1.1.1.2 christos
616 1.1 christos case HOST:
617 1.1 christos skip_token(&val, NULL, cfile);
618 1.1 christos if (type != HOST_DECL && type != CLASS_DECL)
619 1.1 christos parse_host_declaration(cfile);
620 1.1 christos else
621 1.1 christos parse_error(cfile,
622 1.1 christos "host declarations not allowed here.");
623 1.1 christos return 1;
624 1.1 christos
625 1.1 christos case GROUP:
626 1.1 christos skip_token(&val, NULL, cfile);
627 1.1 christos if (type != HOST_DECL && type != CLASS_DECL)
628 1.1 christos parse_group_declaration(cfile);
629 1.1 christos else
630 1.1 christos parse_error(cfile,
631 1.1 christos "group declarations not allowed here.");
632 1.1 christos return 1;
633 1.1 christos
634 1.1 christos case SHARED_NETWORK:
635 1.1 christos skip_token(&val, NULL, cfile);
636 1.1 christos if (type == SHARED_NET_DECL ||
637 1.1 christos type == HOST_DECL ||
638 1.1 christos type == SUBNET_DECL ||
639 1.1 christos type == CLASS_DECL)
640 1.1 christos parse_error(cfile, "shared-network parameters not %s.",
641 1.1 christos "allowed here");
642 1.1 christos parse_shared_net_declaration(cfile);
643 1.1 christos return 1;
644 1.1 christos
645 1.1 christos case SUBNET:
646 1.1 christos case SUBNET6:
647 1.1 christos skip_token(&val, NULL, cfile);
648 1.1 christos if (type == HOST_DECL || type == SUBNET_DECL ||
649 1.1 christos type == CLASS_DECL)
650 1.1 christos parse_error(cfile,
651 1.1 christos "subnet declarations not allowed here.");
652 1.1 christos
653 1.1 christos if (token == SUBNET)
654 1.1 christos parse_subnet_declaration(cfile);
655 1.1 christos else
656 1.1 christos parse_subnet6_declaration(cfile);
657 1.1 christos return 1;
658 1.1 christos
659 1.1 christos case VENDOR_CLASS:
660 1.1 christos case USER_CLASS:
661 1.1 christos case CLASS:
662 1.1 christos case SUBCLASS:
663 1.1 christos skip_token(&val, NULL, cfile);
664 1.1 christos if (token == VENDOR_CLASS)
665 1.1 christos parse_error(cfile, "obsolete 'vendor-class' "
666 1.1 christos "declaration");
667 1.1 christos if (token == USER_CLASS)
668 1.1 christos parse_error(cfile, "obsolete 'user-class' "
669 1.1 christos "declaration");
670 1.1 christos if (type == CLASS_DECL)
671 1.1 christos parse_error(cfile,
672 1.1 christos "class declarations not allowed here.");
673 1.1 christos parse_class_declaration(cfile, token == CLASS
674 1.1 christos ? CLASS_TYPE_CLASS
675 1.1 christos : CLASS_TYPE_SUBCLASS);
676 1.1 christos return 1;
677 1.1 christos
678 1.1 christos case HARDWARE:
679 1.1 christos if (!use_hw_address) {
680 1.1 christos add_host_reservation_identifiers(cfile,
681 1.1 christos "hw-address");
682 1.1 christos use_hw_address = ISC_TRUE;
683 1.1 christos }
684 1.1 christos
685 1.1 christos skip_token(&val, NULL, cfile);
686 1.1 christos if (!host_decl) {
687 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
688 1.1 christos if (cfile->stack[i]->kind == HOST_DECL) {
689 1.1 christos host_decl = i;
690 1.1 christos break;
691 1.1 christos }
692 1.1 christos }
693 1.1 christos }
694 1.1 christos if (!host_decl)
695 1.1 christos parse_error(cfile, "hardware address parameter %s",
696 1.1 christos "not allowed here.");
697 1.1 christos if (mapContains(cfile->stack[host_decl], "hw-address"))
698 1.1 christos parse_error(cfile, "Host hardware address already "
699 1.1 christos "configured.");
700 1.1 christos hardware = parse_hardware_param(cfile);
701 1.1 christos mapSet(cfile->stack[host_decl], hardware, "hw-address");
702 1.1 christos if (hardware->skip)
703 1.1 christos cfile->stack[host_decl]->skip = ISC_TRUE;
704 1.1 christos break;
705 1.1 christos
706 1.1 christos case FIXED_ADDR:
707 1.1 christos case FIXED_ADDR6:
708 1.1 christos skip_token(&val, NULL, cfile);
709 1.1 christos if (!host_decl) {
710 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
711 1.1 christos if (cfile->stack[i]->kind == HOST_DECL) {
712 1.1 christos host_decl = i;
713 1.1 christos break;
714 1.1 christos }
715 1.1 christos }
716 1.1 christos }
717 1.1 christos if (!host_decl)
718 1.1 christos parse_error(cfile,
719 1.1 christos "fixed-address parameter not "
720 1.1 christos "allowed here.");
721 1.1 christos cache = parse_fixed_addr_param(cfile, token);
722 1.1 christos if (token == FIXED_ADDR) {
723 1.1 christos struct element *addr;
724 1.1 christos
725 1.1 christos if (mapContains(cfile->stack[host_decl], "ip-address"))
726 1.1 christos parse_error(cfile, "Only one fixed address "
727 1.1 christos "declaration per host.");
728 1.1 christos addr = listGet(cache, 0);
729 1.1 christos listRemove(cache, 0);
730 1.1 christos mapSet(cfile->stack[host_decl], addr, "ip-address");
731 1.1 christos if (listSize(cache) > 0) {
732 1.1 christos cache->skip = ISC_TRUE;
733 1.1 christos cfile->issue_counter++;
734 1.1 christos mapSet(cfile->stack[host_decl],
735 1.1 christos cache, "extra-ip-addresses");
736 1.1 christos }
737 1.1 christos } else {
738 1.1 christos if (mapContains(cfile->stack[host_decl],
739 1.1 christos "ip-addresses"))
740 1.1 christos parse_error(cfile, "Only one fixed address "
741 1.1 christos "declaration per host.");
742 1.1 christos mapSet(cfile->stack[host_decl], cache, "ip-addresses");
743 1.1 christos }
744 1.1 christos break;
745 1.1 christos
746 1.1 christos case POOL:
747 1.1 christos skip_token(&val, NULL, cfile);
748 1.1 christos if (type == POOL_DECL)
749 1.1 christos parse_error(cfile, "pool declared within pool.");
750 1.1 christos if (type != SUBNET_DECL && type != SHARED_NET_DECL)
751 1.1 christos parse_error(cfile, "pool declared outside of network");
752 1.1 christos parse_pool_statement(cfile, type);
753 1.1 christos
754 1.1 christos return declaration;
755 1.1 christos
756 1.1 christos case RANGE:
757 1.1 christos skip_token(&val, NULL, cfile);
758 1.1 christos if (!subnet) {
759 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
760 1.1 christos if (cfile->stack[i]->kind == SUBNET_DECL) {
761 1.1 christos subnet = i;
762 1.1 christos break;
763 1.1 christos }
764 1.1 christos }
765 1.1 christos }
766 1.1 christos if (type != SUBNET_DECL || !subnet)
767 1.1 christos parse_error(cfile,
768 1.1 christos "range declaration not allowed here.");
769 1.1 christos parse_address_range(cfile, type, subnet);
770 1.1 christos return declaration;
771 1.1 christos
772 1.1 christos case RANGE6:
773 1.1 christos if (local_family != AF_INET6)
774 1.1 christos goto unknown;
775 1.1 christos skip_token(NULL, NULL, cfile);
776 1.1 christos if (!subnet) {
777 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
778 1.1 christos if (cfile->stack[i]->kind == SUBNET_DECL) {
779 1.1 christos subnet = i;
780 1.1 christos break;
781 1.1 christos }
782 1.1 christos }
783 1.1 christos }
784 1.1 christos if ((type != SUBNET_DECL) || !subnet)
785 1.1 christos parse_error(cfile,
786 1.1 christos "range6 declaration not allowed here.");
787 1.1 christos parse_address_range6(cfile, type, subnet);
788 1.1 christos return declaration;
789 1.1 christos
790 1.1 christos case PREFIX6:
791 1.1 christos if (local_family != AF_INET6)
792 1.1 christos goto unknown;
793 1.1 christos skip_token(NULL, NULL, cfile);
794 1.1 christos if (!subnet) {
795 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
796 1.1 christos if (cfile->stack[i]->kind == SUBNET_DECL) {
797 1.1 christos subnet = i;
798 1.1 christos break;
799 1.1 christos }
800 1.1 christos }
801 1.1 christos }
802 1.1 christos if ((type != SUBNET_DECL) || !subnet)
803 1.1 christos parse_error(cfile,
804 1.1 christos "prefix6 declaration not allowed here.");
805 1.1 christos parse_prefix6(cfile, type, subnet);
806 1.1 christos return declaration;
807 1.1 christos
808 1.1 christos case FIXED_PREFIX6:
809 1.1 christos if (local_family != AF_INET6)
810 1.1 christos goto unknown;
811 1.1 christos skip_token(&val, NULL, cfile);
812 1.1 christos if (!host_decl) {
813 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
814 1.1 christos if (cfile->stack[i]->kind == HOST_DECL) {
815 1.1 christos host_decl = i;
816 1.1 christos break;
817 1.1 christos }
818 1.1 christos }
819 1.1 christos }
820 1.1 christos if (!host_decl)
821 1.1 christos parse_error(cfile,
822 1.1 christos "fixed-prefix6 declaration not "
823 1.1 christos "allowed here.");
824 1.1 christos parse_fixed_prefix6(cfile, host_decl);
825 1.1 christos break;
826 1.1 christos
827 1.1 christos case POOL6:
828 1.1 christos if (local_family != AF_INET6)
829 1.1 christos goto unknown;
830 1.1 christos skip_token(&val, NULL, cfile);
831 1.1 christos if (type == POOL_DECL)
832 1.1 christos parse_error(cfile, "pool6 declared within pool.");
833 1.1 christos if (type != SUBNET_DECL)
834 1.1 christos parse_error(cfile,
835 1.1 christos "pool6 declared outside of network");
836 1.1 christos parse_pool6_statement(cfile, type);
837 1.1 christos
838 1.1 christos return declaration;
839 1.1 christos
840 1.1 christos case TOKEN_NOT:
841 1.1 christos skip_token(&val, NULL, cfile);
842 1.1 christos token = next_token(&val, NULL, cfile);
843 1.1 christos switch (token) {
844 1.1 christos case AUTHORITATIVE:
845 1.1 christos authoritative = ISC_FALSE;
846 1.1 christos goto authoritative;
847 1.1 christos default:
848 1.1 christos parse_error(cfile, "expecting assertion");
849 1.1 christos }
850 1.1 christos break;
851 1.1 christos
852 1.1 christos case AUTHORITATIVE:
853 1.1 christos skip_token(&val, NULL, cfile);
854 1.1 christos authoritative = ISC_TRUE;
855 1.1 christos authoritative:
856 1.1 christos if (type == HOST_DECL)
857 1.1 christos parse_error(cfile, "authority makes no sense here.");
858 1.1 christos if (!subnet) {
859 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
860 1.1 christos int kind;
861 1.1 christos
862 1.1 christos kind = cfile->stack[i]->kind;
863 1.1 christos if ((kind == SUBNET_DECL) ||
864 1.1 christos (kind == SHARED_NET_DECL) ||
865 1.1 christos (kind == ROOT_GROUP)) {
866 1.1 christos subnet = i;
867 1.1 christos break;
868 1.1 christos }
869 1.1 christos }
870 1.1 christos }
871 1.1 christos if (!subnet)
872 1.1 christos parse_error(cfile, "can't find root group");
873 1.1 christos if (local_family == AF_INET) {
874 1.1 christos cache = createBool(authoritative);
875 1.1 christos TAILQ_CONCAT(&cache->comments, &cfile->comments);
876 1.1 christos mapSet(cfile->stack[subnet], cache, "authoritative");
877 1.1 christos }
878 1.1 christos parse_semi(cfile);
879 1.1 christos break;
880 1.1 christos
881 1.1 christos /* "server-identifier" is a special hack, equivalent to
882 1.1 christos "option dhcp-server-identifier". */
883 1.1 christos case SERVER_IDENTIFIER:
884 1.1 christos option = option_lookup_code("dhcp",
885 1.1 christos DHO_DHCP_SERVER_IDENTIFIER);
886 1.1 christos assert(option);
887 1.1 christos skip_token(&val, NULL, cfile);
888 1.1 christos goto finish_option;
889 1.1 christos
890 1.1 christos case OPTION:
891 1.1 christos skip_token(&val, NULL, cfile);
892 1.1 christos token = peek_token(&val, NULL, cfile);
893 1.1 christos if (token == SPACE) {
894 1.1 christos if (type != ROOT_GROUP)
895 1.1 christos parse_error(cfile,
896 1.1 christos "option space definitions %s",
897 1.1 christos "may not be scoped.");
898 1.1 christos parse_option_space_decl(cfile);
899 1.1 christos return declaration;
900 1.1 christos }
901 1.1 christos
902 1.1 christos known = ISC_FALSE;
903 1.1 christos option = parse_option_name(cfile, ISC_TRUE, &known);
904 1.1 christos token = peek_token(&val, NULL, cfile);
905 1.1 christos if (token == CODE) {
906 1.1 christos if (type != ROOT_GROUP)
907 1.1 christos parse_error(cfile,
908 1.1 christos "option definitions%s",
909 1.1 christos " may not be scoped.");
910 1.1 christos skip_token(&val, NULL, cfile);
911 1.1 christos
912 1.1 christos /* next function must deal with redefinitions */
913 1.1 christos parse_option_code_definition(cfile, option);
914 1.1 christos return declaration;
915 1.1 christos }
916 1.1 christos /* If this wasn't an option code definition, don't
917 1.1 christos allow an unknown option. */
918 1.1 christos if (!known)
919 1.1 christos parse_error(cfile, "unknown option %s.%s",
920 1.1 christos option->space->old, option->old);
921 1.1 christos finish_option:
922 1.1 christos parse_option_statement(NULL, cfile, option,
923 1.1 christos supersede_option_statement);
924 1.1 christos return declaration;
925 1.1 christos
926 1.1 christos case FAILOVER:
927 1.1 christos if (failover_once)
928 1.1 christos fprintf(stderr, "ignoring failover\n");
929 1.1 christos failover_once = ISC_FALSE;
930 1.1 christos skip_to_semi(cfile);
931 1.1 christos break;
932 1.1.1.2 christos
933 1.1 christos case SERVER_DUID:
934 1.1 christos if (local_family != AF_INET6)
935 1.1 christos goto unknown;
936 1.1 christos parse_server_duid_conf(cfile);
937 1.1 christos break;
938 1.1 christos
939 1.1 christos case LEASE_ID_FORMAT:
940 1.1 christos token = next_token(&val, NULL, cfile);
941 1.1 christos /* ignore: ISC DHCP specific */
942 1.1 christos break;
943 1.1 christos
944 1.1 christos case PERCENT:
945 1.1 christos skip_token(&val, NULL, cfile);
946 1.1 christos if (type != ROOT_GROUP)
947 1.1 christos parse_error(cfile, "directives are only supported "
948 1.1 christos "at toplevel");
949 1.1 christos parse_directive(cfile);
950 1.1 christos return declaration;
951 1.1 christos
952 1.1 christos unknown:
953 1.1 christos skip_token(&val, NULL, cfile);
954 1.1 christos
955 1.1 christos default:
956 1.1 christos et = createMap();
957 1.1 christos TAILQ_CONCAT(&et->comments, &cfile->comments);
958 1.1 christos lose = ISC_FALSE;
959 1.1 christos if (!parse_executable_statement(et, cfile, &lose,
960 1.1 christos context_any, ISC_TRUE)) {
961 1.1 christos if (!lose) {
962 1.1 christos if (declaration)
963 1.1 christos parse_error(cfile,
964 1.1 christos "expecting a declaration");
965 1.1 christos else
966 1.1 christos parse_error(cfile,
967 1.1 christos "expecting a parameter %s",
968 1.1 christos "or declaration");
969 1.1 christos }
970 1.1 christos return declaration;
971 1.1 christos }
972 1.1 christos if (mapSize(et) == 0)
973 1.1 christos return declaration;
974 1.1.1.2 christos
975 1.1 christos et->skip = ISC_TRUE;
976 1.1 christos cfile->issue_counter++;
977 1.1 christos mapSet(cfile->stack[cfile->stack_top], et, "statement");
978 1.1 christos }
979 1.1 christos
980 1.1 christos return 0;
981 1.1 christos }
982 1.1 christos
983 1.1 christos /*!
984 1.1.1.2 christos *
985 1.1 christos * \brief Parse allow and deny statements
986 1.1 christos *
987 1.1 christos * This function handles the common processing code for permit and deny
988 1.1 christos * statements in the parse_pool_statement and parse_pool6_statement functions.
989 1.1.1.2 christos *
990 1.1 christos * The allow or deny token should already be consumed, this function expects
991 1.1 christos * one of the following:
992 1.1 christos * known-clients;
993 1.1 christos * unknown-clients;
994 1.1 christos * known clients;
995 1.1 christos * unknown clients;
996 1.1 christos * authenticated clients;
997 1.1 christos * unauthenticated clients;
998 1.1 christos * all clients;
999 1.1 christos * dynamic bootp clients;
1000 1.1 christos * members of <class name>;
1001 1.1 christos * after <date>;
1002 1.1 christos *
1003 1.1 christos * \param[in] cfile = the configuration file being parsed
1004 1.1 christos * \param[in] permit_head = the head of the permit list (permit or prohibit)
1005 1.1 christos * to which to attach the newly created permit structure
1006 1.1 christos */
1007 1.1 christos
1008 1.1 christos void
1009 1.1 christos get_permit(struct parse *cfile, struct element *permit_head)
1010 1.1 christos {
1011 1.1 christos enum dhcp_token token;
1012 1.1 christos const char *val;
1013 1.1 christos struct string *permit;
1014 1.1 christos struct string *alias = NULL;
1015 1.1 christos struct comment *comment = NULL;
1016 1.1 christos struct element *member;
1017 1.1 christos isc_boolean_t need_clients = ISC_TRUE;
1018 1.1 christos isc_boolean_t negative = ISC_FALSE;
1019 1.1 christos
1020 1.1 christos token = next_token(&val, NULL, cfile);
1021 1.1 christos switch (token) {
1022 1.1 christos case UNKNOWN:
1023 1.1 christos permit = CLASS_KNOWN;
1024 1.1 christos negative = ISC_TRUE;
1025 1.1 christos alias = makeString(-1, "unknown clients");
1026 1.1 christos break;
1027 1.1.1.2 christos
1028 1.1 christos case KNOWN_CLIENTS:
1029 1.1 christos need_clients = ISC_FALSE;
1030 1.1 christos permit = CLASS_KNOWN;
1031 1.1 christos alias = makeString(-1, "known-clients");
1032 1.1 christos break;
1033 1.1 christos
1034 1.1 christos case UNKNOWN_CLIENTS:
1035 1.1 christos need_clients = ISC_FALSE;
1036 1.1 christos permit = CLASS_KNOWN;
1037 1.1 christos negative = ISC_TRUE;
1038 1.1 christos alias = makeString(-1, "unknown-clients");
1039 1.1 christos break;
1040 1.1 christos
1041 1.1 christos case KNOWN:
1042 1.1 christos permit = CLASS_KNOWN;
1043 1.1 christos alias = makeString(-1, "known clients");
1044 1.1 christos break;
1045 1.1.1.2 christos
1046 1.1 christos case AUTHENTICATED:
1047 1.1 christos permit = CLASS_ALL;
1048 1.1 christos alias = makeString(-1, "authenticated clients");
1049 1.1 christos negative = ISC_TRUE;
1050 1.1 christos authenticated_clients:
1051 1.1 christos comment = createComment("/// [un]authenticated-clients is "
1052 1.1 christos "not supported by ISC DHCP and Kea");
1053 1.1 christos break;
1054 1.1.1.2 christos
1055 1.1 christos case UNAUTHENTICATED:
1056 1.1 christos permit = CLASS_ALL;
1057 1.1 christos alias = makeString(-1, "unauthenticated clients");
1058 1.1 christos goto authenticated_clients;
1059 1.1 christos break;
1060 1.1 christos
1061 1.1 christos case ALL:
1062 1.1 christos permit = CLASS_ALL;
1063 1.1 christos alias = makeString(-1, "all clients");
1064 1.1 christos break;
1065 1.1.1.2 christos
1066 1.1 christos case DYNAMIC:
1067 1.1 christos /* bootp is not supported by Kea so the dynamic bootp
1068 1.1 christos * client set is the empty set. */
1069 1.1 christos if (next_token(&val, NULL, cfile) != TOKEN_BOOTP)
1070 1.1 christos parse_error(cfile, "expecting \"bootp\"");
1071 1.1 christos permit = CLASS_ALL;
1072 1.1 christos negative = ISC_TRUE;
1073 1.1 christos alias = makeString(-1, "dynamic bootp clients");
1074 1.1 christos cfile->issue_counter++;
1075 1.1 christos comment = createComment("/// dynamic-bootp-client is not "
1076 1.1 christos "supported by Kea");
1077 1.1 christos break;
1078 1.1 christos
1079 1.1 christos case MEMBERS:
1080 1.1 christos /* we don't check the class... */
1081 1.1 christos need_clients = ISC_FALSE;
1082 1.1 christos if (next_token(&val, NULL, cfile) != OF)
1083 1.1 christos parse_error(cfile, "expecting \"of\"");
1084 1.1 christos if (next_token(&val, NULL, cfile) != STRING)
1085 1.1 christos parse_error(cfile, "expecting class name.");
1086 1.1 christos permit = makeString(-1, val);
1087 1.1 christos break;
1088 1.1 christos
1089 1.1 christos case AFTER:
1090 1.1 christos /* don't use parse_date_code() */
1091 1.1 christos need_clients = ISC_FALSE;
1092 1.1 christos permit = makeString(-1, "AFTER_");
1093 1.1 christos alias = makeString(-1, "after ");
1094 1.1 christos while (peek_raw_token(NULL, NULL, cfile) != SEMI) {
1095 1.1 christos next_raw_token(&val, NULL, cfile);
1096 1.1 christos appendString(permit, val);
1097 1.1 christos appendString(alias, val);
1098 1.1 christos }
1099 1.1 christos permit_head->skip = ISC_TRUE;
1100 1.1 christos cfile->issue_counter++;
1101 1.1 christos comment = createComment("/// after <date> is not yet "
1102 1.1 christos "supported by Kea");
1103 1.1 christos break;
1104 1.1 christos
1105 1.1 christos default:
1106 1.1 christos parse_error(cfile, "expecting permit type.");
1107 1.1 christos }
1108 1.1 christos
1109 1.1 christos /*
1110 1.1 christos * The need_clients flag is set if we are expecting the
1111 1.1 christos * CLIENTS token
1112 1.1 christos */
1113 1.1 christos if (need_clients && (next_token(&val, NULL, cfile) != CLIENTS))
1114 1.1 christos parse_error(cfile, "expecting \"clients\"");
1115 1.1 christos member = createMap();
1116 1.1 christos mapSet(member, createString(permit), "class");
1117 1.1 christos mapSet(member, createBool(!negative), "way");
1118 1.1 christos if (alias != NULL)
1119 1.1 christos mapSet(member, createString(alias), "real");
1120 1.1 christos if (comment != NULL)
1121 1.1 christos TAILQ_INSERT_TAIL(&permit_head->comments, comment);
1122 1.1 christos listPush(permit_head, member);
1123 1.1 christos parse_semi(cfile);
1124 1.1 christos
1125 1.1 christos return;
1126 1.1 christos }
1127 1.1 christos
1128 1.1 christos /*!
1129 1.1 christos *
1130 1.1 christos * \brief Parse a pool statement
1131 1.1 christos *
1132 1.1 christos * Pool statements are used to group declarations and permit & deny information
1133 1.1 christos * with a specific address range. They must be declared within a shared network
1134 1.1 christos * or subnet and there may be multiple pools withing a shared network or subnet.
1135 1.1 christos * Each pool may have a different set of permit or deny options.
1136 1.1 christos *
1137 1.1 christos * \param[in] cfile = the configuration file being parsed
1138 1.1 christos * \param[in] type = the type of the enclosing statement. This must be
1139 1.1 christos * SHARED_NET_DECL or SUBNET_DECL for this function.
1140 1.1 christos *
1141 1.1 christos * \return
1142 1.1 christos * void - This function either parses the statement and updates the structures
1143 1.1 christos * or it generates an error message and possible halts the program if
1144 1.1 christos * it encounters a problem.
1145 1.1 christos */
1146 1.1 christos void
1147 1.1 christos parse_pool_statement(struct parse *cfile, int type)
1148 1.1 christos {
1149 1.1 christos enum dhcp_token token;
1150 1.1 christos const char *val;
1151 1.1 christos isc_boolean_t done = ISC_FALSE;
1152 1.1 christos struct element *pool;
1153 1.1 christos struct element *pools;
1154 1.1 christos struct element *permit;
1155 1.1 christos struct element *prohibit;
1156 1.1 christos int declaration = 0;
1157 1.1 christos unsigned range_counter = 0;
1158 1.1 christos
1159 1.1 christos pool = createMap();
1160 1.1 christos pool->kind = POOL_DECL;
1161 1.1 christos TAILQ_CONCAT(&pool->comments, &cfile->comments);
1162 1.1 christos
1163 1.1 christos if (type != SUBNET_DECL && type != SHARED_NET_DECL)
1164 1.1 christos parse_error(cfile, "Dynamic pools are only valid inside "
1165 1.1 christos "subnet or shared-network statements.");
1166 1.1 christos parse_lbrace(cfile);
1167 1.1 christos
1168 1.1 christos stackPush(cfile, pool);
1169 1.1 christos type = POOL_DECL;
1170 1.1 christos
1171 1.1 christos permit = createList();
1172 1.1 christos prohibit = createList();
1173 1.1 christos
1174 1.1 christos do {
1175 1.1 christos token = peek_token(&val, NULL, cfile);
1176 1.1 christos switch (token) {
1177 1.1 christos case TOKEN_NO:
1178 1.1 christos case FAILOVER:
1179 1.1 christos if (failover_once)
1180 1.1 christos fprintf(stderr, "ignoring failover\n");
1181 1.1 christos failover_once = ISC_FALSE;
1182 1.1 christos skip_to_semi(cfile);
1183 1.1 christos break;
1184 1.1 christos
1185 1.1 christos case RANGE:
1186 1.1 christos skip_token(&val, NULL, cfile);
1187 1.1 christos parse_address_range(cfile, type, cfile->stack_top);
1188 1.1 christos range_counter++;
1189 1.1 christos break;
1190 1.1 christos
1191 1.1 christos case ALLOW:
1192 1.1 christos skip_token(&val, NULL, cfile);
1193 1.1 christos get_permit(cfile, permit);
1194 1.1 christos break;
1195 1.1 christos
1196 1.1 christos case DENY:
1197 1.1 christos skip_token(&val, NULL, cfile);
1198 1.1 christos get_permit(cfile, prohibit);
1199 1.1 christos break;
1200 1.1.1.2 christos
1201 1.1 christos case RBRACE:
1202 1.1 christos skip_token(&val, NULL, cfile);
1203 1.1 christos done = ISC_TRUE;
1204 1.1 christos break;
1205 1.1 christos
1206 1.1 christos case END_OF_FILE:
1207 1.1 christos /*
1208 1.1 christos * We can get to END_OF_FILE if, for instance,
1209 1.1 christos * the parse_statement() reads all available tokens
1210 1.1 christos * and leaves us at the end.
1211 1.1 christos */
1212 1.1 christos parse_error(cfile, "unexpected end of file");
1213 1.1 christos
1214 1.1 christos default:
1215 1.1 christos declaration = parse_statement(cfile, type,
1216 1.1 christos declaration);
1217 1.1 christos break;
1218 1.1 christos }
1219 1.1 christos } while (!done);
1220 1.1 christos
1221 1.1 christos cfile->stack_top--;
1222 1.1 christos
1223 1.1 christos generate_class(cfile, pool, permit, prohibit);
1224 1.1 christos
1225 1.1 christos pools = mapGet(cfile->stack[cfile->stack_top], "pools");
1226 1.1 christos if (pools == NULL) {
1227 1.1 christos pools = createList();
1228 1.1 christos pools->kind = POOL_DECL;
1229 1.1 christos mapSet(cfile->stack[cfile->stack_top], pools, "pools");
1230 1.1 christos }
1231 1.1 christos if (range_counter == 0) {
1232 1.1 christos struct comment *comment;
1233 1.1 christos
1234 1.1 christos /* no range */
1235 1.1 christos comment = createComment("empty pool");
1236 1.1 christos TAILQ_INSERT_TAIL(&pool->comments, comment);
1237 1.1 christos pool->skip = ISC_TRUE;
1238 1.1 christos cfile->issue_counter++;
1239 1.1 christos listPush(pools, pool);
1240 1.1 christos return;
1241 1.1 christos }
1242 1.1 christos /* spread extra ranges into pool copies */
1243 1.1 christos while (--range_counter != 0) {
1244 1.1 christos struct handle *handle;
1245 1.1 christos struct element *first;
1246 1.1 christos struct element *saved;
1247 1.1 christos isc_boolean_t seen = ISC_FALSE;
1248 1.1 christos
1249 1.1 christos first = createMap();
1250 1.1 christos saved = copy(pool);
1251 1.1 christos TAILQ_CONCAT(&first->comments, &pool->comments);
1252 1.1 christos while (mapSize(pool) > 0) {
1253 1.1 christos handle = mapPop(pool);
1254 1.1 christos if ((handle == NULL) || (handle->key == NULL) ||
1255 1.1 christos (handle->value == NULL))
1256 1.1 christos parse_error(cfile, "bad pool entry");
1257 1.1 christos if (strcmp(handle->key, "pool") != 0)
1258 1.1 christos mapSet(first, handle->value, handle->key);
1259 1.1 christos else if (!seen) {
1260 1.1 christos mapSet(first, handle->value, handle->key);
1261 1.1 christos mapRemove(saved, "pool");
1262 1.1 christos seen = ISC_TRUE;
1263 1.1 christos }
1264 1.1 christos }
1265 1.1 christos listPush(pools, first);
1266 1.1 christos pool = saved;
1267 1.1 christos }
1268 1.1 christos listPush(pools, pool);
1269 1.1 christos }
1270 1.1 christos
1271 1.1 christos /* Expect a left brace */
1272 1.1 christos
1273 1.1 christos void
1274 1.1 christos parse_lbrace(struct parse *cfile)
1275 1.1 christos {
1276 1.1 christos enum dhcp_token token;
1277 1.1 christos const char *val;
1278 1.1 christos
1279 1.1 christos token = next_token(&val, NULL, cfile);
1280 1.1 christos if (token != LBRACE)
1281 1.1 christos parse_error(cfile, "expecting left brace.");
1282 1.1 christos }
1283 1.1 christos
1284 1.1 christos /* host-declaration :== hostname RBRACE parameters declarations LBRACE */
1285 1.1 christos
1286 1.1 christos void
1287 1.1 christos parse_host_declaration(struct parse *cfile)
1288 1.1 christos {
1289 1.1 christos const char *val;
1290 1.1 christos enum dhcp_token token;
1291 1.1 christos struct element *host;
1292 1.1 christos struct string *name;
1293 1.1 christos struct element *where;
1294 1.1 christos struct element *hosts = NULL;
1295 1.1 christos int declaration = 0;
1296 1.1 christos isc_boolean_t used_heuristic = ISC_FALSE;
1297 1.1 christos
1298 1.1 christos host = createMap();
1299 1.1 christos host->kind = HOST_DECL;
1300 1.1 christos TAILQ_CONCAT(&host->comments, &cfile->comments);
1301 1.1 christos
1302 1.1 christos name = parse_host_name(cfile);
1303 1.1 christos if (!name)
1304 1.1 christos parse_error(cfile, "expecting a name for host declaration.");
1305 1.1 christos
1306 1.1 christos mapSet(host, createString(name), "hostname");
1307 1.1 christos
1308 1.1 christos parse_lbrace(cfile);
1309 1.1 christos
1310 1.1 christos stackPush(cfile, host);
1311 1.1 christos
1312 1.1 christos for (;;) {
1313 1.1 christos token = peek_token(&val, NULL, cfile);
1314 1.1 christos if (token == RBRACE) {
1315 1.1 christos skip_token(&val, NULL, cfile);
1316 1.1 christos break;
1317 1.1 christos }
1318 1.1 christos if (token == END_OF_FILE)
1319 1.1 christos parse_error(cfile, "unexpected end of file");
1320 1.1 christos /* If the host declaration was created by the server,
1321 1.1 christos remember to save it. */
1322 1.1 christos if (token == DYNAMIC) {
1323 1.1 christos skip_token(&val, NULL, cfile);
1324 1.1 christos parse_error(cfile, "dynamic hosts don't exist "
1325 1.1 christos "in the config file");
1326 1.1 christos }
1327 1.1 christos /* If the host declaration was created by the server,
1328 1.1 christos remember to save it. */
1329 1.1 christos if (token == TOKEN_DELETED) {
1330 1.1 christos skip_token(&val, NULL, cfile);
1331 1.1 christos parse_error(cfile, "deleted hosts don't exist "
1332 1.1 christos "in the config file");
1333 1.1 christos }
1334 1.1 christos
1335 1.1 christos if (token == GROUP) {
1336 1.1 christos struct element *group;
1337 1.1 christos struct comment *comment;
1338 1.1 christos
1339 1.1 christos skip_token(&val, NULL, cfile);
1340 1.1 christos token = next_token(&val, NULL, cfile);
1341 1.1 christos if (token != STRING && !is_identifier(token))
1342 1.1 christos parse_error(cfile,
1343 1.1 christos "expecting string or identifier.");
1344 1.1 christos group = createString(makeString(-1, val));
1345 1.1 christos group->skip = ISC_TRUE;
1346 1.1 christos cfile->issue_counter++;
1347 1.1 christos comment = createComment("/// Unsupported group in "
1348 1.1 christos "host reservations");
1349 1.1 christos TAILQ_INSERT_TAIL(&group->comments, comment);
1350 1.1 christos comment = createComment("/// Reference Kea #233");
1351 1.1 christos TAILQ_INSERT_TAIL(&group->comments, comment);
1352 1.1 christos mapSet(host, group, "group");
1353 1.1 christos parse_semi(cfile);
1354 1.1 christos continue;
1355 1.1 christos }
1356 1.1 christos
1357 1.1 christos if (token == UID) {
1358 1.1 christos struct string *client_id;
1359 1.1 christos
1360 1.1 christos if (!use_client_id) {
1361 1.1 christos add_host_reservation_identifiers(cfile,
1362 1.1 christos "client-id");
1363 1.1 christos use_client_id = ISC_TRUE;
1364 1.1 christos }
1365 1.1 christos
1366 1.1 christos skip_token(&val, NULL, cfile);
1367 1.1 christos
1368 1.1 christos if (mapContains(host, "client-id"))
1369 1.1 christos parse_error(cfile, "Host %s already has a "
1370 1.1 christos "client identifier.",
1371 1.1 christos name->content);
1372 1.1 christos
1373 1.1 christos /* See if it's a string or a cshl. */
1374 1.1 christos token = peek_token(&val, NULL, cfile);
1375 1.1 christos if (token == STRING) {
1376 1.1 christos skip_token(&val, NULL, cfile);
1377 1.1 christos client_id = makeString(-1, val);
1378 1.1 christos } else {
1379 1.1 christos struct string *bin;
1380 1.1 christos unsigned len = 0;
1381 1.1 christos
1382 1.1 christos bin = parse_numeric_aggregate
1383 1.1 christos (cfile, NULL, &len, ':', 16, 8);
1384 1.1 christos if (!bin)
1385 1.1 christos parse_error(cfile,
1386 1.1 christos "expecting hex list.");
1387 1.1 christos client_id = makeStringExt(bin->length,
1388 1.1 christos bin->content, 'H');
1389 1.1 christos }
1390 1.1 christos mapSet(host, createString(client_id), "client-id");
1391 1.1 christos
1392 1.1 christos parse_semi(cfile);
1393 1.1 christos continue;
1394 1.1 christos }
1395 1.1 christos
1396 1.1 christos if (token == HOST_IDENTIFIER) {
1397 1.1 christos struct string *host_id;
1398 1.1 christos isc_boolean_t known;
1399 1.1 christos struct option *option;
1400 1.1 christos struct element *expr;
1401 1.1 christos struct string *data;
1402 1.1 christos int relays = 0;
1403 1.1 christos
1404 1.1 christos if (!use_flex_id) {
1405 1.1 christos add_host_reservation_identifiers(cfile,
1406 1.1 christos "flex-id");
1407 1.1 christos use_flex_id = ISC_TRUE;
1408 1.1 christos }
1409 1.1 christos
1410 1.1 christos if (mapContains(host, "host-identifier") ||
1411 1.1 christos mapContains(host, "flex-id"))
1412 1.1 christos parse_error(cfile,
1413 1.1 christos "only one host-identifier allowed "
1414 1.1 christos "per host");
1415 1.1 christos skip_token(&val, NULL, cfile);
1416 1.1 christos token = next_token(&val, NULL, cfile);
1417 1.1 christos host_id = makeString(-1, val);
1418 1.1 christos appendString(host_id, " ");
1419 1.1 christos if (token == V6RELOPT) {
1420 1.1.1.2 christos token = next_token(&val, NULL, cfile);
1421 1.1 christos
1422 1.1 christos if (token != NUMBER)
1423 1.1 christos parse_error(cfile,
1424 1.1 christos "host-identifier v6relopt "
1425 1.1 christos "must have a number");
1426 1.1 christos appendString(host_id, val);
1427 1.1 christos appendString(host_id, " ");
1428 1.1 christos relays = atoi(val);
1429 1.1 christos if (relays < 0)
1430 1.1 christos parse_error(cfile,
1431 1.1 christos "host-identifier v6relopt "
1432 1.1 christos "must have a number >= 0");
1433 1.1 christos if (relays > MAX_V6RELAY_HOPS)
1434 1.1 christos relays = MAX_V6RELAY_HOPS + 1;
1435 1.1 christos } else if (token != OPTION)
1436 1.1.1.2 christos parse_error(cfile,
1437 1.1 christos "host-identifier must be an option"
1438 1.1 christos " or v6relopt");
1439 1.1 christos known = ISC_FALSE;
1440 1.1 christos option = parse_option_name(cfile, ISC_TRUE, &known);
1441 1.1 christos if (!known)
1442 1.1 christos parse_error(cfile, "unknown option %s.%s",
1443 1.1 christos option->space->old, option->old);
1444 1.1 christos appendString(host_id, option->space->name);
1445 1.1 christos appendString(host_id, ".");
1446 1.1 christos appendString(host_id, option->name);
1447 1.1 christos appendString(host_id, " ");
1448 1.1 christos
1449 1.1 christos data = parse_option_textbin(cfile, option);
1450 1.1 christos parse_semi(cfile);
1451 1.1 christos
1452 1.1 christos if (data == NULL)
1453 1.1 christos parse_error(cfile, "can't get option data");
1454 1.1 christos concatString(host_id, data);
1455 1.1 christos expr = createString(host_id);
1456 1.1 christos expr->skip = ISC_TRUE;
1457 1.1 christos cfile->issue_counter++;
1458 1.1 christos mapSet(host, expr, "host-identifier");
1459 1.1 christos
1460 1.1 christos if (host_id_option == NULL)
1461 1.1 christos add_host_id_option(cfile, option, relays);
1462 1.1 christos else if ((host_id_option != option) ||
1463 1.1 christos (host_id_relays != relays)) {
1464 1.1 christos struct string *msg;
1465 1.1 christos struct comment *comment;
1466 1.1 christos
1467 1.1 christos msg = allocString();
1468 1.1 christos appendString(msg, "/// Another option (");
1469 1.1 christos appendString(msg, host_id_option->name);
1470 1.1 christos appendString(msg, ") is already used as ");
1471 1.1 christos appendString(msg, "host-identifier");
1472 1.1 christos comment = createComment(msg->content);
1473 1.1 christos TAILQ_INSERT_TAIL(&expr->comments, comment);
1474 1.1 christos continue;
1475 1.1 christos }
1476 1.1 christos
1477 1.1 christos /*
1478 1.1 christos * Everything good: set a flex-id and remove
1479 1.1 christos * the host-identifier entry.
1480 1.1 christos */
1481 1.1 christos mapSet(host, createString(data), "flex-id");
1482 1.1 christos mapRemove(host, "host-identifier");
1483 1.1 christos continue;
1484 1.1 christos }
1485 1.1 christos
1486 1.1 christos declaration = parse_statement(cfile, HOST_DECL, declaration);
1487 1.1 christos }
1488 1.1 christos
1489 1.1 christos cfile->stack_top--;
1490 1.1 christos
1491 1.1 christos where = find_match(cfile, host, &used_heuristic);
1492 1.1 christos hosts = mapGet(where, "reservations");
1493 1.1 christos if (hosts == NULL) {
1494 1.1 christos hosts = createList();
1495 1.1 christos hosts->kind = HOST_DECL;
1496 1.1 christos mapSet(where, hosts, "reservations");
1497 1.1 christos if (used_heuristic) {
1498 1.1 christos struct comment *comment;
1499 1.1 christos
1500 1.1 christos comment = createComment("/// Host reservations "
1501 1.1 christos "without fixed addresses "
1502 1.1 christos "were put in the last "
1503 1.1 christos "declared subnet");
1504 1.1 christos TAILQ_INSERT_TAIL(&hosts->comments, comment);
1505 1.1 christos comment = createComment("/// Reference Kea #231");
1506 1.1 christos TAILQ_INSERT_TAIL(&hosts->comments, comment);
1507 1.1 christos }
1508 1.1 christos }
1509 1.1 christos listPush(hosts, host);
1510 1.1 christos }
1511 1.1 christos
1512 1.1 christos /* Simple tool to declare used (and only used) reservation identifiers */
1513 1.1 christos static void
1514 1.1 christos add_host_reservation_identifiers(struct parse *cfile, const char *id)
1515 1.1 christos {
1516 1.1 christos struct element *ids;
1517 1.1 christos
1518 1.1 christos ids = mapGet(cfile->stack[1], "host-reservation-identifiers");
1519 1.1 christos if (ids == NULL) {
1520 1.1 christos ids = createList();
1521 1.1 christos mapSet(cfile->stack[1], ids, "host-reservation-identifiers");
1522 1.1 christos }
1523 1.1 christos listPush(ids, createString(makeString(-1, id)));
1524 1.1 christos }
1525 1.1 christos
1526 1.1 christos /* Add the flexible host identifier glue */
1527 1.1 christos static void
1528 1.1 christos add_host_id_option(struct parse *cfile,
1529 1.1 christos const struct option *option, int relays)
1530 1.1 christos {
1531 1.1 christos struct string *path;
1532 1.1 christos struct string *expr;
1533 1.1 christos struct element *params;
1534 1.1 christos struct element *entry;
1535 1.1 christos struct element *hooks;
1536 1.1 christos struct comment *comment;
1537 1.1 christos char buf[40];
1538 1.1 christos
1539 1.1 christos host_id_option = option;
1540 1.1 christos host_id_relays = relays;
1541 1.1 christos
1542 1.1 christos /*
1543 1.1 christos * Using the example from the Kea Administrator Reference Manual
1544 1.1 christos * as recommended by Tomek
1545 1.1 christos */
1546 1.1 christos hooks = createList();
1547 1.1 christos mapSet(cfile->stack[1], hooks, "hooks-libraries");
1548 1.1 christos comment = createComment("/// The flexible host identifier "
1549 1.1 christos "is a premium feature");
1550 1.1 christos TAILQ_INSERT_TAIL(&hooks->comments, comment);
1551 1.1 christos entry = createMap();
1552 1.1 christos listPush(hooks, entry);
1553 1.1 christos if (hook_library_path != NULL)
1554 1.1 christos path = makeString(-1, hook_library_path);
1555 1.1 christos else
1556 1.1 christos path = makeString(-1, "/path/");
1557 1.1 christos appendString(path, "libdhcp_flex_id.so");
1558 1.1 christos params = createString(path);
1559 1.1 christos if (hook_library_path == NULL) {
1560 1.1 christos comment = createComment("/// Please update the path here");
1561 1.1 christos TAILQ_INSERT_TAIL(¶ms->comments, comment);
1562 1.1 christos }
1563 1.1 christos mapSet(entry, params, "library");
1564 1.1 christos params = createMap();
1565 1.1 christos mapSet(entry, params, "parameters");
1566 1.1 christos
1567 1.1 christos snprintf(buf, sizeof(buf), "%soption[%u].hex",
1568 1.1 christos relays > 0 ? "relay[0]." : "", option->code);
1569 1.1 christos expr = makeString(-1, buf);
1570 1.1 christos mapSet(params, createString(expr), "identifier-expression");
1571 1.1 christos }
1572 1.1 christos
1573 1.1 christos static void add_host_reservation_identifiers(struct parse *, const char *);
1574 1.1 christos /* class-declaration :== STRING LBRACE parameters declarations RBRACE
1575 1.1 christos *
1576 1.1 christos * in fact:
1577 1.1 christos * (CLASS) NAME(STRING) LBRACE ... RBRACE
1578 1.1 christos * (SUBCLASS) SUPER(STRING) DATA/HASH(STRING | <hexa>) [BRACE ... RBRACE]
1579 1.1.1.2 christos *
1580 1.1 christos * class "name" { MATCH IF <boolean-expr> }: direct: belong when true
1581 1.1 christos * class "name" { MATCH <data-expr> }: indirect: use subclasses
1582 1.1 christos * class "name" { MATCH <data-expr> SPAWN WITH <data-expr> }: indirect:
1583 1.1 christos * create dynamically a subclass
1584 1.1 christos * subclass "super" <data-expr = string or binary aka hash>: belongs when
1585 1.1 christos * super <data-expr> == <hash>
1586 1.1 christos */
1587 1.1 christos
1588 1.1 christos void
1589 1.1 christos parse_class_declaration(struct parse *cfile, int type)
1590 1.1 christos {
1591 1.1 christos const char *val = NULL;
1592 1.1 christos enum dhcp_token token;
1593 1.1 christos size_t group = 0;
1594 1.1 christos size_t i = 0;
1595 1.1 christos struct element *group_classes = NULL;
1596 1.1 christos struct element *classes = NULL;
1597 1.1 christos struct element *class = NULL;
1598 1.1 christos struct element *pc = NULL; /* p(arent)c(lass) */
1599 1.1 christos struct element *tmp = NULL;
1600 1.1 christos struct element *expr = NULL;
1601 1.1 christos struct element *data = NULL;
1602 1.1 christos isc_boolean_t binary = ISC_FALSE;
1603 1.1 christos int declaration = 0;
1604 1.1 christos struct string *name = NULL;
1605 1.1 christos isc_boolean_t lose = ISC_FALSE;
1606 1.1 christos isc_boolean_t matchedonce = ISC_FALSE;
1607 1.1 christos isc_boolean_t submatchedonce = ISC_FALSE;
1608 1.1.1.2 christos
1609 1.1 christos token = next_token(&val, NULL, cfile);
1610 1.1 christos if (token != STRING)
1611 1.1 christos parse_error(cfile, "Expecting class name");
1612 1.1 christos
1613 1.1 christos /* Find group and root classes */
1614 1.1 christos classes = mapGet(cfile->stack[1], "client-classes");
1615 1.1 christos if (classes == NULL) {
1616 1.1 christos classes = createList();
1617 1.1 christos classes->kind = CLASS_DECL;
1618 1.1 christos mapSet(cfile->stack[1], classes, "client-classes");
1619 1.1 christos }
1620 1.1 christos for (group = cfile->stack_top; group > 0; --group) {
1621 1.1 christos int kind;
1622 1.1 christos
1623 1.1 christos kind = cfile->stack[group]->kind;
1624 1.1 christos if (kind == CLASS_DECL)
1625 1.1 christos parse_error(cfile, "class in class");
1626 1.1 christos if ((kind == GROUP_DECL) || (kind == ROOT_GROUP))
1627 1.1 christos break;
1628 1.1 christos }
1629 1.1 christos if (!group)
1630 1.1 christos parse_error(cfile, "can't find root group");
1631 1.1 christos if (cfile->stack[group]->kind == GROUP_DECL) {
1632 1.1 christos group_classes = mapGet(cfile->stack[group], "client-classes");
1633 1.1 christos if (group_classes == NULL) {
1634 1.1 christos group_classes = createList();
1635 1.1 christos group_classes->kind = CLASS_DECL;
1636 1.1 christos mapSet(cfile->stack[group], group_classes,
1637 1.1 christos "client-classes");
1638 1.1 christos }
1639 1.1 christos } else
1640 1.1 christos group_classes = classes;
1641 1.1 christos
1642 1.1 christos /* See if there's already a class with the specified name. */
1643 1.1 christos for (i = 0; i < listSize(classes); i++) {
1644 1.1 christos struct element *name;
1645 1.1 christos
1646 1.1 christos tmp = listGet(classes, i);
1647 1.1 christos name = mapGet(tmp, "name");
1648 1.1 christos if (name == NULL)
1649 1.1 christos continue;
1650 1.1 christos if (strcmp(stringValue(name)->content, val) == 0) {
1651 1.1 christos pc = tmp;
1652 1.1 christos break;
1653 1.1 christos }
1654 1.1 christos }
1655 1.1 christos
1656 1.1 christos /* If it is a class, we're updating it. If it's any of the other
1657 1.1 christos * types (subclass, vendor or user class), the named class is a
1658 1.1 christos * reference to the parent class so its mandatory.
1659 1.1 christos */
1660 1.1 christos if ((pc != NULL) && (type == CLASS_TYPE_CLASS)) {
1661 1.1 christos class = pc;
1662 1.1 christos pc = NULL;
1663 1.1 christos } else if (type != CLASS_TYPE_CLASS) {
1664 1.1 christos if (pc == NULL)
1665 1.1 christos parse_error(cfile, "no class named %s", val);
1666 1.1 christos if (!mapContains(pc, "spawning") ||
1667 1.1 christos !mapContains(pc, "submatch"))
1668 1.1 christos parse_error(cfile, "found class name %s but it is "
1669 1.1 christos "not a suitable superclass", val);
1670 1.1 christos }
1671 1.1 christos
1672 1.1 christos name = makeString(-1, val);
1673 1.1 christos /* If this is a straight subclass, parse the hash string. */
1674 1.1 christos if (type == CLASS_TYPE_SUBCLASS) {
1675 1.1 christos token = peek_token(&val, NULL, cfile);
1676 1.1 christos if (token == STRING) {
1677 1.1 christos unsigned len;
1678 1.1 christos
1679 1.1 christos skip_token(&val, &len, cfile);
1680 1.1 christos data = createString(makeString(len, val));
1681 1.1 christos } else if (token == NUMBER_OR_NAME || token == NUMBER) {
1682 1.1 christos data = createHexa(parse_hexa(cfile));
1683 1.1 christos binary = ISC_TRUE;
1684 1.1 christos } else {
1685 1.1 christos skip_token(&val, NULL, cfile);
1686 1.1 christos parse_error(cfile, "Expecting string or hex list.");
1687 1.1 christos }
1688 1.1 christos }
1689 1.1 christos
1690 1.1 christos /* See if there's already a class in the hash table matching the
1691 1.1 christos hash data. */
1692 1.1 christos if (type != CLASS_TYPE_CLASS) {
1693 1.1 christos for (i = 0; i < listSize(classes); i++) {
1694 1.1 christos struct element *super;
1695 1.1 christos struct element *selector;
1696 1.1 christos
1697 1.1 christos tmp = listGet(classes, i);
1698 1.1 christos super = mapGet(tmp, "super");
1699 1.1 christos if (super == NULL)
1700 1.1 christos continue;
1701 1.1 christos if (!eqString(stringValue(super), name))
1702 1.1 christos continue;
1703 1.1 christos if (binary)
1704 1.1 christos selector = mapGet(tmp, "binary");
1705 1.1 christos else
1706 1.1 christos selector = mapGet(tmp, "string");
1707 1.1 christos if (selector == NULL)
1708 1.1 christos continue;
1709 1.1 christos if (eqString(stringValue(selector),
1710 1.1 christos stringValue(data))) {
1711 1.1 christos class = tmp;
1712 1.1 christos break;
1713 1.1 christos }
1714 1.1 christos }
1715 1.1 christos }
1716 1.1.1.2 christos
1717 1.1 christos /* Note the class declaration in the enclosing group */
1718 1.1 christos if (group_classes != classes) {
1719 1.1 christos struct element *gc;
1720 1.1 christos
1721 1.1 christos gc = createMap();
1722 1.1 christos gc->kind = CLASS_DECL;
1723 1.1 christos tmp = createString(name);
1724 1.1 christos if (type == CLASS_TYPE_CLASS)
1725 1.1 christos mapSet(gc, tmp, "name");
1726 1.1 christos else {
1727 1.1 christos tmp->skip = ISC_TRUE;
1728 1.1 christos mapSet(gc, tmp, "super");
1729 1.1 christos data->skip = ISC_TRUE;
1730 1.1 christos if (binary)
1731 1.1 christos mapSet(gc, data, "binary");
1732 1.1 christos else
1733 1.1 christos mapSet(gc, data, "string");
1734 1.1 christos }
1735 1.1 christos listPush(group_classes, gc);
1736 1.1 christos }
1737 1.1 christos
1738 1.1 christos /* If we didn't find an existing class, allocate a new one. */
1739 1.1 christos if (!class) {
1740 1.1 christos /* Allocate the class structure... */
1741 1.1 christos class = createMap();
1742 1.1 christos class->kind = CLASS_DECL;
1743 1.1 christos TAILQ_CONCAT(&class->comments, &cfile->comments);
1744 1.1 christos if (type == CLASS_TYPE_SUBCLASS) {
1745 1.1 christos struct string *subname;
1746 1.1 christos char buf[40];
1747 1.1 christos
1748 1.1 christos cfile->issue_counter++;
1749 1.1 christos tmp = createString(name);
1750 1.1 christos tmp->skip = ISC_TRUE;
1751 1.1 christos mapSet(class, tmp, "super");
1752 1.1 christos data->skip = ISC_TRUE;
1753 1.1 christos if (binary)
1754 1.1 christos mapSet(class, data, "binary");
1755 1.1 christos else
1756 1.1 christos mapSet(class, data, "string");
1757 1.1 christos subname = makeString(-1, "sub#");
1758 1.1 christos concatString(subname, name);
1759 1.1 christos snprintf(buf, sizeof(buf),
1760 1.1 christos "#%u", subclass_counter++);
1761 1.1 christos appendString(subname, buf);
1762 1.1 christos mapSet(class, createString(subname), "name");
1763 1.1 christos } else
1764 1.1 christos /* Save the name, if there is one. */
1765 1.1 christos mapSet(class, createString(name), "name");
1766 1.1 christos listPush(classes, class);
1767 1.1 christos }
1768 1.1 christos
1769 1.1 christos /* Spawned classes don't have to have their own settings. */
1770 1.1 christos if (type == CLASS_TYPE_SUBCLASS) {
1771 1.1 christos token = peek_token(&val, NULL, cfile);
1772 1.1 christos if (token == SEMI) {
1773 1.1 christos skip_token(&val, NULL, cfile);
1774 1.1 christos subclass_inherit(cfile, class, copy(pc));
1775 1.1 christos return;
1776 1.1 christos }
1777 1.1 christos }
1778 1.1 christos
1779 1.1 christos parse_lbrace(cfile);
1780 1.1 christos
1781 1.1 christos stackPush(cfile, class);
1782 1.1 christos
1783 1.1 christos for (;;) {
1784 1.1 christos token = peek_token(&val, NULL, cfile);
1785 1.1 christos if (token == RBRACE) {
1786 1.1 christos skip_token(&val, NULL, cfile);
1787 1.1 christos break;
1788 1.1 christos } else if (token == END_OF_FILE) {
1789 1.1 christos skip_token(&val, NULL, cfile);
1790 1.1 christos parse_error(cfile, "unexpected end of file");
1791 1.1 christos } else if (token == DYNAMIC) {
1792 1.1 christos skip_token(&val, NULL, cfile);
1793 1.1 christos parse_error(cfile, "dynamic classes don't exist "
1794 1.1 christos "in the config file");
1795 1.1 christos } else if (token == TOKEN_DELETED) {
1796 1.1 christos skip_token(&val, NULL, cfile);
1797 1.1 christos parse_error(cfile, "deleted hosts don't exist "
1798 1.1 christos "in the config file");
1799 1.1 christos } else if (token == MATCH) {
1800 1.1 christos skip_token(&val, NULL, cfile);
1801 1.1 christos if (pc)
1802 1.1 christos parse_error(cfile,
1803 1.1 christos "invalid match in subclass.");
1804 1.1 christos token = peek_token(&val, NULL, cfile);
1805 1.1 christos if (token != IF) {
1806 1.1 christos expr = createBool(ISC_FALSE);
1807 1.1 christos expr->skip = 1;
1808 1.1 christos mapSet(class, expr, "spawning");
1809 1.1 christos goto submatch;
1810 1.1 christos }
1811 1.1 christos
1812 1.1 christos skip_token(&val, NULL, cfile);
1813 1.1 christos if (matchedonce)
1814 1.1 christos parse_error(cfile,
1815 1.1 christos "A class may only have "
1816 1.1 christos "one 'match if' clause.");
1817 1.1 christos matchedonce = ISC_TRUE;
1818 1.1 christos expr = createMap();
1819 1.1 christos if (!parse_boolean_expression(expr, cfile, &lose)) {
1820 1.1 christos if (!lose)
1821 1.1 christos parse_error(cfile,
1822 1.1 christos "expecting boolean expr.");
1823 1.1 christos } else {
1824 1.1 christos expr->skip = ISC_TRUE;
1825 1.1 christos mapSet(class, expr, "match-if");
1826 1.1 christos add_match_class(cfile, class, copy(expr));
1827 1.1 christos parse_semi(cfile);
1828 1.1 christos }
1829 1.1 christos } else if (token == SPAWN) {
1830 1.1 christos skip_token(&val, NULL, cfile);
1831 1.1 christos if (pc)
1832 1.1 christos parse_error(cfile,
1833 1.1 christos "invalid spawn in subclass.");
1834 1.1 christos expr = createBool(ISC_TRUE);
1835 1.1 christos expr->skip = ISC_TRUE;
1836 1.1 christos cfile->issue_counter++;
1837 1.1 christos mapSet(class, expr, "spawning");
1838 1.1 christos token = next_token(&val, NULL, cfile);
1839 1.1 christos if (token != WITH)
1840 1.1 christos parse_error(cfile,
1841 1.1 christos "expecting with after spawn");
1842 1.1 christos submatch:
1843 1.1 christos if (submatchedonce)
1844 1.1 christos parse_error(cfile,
1845 1.1 christos "can't override existing "
1846 1.1 christos "submatch/spawn");
1847 1.1 christos submatchedonce = ISC_TRUE;
1848 1.1 christos expr = createMap();
1849 1.1 christos if (!parse_data_expression(expr, cfile, &lose)) {
1850 1.1 christos if (!lose)
1851 1.1 christos parse_error(cfile,
1852 1.1 christos "expecting data expr.");
1853 1.1 christos } else {
1854 1.1 christos expr->skip = ISC_TRUE;
1855 1.1 christos cfile->issue_counter++;
1856 1.1 christos mapSet(class, expr, "submatch");
1857 1.1 christos parse_semi(cfile);
1858 1.1 christos }
1859 1.1 christos } else if (token == LEASE) {
1860 1.1 christos struct comment *comment;
1861 1.1 christos
1862 1.1 christos skip_token(&val, NULL, cfile);
1863 1.1 christos token = next_token(&val, NULL, cfile);
1864 1.1 christos if (token != LIMIT)
1865 1.1 christos parse_error(cfile, "expecting \"limit\"");
1866 1.1 christos token = next_token(&val, NULL, cfile);
1867 1.1 christos if (token != NUMBER)
1868 1.1 christos parse_error(cfile, "expecting a number");
1869 1.1 christos tmp = createInt(atoll(val));
1870 1.1 christos tmp->skip = ISC_TRUE;
1871 1.1 christos cfile->issue_counter++;
1872 1.1 christos comment = createComment("/// Per-class limit is not "
1873 1.1 christos "supported by Kea");
1874 1.1 christos TAILQ_INSERT_TAIL(&tmp->comments, comment);
1875 1.1 christos comment = createComment("/// Reference Kea #237");
1876 1.1 christos TAILQ_INSERT_TAIL(&tmp->comments, comment);
1877 1.1 christos mapSet(class, tmp, "lease-limit");
1878 1.1 christos parse_semi(cfile);
1879 1.1 christos } else
1880 1.1 christos declaration = parse_statement(cfile, CLASS_DECL,
1881 1.1 christos declaration);
1882 1.1 christos }
1883 1.1 christos
1884 1.1 christos cfile->stack_top--;
1885 1.1 christos
1886 1.1 christos if (type == CLASS_TYPE_SUBCLASS)
1887 1.1 christos subclass_inherit(cfile, class, copy(pc));
1888 1.1 christos }
1889 1.1 christos
1890 1.1 christos /*
1891 1.1 christos * Inherit entries:
1892 1.1 christos * - first copy entries from the current superclass to the subclass
1893 1.1 christos * - second try to reduce the subclass matching condition
1894 1.1 christos */
1895 1.1 christos
1896 1.1 christos static void
1897 1.1 christos subclass_inherit(struct parse *cfile,
1898 1.1 christos struct element *class,
1899 1.1 christos struct element *superclass)
1900 1.1 christos {
1901 1.1 christos struct string *name;
1902 1.1 christos struct element *guard;
1903 1.1 christos struct element *submatch;
1904 1.1 christos struct handle *handle;
1905 1.1 christos struct string *gmsg;
1906 1.1 christos struct string *mmsg;
1907 1.1 christos struct string *dmsg;
1908 1.1 christos struct element *expr;
1909 1.1 christos struct element *data;
1910 1.1 christos struct element *match;
1911 1.1 christos struct element *reduced;
1912 1.1 christos unsigned order = 0;
1913 1.1 christos struct comment *comment;
1914 1.1 christos isc_boolean_t marked = ISC_FALSE;
1915 1.1 christos isc_boolean_t lose = ISC_FALSE;
1916 1.1 christos isc_boolean_t modified = ISC_FALSE;
1917 1.1 christos
1918 1.1 christos expr = mapGet(superclass, "name");
1919 1.1 christos if (expr == NULL)
1920 1.1 christos parse_error(cfile, "can't get superclass name");
1921 1.1 christos name = stringValue(expr);
1922 1.1 christos guard = mapGet(superclass, "match-if");
1923 1.1 christos submatch = mapGet(superclass, "submatch");
1924 1.1 christos if (submatch == NULL)
1925 1.1 christos parse_error(cfile, "can't get superclass submatch");
1926 1.1 christos
1927 1.1 christos /* Iterates on (copy of) superclass entries */
1928 1.1 christos while (mapSize(superclass) > 0) {
1929 1.1 christos handle = mapPop(superclass);
1930 1.1 christos if ((handle == NULL) || (handle->key == NULL) ||
1931 1.1 christos (handle->value == NULL))
1932 1.1 christos parse_error(cfile, "can't get superclass %s item at "
1933 1.1 christos "%u", name->content, order);
1934 1.1 christos handle->order = order++;
1935 1.1 christos /* Superclass specific entries */
1936 1.1 christos if ((strcmp(handle->key, "name") == 0) ||
1937 1.1 christos (strcmp(handle->key, "spawning") == 0) ||
1938 1.1 christos (strcmp(handle->key, "match-if") == 0) ||
1939 1.1 christos (strcmp(handle->key, "test") == 0) ||
1940 1.1 christos (strcmp(handle->key, "submatch") == 0))
1941 1.1 christos continue;
1942 1.1 christos /* Subclass specific so impossible entries */
1943 1.1 christos if ((strcmp(handle->key, "super") == 0) ||
1944 1.1 christos (strcmp(handle->key, "binary") == 0) ||
1945 1.1 christos (strcmp(handle->key, "string") == 0))
1946 1.1 christos parse_error(cfile, "superclass %s has unexpected %s "
1947 1.1 christos "at %u",
1948 1.1 christos name->content, handle->key, order);
1949 1.1 christos /* Special entries */
1950 1.1 christos if (strcmp(handle->key, "option-data") == 0) {
1951 1.1 christos struct element *opt_list;
1952 1.1 christos
1953 1.1 christos opt_list = mapGet(class, handle->key);
1954 1.1 christos if (opt_list != NULL)
1955 1.1 christos merge_option_data(handle->value, opt_list);
1956 1.1 christos else
1957 1.1 christos mapSet(class, handle->value, handle->key);
1958 1.1 christos continue;
1959 1.1 christos }
1960 1.1 christos /* Just copy */
1961 1.1 christos if ((strcmp(handle->key, "lease-limit") == 0) ||
1962 1.1 christos (strcmp(handle->key, "boot-file-name") == 0) ||
1963 1.1 christos (strcmp(handle->key, "serverhostname") == 0) ||
1964 1.1 christos (strcmp(handle->key, "next-server") == 0)) {
1965 1.1 christos mapSet(class, handle->value, handle->key);
1966 1.1 christos continue;
1967 1.1 christos }
1968 1.1 christos /* Unknown */
1969 1.1 christos if (!marked) {
1970 1.1 christos marked = ISC_TRUE;
1971 1.1 christos comment = createComment("/// copied from superclass");
1972 1.1 christos TAILQ_INSERT_TAIL(&handle->value->comments, comment);
1973 1.1 christos }
1974 1.1 christos comment = createComment("/// unhandled entry");
1975 1.1 christos TAILQ_INSERT_TAIL(&handle->value->comments, comment);
1976 1.1 christos if (!handle->value->skip) {
1977 1.1 christos handle->value->skip = ISC_TRUE;
1978 1.1 christos cfile->issue_counter++;
1979 1.1 christos }
1980 1.1 christos mapSet(class, handle->value, handle->key);
1981 1.1 christos }
1982 1.1 christos
1983 1.1 christos /* build [guard and] submatch = data */
1984 1.1 christos expr = mapGet(class, "binary");
1985 1.1 christos if (expr != NULL) {
1986 1.1 christos data = createMap();
1987 1.1 christos mapSet(data, copy(expr), "const-data");
1988 1.1 christos } else
1989 1.1 christos data = mapGet(class, "string");
1990 1.1 christos if (data == NULL)
1991 1.1 christos parse_error(cfile, "can't get subclass %s data",
1992 1.1 christos name->content);
1993 1.1 christos match = createMap();
1994 1.1 christos mapSet(match, copy(submatch), "left");
1995 1.1 christos mapSet(match, copy(data), "right");
1996 1.1 christos expr = createMap();
1997 1.1 christos mapSet(expr, match, "equal");
1998 1.1 christos
1999 1.1 christos if (guard != NULL) {
2000 1.1 christos match = createMap();
2001 1.1 christos mapSet(match, copy(guard), "left");
2002 1.1 christos mapSet(match, expr, "right");
2003 1.1 christos expr = createMap();
2004 1.1 christos mapSet(expr, match, "and");
2005 1.1 christos
2006 1.1 christos gmsg = makeString(-1, "/// from: match-if ");
2007 1.1 christos appendString(gmsg, print_boolean_expression(guard, &lose));
2008 1.1 christos mmsg = makeString(-1, "/// match: ");
2009 1.1 christos } else {
2010 1.1 christos gmsg = NULL;
2011 1.1 christos mmsg = makeString(-1, "/// from: match ");
2012 1.1 christos }
2013 1.1 christos
2014 1.1 christos appendString(mmsg, print_data_expression(submatch, &lose));
2015 1.1 christos dmsg = makeString(-1, "/// data: ");
2016 1.1 christos appendString(dmsg, print_data_expression(data, &lose));
2017 1.1 christos
2018 1.1 christos /* evaluate the expression and try to reduce it */
2019 1.1 christos reduced = eval_boolean_expression(expr, &modified);
2020 1.1 christos reduced = reduce_boolean_expression(reduced);
2021 1.1 christos if ((reduced != NULL) && (reduced->type == ELEMENT_BOOLEAN))
2022 1.1 christos parse_error(cfile, "class matching rule evaluated to a "
2023 1.1 christos "constant boolean expression: %s = %s",
2024 1.1 christos print_data_expression(submatch, &lose),
2025 1.1 christos print_data_expression(data, &lose));
2026 1.1 christos if ((reduced == NULL) || (reduced->type != ELEMENT_STRING))
2027 1.1 christos return;
2028 1.1 christos if (!lose) {
2029 1.1 christos if (gmsg != NULL) {
2030 1.1 christos comment = createComment(gmsg->content);
2031 1.1 christos TAILQ_INSERT_TAIL(&reduced->comments, comment);
2032 1.1 christos }
2033 1.1 christos comment = createComment(mmsg->content);
2034 1.1 christos TAILQ_INSERT_TAIL(&reduced->comments, comment);
2035 1.1 christos comment = createComment(dmsg->content);
2036 1.1 christos TAILQ_INSERT_TAIL(&reduced->comments, comment);
2037 1.1 christos }
2038 1.1 christos mapSet(class, reduced, "test");
2039 1.1 christos }
2040 1.1 christos
2041 1.1 christos /*
2042 1.1 christos * Try to reduce a match-if condition into a Kea evaluate bool "test"
2043 1.1 christos */
2044 1.1 christos
2045 1.1 christos static void
2046 1.1 christos add_match_class(struct parse *cfile,
2047 1.1 christos struct element *class,
2048 1.1 christos struct element *expr)
2049 1.1 christos {
2050 1.1 christos struct element *reduced;
2051 1.1 christos isc_boolean_t modified = ISC_FALSE;
2052 1.1 christos isc_boolean_t lose = ISC_FALSE;
2053 1.1 christos
2054 1.1 christos /* evaluate the expression and try to reduce it */
2055 1.1 christos reduced = eval_boolean_expression(expr, &modified);
2056 1.1 christos reduced = reduce_boolean_expression(reduced);
2057 1.1 christos if ((reduced != NULL) && (reduced->type == ELEMENT_BOOLEAN))
2058 1.1 christos parse_error(cfile, "'match if' with a constant boolean "
2059 1.1 christos "expression %s",
2060 1.1 christos print_boolean_expression(expr, &lose));
2061 1.1 christos if ((reduced != NULL) && (reduced->type == ELEMENT_STRING))
2062 1.1 christos mapSet(class, reduced, "test");
2063 1.1 christos else
2064 1.1 christos cfile->issue_counter++;
2065 1.1 christos }
2066 1.1 christos
2067 1.1 christos /* Move pools to subnets */
2068 1.1 christos
2069 1.1 christos static void
2070 1.1 christos relocate_pools(struct element *share)
2071 1.1 christos {
2072 1.1 christos struct element *srcs;
2073 1.1 christos struct element *dsts;
2074 1.1 christos struct element *subnet;
2075 1.1 christos struct range *range;
2076 1.1 christos size_t i;
2077 1.1 christos
2078 1.1 christos srcs = mapGet(share, "pools");
2079 1.1 christos if (srcs == NULL)
2080 1.1 christos return;
2081 1.1 christos if (listSize(srcs) == 0)
2082 1.1 christos return;
2083 1.1 christos TAILQ_FOREACH(range, &known_ranges) {
2084 1.1 christos if (range->share != share)
2085 1.1 christos continue;
2086 1.1 christos subnet = find_location(share, range);
2087 1.1 christos if (subnet == NULL)
2088 1.1 christos continue;
2089 1.1 christos for (i = 0; i < listSize(srcs); i++) {
2090 1.1 christos struct element *pool;
2091 1.1 christos
2092 1.1 christos pool = listGet(srcs, i);
2093 1.1 christos if (range->pool != pool)
2094 1.1 christos continue;
2095 1.1 christos listRemove(srcs, i);
2096 1.1 christos dsts = mapGet(subnet, "pools");
2097 1.1 christos if (dsts == NULL) {
2098 1.1 christos dsts = createList();
2099 1.1 christos mapSet(subnet, dsts, "pools");
2100 1.1 christos }
2101 1.1 christos listPush(dsts, pool);
2102 1.1 christos }
2103 1.1 christos }
2104 1.1 christos }
2105 1.1 christos
2106 1.1 christos /* shared-network-declaration :==
2107 1.1 christos hostname LBRACE declarations parameters RBRACE */
2108 1.1 christos
2109 1.1 christos void
2110 1.1 christos parse_shared_net_declaration(struct parse *cfile)
2111 1.1 christos {
2112 1.1 christos const char *val;
2113 1.1 christos enum dhcp_token token;
2114 1.1 christos struct element *share;
2115 1.1 christos struct element *subnets;
2116 1.1 christos struct element *interface;
2117 1.1 christos struct element *subnet;
2118 1.1 christos struct string *name;
2119 1.1 christos int declaration = 0;
2120 1.1 christos
2121 1.1 christos share = createMap();
2122 1.1 christos share->kind = SHARED_NET_DECL;
2123 1.1 christos TAILQ_CONCAT(&share->comments, &cfile->comments);
2124 1.1 christos
2125 1.1 christos /* Get the name of the shared network... */
2126 1.1 christos token = peek_token(&val, NULL, cfile);
2127 1.1 christos if (token == STRING) {
2128 1.1 christos skip_token(&val, NULL, cfile);
2129 1.1 christos
2130 1.1 christos if (val[0] == 0)
2131 1.1 christos parse_error(cfile, "zero-length shared network name");
2132 1.1 christos name = makeString(-1, val);
2133 1.1 christos } else {
2134 1.1 christos name = parse_host_name(cfile);
2135 1.1 christos if (!name)
2136 1.1 christos parse_error(cfile,
2137 1.1 christos "expecting a name for shared-network");
2138 1.1 christos }
2139 1.1 christos mapSet(share, createString(name), "name");
2140 1.1 christos
2141 1.1 christos subnets = createList();
2142 1.1 christos mapSet(share, subnets,
2143 1.1 christos local_family == AF_INET ? "subnet4" : "subnet6");
2144 1.1 christos
2145 1.1 christos parse_lbrace(cfile);
2146 1.1 christos
2147 1.1 christos stackPush(cfile, share);
2148 1.1 christos
2149 1.1 christos for (;;) {
2150 1.1 christos token = peek_token(&val, NULL, cfile);
2151 1.1 christos if (token == RBRACE) {
2152 1.1 christos skip_token(&val, NULL, cfile);
2153 1.1 christos break;
2154 1.1 christos } else if (token == END_OF_FILE) {
2155 1.1 christos skip_token(&val, NULL, cfile);
2156 1.1 christos parse_error(cfile, "unexpected end of file");
2157 1.1 christos } else if (token == INTERFACE) {
2158 1.1 christos skip_token(&val, NULL, cfile);
2159 1.1 christos token = next_token(&val, NULL, cfile);
2160 1.1 christos if (mapContains(share, "interface"))
2161 1.1 christos parse_error(cfile,
2162 1.1 christos "A shared network can't be "
2163 1.1 christos "connected to two interfaces.");
2164 1.1 christos interface = createString(makeString(-1, val));
2165 1.1 christos mapSet(share, interface, "interface");
2166 1.1 christos new_network_interface(cfile, interface);
2167 1.1 christos parse_semi(cfile);
2168 1.1 christos continue;
2169 1.1 christos }
2170 1.1 christos
2171 1.1 christos declaration = parse_statement(cfile, SHARED_NET_DECL,
2172 1.1 christos declaration);
2173 1.1 christos }
2174 1.1 christos
2175 1.1 christos cfile->stack_top--;
2176 1.1 christos
2177 1.1 christos if (listSize(subnets) == 0)
2178 1.1 christos parse_error(cfile, "empty shared-network decl");
2179 1.1 christos if (listSize(subnets) > 1) {
2180 1.1 christos struct element *shares;
2181 1.1 christos struct element *pools;
2182 1.1 christos
2183 1.1 christos shares = mapGet(cfile->stack[cfile->stack_top],
2184 1.1 christos "shared-networks");
2185 1.1 christos if (shares == NULL) {
2186 1.1 christos struct comment *comment;
2187 1.1 christos
2188 1.1 christos shares = createList();
2189 1.1 christos shares->kind = SHARED_NET_DECL;
2190 1.1 christos mapSet(cfile->stack[cfile->stack_top],
2191 1.1 christos shares, "shared-networks");
2192 1.1 christos comment = createComment("/// Kea shared-networks "
2193 1.1 christos "are different, cf Kea #236");
2194 1.1 christos TAILQ_INSERT_TAIL(&shares->comments, comment);
2195 1.1 christos }
2196 1.1 christos listPush(shares, share);
2197 1.1 christos
2198 1.1 christos /* Pools are forbidden at shared-network level in Kea */
2199 1.1 christos relocate_pools(share);
2200 1.1 christos pools = mapGet(share, "pools");
2201 1.1 christos if ((pools != NULL) && (listSize(pools) == 0)) {
2202 1.1 christos mapRemove(share, "pools");
2203 1.1 christos pools = NULL;
2204 1.1 christos }
2205 1.1 christos if (pools != NULL) {
2206 1.1 christos struct comment *comment;
2207 1.1 christos
2208 1.1 christos pools->skip = ISC_TRUE;
2209 1.1 christos cfile->issue_counter++;
2210 1.1 christos comment = createComment("/// Kea pools must be "
2211 1.1 christos "in a subnet");
2212 1.1 christos TAILQ_INSERT_TAIL(&pools->comments, comment);
2213 1.1 christos comment = createComment("/// Reference Kea #249");
2214 1.1 christos TAILQ_INSERT_TAIL(&pools->comments, comment);
2215 1.1 christos }
2216 1.1 christos pools = mapGet(share, "pd-pools");
2217 1.1 christos if ((pools != NULL) && (listSize(pools) == 0)) {
2218 1.1 christos mapRemove(share, "pd-pools");
2219 1.1 christos pools = NULL;
2220 1.1 christos }
2221 1.1 christos if (pools != NULL) {
2222 1.1 christos struct comment *comment;
2223 1.1 christos
2224 1.1 christos pools->skip = ISC_TRUE;
2225 1.1 christos cfile->issue_counter++;
2226 1.1 christos comment = createComment("/// Kea pools must be "
2227 1.1 christos "in a subnet");
2228 1.1 christos TAILQ_INSERT_TAIL(&pools->comments, comment);
2229 1.1 christos comment = createComment("/// Reference Kea #249");
2230 1.1 christos TAILQ_INSERT_TAIL(&pools->comments, comment);
2231 1.1 christos }
2232 1.1 christos return;
2233 1.1 christos }
2234 1.1 christos
2235 1.1 christos /* There is one subnet so the shared network is useless */
2236 1.1 christos subnet = listGet(subnets, 0);
2237 1.1 christos listRemove(subnets, 0);
2238 1.1 christos mapRemove(share, "name");
2239 1.1 christos mapRemove(share, local_family == AF_INET ? "subnet4" : "subnet6");
2240 1.1 christos /* specific case before calling generic merge */
2241 1.1 christos if (mapContains(share, "pools") &&
2242 1.1 christos mapContains(subnet, "pools")) {
2243 1.1 christos struct element *pools;
2244 1.1 christos struct element *sub;
2245 1.1 christos
2246 1.1 christos pools = mapGet(share, "pools");
2247 1.1 christos mapRemove(share, "pools");
2248 1.1 christos sub = mapGet(subnet, "pools");
2249 1.1 christos concat(sub, pools);
2250 1.1 christos }
2251 1.1 christos if (mapContains(share, "pd-pools") &&
2252 1.1 christos mapContains(subnet, "pd-pools")) {
2253 1.1 christos struct element *pools;
2254 1.1 christos struct element *sub;
2255 1.1 christos
2256 1.1 christos pools = mapGet(share, "pd-pools");
2257 1.1 christos mapRemove(share, "pd-pools");
2258 1.1 christos sub = mapGet(subnet, "pd-pools");
2259 1.1 christos concat(sub, pools);
2260 1.1 christos }
2261 1.1 christos if (mapContains(share, "option-data") &&
2262 1.1 christos mapContains(subnet, "option-data")) {
2263 1.1 christos struct element *opt_list;
2264 1.1 christos struct element *sub;
2265 1.1 christos
2266 1.1 christos opt_list = mapGet(share, "option-data");
2267 1.1 christos mapRemove(share, "option-data");
2268 1.1 christos sub = mapGet(subnet, "option-data");
2269 1.1 christos merge_option_data(opt_list, sub);
2270 1.1 christos }
2271 1.1 christos merge(subnet, share);
2272 1.1 christos
2273 1.1 christos if (local_family == AF_INET) {
2274 1.1 christos subnets = mapGet(cfile->stack[1], "subnet4");
2275 1.1 christos if (subnets == NULL) {
2276 1.1 christos subnets = createList();
2277 1.1 christos subnets->kind = SUBNET_DECL;
2278 1.1 christos mapSet(cfile->stack[1], subnets, "subnet4");
2279 1.1 christos }
2280 1.1 christos } else {
2281 1.1 christos subnets = mapGet(cfile->stack[1], "subnet6");
2282 1.1 christos if (subnets == NULL) {
2283 1.1 christos subnets = createList();
2284 1.1 christos subnets->kind = SUBNET_DECL;
2285 1.1 christos mapSet(cfile->stack[1], subnets, "subnet6");
2286 1.1 christos }
2287 1.1 christos }
2288 1.1 christos listPush(subnets, subnet);
2289 1.1 christos }
2290 1.1 christos
2291 1.1 christos static void
2292 1.1 christos common_subnet_parsing(struct parse *cfile,
2293 1.1 christos struct element *subnets,
2294 1.1 christos struct element *subnet)
2295 1.1 christos {
2296 1.1 christos enum dhcp_token token;
2297 1.1 christos const char *val;
2298 1.1 christos struct element *interface;
2299 1.1 christos int declaration = 0;
2300 1.1 christos
2301 1.1 christos parse_lbrace(cfile);
2302 1.1 christos
2303 1.1 christos stackPush(cfile, subnet);
2304 1.1 christos
2305 1.1 christos for (;;) {
2306 1.1 christos token = peek_token(&val, NULL, cfile);
2307 1.1 christos if (token == RBRACE) {
2308 1.1 christos skip_token(&val, NULL, cfile);
2309 1.1 christos break;
2310 1.1 christos } else if (token == END_OF_FILE) {
2311 1.1 christos skip_token(&val, NULL, cfile);
2312 1.1 christos parse_error(cfile, "unexpected end of file");
2313 1.1 christos break;
2314 1.1 christos } else if (token == INTERFACE) {
2315 1.1 christos skip_token(&val, NULL, cfile);
2316 1.1 christos token = next_token(&val, NULL, cfile);
2317 1.1 christos if (mapContains(subnet, "interface"))
2318 1.1 christos parse_error(cfile,
2319 1.1 christos "A subnet can't be connected "
2320 1.1 christos "to two interfaces.");
2321 1.1 christos interface = createString(makeString(-1, val));
2322 1.1 christos mapSet(subnet, interface, "interface");
2323 1.1 christos new_network_interface(cfile, interface);
2324 1.1 christos parse_semi(cfile);
2325 1.1 christos continue;
2326 1.1 christos }
2327 1.1 christos declaration = parse_statement(cfile, SUBNET_DECL, declaration);
2328 1.1 christos }
2329 1.1 christos
2330 1.1 christos cfile->stack_top--;
2331 1.1 christos
2332 1.1 christos /* Add the subnet to the list of subnets in this shared net. */
2333 1.1 christos listPush(subnets, subnet);
2334 1.1 christos
2335 1.1 christos return;
2336 1.1 christos }
2337 1.1 christos
2338 1.1 christos /* subnet-declaration :==
2339 1.1 christos net NETMASK netmask RBRACE parameters declarations LBRACE */
2340 1.1 christos
2341 1.1 christos void
2342 1.1 christos parse_subnet_declaration(struct parse *cfile)
2343 1.1 christos {
2344 1.1 christos const char *val;
2345 1.1 christos enum dhcp_token token;
2346 1.1 christos struct element *subnet;
2347 1.1 christos struct subnet *chain;
2348 1.1 christos struct element *subnets;
2349 1.1 christos struct string *address;
2350 1.1 christos struct string *netmask;
2351 1.1 christos struct string *prefix;
2352 1.1 christos unsigned char addr[4];
2353 1.1 christos unsigned len = sizeof(addr);
2354 1.1 christos size_t parent = 0;
2355 1.1 christos size_t i;
2356 1.1 christos int kind = 0;
2357 1.1 christos
2358 1.1 christos subnet = createMap();
2359 1.1 christos subnet->kind = SUBNET_DECL;
2360 1.1 christos TAILQ_CONCAT(&subnet->comments, &cfile->comments);
2361 1.1 christos
2362 1.1 christos subnet_counter++;
2363 1.1 christos mapSet(subnet, createInt(subnet_counter), "id");
2364 1.1 christos
2365 1.1 christos chain = (struct subnet *)malloc(sizeof(*chain));
2366 1.1 christos if (chain == NULL)
2367 1.1 christos parse_error(cfile, "can't allocate subnet");
2368 1.1 christos memset(chain, 0, sizeof(*chain));
2369 1.1 christos chain->subnet = subnet;
2370 1.1 christos TAILQ_INSERT_TAIL(&known_subnets, chain);
2371 1.1 christos
2372 1.1 christos /* Find parent */
2373 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
2374 1.1 christos kind = cfile->stack[i]->kind;
2375 1.1 christos if ((kind == SHARED_NET_DECL) ||
2376 1.1 christos (kind == GROUP_DECL) ||
2377 1.1 christos (kind == ROOT_GROUP)) {
2378 1.1 christos parent = i;
2379 1.1 christos break;
2380 1.1 christos }
2381 1.1 christos }
2382 1.1 christos if (kind == 0)
2383 1.1 christos parse_error(cfile, "can't find a place to put subnet");
2384 1.1 christos if (kind == SHARED_NET_DECL)
2385 1.1 christos chain->share = cfile->stack[parent];
2386 1.1 christos subnets = mapGet(cfile->stack[parent], "subnet4");
2387 1.1 christos if (subnets == NULL) {
2388 1.1 christos if (kind == SHARED_NET_DECL)
2389 1.1 christos parse_error(cfile, "shared network without subnets");
2390 1.1 christos subnets = createList();
2391 1.1 christos subnets->kind = SUBNET_DECL;
2392 1.1 christos mapSet(cfile->stack[parent], subnets, "subnet4");
2393 1.1 christos }
2394 1.1 christos
2395 1.1 christos /* Get the network number... */
2396 1.1 christos address = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
2397 1.1 christos if (address == NULL)
2398 1.1 christos parse_error(cfile, "can't decode network number");
2399 1.1 christos if (address->length != 4)
2400 1.1 christos parse_error(cfile, "bad IPv4 address length");
2401 1.1 christos chain->addr = address;
2402 1.1 christos
2403 1.1 christos token = next_token(&val, NULL, cfile);
2404 1.1 christos if (token != NETMASK)
2405 1.1 christos parse_error(cfile, "Expecting netmask");
2406 1.1 christos
2407 1.1 christos /* Get the netmask... */
2408 1.1 christos netmask = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
2409 1.1 christos if (netmask == NULL)
2410 1.1 christos parse_error(cfile, "can't decode network mask");
2411 1.1 christos if (netmask->length != address->length)
2412 1.1 christos parse_error(cfile, "bad IPv4 mask length");
2413 1.1 christos chain->mask = netmask;
2414 1.1 christos
2415 1.1 christos prefix = addrmask(address, netmask);
2416 1.1 christos if (prefix == NULL) {
2417 1.1 christos char bufa[INET_ADDRSTRLEN];
2418 1.1 christos char bufm[INET_ADDRSTRLEN];
2419 1.1 christos
2420 1.1 christos inet_ntop(AF_INET, address->content, bufa, INET_ADDRSTRLEN);
2421 1.1 christos inet_ntop(AF_INET, netmask->content, bufm, INET_ADDRSTRLEN);
2422 1.1 christos parse_error(cfile, "can't get a prefix from %s mask %s",
2423 1.1 christos bufa, bufm);
2424 1.1 christos }
2425 1.1 christos mapSet(subnet, createString(prefix), "subnet");
2426 1.1 christos
2427 1.1 christos common_subnet_parsing(cfile, subnets, subnet);
2428 1.1 christos }
2429 1.1 christos
2430 1.1 christos /* subnet6-declaration :==
2431 1.1 christos net / bits RBRACE parameters declarations LBRACE */
2432 1.1 christos
2433 1.1 christos void
2434 1.1 christos parse_subnet6_declaration(struct parse *cfile)
2435 1.1 christos {
2436 1.1 christos enum dhcp_token token;
2437 1.1 christos const char *val;
2438 1.1 christos struct element *subnet;
2439 1.1 christos struct subnet *chain;
2440 1.1 christos struct element *subnets;
2441 1.1 christos struct string *address;
2442 1.1 christos struct string *prefix;
2443 1.1 christos struct string *netmask;
2444 1.1 christos size_t parent = 0;
2445 1.1 christos size_t i;
2446 1.1 christos int kind = 0;
2447 1.1 christos char *p;
2448 1.1 christos
2449 1.1 christos if (local_family != AF_INET6)
2450 1.1 christos parse_error(cfile, "subnet6 statement is only supported "
2451 1.1 christos "in DHCPv6 mode.");
2452 1.1 christos
2453 1.1 christos subnet = createMap();
2454 1.1 christos subnet->kind = SUBNET_DECL;
2455 1.1 christos TAILQ_CONCAT(&subnet->comments, &cfile->comments);
2456 1.1 christos
2457 1.1 christos subnet_counter++;
2458 1.1 christos mapSet(subnet, createInt(subnet_counter), "id");
2459 1.1 christos
2460 1.1 christos chain = (struct subnet *)malloc(sizeof(*chain));
2461 1.1 christos if (chain == NULL)
2462 1.1 christos parse_error(cfile, "can't allocate subnet");
2463 1.1 christos memset(chain, 0, sizeof(*chain));
2464 1.1 christos chain->subnet = subnet;
2465 1.1 christos TAILQ_INSERT_TAIL(&known_subnets, chain);
2466 1.1 christos
2467 1.1 christos /* Find parent */
2468 1.1 christos for (i = cfile->stack_top; i > 0; --i) {
2469 1.1 christos kind = cfile->stack[i]->kind;
2470 1.1 christos if ((kind == SHARED_NET_DECL) ||
2471 1.1 christos (kind == GROUP_DECL) ||
2472 1.1 christos (kind == ROOT_GROUP)) {
2473 1.1 christos parent = i;
2474 1.1 christos break;
2475 1.1 christos }
2476 1.1 christos }
2477 1.1 christos if (kind == 0)
2478 1.1 christos parse_error(cfile, "can't find a place to put subnet");
2479 1.1 christos if (kind == SHARED_NET_DECL)
2480 1.1 christos chain->share = cfile->stack[parent];
2481 1.1 christos subnets = mapGet(cfile->stack[parent], "subnet6");
2482 1.1 christos if (subnets == NULL) {
2483 1.1 christos if (kind == SHARED_NET_DECL)
2484 1.1 christos parse_error(cfile, "shared network without subnets");
2485 1.1 christos subnets = createList();
2486 1.1 christos subnets->kind = SUBNET_DECL;
2487 1.1 christos mapSet(cfile->stack[parent], subnets, "subnet6");
2488 1.1 christos }
2489 1.1 christos
2490 1.1 christos address = parse_ip6_addr(cfile);
2491 1.1 christos if (address == NULL)
2492 1.1 christos parse_error(cfile, "can't decode network number");
2493 1.1 christos if (address->length != 16)
2494 1.1 christos parse_error(cfile, "bad IPv6 address length");
2495 1.1 christos chain->addr = address;
2496 1.1 christos
2497 1.1 christos prefix = makeStringExt(address->length, address->content, '6');
2498 1.1 christos
2499 1.1 christos token = next_token(&val, NULL, cfile);
2500 1.1 christos if (token != SLASH)
2501 1.1 christos parse_error(cfile, "Expecting a '/'.");
2502 1.1 christos appendString(prefix, val);
2503 1.1 christos
2504 1.1 christos token = next_token(&val, NULL, cfile);
2505 1.1 christos if (token != NUMBER)
2506 1.1 christos parse_error(cfile, "Expecting a number.");
2507 1.1 christos appendString(prefix, val);
2508 1.1 christos
2509 1.1 christos netmask = makeString(16, "0123456789abcdef");
2510 1.1 christos memset(netmask->content, 0, 16);
2511 1.1 christos p = netmask->content;
2512 1.1 christos for (i = atoi(val); i >= 8; i -= 8)
2513 1.1 christos *p++ = 0xff;
2514 1.1 christos *p = 0xff << (8 - i);
2515 1.1 christos chain->mask = netmask;
2516 1.1 christos
2517 1.1 christos mapSet(subnet, createString(prefix), "subnet");
2518 1.1 christos
2519 1.1 christos common_subnet_parsing(cfile, subnets, subnet);
2520 1.1 christos }
2521 1.1 christos
2522 1.1 christos /* group-declaration :== RBRACE parameters declarations LBRACE */
2523 1.1 christos
2524 1.1 christos void
2525 1.1 christos parse_group_declaration(struct parse *cfile)
2526 1.1 christos {
2527 1.1 christos const char *val;
2528 1.1 christos enum dhcp_token token;
2529 1.1 christos struct element *group;
2530 1.1 christos int declaration = 0;
2531 1.1 christos struct string *name = NULL;
2532 1.1 christos
2533 1.1 christos if (mapContains(cfile->stack[cfile->stack_top], "group"))
2534 1.1 christos parse_error(cfile, "another group is already open");
2535 1.1 christos group = createMap();
2536 1.1 christos group->skip = ISC_TRUE;
2537 1.1 christos group->kind = GROUP_DECL;
2538 1.1 christos TAILQ_CONCAT(&group->comments, &cfile->comments);
2539 1.1 christos mapSet(cfile->stack[cfile->stack_top], group, "group");
2540 1.1 christos
2541 1.1 christos token = peek_token(&val, NULL, cfile);
2542 1.1 christos if (is_identifier(token) || token == STRING) {
2543 1.1 christos skip_token(&val, NULL, cfile);
2544 1.1.1.2 christos
2545 1.1 christos name = makeString(-1, val);
2546 1.1 christos if (!name)
2547 1.1 christos parse_error(cfile, "no memory for group decl name %s",
2548 1.1 christos val);
2549 1.1.1.2 christos }
2550 1.1 christos
2551 1.1 christos parse_lbrace(cfile);
2552 1.1 christos
2553 1.1 christos stackPush(cfile, group);
2554 1.1 christos
2555 1.1 christos for (;;) {
2556 1.1 christos token = peek_token(&val, NULL, cfile);
2557 1.1 christos if (token == RBRACE) {
2558 1.1 christos skip_token(&val, NULL, cfile);
2559 1.1 christos break;
2560 1.1 christos } else if (token == END_OF_FILE) {
2561 1.1 christos skip_token(&val, NULL, cfile);
2562 1.1 christos parse_error(cfile, "unexpected end of file");
2563 1.1 christos break;
2564 1.1 christos } else if (token == TOKEN_DELETED) {
2565 1.1 christos skip_token(&val, NULL, cfile);
2566 1.1 christos parse_error(cfile, "deleted groups don't exist "
2567 1.1 christos "in the config file");
2568 1.1 christos } else if (token == DYNAMIC) {
2569 1.1 christos skip_token(&val, NULL, cfile);
2570 1.1 christos parse_error(cfile, "dynamic groups don't exist "
2571 1.1 christos "in the config file");
2572 1.1 christos } else if (token == STATIC) {
2573 1.1 christos skip_token(&val, NULL, cfile);
2574 1.1 christos parse_error(cfile, "static groups don't exist "
2575 1.1 christos "in the config file");
2576 1.1 christos }
2577 1.1 christos declaration = parse_statement(cfile, GROUP_DECL, declaration);
2578 1.1 christos }
2579 1.1 christos
2580 1.1 christos cfile->stack_top--;
2581 1.1 christos
2582 1.1 christos if (name != NULL)
2583 1.1 christos mapSet(group, createString(name), "name");
2584 1.1 christos close_group(cfile, group);
2585 1.1 christos }
2586 1.1 christos
2587 1.1 christos /*
2588 1.1 christos * Close a group. Called when a group is closed.
2589 1.1 christos * - spread parameters to children
2590 1.1 christos * - attach declarations at an upper level
2591 1.1 christos */
2592 1.1 christos
2593 1.1 christos void
2594 1.1 christos close_group(struct parse *cfile, struct element *group)
2595 1.1 christos {
2596 1.1 christos struct handle *handle;
2597 1.1 christos struct handle *nh;
2598 1.1 christos struct element *parent;
2599 1.1 christos struct element *item;
2600 1.1 christos struct element *param;
2601 1.1 christos struct handle *hosts = NULL;
2602 1.1 christos struct handle *shares = NULL;
2603 1.1 christos struct handle *subnets = NULL;
2604 1.1 christos struct handle *classes = NULL;
2605 1.1 christos struct handle *pdpools = NULL;
2606 1.1 christos struct handle *pools = NULL;
2607 1.1 christos struct handles downs;
2608 1.1 christos struct comment *comment;
2609 1.1 christos const char *key = NULL;
2610 1.1 christos const char *name = NULL;
2611 1.1 christos unsigned order = 0;
2612 1.1 christos isc_boolean_t marked = ISC_FALSE;
2613 1.1 christos
2614 1.1 christos TAILQ_INIT(&downs);
2615 1.1 christos
2616 1.1 christos /* check that group is in its parent */
2617 1.1 christos parent = cfile->stack[cfile->stack_top];
2618 1.1 christos if (parent->kind == PARAMETER)
2619 1.1 christos parse_error(cfile, "unexpected kind for group parent %d",
2620 1.1 christos parent->kind);
2621 1.1 christos item = mapGet(parent, "group");
2622 1.1 christos if (item == NULL)
2623 1.1 christos parse_error(cfile, "no group in parent");
2624 1.1 christos if (item != group)
2625 1.1 christos parse_error(cfile, "got a different group from parent");
2626 1.1 christos mapRemove(parent, "group");
2627 1.1 christos
2628 1.1 christos /* classify content */
2629 1.1 christos while (mapSize(group) > 0) {
2630 1.1 christos handle = mapPop(group);
2631 1.1 christos if ((handle == NULL) || (handle->key == NULL) ||
2632 1.1 christos (handle->value == NULL))
2633 1.1 christos parse_error(cfile, "can't get group item at %u",
2634 1.1 christos order);
2635 1.1 christos handle->order = order++;
2636 1.1 christos switch (handle->value->kind) {
2637 1.1 christos case TOPLEVEL:
2638 1.1 christos case ROOT_GROUP:
2639 1.1 christos case GROUP_DECL:
2640 1.1 christos badkind:
2641 1.1 christos parse_error(cfile, "impossible group item (kind %d) "
2642 1.1 christos "for %s at order %u",
2643 1.1 christos handle->value->kind, handle->key, order);
2644 1.1 christos
2645 1.1 christos case HOST_DECL:
2646 1.1 christos if (strcmp(handle->key, "reservations") != 0)
2647 1.1 christos parse_error(cfile, "expected reservations "
2648 1.1 christos "got %s at %u",
2649 1.1 christos handle->key, order);
2650 1.1 christos if (hosts != NULL)
2651 1.1 christos parse_error(cfile, "got reservations twice "
2652 1.1 christos "at %u and %u",
2653 1.1 christos hosts->order, order);
2654 1.1 christos if ((parent->kind == HOST_DECL) ||
2655 1.1 christos (parent->kind == CLASS_DECL))
2656 1.1 christos parse_error(cfile, "host declarations not "
2657 1.1 christos "allowed here.");
2658 1.1 christos hosts = handle;
2659 1.1 christos handle = NULL;
2660 1.1 christos break;
2661 1.1 christos
2662 1.1 christos case SHARED_NET_DECL:
2663 1.1 christos if (strcmp(handle->key, "shared-networks") != 0)
2664 1.1 christos parse_error(cfile, "expected shared-networks "
2665 1.1 christos "got %s at %u",
2666 1.1 christos handle->key, order);
2667 1.1 christos if ((parent->kind == SHARED_NET_DECL) ||
2668 1.1 christos (parent->kind == HOST_DECL) ||
2669 1.1 christos (parent->kind == SUBNET_DECL) ||
2670 1.1 christos (parent->kind == CLASS_DECL))
2671 1.1 christos parse_error(cfile, "shared-network parameters "
2672 1.1 christos "not allowed here.");
2673 1.1 christos shares = handle;
2674 1.1 christos handle = NULL;
2675 1.1 christos break;
2676 1.1 christos
2677 1.1 christos case SUBNET_DECL:
2678 1.1 christos key = local_family == AF_INET ? "subnet4" : "subnet6";
2679 1.1 christos if (strcmp(handle->key, key) != 0)
2680 1.1 christos parse_error(cfile, "expected %s got %s at %u",
2681 1.1 christos key, handle->key, order);
2682 1.1 christos if (subnets != NULL)
2683 1.1 christos parse_error(cfile, "got %s twice at %u and %u",
2684 1.1 christos key, subnets->order, order);
2685 1.1 christos if ((parent->kind == HOST_DECL) ||
2686 1.1 christos (parent->kind == SUBNET_DECL) ||
2687 1.1 christos (parent->kind == CLASS_DECL))
2688 1.1 christos parse_error(cfile, "subnet declarations not "
2689 1.1 christos "allowed here.");
2690 1.1 christos subnets = handle;
2691 1.1 christos handle = NULL;
2692 1.1 christos break;
2693 1.1 christos
2694 1.1 christos case CLASS_DECL:
2695 1.1 christos if (strcmp(handle->key, "client-classes") != 0)
2696 1.1 christos parse_error(cfile, "expected client-classes "
2697 1.1 christos "got %s at %u",
2698 1.1 christos handle->key, order);
2699 1.1 christos if (classes != NULL)
2700 1.1 christos parse_error(cfile, "got %s twice at %u and %u",
2701 1.1 christos key, classes->order, order);
2702 1.1 christos if (parent->kind == CLASS_DECL)
2703 1.1 christos parse_error(cfile, "class declarations not "
2704 1.1 christos "allowed here.");
2705 1.1 christos classes = handle;
2706 1.1 christos handle = NULL;
2707 1.1 christos break;
2708 1.1 christos
2709 1.1 christos case POOL_DECL:
2710 1.1 christos if (strcmp(handle->key, "pd-pools") == 0) {
2711 1.1 christos if (pdpools != NULL)
2712 1.1 christos parse_error(cfile, "got pd-pools "
2713 1.1 christos "twice at %u and %u",
2714 1.1 christos pdpools->order, order);
2715 1.1 christos pdpools = handle;
2716 1.1 christos } else if (strcmp(handle->key, "pools") == 0) {
2717 1.1 christos if (pools != NULL)
2718 1.1 christos parse_error(cfile, "got pools twice "
2719 1.1 christos "at %u and %u",
2720 1.1 christos pools->order, order);
2721 1.1 christos pools = handle;
2722 1.1 christos } else
2723 1.1 christos parse_error(cfile, "expecyed [pd-]pools got "
2724 1.1 christos "%s at %u",
2725 1.1 christos handle->key, order);
2726 1.1 christos if (parent->kind == POOL_DECL)
2727 1.1 christos parse_error(cfile, "pool declared within "
2728 1.1 christos "pool.");
2729 1.1 christos if ((parent->kind == HOST_DECL) ||
2730 1.1 christos (parent->kind == CLASS_DECL))
2731 1.1 christos parse_error(cfile, "pool declared outside "
2732 1.1 christos "of network");
2733 1.1 christos handle = NULL;
2734 1.1 christos break;
2735 1.1 christos default:
2736 1.1 christos if (handle->value->kind != PARAMETER)
2737 1.1 christos goto badkind;
2738 1.1 christos }
2739 1.1 christos if (handle == NULL)
2740 1.1 christos continue;
2741 1.1 christos
2742 1.1 christos /* we have a parameter */
2743 1.1 christos param = handle->value;
2744 1.1 christos /* group name */
2745 1.1 christos if (strcmp(handle->key, "name") == 0) {
2746 1.1 christos name = stringValue(param)->content;
2747 1.1 christos continue;
2748 1.1 christos }
2749 1.1 christos /* unexpected values */
2750 1.1 christos if ((strcmp(handle->key, "reservations") == 0) ||
2751 1.1 christos (strcmp(handle->key, "group") == 0) ||
2752 1.1 christos (strcmp(handle->key, "shared-networks") == 0) ||
2753 1.1 christos (strcmp(handle->key, "subnet4") == 0) ||
2754 1.1 christos (strcmp(handle->key, "subnet6") == 0) ||
2755 1.1 christos (strcmp(handle->key, "subnet") == 0) ||
2756 1.1 christos (strcmp(handle->key, "client-classes") == 0) ||
2757 1.1 christos (strcmp(handle->key, "hw-address") == 0) ||
2758 1.1 christos (strcmp(handle->key, "ip-address") == 0) ||
2759 1.1 christos (strcmp(handle->key, "extra-ip-addresses") == 0) ||
2760 1.1 christos (strcmp(handle->key, "ip-addresses") == 0) ||
2761 1.1 christos (strcmp(handle->key, "prefixes") == 0) ||
2762 1.1 christos (strcmp(handle->key, "pool") == 0) ||
2763 1.1 christos (strcmp(handle->key, "prefix") == 0) ||
2764 1.1 christos (strcmp(handle->key, "delegated-len") == 0) ||
2765 1.1 christos (strcmp(handle->key, "prefix-len") == 0) ||
2766 1.1 christos (strcmp(handle->key, "prefix-highest") == 0) ||
2767 1.1 christos (strcmp(handle->key, "option-def") == 0) ||
2768 1.1 christos (strcmp(handle->key, "hostname") == 0) ||
2769 1.1 christos (strcmp(handle->key, "client-id") == 0) ||
2770 1.1 christos (strcmp(handle->key, "host-identifier") == 0) ||
2771 1.1 christos (strcmp(handle->key, "flex-id") == 0) ||
2772 1.1 christos (strcmp(handle->key, "test") == 0) ||
2773 1.1 christos (strcmp(handle->key, "authoritative") == 0) ||
2774 1.1 christos (strcmp(handle->key, "dhcp-ddns") == 0) ||
2775 1.1 christos (strcmp(handle->key, "host-reservation-identifiers") == 0))
2776 1.1 christos parse_error(cfile, "unexpected parameter %s "
2777 1.1 christos "in group at %u",
2778 1.1 christos handle->key, order);
2779 1.1 christos
2780 1.1 christos /* to parent at group position */
2781 1.1 christos if ((strcmp(handle->key, "option-space") == 0) ||
2782 1.1 christos (strcmp(handle->key, "server-duid") == 0) ||
2783 1.1 christos (strcmp(handle->key, "statement") == 0) ||
2784 1.1 christos (strcmp(handle->key, "config") == 0) ||
2785 1.1 christos (strcmp(handle->key, "ddns-update-style") == 0) ||
2786 1.1 christos (strcmp(handle->key, "echo-client-id") == 0)) {
2787 1.1 christos if (!marked) {
2788 1.1 christos struct string *msg;
2789 1.1 christos
2790 1.1 christos marked = ISC_TRUE;
2791 1.1 christos msg = makeString(-1, "/// moved from group");
2792 1.1 christos if (name != NULL)
2793 1.1 christos appendString(msg, " ");
2794 1.1 christos appendString(msg, name);
2795 1.1 christos comment = createComment(msg->content);
2796 1.1 christos TAILQ_INSERT_TAIL(¶m->comments, comment);
2797 1.1 christos }
2798 1.1 christos mapSet(parent, param, handle->key);
2799 1.1 christos free(handle);
2800 1.1 christos continue;
2801 1.1 christos }
2802 1.1 christos /* To reconsider: qualifying-suffix, enable-updates */
2803 1.1 christos if ((strcmp(handle->key, "option-data") == 0) ||
2804 1.1 christos (strcmp(handle->key, "allow") == 0) ||
2805 1.1 christos (strcmp(handle->key, "deny") == 0) ||
2806 1.1 christos (strcmp(handle->key, "interface") == 0) ||
2807 1.1 christos (strcmp(handle->key, "valid-lifetime") == 0) ||
2808 1.1 christos (strcmp(handle->key, "preferred-lifetime") == 0) ||
2809 1.1 christos (strcmp(handle->key, "renew-timer") == 0) ||
2810 1.1 christos (strcmp(handle->key, "rebind-timer") == 0) ||
2811 1.1 christos (strcmp(handle->key, "boot-file-name") == 0) ||
2812 1.1 christos (strcmp(handle->key, "server-hostname") == 0) ||
2813 1.1 christos (strcmp(handle->key, "next-server") == 0) ||
2814 1.1 christos (strcmp(handle->key, "match-client-id") == 0)) {
2815 1.1 christos TAILQ_INSERT_TAIL(&downs, handle);
2816 1.1 christos continue;
2817 1.1 christos }
2818 1.1 christos /* unknown */
2819 1.1 christos if (!marked) {
2820 1.1 christos struct string *msg;
2821 1.1 christos
2822 1.1 christos marked = ISC_TRUE;
2823 1.1 christos msg = makeString(-1, "/// moved from group");
2824 1.1 christos if (name != NULL)
2825 1.1 christos appendString(msg, " ");
2826 1.1 christos appendString(msg, name);
2827 1.1 christos comment = createComment(msg->content);
2828 1.1 christos TAILQ_INSERT_TAIL(¶m->comments, comment);
2829 1.1 christos }
2830 1.1 christos comment = createComment("/// unhandled parameter");
2831 1.1 christos TAILQ_INSERT_TAIL(¶m->comments, comment);
2832 1.1 christos param->skip = ISC_TRUE;
2833 1.1 christos cfile->issue_counter++;
2834 1.1 christos mapSet(parent, param, handle->key);
2835 1.1 christos free(handle);
2836 1.1 christos }
2837 1.1 christos TAILQ_FOREACH_SAFE(handle, &downs, nh) {
2838 1.1 christos if (strcmp(handle->key, "option-data") == 0) {
2839 1.1 christos option_data_derive(cfile, handle, hosts);
2840 1.1 christos option_data_derive(cfile, handle, shares);
2841 1.1 christos option_data_derive(cfile, handle, subnets);
2842 1.1 christos derive_classes(cfile, handle, classes);
2843 1.1 christos option_data_derive(cfile, handle, pdpools);
2844 1.1 christos option_data_derive(cfile, handle, pools);
2845 1.1 christos } else if ((strcmp(handle->key, "allow") == 0) ||
2846 1.1 christos (strcmp(handle->key, "deny") == 0)) {
2847 1.1 christos derive(handle, pdpools);
2848 1.1 christos derive(handle, pools);
2849 1.1 christos } else if ((strcmp(handle->key, "interface") == 0) ||
2850 1.1 christos (strcmp(handle->key, "valid-lifetime") == 0) ||
2851 1.1 christos (strcmp(handle->key, "preferred-lifetime") == 0) ||
2852 1.1 christos (strcmp(handle->key, "renew-timer") == 0) ||
2853 1.1 christos (strcmp(handle->key, "rebind-timer") == 0) ||
2854 1.1 christos (strcmp(handle->key, "match-client-id") == 0)) {
2855 1.1 christos derive(handle, shares);
2856 1.1 christos derive(handle, subnets);
2857 1.1 christos } else if ((strcmp(handle->key, "boot-file-name") == 0) ||
2858 1.1 christos (strcmp(handle->key, "server-hostname") == 0)) {
2859 1.1 christos derive(handle, hosts);
2860 1.1 christos derive_classes(cfile, handle, classes);
2861 1.1 christos } else if (strcmp(handle->key, "next-server") == 0) {
2862 1.1 christos derive(handle, hosts);
2863 1.1 christos derive(handle, subnets);
2864 1.1 christos derive_classes(cfile, handle, classes);
2865 1.1 christos } else
2866 1.1 christos parse_error(cfile, "unexpected parameter %s to derive",
2867 1.1 christos handle->key);
2868 1.1 christos }
2869 1.1 christos if (hosts != NULL) {
2870 1.1 christos struct element *root;
2871 1.1 christos
2872 1.1 christos root = mapGet(cfile->stack[1], "reservations");
2873 1.1 christos if (root == NULL)
2874 1.1 christos mapSet(cfile->stack[1], hosts->value, "reservations");
2875 1.1 christos else
2876 1.1 christos concat(root, hosts->value);
2877 1.1 christos }
2878 1.1 christos if (shares != NULL) {
2879 1.1 christos struct element *upper;
2880 1.1 christos
2881 1.1 christos upper = mapGet(parent, "shared-networks");
2882 1.1 christos if (upper == NULL)
2883 1.1 christos mapSet(parent, shares->value, "shared-networks");
2884 1.1 christos else
2885 1.1 christos concat(upper, shares->value);
2886 1.1 christos }
2887 1.1 christos key = local_family == AF_INET ? "subnet4" : "subnet6";
2888 1.1 christos if (subnets != NULL) {
2889 1.1 christos struct element *upper;
2890 1.1 christos
2891 1.1 christos upper = mapGet(parent, key);
2892 1.1 christos if (upper == NULL)
2893 1.1 christos mapSet(parent, subnets->value, key);
2894 1.1 christos else
2895 1.1 christos concat(upper, subnets->value);
2896 1.1 christos }
2897 1.1 christos if (classes != NULL) {
2898 1.1 christos struct element *upper;
2899 1.1 christos size_t where;
2900 1.1 christos int kind = 0;
2901 1.1 christos
2902 1.1 christos for (where = cfile->stack_top; where > 0; --where) {
2903 1.1 christos kind = cfile->stack[where]->kind;
2904 1.1 christos if ((kind == GROUP_DECL) || (kind == ROOT_GROUP))
2905 1.1 christos break;
2906 1.1 christos }
2907 1.1 christos if (kind == GROUP_DECL) {
2908 1.1 christos upper = mapGet(cfile->stack[where], "client-classes");
2909 1.1 christos if (upper == NULL)
2910 1.1 christos mapSet(cfile->stack[where],
2911 1.1 christos classes->value,
2912 1.1 christos "client-classes");
2913 1.1 christos else
2914 1.1 christos concat_classes(cfile, upper, classes->value);
2915 1.1 christos }
2916 1.1 christos }
2917 1.1 christos if (pdpools != NULL) {
2918 1.1 christos struct element *upper;
2919 1.1 christos
2920 1.1 christos upper = mapGet(parent, "pd-pools");
2921 1.1 christos if (upper == NULL)
2922 1.1 christos mapSet(parent, pdpools->value, "pools");
2923 1.1 christos else
2924 1.1 christos concat(upper, pdpools->value);
2925 1.1 christos }
2926 1.1 christos if (pools != NULL) {
2927 1.1 christos struct element *upper;
2928 1.1 christos
2929 1.1 christos upper = mapGet(parent, "pools");
2930 1.1 christos if (upper == NULL)
2931 1.1 christos mapSet(parent, pools->value, "pools");
2932 1.1 christos else
2933 1.1 christos concat(upper, pools->value);
2934 1.1 christos }
2935 1.1 christos }
2936 1.1 christos
2937 1.1 christos /*
2938 1.1 christos * Specialized derivation routine for option-data
2939 1.1 christos * (options are identified by space + name and/or code
2940 1.1 christos */
2941 1.1 christos
2942 1.1 christos static void
2943 1.1 christos option_data_derive(struct parse *cfile, struct handle *src, struct handle *dst)
2944 1.1 christos {
2945 1.1 christos struct element *list;
2946 1.1 christos struct element *item;
2947 1.1 christos struct element *opt_list;
2948 1.1 christos size_t i;
2949 1.1 christos
2950 1.1 christos if (dst == NULL)
2951 1.1 christos return;
2952 1.1 christos list = dst->value;
2953 1.1 christos assert(list != NULL);
2954 1.1 christos assert(list->type == ELEMENT_LIST);
2955 1.1 christos for (i = 0; i < listSize(list); i++) {
2956 1.1 christos item = listGet(list, i);
2957 1.1 christos assert(item != NULL);
2958 1.1 christos assert(item->type == ELEMENT_MAP);
2959 1.1 christos opt_list = mapGet(item, src->key);
2960 1.1 christos if (opt_list != NULL) {
2961 1.1 christos merge_option_data(src->value, opt_list);
2962 1.1 christos continue;
2963 1.1 christos }
2964 1.1 christos opt_list = copy(src->value);
2965 1.1 christos mapSet(item, opt_list, src->key);
2966 1.1 christos }
2967 1.1 christos }
2968 1.1 christos
2969 1.1 christos /*
2970 1.1 christos * Specialized derivation routine for classes
2971 1.1 christos * (which are by reference so a resolution step is needed)
2972 1.1 christos */
2973 1.1 christos static void
2974 1.1 christos derive_classes(struct parse *cfile, struct handle *src, struct handle *dst)
2975 1.1 christos {
2976 1.1 christos struct element *list;
2977 1.1 christos struct element *item;
2978 1.1 christos size_t i;
2979 1.1 christos
2980 1.1 christos if (dst == NULL)
2981 1.1 christos return;
2982 1.1 christos list = dst->value;
2983 1.1 christos assert(list != NULL);
2984 1.1 christos assert(list->type == ELEMENT_LIST);
2985 1.1 christos for (i = 0; i < listSize(list); i++) {
2986 1.1 christos item = listGet(list, i);
2987 1.1 christos assert(item != NULL);
2988 1.1 christos assert(item->type == ELEMENT_MAP);
2989 1.1 christos item = get_class(cfile, item);
2990 1.1 christos if (item == NULL)
2991 1.1 christos parse_error(cfile, "dangling class reference");
2992 1.1 christos if (strcmp(src->key, "option-data") == 0) {
2993 1.1 christos struct element *opt_list;
2994 1.1 christos
2995 1.1 christos opt_list = mapGet(item, "option-data");
2996 1.1 christos if (opt_list != NULL)
2997 1.1 christos merge_option_data(src->value, opt_list);
2998 1.1 christos else
2999 1.1 christos mapSet(item, copy(src->value), "option-data");
3000 1.1 christos continue;
3001 1.1 christos }
3002 1.1 christos if (mapContains(item, src->key))
3003 1.1 christos continue;
3004 1.1 christos mapSet(item, copy(src->value), src->key);
3005 1.1 christos }
3006 1.1 christos }
3007 1.1 christos
3008 1.1 christos /* fixed-addr-parameter :== ip-addrs-or-hostnames SEMI
3009 1.1 christos ip-addrs-or-hostnames :== ip-addr-or-hostname
3010 1.1 christos | ip-addrs-or-hostnames ip-addr-or-hostname */
3011 1.1 christos
3012 1.1 christos struct element *
3013 1.1 christos parse_fixed_addr_param(struct parse *cfile, enum dhcp_token type) {
3014 1.1 christos const char *val;
3015 1.1 christos enum dhcp_token token;
3016 1.1 christos struct element *addr;
3017 1.1 christos struct element *addresses;
3018 1.1 christos struct string *address;
3019 1.1 christos
3020 1.1 christos addresses = createList();
3021 1.1 christos TAILQ_CONCAT(&addresses->comments, &cfile->comments);
3022 1.1 christos
3023 1.1 christos do {
3024 1.1 christos address = NULL;
3025 1.1 christos if (type == FIXED_ADDR)
3026 1.1 christos address = parse_ip_addr_or_hostname(cfile, ISC_TRUE);
3027 1.1 christos else if (type == FIXED_ADDR6)
3028 1.1 christos address = parse_ip6_addr_txt(cfile);
3029 1.1 christos else
3030 1.1 christos parse_error(cfile, "requires FIXED_ADDR[6]");
3031 1.1 christos if (address == NULL)
3032 1.1 christos parse_error(cfile, "can't parse fixed address");
3033 1.1 christos addr = createString(address);
3034 1.1 christos /* Take the comment for resolution into multiple addresses */
3035 1.1 christos TAILQ_CONCAT(&addr->comments, &cfile->comments);
3036 1.1 christos listPush(addresses, addr);
3037 1.1 christos token = peek_token(&val, NULL, cfile);
3038 1.1 christos if (token == COMMA)
3039 1.1 christos token = next_token(&val, NULL, cfile);
3040 1.1 christos } while (token == COMMA);
3041 1.1 christos
3042 1.1 christos parse_semi(cfile);
3043 1.1 christos
3044 1.1 christos /* Sanity */
3045 1.1 christos if (listSize(addresses) == 0)
3046 1.1 christos parse_error(cfile, "can't get fixed address");
3047 1.1 christos
3048 1.1 christos return addresses;
3049 1.1 christos
3050 1.1 christos }
3051 1.1 christos
3052 1.1 christos #ifdef notyet
3053 1.1 christos /* Parse the right side of a 'binding value'.
3054 1.1 christos *
3055 1.1 christos * set foo = "bar"; is a string
3056 1.1 christos * set foo = false; is a boolean
3057 1.1 christos * set foo = %31; is a numeric value.
3058 1.1 christos */
3059 1.1 christos static struct element *
3060 1.1 christos parse_binding_value(struct parse *cfile)
3061 1.1 christos {
3062 1.1 christos struct element *value = NULL;
3063 1.1 christos struct string *data;
3064 1.1 christos const char *val;
3065 1.1 christos unsigned buflen;
3066 1.1 christos int token;
3067 1.1 christos
3068 1.1 christos token = peek_token(&val, NULL, cfile);
3069 1.1 christos if (token == STRING) {
3070 1.1 christos skip_token(&val, &buflen, cfile);
3071 1.1 christos data = makeString(buflen, val);
3072 1.1 christos value = createString(data);
3073 1.1 christos } else if (token == NUMBER_OR_NAME) {
3074 1.1 christos value = createMap();
3075 1.1 christos data = parse_hexa(cfile);
3076 1.1 christos mapSet(value, createHexa(data), "const-data");
3077 1.1 christos } else if (token == PERCENT) {
3078 1.1 christos skip_token(&val, NULL, cfile);
3079 1.1 christos token = next_token(&val, NULL, cfile);
3080 1.1 christos if (token != NUMBER)
3081 1.1 christos parse_error(cfile, "expecting decimal number.");
3082 1.1 christos value = createInt(atol(val));
3083 1.1 christos } else if (token == NAME) {
3084 1.1 christos token = next_token(&val, NULL, cfile);
3085 1.1 christos if (!strcasecmp(val, "true"))
3086 1.1 christos value = createBool(ISC_TRUE);
3087 1.1 christos else if (!strcasecmp(val, "false"))
3088 1.1 christos value = createBool(ISC_FALSE);
3089 1.1 christos else
3090 1.1 christos parse_error(cfile, "expecting true or false");
3091 1.1 christos } else
3092 1.1 christos parse_error(cfile, "expecting a constant value.");
3093 1.1 christos
3094 1.1 christos return value;
3095 1.1 christos }
3096 1.1 christos #endif
3097 1.1 christos
3098 1.1 christos /* address-range-declaration :== ip-address ip-address SEMI
3099 1.1 christos | DYNAMIC_BOOTP ip-address ip-address SEMI */
3100 1.1 christos
3101 1.1 christos void
3102 1.1 christos parse_address_range(struct parse *cfile, int type, size_t where)
3103 1.1 christos {
3104 1.1 christos struct string *low, *high, *range;
3105 1.1 christos unsigned char addr[4];
3106 1.1 christos unsigned len = sizeof(addr);
3107 1.1 christos enum dhcp_token token;
3108 1.1 christos const char *val;
3109 1.1 christos struct element *pool;
3110 1.1 christos struct element *r;
3111 1.1 christos struct range *chain;
3112 1.1 christos size_t i;
3113 1.1 christos int kind;
3114 1.1 christos
3115 1.1 christos if ((token = peek_token(&val, NULL, cfile)) == DYNAMIC_BOOTP) {
3116 1.1 christos skip_token(&val, NULL, cfile);
3117 1.1 christos }
3118 1.1 christos
3119 1.1 christos /* Get the bottom address in the range... */
3120 1.1 christos low = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
3121 1.1 christos if (low == NULL)
3122 1.1 christos parse_error(cfile, "can't parse range (low)");
3123 1.1 christos
3124 1.1 christos /* Only one address? */
3125 1.1 christos token = peek_token(&val, NULL, cfile);
3126 1.1 christos if (token == SEMI)
3127 1.1 christos high = low;
3128 1.1 christos else {
3129 1.1 christos /* Get the top address in the range... */
3130 1.1 christos high = parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8);
3131 1.1 christos if (high == NULL)
3132 1.1 christos parse_error(cfile, "can't parse range (high)");
3133 1.1 christos }
3134 1.1 christos
3135 1.1 christos token = next_token(&val, NULL, cfile);
3136 1.1 christos if (token != SEMI)
3137 1.1 christos parse_error(cfile, "semicolon expected.");
3138 1.1 christos
3139 1.1 christos if (type != POOL_DECL) {
3140 1.1 christos struct element *group;
3141 1.1 christos struct element *pools;
3142 1.1 christos #ifdef want_bootp
3143 1.1 christos struct element *permit;
3144 1.1 christos #endif
3145 1.1 christos
3146 1.1 christos group = cfile->stack[where];
3147 1.1 christos pool = createMap();
3148 1.1 christos #ifdef want_bootp
3149 1.1 christos permit = createList();
3150 1.1 christos permit->skip = ISC_TRUE;
3151 1.1 christos
3152 1.1 christos /* Dynamic pools permit all clients. Otherwise
3153 1.1 christos we prohibit BOOTP clients. */
3154 1.1 christos if (dynamic) {
3155 1.1 christos struct string *all;
3156 1.1 christos
3157 1.1 christos all = makeString(-1, "all clients");
3158 1.1 christos listPush(permit, createString(all));
3159 1.1 christos mapSet(pool, permit, "allow");
3160 1.1 christos } else {
3161 1.1 christos struct string *dyn_bootp;
3162 1.1 christos
3163 1.1 christos dyn_bootp = makeString(-1, "dynamic bootp clients");
3164 1.1 christos listPush(permit, createString(dyn_bootp));
3165 1.1 christos mapSet(pool, permit, "deny");
3166 1.1 christos }
3167 1.1 christos #endif
3168 1.1 christos
3169 1.1 christos pools = mapGet(group, "pools");
3170 1.1 christos if (pools == NULL) {
3171 1.1 christos pools = createList();
3172 1.1 christos pools->kind = POOL_DECL;
3173 1.1 christos mapSet(group, pools, "pools");
3174 1.1 christos }
3175 1.1 christos listPush(pools, pool);
3176 1.1 christos } else
3177 1.1 christos pool = cfile->stack[where];
3178 1.1 christos
3179 1.1 christos /* Create the new address range... */
3180 1.1 christos if (memcmp(high->content, low->content, high->length) < 0) {
3181 1.1 christos struct string *swap;
3182 1.1 christos
3183 1.1 christos swap = low;
3184 1.1 christos low = high;
3185 1.1 christos high = swap;
3186 1.1 christos }
3187 1.1 christos range = makeStringExt(low->length, low->content, 'I');
3188 1.1 christos appendString(range, " - ");
3189 1.1 christos concatString(range, makeStringExt(high->length, high->content, 'I'));
3190 1.1 christos
3191 1.1 christos r = createString(range);
3192 1.1 christos TAILQ_CONCAT(&r->comments, &cfile->comments);
3193 1.1 christos
3194 1.1 christos mapSet(pool, r, "pool");
3195 1.1 christos
3196 1.1 christos chain = (struct range *)malloc(sizeof(*chain));
3197 1.1 christos if (chain == NULL)
3198 1.1 christos parse_error(cfile, "can't allocate range");
3199 1.1 christos memset(chain, 0, sizeof(*chain));
3200 1.1 christos chain->pool = pool;
3201 1.1 christos for (i = where; i > 0; --i) {
3202 1.1 christos kind = cfile->stack[i]->kind;
3203 1.1 christos if (kind == SHARED_NET_DECL) {
3204 1.1 christos chain->share = cfile->stack[i];
3205 1.1 christos break;
3206 1.1 christos }
3207 1.1 christos }
3208 1.1 christos chain->low = low;
3209 1.1 christos TAILQ_INSERT_TAIL(&known_ranges, chain);
3210 1.1 christos }
3211 1.1 christos
3212 1.1 christos /* address-range6-declaration :== ip-address6 ip-address6 SEMI
3213 1.1 christos | ip-address6 SLASH number SEMI
3214 1.1 christos | ip-address6 [SLASH number] TEMPORARY SEMI */
3215 1.1 christos
3216 1.1.1.2 christos void
3217 1.1 christos parse_address_range6(struct parse *cfile, int type, size_t where)
3218 1.1 christos {
3219 1.1 christos struct string *low, *high, *range;
3220 1.1 christos enum dhcp_token token;
3221 1.1 christos const char *val;
3222 1.1 christos isc_boolean_t is_temporary = ISC_FALSE;
3223 1.1 christos struct element *pool;
3224 1.1 christos struct element *r;
3225 1.1 christos struct range *chain;
3226 1.1 christos size_t i;
3227 1.1 christos int kind;
3228 1.1 christos
3229 1.1 christos if (local_family != AF_INET6)
3230 1.1 christos parse_error(cfile, "range6 statement is only supported "
3231 1.1 christos "in DHCPv6 mode.");
3232 1.1 christos
3233 1.1 christos /*
3234 1.1 christos * Read starting address as text.
3235 1.1 christos */
3236 1.1 christos low = parse_ip6_addr_txt(cfile);
3237 1.1 christos if (low == NULL)
3238 1.1 christos parse_error(cfile, "can't parse range6 address (low)");
3239 1.1 christos range = allocString();
3240 1.1 christos concatString(range, low);
3241 1.1 christos
3242 1.1.1.2 christos /*
3243 1.1 christos * See if we we're using range or CIDR notation or TEMPORARY
3244 1.1 christos */
3245 1.1 christos token = peek_token(&val, NULL, cfile);
3246 1.1 christos if (token == SLASH) {
3247 1.1 christos appendString(range, val);
3248 1.1 christos /*
3249 1.1 christos * '/' means CIDR notation, so read the bits we want.
3250 1.1 christos */
3251 1.1 christos skip_token(NULL, NULL, cfile);
3252 1.1 christos token = next_token(&val, NULL, cfile);
3253 1.1 christos if (token != NUMBER)
3254 1.1 christos parse_error(cfile, "expecting number");
3255 1.1 christos /*
3256 1.1 christos * no sanity checks
3257 1.1 christos */
3258 1.1 christos appendString(range, val);
3259 1.1 christos /*
3260 1.1 christos * can be temporary (RFC 4941 like)
3261 1.1 christos */
3262 1.1 christos token = peek_token(&val, NULL, cfile);
3263 1.1 christos if (token == TEMPORARY) {
3264 1.1 christos is_temporary = ISC_TRUE;
3265 1.1 christos appendString(range, " ");
3266 1.1 christos appendString(range, val);
3267 1.1 christos skip_token(NULL, NULL, cfile);
3268 1.1.1.2 christos }
3269 1.1 christos } else if (token == TEMPORARY) {
3270 1.1 christos /*
3271 1.1 christos * temporary (RFC 4941)
3272 1.1 christos */
3273 1.1 christos is_temporary = ISC_TRUE;
3274 1.1 christos appendString(range, "/64 ");
3275 1.1 christos appendString(range, val);
3276 1.1 christos skip_token(NULL, NULL, cfile);
3277 1.1 christos } else {
3278 1.1 christos /*
3279 1.1.1.2 christos * No '/', so we are looking for the end address of
3280 1.1 christos * the IPv6 pool.
3281 1.1 christos */
3282 1.1 christos high = parse_ip6_addr_txt(cfile);
3283 1.1 christos if (high == NULL)
3284 1.1 christos parse_error(cfile,
3285 1.1 christos "can't parse range6 address (high)");
3286 1.1 christos /* No sanity checks */
3287 1.1 christos appendString(range, " - ");
3288 1.1 christos appendString(range, high->content);
3289 1.1 christos }
3290 1.1 christos
3291 1.1 christos token = next_token(NULL, NULL, cfile);
3292 1.1 christos if (token != SEMI)
3293 1.1 christos parse_error(cfile, "semicolon expected.");
3294 1.1 christos
3295 1.1 christos if (type != POOL_DECL) {
3296 1.1 christos struct element *group;
3297 1.1 christos struct element *pools;
3298 1.1 christos
3299 1.1 christos group = cfile->stack[where];
3300 1.1 christos pool = createMap();
3301 1.1 christos pools = mapGet(group, "pools");
3302 1.1 christos if (pools == NULL) {
3303 1.1 christos pools = createList();
3304 1.1 christos pools->kind = POOL_DECL;
3305 1.1 christos mapSet(group, pools, "pools");
3306 1.1 christos }
3307 1.1 christos listPush(pools, pool);
3308 1.1 christos } else
3309 1.1 christos pool = cfile->stack[where];
3310 1.1 christos
3311 1.1 christos r = createString(range);
3312 1.1 christos TAILQ_CONCAT(&r->comments, &cfile->comments);
3313 1.1 christos if (is_temporary) {
3314 1.1 christos pool->skip = ISC_TRUE;
3315 1.1 christos cfile->issue_counter++;
3316 1.1 christos }
3317 1.1 christos mapSet(pool, r, "pool");
3318 1.1 christos
3319 1.1 christos chain = (struct range *)malloc(sizeof(*chain));
3320 1.1 christos if (chain == NULL)
3321 1.1 christos parse_error(cfile, "can't allocate range");
3322 1.1 christos memset(chain, 0, sizeof(*chain));
3323 1.1 christos chain->pool = pool;
3324 1.1 christos for (i = where; i > 0; --i) {
3325 1.1 christos kind = cfile->stack[i]->kind;
3326 1.1 christos if (kind == SHARED_NET_DECL) {
3327 1.1 christos chain->share = cfile->stack[i];
3328 1.1 christos break;
3329 1.1 christos }
3330 1.1 christos }
3331 1.1 christos chain->low = low;
3332 1.1 christos TAILQ_INSERT_TAIL(&known_ranges, chain);
3333 1.1 christos }
3334 1.1 christos
3335 1.1 christos /* prefix6-declaration :== ip-address6 ip-address6 SLASH number SEMI */
3336 1.1 christos
3337 1.1.1.2 christos void
3338 1.1 christos parse_prefix6(struct parse *cfile, int type, size_t where)
3339 1.1 christos {
3340 1.1 christos struct string *lo, *hi;
3341 1.1 christos int plen;
3342 1.1 christos int bits;
3343 1.1 christos enum dhcp_token token;
3344 1.1 christos const char *val;
3345 1.1 christos struct element *pool;
3346 1.1 christos struct element *prefix;
3347 1.1 christos
3348 1.1 christos if (local_family != AF_INET6)
3349 1.1 christos parse_error(cfile, "prefix6 statement is only supported "
3350 1.1 christos "in DHCPv6 mode.");
3351 1.1 christos
3352 1.1 christos /*
3353 1.1 christos * Read starting and ending address as text.
3354 1.1 christos */
3355 1.1 christos lo = parse_ip6_addr_txt(cfile);
3356 1.1 christos if (lo == NULL)
3357 1.1 christos parse_error(cfile, "can't parse prefix6 address (low)");
3358 1.1 christos
3359 1.1 christos hi = parse_ip6_addr_txt(cfile);
3360 1.1 christos if (hi == NULL)
3361 1.1 christos parse_error(cfile, "can't parse prefix6 address (high)");
3362 1.1 christos
3363 1.1 christos /*
3364 1.1 christos * Next is '/' number ';'.
3365 1.1 christos */
3366 1.1 christos token = next_token(NULL, NULL, cfile);
3367 1.1 christos if (token != SLASH)
3368 1.1 christos parse_error(cfile, "expecting '/'");
3369 1.1 christos token = next_token(&val, NULL, cfile);
3370 1.1 christos if (token != NUMBER)
3371 1.1 christos parse_error(cfile, "expecting number");
3372 1.1 christos bits = atoi(val);
3373 1.1 christos if ((bits <= 0) || (bits >= 128))
3374 1.1 christos parse_error(cfile, "networks have 0 to 128 bits (exclusive)");
3375 1.1 christos
3376 1.1 christos token = next_token(NULL, NULL, cfile);
3377 1.1 christos if (token != SEMI)
3378 1.1 christos parse_error(cfile, "semicolon expected.");
3379 1.1 christos
3380 1.1 christos if (type != POOL_DECL) {
3381 1.1 christos struct element *group;
3382 1.1 christos struct element *pools;
3383 1.1 christos
3384 1.1 christos group = cfile->stack[where];
3385 1.1 christos pool = createMap();
3386 1.1 christos pools = mapGet(group, "pd-pools");
3387 1.1 christos if (pools == NULL) {
3388 1.1 christos pools = createList();
3389 1.1 christos pools->kind = POOL_DECL;
3390 1.1 christos mapSet(group, pools, "pd-pools");
3391 1.1 christos }
3392 1.1 christos listPush(pools, pool);
3393 1.1 christos } else
3394 1.1 christos pool = cfile->stack[where];
3395 1.1 christos
3396 1.1 christos prefix = createString(lo);
3397 1.1 christos TAILQ_CONCAT(&prefix->comments, &cfile->comments);
3398 1.1 christos mapSet(pool, prefix, "prefix");
3399 1.1 christos mapSet(pool, createInt(bits), "delegated-len");
3400 1.1 christos plen = get_prefix_length(lo->content, hi->content);
3401 1.1 christos if (plen >= 0)
3402 1.1 christos mapSet(pool, createInt(plen), "prefix-len");
3403 1.1 christos else {
3404 1.1 christos if (!pool->skip)
3405 1.1 christos cfile->issue_counter++;
3406 1.1 christos pool->skip = ISC_TRUE;
3407 1.1 christos mapSet(pool, createString(hi), "prefix-highest");
3408 1.1 christos }
3409 1.1 christos }
3410 1.1 christos
3411 1.1 christos /* fixed-prefix6 :== ip6-address SLASH number SEMI */
3412 1.1 christos
3413 1.1 christos void
3414 1.1 christos parse_fixed_prefix6(struct parse *cfile, size_t host_decl)
3415 1.1 christos {
3416 1.1 christos struct string *ia;
3417 1.1 christos enum dhcp_token token;
3418 1.1 christos const char *val;
3419 1.1 christos struct element *host;
3420 1.1 christos struct element *prefixes;
3421 1.1 christos struct element *prefix;
3422 1.1 christos
3423 1.1 christos if (local_family != AF_INET6)
3424 1.1 christos parse_error(cfile, "fixed-prefix6 statement is only "
3425 1.1 christos "supported in DHCPv6 mode.");
3426 1.1 christos
3427 1.1 christos /*
3428 1.1 christos * Get the fixed-prefix list.
3429 1.1 christos */
3430 1.1 christos host = cfile->stack[host_decl];
3431 1.1 christos prefixes = mapGet(host, "prefixes");
3432 1.1 christos if (prefixes == NULL) {
3433 1.1 christos prefixes = createList();
3434 1.1 christos mapSet(host, prefixes, "prefixes");
3435 1.1 christos }
3436 1.1 christos
3437 1.1 christos ia = parse_ip6_addr_txt(cfile);
3438 1.1 christos if (ia == NULL)
3439 1.1 christos parse_error(cfile, "can't parse fixed-prefix6 address");
3440 1.1 christos token = next_token(&val, NULL, cfile);
3441 1.1 christos if (token != SLASH)
3442 1.1 christos parse_error(cfile, "expecting '/'");
3443 1.1 christos appendString(ia, val);
3444 1.1 christos token = next_token(&val, NULL, cfile);
3445 1.1 christos if (token != NUMBER)
3446 1.1 christos parse_error(cfile, "expecting number");
3447 1.1 christos appendString(ia, val);
3448 1.1 christos token = next_token(NULL, NULL, cfile);
3449 1.1 christos if (token != SEMI)
3450 1.1 christos parse_error(cfile, "semicolon expected.");
3451 1.1 christos
3452 1.1 christos prefix = createString(ia);
3453 1.1 christos TAILQ_CONCAT(&prefix->comments, &cfile->comments);
3454 1.1 christos listPush(prefixes, prefix);
3455 1.1 christos }
3456 1.1 christos
3457 1.1 christos /*!
3458 1.1 christos *
3459 1.1 christos * \brief Parse a pool6 statement
3460 1.1 christos *
3461 1.1 christos * Pool statements are used to group declarations and permit & deny information
3462 1.1 christos * with a specific address range. They must be declared within a shared network
3463 1.1 christos * or subnet and there may be multiple pools withing a shared network or subnet.
3464 1.1 christos * Each pool may have a different set of permit or deny options.
3465 1.1 christos *
3466 1.1 christos * \param[in] cfile = the configuration file being parsed
3467 1.1 christos * \param[in] type = the type of the enclosing statement. This must be
3468 1.1 christos * SUBNET_DECL for this function.
3469 1.1 christos *
3470 1.1 christos * \return
3471 1.1 christos * void - This function either parses the statement and updates the structures
3472 1.1 christos * or it generates an error message and possible halts the program if
3473 1.1 christos * it encounters a problem.
3474 1.1 christos */
3475 1.1 christos
3476 1.1 christos void
3477 1.1 christos parse_pool6_statement(struct parse *cfile, int type)
3478 1.1 christos {
3479 1.1 christos enum dhcp_token token;
3480 1.1 christos const char *val;
3481 1.1 christos isc_boolean_t done = ISC_FALSE;
3482 1.1 christos struct element *pool;
3483 1.1 christos struct element *pools;
3484 1.1 christos struct element *pdpool;
3485 1.1 christos struct element *pdpools;
3486 1.1 christos struct element *permit;
3487 1.1 christos struct element *prohibit;
3488 1.1 christos int declaration = 0;
3489 1.1 christos unsigned range_counter = 0;
3490 1.1 christos unsigned prefix_counter = 0;
3491 1.1 christos
3492 1.1 christos if (local_family != AF_INET6)
3493 1.1 christos parse_error(cfile, "pool6 statement is only supported "
3494 1.1 christos "in DHCPv6 mode.");
3495 1.1 christos
3496 1.1 christos pool = createMap();
3497 1.1 christos pool->kind = POOL_DECL;
3498 1.1 christos TAILQ_CONCAT(&pool->comments, &cfile->comments);
3499 1.1 christos
3500 1.1 christos if (type != SUBNET_DECL)
3501 1.1 christos parse_error(cfile, "pool6s are only valid inside "
3502 1.1 christos "subnet statements.");
3503 1.1 christos parse_lbrace(cfile);
3504 1.1 christos
3505 1.1 christos stackPush(cfile, pool);
3506 1.1 christos type = POOL_DECL;
3507 1.1 christos
3508 1.1 christos permit = createList();
3509 1.1 christos prohibit = createList();
3510 1.1 christos
3511 1.1 christos do {
3512 1.1 christos token = peek_token(&val, NULL, cfile);
3513 1.1 christos switch (token) {
3514 1.1 christos case RANGE6:
3515 1.1 christos skip_token(NULL, NULL, cfile);
3516 1.1 christos parse_address_range6(cfile, type, cfile->stack_top);
3517 1.1 christos range_counter++;
3518 1.1 christos break;
3519 1.1 christos
3520 1.1 christos case PREFIX6:
3521 1.1 christos skip_token(NULL, NULL, cfile);
3522 1.1 christos parse_prefix6(cfile, type, cfile->stack_top);
3523 1.1 christos mapSet(pool, createNull(), "***mark***");
3524 1.1 christos prefix_counter++;
3525 1.1 christos break;
3526 1.1 christos
3527 1.1 christos case ALLOW:
3528 1.1 christos skip_token(NULL, NULL, cfile);
3529 1.1 christos get_permit(cfile, permit);
3530 1.1 christos break;
3531 1.1 christos
3532 1.1 christos case DENY:
3533 1.1 christos skip_token(NULL, NULL, cfile);
3534 1.1 christos get_permit(cfile, prohibit);
3535 1.1 christos break;
3536 1.1.1.2 christos
3537 1.1 christos case RBRACE:
3538 1.1 christos skip_token(&val, NULL, cfile);
3539 1.1 christos done = ISC_TRUE;
3540 1.1 christos break;
3541 1.1 christos
3542 1.1 christos case END_OF_FILE:
3543 1.1 christos /*
3544 1.1 christos * We can get to END_OF_FILE if, for instance,
3545 1.1 christos * the parse_statement() reads all available tokens
3546 1.1 christos * and leaves us at the end.
3547 1.1 christos */
3548 1.1 christos parse_error(cfile, "unexpected end of file");
3549 1.1 christos
3550 1.1 christos default:
3551 1.1 christos declaration = parse_statement(cfile, POOL_DECL,
3552 1.1 christos declaration);
3553 1.1 christos break;
3554 1.1 christos }
3555 1.1 christos } while (!done);
3556 1.1 christos
3557 1.1 christos cfile->stack_top--;
3558 1.1 christos
3559 1.1 christos generate_class(cfile, pool, permit, prohibit);
3560 1.1 christos
3561 1.1 christos /*
3562 1.1 christos * Spread and eventually split between pools and pd-pools
3563 1.1 christos */
3564 1.1 christos if (prefix_counter == 0) {
3565 1.1 christos /* we need pools list */
3566 1.1 christos pools = mapGet(cfile->stack[cfile->stack_top], "pools");
3567 1.1 christos if (pools == NULL) {
3568 1.1 christos pools = createList();
3569 1.1 christos pools->kind = POOL_DECL;
3570 1.1 christos mapSet(cfile->stack[cfile->stack_top], pools, "pools");
3571 1.1 christos }
3572 1.1 christos
3573 1.1 christos /* no address or prefix range */
3574 1.1 christos if (range_counter == 0) {
3575 1.1 christos struct comment *comment;
3576 1.1 christos
3577 1.1 christos comment = createComment("empty pool6");
3578 1.1 christos TAILQ_INSERT_TAIL(&pool->comments, comment);
3579 1.1 christos pool->skip = ISC_TRUE;
3580 1.1 christos cfile->issue_counter++;
3581 1.1 christos listPush(pools, pool);
3582 1.1 christos return;
3583 1.1 christos }
3584 1.1 christos } else {
3585 1.1 christos /* we need pd-pools list */
3586 1.1 christos pdpools = mapGet(cfile->stack[cfile->stack_top], "pd-pools");
3587 1.1 christos if (pdpools == NULL) {
3588 1.1 christos pdpools = createList();
3589 1.1 christos pdpools->kind = POOL_DECL;
3590 1.1 christos mapSet(cfile->stack[cfile->stack_top],
3591 1.1 christos pdpools, "pd-pools");
3592 1.1 christos }
3593 1.1 christos
3594 1.1 christos /* split and purge copies */
3595 1.1 christos pdpool = copy(pool);
3596 1.1 christos while (mapContains(pdpool, "pool"))
3597 1.1 christos mapRemove(pdpool, "pool");
3598 1.1 christos while (mapContains(pool, "prefix"))
3599 1.1 christos mapRemove(pool, "prefix");
3600 1.1 christos while (mapContains(pool, "prefix-len"))
3601 1.1 christos mapRemove(pool, "prefix-len");
3602 1.1 christos while (mapContains(pool, "delegated-len"))
3603 1.1 christos mapRemove(pool, "delegated-len");
3604 1.1 christos while (mapContains(pool, "excluded-prefix"))
3605 1.1 christos mapRemove(pool, "excluded-prefix");
3606 1.1 christos while (mapContains(pool, "excluded-prefix-len"))
3607 1.1 christos mapRemove(pool, "excluded-prefix-len");
3608 1.1 christos while (mapContains(pool, "***mark***"))
3609 1.1 christos mapRemove(pool, "***mark***");
3610 1.1 christos
3611 1.1 christos /* spread extra prefixes into pdpool copies */
3612 1.1 christos while (--prefix_counter != 0) {
3613 1.1 christos struct handle *handle;
3614 1.1 christos struct element *first;
3615 1.1 christos struct element *saved;
3616 1.1 christos isc_boolean_t seen = ISC_FALSE;
3617 1.1 christos
3618 1.1 christos first = createMap();
3619 1.1 christos saved = copy(pdpool);
3620 1.1 christos while (mapSize(pdpool) > 0) {
3621 1.1 christos handle = mapPop(pdpool);
3622 1.1 christos if ((handle == NULL) ||
3623 1.1 christos (handle->key == NULL) ||
3624 1.1 christos (handle->value == NULL))
3625 1.1 christos parse_error(cfile, "bad pdpool entry");
3626 1.1 christos if (strcmp(handle->key, "***mark***") == 0) {
3627 1.1 christos if (!seen) {
3628 1.1 christos mapRemove(saved, handle->key);
3629 1.1 christos seen = ISC_TRUE;
3630 1.1 christos }
3631 1.1 christos continue;
3632 1.1 christos }
3633 1.1 christos if ((strcmp(handle->key, "prefix") != 0) &&
3634 1.1 christos (strcmp(handle->key, "prefix-len") != 0) &&
3635 1.1 christos (strcmp(handle->key,
3636 1.1 christos "delegated-len") != 0) &&
3637 1.1 christos (strcmp(handle->key,
3638 1.1 christos "excluded-prefix") != 0) &&
3639 1.1 christos (strcmp(handle->key,
3640 1.1 christos "excluded-prefix-len") != 0))
3641 1.1 christos mapSet(first, handle->value,
3642 1.1 christos handle->key);
3643 1.1 christos else if (!seen) {
3644 1.1 christos mapSet(first, handle->value,
3645 1.1 christos handle->key);
3646 1.1 christos mapRemove(saved, handle->key);
3647 1.1 christos }
3648 1.1 christos }
3649 1.1 christos listPush(pdpools, first);
3650 1.1 christos pdpool = saved;
3651 1.1 christos }
3652 1.1 christos if (!mapContains(pdpool, "***mark***"))
3653 1.1 christos parse_error(cfile, "can't find prefix marker");
3654 1.1 christos mapRemove(pdpool, "***mark***");
3655 1.1 christos if (mapContains(pdpool, "***mark***"))
3656 1.1 christos parse_error(cfile, "unexpected prefix marker");
3657 1.1 christos listPush(pdpools, pdpool);
3658 1.1 christos }
3659 1.1 christos
3660 1.1 christos /* Do pools now */
3661 1.1 christos if (range_counter != 0) {
3662 1.1 christos /* we need pools list */
3663 1.1 christos pools = mapGet(cfile->stack[cfile->stack_top], "pools");
3664 1.1 christos if (pools == NULL) {
3665 1.1 christos pools = createList();
3666 1.1 christos pools->kind = POOL_DECL;
3667 1.1 christos mapSet(cfile->stack[cfile->stack_top], pools, "pools");
3668 1.1 christos }
3669 1.1 christos
3670 1.1 christos /* spread extra prefixes into pool copies */
3671 1.1 christos while (--range_counter != 0) {
3672 1.1 christos struct handle *handle;
3673 1.1 christos struct element *first;
3674 1.1 christos struct element *saved;
3675 1.1 christos isc_boolean_t seen = ISC_FALSE;
3676 1.1 christos
3677 1.1 christos first = createMap();
3678 1.1 christos saved = copy(pool);
3679 1.1 christos while (mapSize(pool) > 0) {
3680 1.1 christos handle = mapPop(pool);
3681 1.1 christos if ((handle == NULL) ||
3682 1.1 christos (handle->key == NULL) ||
3683 1.1 christos (handle->value == NULL))
3684 1.1 christos parse_error(cfile, "bad pool entry");
3685 1.1 christos if (strcmp(handle->key, "pool") != 0)
3686 1.1 christos mapSet(first, handle->value,
3687 1.1 christos handle->key);
3688 1.1 christos else if (!seen) {
3689 1.1 christos mapSet(first, handle->value,
3690 1.1 christos handle->key);
3691 1.1 christos mapRemove(saved, "pool");
3692 1.1 christos seen = ISC_TRUE;
3693 1.1 christos }
3694 1.1 christos }
3695 1.1 christos listPush(pools, first);
3696 1.1 christos pool = saved;
3697 1.1 christos }
3698 1.1 christos listPush(pools, pool);
3699 1.1 christos }
3700 1.1 christos }
3701 1.1 christos
3702 1.1 christos /* allow-deny-keyword :== BOOTP
3703 1.1 christos | BOOTING
3704 1.1 christos | DYNAMIC_BOOTP
3705 1.1 christos | UNKNOWN_CLIENTS */
3706 1.1 christos
3707 1.1 christos struct element *
3708 1.1 christos parse_allow_deny(struct parse *cfile, int flag)
3709 1.1 christos {
3710 1.1 christos enum dhcp_token token;
3711 1.1 christos const char *val;
3712 1.1 christos const char *value;
3713 1.1 christos const char *name;
3714 1.1 christos struct element *config;
3715 1.1 christos struct option *option;
3716 1.1 christos
3717 1.1 christos switch (flag) {
3718 1.1 christos case 0:
3719 1.1 christos value = "deny";
3720 1.1 christos break;
3721 1.1 christos case 1:
3722 1.1 christos value = "allow";
3723 1.1 christos break;
3724 1.1 christos case 2:
3725 1.1 christos value = "ignore";
3726 1.1 christos break;
3727 1.1 christos default:
3728 1.1 christos value = "unknown?";
3729 1.1 christos break;
3730 1.1 christos }
3731 1.1 christos
3732 1.1 christos token = next_token(&val, NULL, cfile);
3733 1.1 christos switch (token) {
3734 1.1 christos case TOKEN_BOOTP:
3735 1.1 christos name = "allow-bootp";
3736 1.1 christos break;
3737 1.1 christos
3738 1.1 christos case BOOTING:
3739 1.1 christos name = "allow-booting";
3740 1.1 christos break;
3741 1.1 christos
3742 1.1 christos case DYNAMIC_BOOTP:
3743 1.1 christos name = "dynamic-bootp";
3744 1.1 christos break;
3745 1.1 christos
3746 1.1 christos case UNKNOWN_CLIENTS:
3747 1.1 christos name = "boot-unknown-clients";
3748 1.1 christos break;
3749 1.1 christos
3750 1.1 christos case DUPLICATES:
3751 1.1 christos name = "duplicates";
3752 1.1 christos break;
3753 1.1 christos
3754 1.1 christos case DECLINES:
3755 1.1 christos name = "declines";
3756 1.1 christos break;
3757 1.1 christos
3758 1.1 christos case CLIENT_UPDATES:
3759 1.1 christos name = "client-updates";
3760 1.1 christos break;
3761 1.1 christos
3762 1.1 christos case LEASEQUERY:
3763 1.1 christos name = "leasequery";
3764 1.1 christos break;
3765 1.1 christos
3766 1.1 christos default:
3767 1.1 christos parse_error(cfile, "expecting allow/deny key");
3768 1.1 christos }
3769 1.1 christos parse_semi(cfile);
3770 1.1 christos
3771 1.1 christos config = createMap();
3772 1.1 christos mapSet(config, createString(makeString(-1, value)), "value");
3773 1.1 christos mapSet(config, createString(makeString(-1, name)), "name");
3774 1.1 christos option = option_lookup_name("server", name);
3775 1.1 christos if (option == NULL)
3776 1.1 christos parse_error(cfile, "unknown allow/deny keyword (%s)", name);
3777 1.1 christos mapSet(config, createInt(option->code), "code");
3778 1.1 christos config->skip = ISC_TRUE;
3779 1.1 christos cfile->issue_counter++;
3780 1.1 christos return config;
3781 1.1 christos }
3782 1.1 christos
3783 1.1 christos /*
3784 1.1 christos * When we parse a server-duid statement in a config file, we will
3785 1.1 christos * have the type of the server DUID to generate, and possibly the
3786 1.1 christos * actual value defined.
3787 1.1 christos *
3788 1.1 christos * server-duid llt;
3789 1.1 christos * server-duid llt ethernet|ieee802|fddi 213982198 00:16:6F:49:7D:9B;
3790 1.1 christos * server-duid ll;
3791 1.1 christos * server-duid ll ethernet|ieee802|fddi 00:16:6F:49:7D:9B;
3792 1.1 christos * server-duid en 2495 "enterprise-specific-identifier-1234";
3793 1.1 christos */
3794 1.1.1.2 christos void
3795 1.1 christos parse_server_duid_conf(struct parse *cfile) {
3796 1.1 christos enum dhcp_token token;
3797 1.1 christos const char *val;
3798 1.1 christos unsigned int len;
3799 1.1 christos struct string *ll_addr;
3800 1.1 christos struct element *duid;
3801 1.1 christos struct element *item;
3802 1.1 christos int ll_type;
3803 1.1 christos
3804 1.1 christos duid = createMap();
3805 1.1 christos TAILQ_CONCAT(&duid->comments, &cfile->comments);
3806 1.1 christos
3807 1.1 christos /*
3808 1.1 christos * Consume the SERVER_DUID token.
3809 1.1 christos */
3810 1.1 christos next_token(&val, NULL, cfile);
3811 1.1 christos
3812 1.1 christos /*
3813 1.1 christos * Obtain the DUID type.
3814 1.1 christos */
3815 1.1 christos token = next_token(&val, NULL, cfile);
3816 1.1 christos
3817 1.1.1.2 christos /*
3818 1.1 christos * Enterprise is the easiest - enterprise number and raw data
3819 1.1 christos * are required.
3820 1.1 christos */
3821 1.1 christos if (token == EN) {
3822 1.1 christos item = createString(makeString(-1, "EN"));
3823 1.1 christos mapSet(duid, item, "type");
3824 1.1 christos
3825 1.1 christos /*
3826 1.1 christos * Get enterprise number and identifier.
3827 1.1 christos */
3828 1.1 christos token = next_token(&val, NULL, cfile);
3829 1.1 christos if (token != NUMBER)
3830 1.1 christos parse_error(cfile, "enterprise number expected");
3831 1.1 christos item = createInt(atoi(val));
3832 1.1 christos mapSet(duid, item, "enterprise-id");
3833 1.1 christos
3834 1.1 christos token = next_token(&val, &len, cfile);
3835 1.1 christos if (token != STRING)
3836 1.1 christos parse_error(cfile, "identifier expected");
3837 1.1 christos /* Kea requires a hexadecimal identifier */
3838 1.1 christos if (is_hexa_only(val, len))
3839 1.1 christos item = createString(makeString(len, val));
3840 1.1 christos else
3841 1.1 christos item = createString(makeStringExt(len, val, 'X'));
3842 1.1 christos mapSet(duid, item, "identifier");
3843 1.1 christos }
3844 1.1 christos
3845 1.1.1.2 christos /*
3846 1.1 christos * Next easiest is the link-layer DUID. It consists only of
3847 1.1 christos * the LL directive, or optionally the specific value to use.
3848 1.1 christos *
3849 1.1 christos * If we have LL only, then we set the type. If we have the
3850 1.1 christos * value, then we set the actual DUID.
3851 1.1 christos */
3852 1.1 christos else if (token == LL) {
3853 1.1 christos item = createString(makeString(-1, "LL"));
3854 1.1 christos mapSet(duid, item, "type");
3855 1.1 christos
3856 1.1 christos if (peek_token(NULL, NULL, cfile) != SEMI) {
3857 1.1 christos /*
3858 1.1 christos * Get our hardware type and address.
3859 1.1 christos */
3860 1.1 christos token = next_token(NULL, NULL, cfile);
3861 1.1 christos switch (token) {
3862 1.1 christos case ETHERNET:
3863 1.1 christos ll_type = HTYPE_ETHER;
3864 1.1 christos break;
3865 1.1 christos case TOKEN_RING:
3866 1.1 christos ll_type = HTYPE_IEEE802;
3867 1.1 christos break;
3868 1.1 christos case TOKEN_FDDI:
3869 1.1 christos ll_type = HTYPE_FDDI;
3870 1.1 christos break;
3871 1.1 christos default:
3872 1.1 christos parse_error(cfile, "hardware type expected");
3873 1.1 christos }
3874 1.1 christos item = createInt(ll_type);
3875 1.1 christos mapSet(duid, item, "htype");
3876 1.1 christos
3877 1.1 christos ll_addr = parse_hexa(cfile);
3878 1.1 christos if (ll_addr == NULL)
3879 1.1 christos parse_error(cfile,
3880 1.1 christos "can't get hardware address");
3881 1.1 christos item = createString(ll_addr);
3882 1.1 christos mapSet(duid, item, "identifier");
3883 1.1 christos }
3884 1.1 christos }
3885 1.1 christos
3886 1.1.1.2 christos /*
3887 1.1 christos * Finally the link-layer DUID plus time. It consists only of
3888 1.1 christos * the LLT directive, or optionally the specific value to use.
3889 1.1 christos *
3890 1.1 christos * If we have LLT only, then we set the type. If we have the
3891 1.1 christos * value, then we set the actual DUID.
3892 1.1 christos */
3893 1.1 christos else if (token == LLT) {
3894 1.1 christos item = createString(makeString(-1, "LLT"));
3895 1.1 christos mapSet(duid, item, "type");
3896 1.1 christos
3897 1.1 christos if (peek_token(NULL, NULL, cfile) != SEMI) {
3898 1.1 christos /*
3899 1.1 christos * Get our hardware type, timestamp, and address.
3900 1.1 christos */
3901 1.1 christos token = next_token(NULL, NULL, cfile);
3902 1.1 christos switch (token) {
3903 1.1 christos case ETHERNET:
3904 1.1 christos ll_type = HTYPE_ETHER;
3905 1.1 christos break;
3906 1.1 christos case TOKEN_RING:
3907 1.1 christos ll_type = HTYPE_IEEE802;
3908 1.1 christos break;
3909 1.1 christos case TOKEN_FDDI:
3910 1.1 christos ll_type = HTYPE_FDDI;
3911 1.1 christos break;
3912 1.1 christos default:
3913 1.1 christos parse_error(cfile, "hardware type expected");
3914 1.1 christos }
3915 1.1 christos item = createInt(ll_type);
3916 1.1 christos mapSet(duid, item, "htype");
3917 1.1 christos
3918 1.1 christos token = next_token(&val, NULL, cfile);
3919 1.1 christos if (token != NUMBER)
3920 1.1 christos parse_error(cfile, "timestamp expected");
3921 1.1 christos item = createInt(atoi(val));
3922 1.1 christos mapSet(duid, item, "time");
3923 1.1 christos
3924 1.1 christos ll_addr = parse_hexa(cfile);
3925 1.1 christos if (ll_addr == NULL)
3926 1.1 christos parse_error(cfile,
3927 1.1 christos "can't get hardware address");
3928 1.1 christos item = createString(ll_addr);
3929 1.1 christos mapSet(duid, item, "identifier");
3930 1.1 christos }
3931 1.1 christos }
3932 1.1 christos
3933 1.1 christos /*
3934 1.1 christos * If users want they can use a number for DUID types.
3935 1.1 christos * This is useful for supporting future, not-yet-defined
3936 1.1 christos * DUID types.
3937 1.1 christos *
3938 1.1 christos * In this case, they have to put in the complete value.
3939 1.1 christos *
3940 1.1.1.2 christos * This also works for existing DUID types of course.
3941 1.1 christos */
3942 1.1 christos else if (token == NUMBER) {
3943 1.1 christos item = createString(makeString(-1, val));
3944 1.1 christos item->skip = ISC_TRUE;
3945 1.1 christos /* Kea wants EN, LL or LLT so skip the whole thing */
3946 1.1 christos duid->skip = ISC_TRUE;
3947 1.1 christos cfile->issue_counter++;
3948 1.1 christos mapSet(duid, item, "type");
3949 1.1 christos
3950 1.1 christos token = next_token(&val, &len, cfile);
3951 1.1 christos if (token != STRING)
3952 1.1 christos parse_error(cfile, "identifier expected");
3953 1.1 christos item = createString(makeString(len, val));
3954 1.1 christos mapSet(duid, item, "identifier");
3955 1.1 christos }
3956 1.1 christos
3957 1.1 christos /*
3958 1.1 christos * Anything else is an error.
3959 1.1 christos */
3960 1.1 christos else
3961 1.1 christos parse_error(cfile, "DUID type of LLT, EN, or LL expected");
3962 1.1 christos
3963 1.1 christos /*
3964 1.1 christos * Finally consume our trailing semicolon.
3965 1.1 christos */
3966 1.1 christos token = next_token(NULL, NULL, cfile);
3967 1.1 christos if (token != SEMI)
3968 1.1 christos parse_error(cfile, "semicolon expected");
3969 1.1 christos
3970 1.1 christos /* server-id is a global parameter */
3971 1.1 christos if (mapContains(cfile->stack[1], "server-id"))
3972 1.1 christos parse_error(cfile, "there is already a server-id");
3973 1.1 christos /* DHCPv6 only but not fatal */
3974 1.1 christos if ((local_family != AF_INET6) && !duid->skip) {
3975 1.1 christos duid->skip = ISC_TRUE;
3976 1.1 christos cfile->issue_counter++;
3977 1.1 christos }
3978 1.1 christos mapSet(cfile->stack[1], duid, "server-id");
3979 1.1 christos }
3980 1.1 christos
3981 1.1 christos /* Check whether the argument is encoded in hexadecimal or not */
3982 1.1 christos static isc_boolean_t
3983 1.1 christos is_hexa_only(const char *s, unsigned l)
3984 1.1 christos {
3985 1.1 christos unsigned i;
3986 1.1 christos
3987 1.1 christos for (i = 0; i < l; i++)
3988 1.1 christos if (!isxdigit((int)s[i]))
3989 1.1 christos return ISC_FALSE;
3990 1.1 christos return ISC_TRUE;
3991 1.1 christos }
3992 1.1 christos
3993 1.1 christos /*!
3994 1.1 christos *
3995 1.1 christos * \brief Parse (and execute) a directive (extension)
3996 1.1 christos *
3997 1.1 christos * OPTION SPACE <name> [ALIAS <kea-name>] [KNOWN*2|UNKNOWN*2|DYNAMIC]
3998 1.1 christos * OPTION <universe>.<name> [CHECK]
3999 1.1 christos * [ALIAS <name>]
4000 1.1 christos * [CODE <code> = "<format>"]
4001 1.1 christos * [KNOWN*2|UNKNOWN*2|DYNAMIC]
4002 1.1 christos * [LOCAL|DEFINE]
4003 1.1 christos */
4004 1.1 christos
4005 1.1 christos void
4006 1.1 christos parse_directive(struct parse *cfile)
4007 1.1 christos {
4008 1.1 christos enum dhcp_token token;
4009 1.1 christos const char *val;
4010 1.1 christos isc_boolean_t known;
4011 1.1 christos struct option *option;
4012 1.1 christos
4013 1.1 christos token = peek_token(&val, NULL, cfile);
4014 1.1 christos
4015 1.1 christos switch (token) {
4016 1.1 christos case OPTION:
4017 1.1 christos skip_token(&val, NULL, cfile);
4018 1.1 christos token = peek_token(&val, NULL, cfile);
4019 1.1 christos if (token == SPACE) {
4020 1.1 christos parse_option_space_dir(cfile);
4021 1.1 christos return;
4022 1.1 christos }
4023 1.1 christos
4024 1.1 christos known = ISC_FALSE;
4025 1.1 christos option = parse_option_name(cfile, ISC_TRUE, &known);
4026 1.1 christos token = next_token(&val, NULL, cfile);
4027 1.1 christos if (token == CHECK) {
4028 1.1 christos struct string *datatype;
4029 1.1 christos isc_boolean_t is_array = ISC_FALSE;
4030 1.1 christos isc_boolean_t encapsulate = ISC_FALSE;
4031 1.1 christos
4032 1.1 christos datatype = convert_format(option->format,
4033 1.1 christos &is_array,
4034 1.1 christos &encapsulate);
4035 1.1 christos printf("option ISC DHCP (Kea)\n"
4036 1.1 christos " %s.%s (%s.%s)\n"
4037 1.1 christos " format \"%s\" (type \"%s\" "
4038 1.1 christos "array %s encap %s)\n"
4039 1.1 christos " status %s\n",
4040 1.1 christos option->space->old, option->old,
4041 1.1 christos option->space->name, option->name,
4042 1.1 christos option->format, datatype->content,
4043 1.1 christos is_array ? "true" : "false",
4044 1.1 christos encapsulate ? "true" : "false",
4045 1.1 christos display_status(option->status));
4046 1.1 christos parse_semi(cfile);
4047 1.1 christos return;
4048 1.1 christos }
4049 1.1 christos if (option->space->status == special)
4050 1.1 christos parse_error(cfile, "attempt to modify config %s.%s",
4051 1.1 christos option->space->old, option->name);
4052 1.1 christos if (token == ALIAS) {
4053 1.1 christos token = next_token(&val, NULL, cfile);
4054 1.1 christos if (!is_identifier(token))
4055 1.1 christos parse_error(cfile,
4056 1.1 christos "expecting identifier after "
4057 1.1 christos "alias keyword.");
4058 1.1 christos if (option->status != dynamic)
4059 1.1 christos parse_error(cfile,
4060 1.1 christos "attempt to rename %s.%s to %s",
4061 1.1 christos option->space->name,
4062 1.1 christos option->name, val);
4063 1.1 christos option->name = strdup(val);
4064 1.1 christos parse_semi(cfile);
4065 1.1 christos return;
4066 1.1 christos }
4067 1.1 christos if (token == CODE) {
4068 1.1 christos parse_option_code_dir(cfile, option);
4069 1.1 christos return;
4070 1.1 christos }
4071 1.1 christos if ((token == KNOWN) || (token == UNKNOWN) ||
4072 1.1 christos (token == DYNAMIC)) {
4073 1.1 christos parse_option_status_dir(cfile, option, token);
4074 1.1 christos return;
4075 1.1 christos }
4076 1.1 christos if (token == LOCAL) {
4077 1.1 christos parse_option_local_dir(cfile, option);
4078 1.1 christos parse_semi(cfile);
4079 1.1 christos return;
4080 1.1 christos }
4081 1.1 christos if (token == DEFINE) {
4082 1.1 christos parse_option_define_dir(cfile, option);
4083 1.1 christos parse_semi(cfile);
4084 1.1 christos return;
4085 1.1 christos }
4086 1.1 christos parse_error(cfile, "unknown option directive %s", val);
4087 1.1 christos
4088 1.1 christos default:
4089 1.1 christos parse_error(cfile, "unknown directive %s", val);
4090 1.1 christos }
4091 1.1 christos }
4092 1.1 christos
4093 1.1 christos /* Set alias and status for option spaces */
4094 1.1 christos
4095 1.1 christos void
4096 1.1 christos parse_option_space_dir(struct parse *cfile)
4097 1.1 christos {
4098 1.1 christos enum dhcp_token token;
4099 1.1 christos const char *val;
4100 1.1 christos struct space *space;
4101 1.1 christos
4102 1.1 christos skip_token(NULL, NULL, cfile); /* Discard SPACE */
4103 1.1 christos token = next_token(&val, NULL, cfile);
4104 1.1 christos if (!is_identifier(token))
4105 1.1 christos parse_error(cfile, "expecting identifier.");
4106 1.1 christos space = space_lookup(val);
4107 1.1 christos if (space == NULL)
4108 1.1 christos parse_error(cfile, "can't find space '%s", val);
4109 1.1 christos
4110 1.1 christos token = next_token(&val, NULL, cfile);
4111 1.1 christos if (token == CHECK) {
4112 1.1 christos printf("space ISC DHCP (kea)\n"
4113 1.1 christos " %s (%s)\n status %s\n%s",
4114 1.1 christos space->old, space->name,
4115 1.1 christos display_status(space->status),
4116 1.1 christos space->vendor != NULL ? " vendor\n" : "");
4117 1.1 christos parse_semi(cfile);
4118 1.1 christos return;
4119 1.1 christos }
4120 1.1 christos if (token == ALIAS) {
4121 1.1 christos token = next_token(&val, NULL, cfile);
4122 1.1 christos if (!is_identifier(token))
4123 1.1 christos parse_error(cfile,
4124 1.1 christos "expecting identifier after "
4125 1.1 christos "alias keyword.");
4126 1.1 christos if (space->status != dynamic)
4127 1.1 christos parse_error(cfile,
4128 1.1 christos "attempt to rename %s to %s",
4129 1.1 christos space->name, val);
4130 1.1 christos space->name = strdup(val);
4131 1.1 christos parse_semi(cfile);
4132 1.1 christos return;
4133 1.1 christos }
4134 1.1 christos if (token == DYNAMIC)
4135 1.1 christos space->status = dynamic;
4136 1.1 christos else if (token == UNKNOWN) {
4137 1.1 christos token = next_token(NULL, NULL, cfile);
4138 1.1 christos if (token == KNOWN)
4139 1.1 christos space->status = known;
4140 1.1 christos else if (token == UNKNOWN)
4141 1.1 christos space->status = kea_unknown;
4142 1.1 christos else
4143 1.1 christos parse_error(cfile, "expected KNOW or UNKNOWN");
4144 1.1 christos } else if (token != UNKNOWN)
4145 1.1 christos parse_error(cfile, "expected KNOW or UNKNOWN or DYNAMIC");
4146 1.1 christos else {
4147 1.1 christos if (token == KNOWN)
4148 1.1 christos space->status = isc_dhcp_unknown;
4149 1.1 christos else if (token == UNKNOWN)
4150 1.1 christos parse_error(cfile, "illicit combination: space "
4151 1.1 christos "%s is known by nobody", space->name);
4152 1.1 christos else
4153 1.1 christos parse_error(cfile, "expected KNOW or UNKNOWN");
4154 1.1 christos }
4155 1.1 christos parse_semi(cfile);
4156 1.1 christos }
4157 1.1 christos
4158 1.1 christos /* Alternative to parse_option_code_decl using the raw ISC DHCP format */
4159 1.1 christos
4160 1.1 christos void
4161 1.1 christos parse_option_code_dir(struct parse *cfile, struct option *option)
4162 1.1 christos {
4163 1.1 christos const char *val;
4164 1.1 christos enum dhcp_token token;
4165 1.1 christos unsigned code;
4166 1.1 christos struct element *def;
4167 1.1 christos struct element *optdef;
4168 1.1 christos struct string *datatype;
4169 1.1 christos isc_boolean_t is_array = ISC_FALSE;
4170 1.1 christos isc_boolean_t encapsulate = ISC_FALSE;
4171 1.1 christos
4172 1.1 christos def = createMap();
4173 1.1 christos mapSet(def,
4174 1.1 christos createString(makeString(-1, option->space->name)),
4175 1.1 christos "space");
4176 1.1 christos mapSet(def, createString(makeString(-1, option->name)), "name");
4177 1.1 christos
4178 1.1 christos /* Parse the option code. */
4179 1.1 christos token = next_token(&val, NULL, cfile);
4180 1.1 christos if (token != NUMBER)
4181 1.1 christos parse_error(cfile, "expecting option code number.");
4182 1.1 christos code = atoi(val);
4183 1.1 christos mapSet(def, createInt(code), "code");
4184 1.1 christos
4185 1.1 christos /* We have the code so we can get the real option now */
4186 1.1 christos if (option->code == 0) {
4187 1.1 christos struct option *from_code;
4188 1.1 christos
4189 1.1 christos option->code = code;
4190 1.1 christos from_code = option_lookup_code(option->space->old, code);
4191 1.1 christos if (from_code != NULL)
4192 1.1 christos option = from_code;
4193 1.1 christos }
4194 1.1 christos
4195 1.1 christos /* Redefinitions are not allowed */
4196 1.1 christos if ((option->status != dynamic) ||
4197 1.1 christos (strcmp(option->format, "u") != 0))
4198 1.1 christos parse_error(cfile, "attempt to redefine %s.%s code %u",
4199 1.1 christos option->space->name, option->name, code);
4200 1.1 christos
4201 1.1 christos token = next_token(&val, NULL, cfile);
4202 1.1 christos if (token != EQUAL)
4203 1.1 christos parse_error(cfile, "expecting \"=\"");
4204 1.1 christos token = next_token(&val, NULL, cfile);
4205 1.1 christos if (token != STRING)
4206 1.1 christos parse_error(cfile, "expecting format string");
4207 1.1 christos option->format = strdup(val);
4208 1.1 christos parse_semi(cfile);
4209 1.1 christos
4210 1.1 christos datatype = convert_format(val, &is_array, &encapsulate);
4211 1.1 christos
4212 1.1 christos if ((datatype == NULL) && (strchr(datatype->content, '?') != NULL))
4213 1.1 christos parse_error(cfile, "failed to convert format \"%s\" for "
4214 1.1 christos "option %s.%s code %u",
4215 1.1 christos val, option->space->name, option->name, code);
4216 1.1 christos /* todo */
4217 1.1 christos if (encapsulate)
4218 1.1 christos parse_error(cfile, "option %s.%s code %u encapsulate?",
4219 1.1 christos option->space->name, option->name, code);
4220 1.1 christos
4221 1.1 christos if (strchr(datatype->content, ',') == NULL)
4222 1.1 christos mapSet(def, createString(datatype), "type");
4223 1.1 christos else {
4224 1.1 christos mapSet(def, createString(datatype), "record-types");
4225 1.1 christos mapSet(def, createString(makeString(-1, "record")), "type");
4226 1.1 christos }
4227 1.1 christos if (is_array)
4228 1.1 christos mapSet(def, createBool(ISC_TRUE), "array");
4229 1.1 christos
4230 1.1 christos optdef = mapGet(cfile->stack[1], "option-def");
4231 1.1 christos if (optdef == NULL) {
4232 1.1 christos optdef = createList();
4233 1.1 christos mapSet(cfile->stack[1], optdef, "option-def");
4234 1.1 christos }
4235 1.1 christos listPush(optdef, def);
4236 1.1 christos }
4237 1.1 christos
4238 1.1 christos /* Update the option status for instance to add standard options */
4239 1.1 christos
4240 1.1 christos void
4241 1.1 christos parse_option_status_dir(struct parse *cfile, struct option *option,
4242 1.1 christos enum dhcp_token token)
4243 1.1 christos {
4244 1.1 christos if (token == DYNAMIC)
4245 1.1 christos option->status = dynamic;
4246 1.1 christos else if (token == KNOWN) {
4247 1.1 christos token = next_token(NULL, NULL, cfile);
4248 1.1 christos if (token == KNOWN)
4249 1.1 christos option->status = known;
4250 1.1 christos else if (token == UNKNOWN)
4251 1.1 christos option->status = kea_unknown;
4252 1.1 christos else
4253 1.1 christos parse_error(cfile, "expected KNOW or UNKNOWN");
4254 1.1 christos } else if (token != UNKNOWN)
4255 1.1 christos parse_error(cfile, "expected KNOW or UNKNOWN or DYNAMIC");
4256 1.1 christos else {
4257 1.1 christos if (token == KNOWN)
4258 1.1 christos option->status = isc_dhcp_unknown;
4259 1.1 christos else if (token == UNKNOWN)
4260 1.1 christos parse_error(cfile, "illicit combination: option "
4261 1.1 christos "%s.%s code %u is known by nobody",
4262 1.1 christos option->space->name, option->name,
4263 1.1 christos option->code);
4264 1.1 christos else
4265 1.1 christos parse_error(cfile, "expected KNOW or UNKNOWN");
4266 1.1 christos }
4267 1.1 christos parse_semi(cfile);
4268 1.1 christos }
4269 1.1 christos
4270 1.1 christos /* Make the option definition not exported to Kea */
4271 1.1 christos
4272 1.1 christos void
4273 1.1 christos parse_option_local_dir(struct parse *cfile, struct option *option)
4274 1.1 christos {
4275 1.1 christos struct element *optdef;
4276 1.1 christos struct element *def;
4277 1.1 christos struct element *elem;
4278 1.1 christos size_t i;
4279 1.1 christos
4280 1.1 christos def = NULL;
4281 1.1 christos if (option->code == 0)
4282 1.1 christos parse_error(cfile, "unknown code for option %s.%s",
4283 1.1 christos option->space->name, option->name);
4284 1.1 christos
4285 1.1 christos optdef = mapGet(cfile->stack[1], "option-def");
4286 1.1 christos if (optdef == NULL) {
4287 1.1 christos optdef = createList();
4288 1.1 christos mapSet(cfile->stack[1], optdef, "option-def");
4289 1.1 christos goto not_found;
4290 1.1 christos }
4291 1.1 christos for (i = 0; i < listSize(optdef); i++) {
4292 1.1 christos def = listGet(optdef, i);
4293 1.1 christos elem = mapGet(def, "space");
4294 1.1 christos if ((elem == NULL) || (elem->type != ELEMENT_STRING))
4295 1.1 christos parse_error(cfile, "got an option definition "
4296 1.1 christos "without space at %u", (unsigned)i);
4297 1.1 christos if (strcmp(option->space->name,
4298 1.1 christos stringValue(elem)->content) != 0)
4299 1.1 christos continue;
4300 1.1 christos elem = mapGet(def, "code");
4301 1.1 christos if ((elem == NULL) || (elem->type != ELEMENT_INTEGER))
4302 1.1 christos parse_error(cfile, "got an option definition "
4303 1.1 christos "without code at %u", (unsigned)i);
4304 1.1 christos if (intValue(elem) == option->code)
4305 1.1 christos break;
4306 1.1 christos }
4307 1.1 christos if (def == NULL)
4308 1.1 christos goto not_found;
4309 1.1 christos def->skip = ISC_TRUE;
4310 1.1 christos mapSet(def, createNull(), "no-export");
4311 1.1 christos return;
4312 1.1 christos
4313 1.1 christos not_found:
4314 1.1 christos parse_error(cfile, "can't find option %s.%s code %u in definitions",
4315 1.1 christos option->space->name, option->name, option->code);
4316 1.1 christos }
4317 1.1 christos
4318 1.1 christos /* Make the opposite: force the definition */
4319 1.1 christos
4320 1.1 christos void
4321 1.1 christos parse_option_define_dir(struct parse *cfile, struct option *option)
4322 1.1 christos {
4323 1.1 christos struct element *optdef;
4324 1.1 christos struct element *def;
4325 1.1 christos struct element *elem;
4326 1.1 christos struct string *datatype;
4327 1.1 christos isc_boolean_t is_array = ISC_FALSE;
4328 1.1 christos isc_boolean_t encapsulate = ISC_FALSE;
4329 1.1 christos size_t i;
4330 1.1 christos
4331 1.1 christos def = NULL;
4332 1.1 christos if (option->code == 0)
4333 1.1 christos parse_error(cfile, "unknown code for option %s.%s",
4334 1.1 christos option->space->name, option->name);
4335 1.1 christos
4336 1.1 christos optdef = mapGet(cfile->stack[1], "option-def");
4337 1.1 christos if (optdef == NULL) {
4338 1.1 christos optdef = createList();
4339 1.1 christos mapSet(cfile->stack[1], optdef, "option-def");
4340 1.1 christos goto no_search;
4341 1.1 christos }
4342 1.1 christos for (i = 0; i < listSize(optdef); i++) {
4343 1.1 christos def = listGet(optdef, i);
4344 1.1 christos elem = mapGet(def, "space");
4345 1.1 christos if ((elem == NULL) || (elem->type != ELEMENT_STRING))
4346 1.1 christos parse_error(cfile, "got an option definition "
4347 1.1 christos "without space at %u", (unsigned)i);
4348 1.1 christos if (strcmp(option->space->name,
4349 1.1 christos stringValue(elem)->content) != 0)
4350 1.1 christos continue;
4351 1.1 christos elem = mapGet(def, "code");
4352 1.1 christos if ((elem == NULL) || (elem->type != ELEMENT_INTEGER))
4353 1.1 christos parse_error(cfile, "got an option definition "
4354 1.1 christos "without code at %u", (unsigned)i);
4355 1.1 christos if (intValue(elem) == option->code)
4356 1.1 christos parse_error(cfile, "unexpected definition for "
4357 1.1 christos "option %s.%s code %u",
4358 1.1 christos option->space->name, option->name,
4359 1.1 christos option->code);
4360 1.1 christos }
4361 1.1 christos no_search:
4362 1.1 christos def = createMap();
4363 1.1 christos mapSet(def,
4364 1.1 christos createString(makeString(-1, option->space->name)),
4365 1.1 christos "space");
4366 1.1 christos mapSet(def, createString(makeString(-1, option->name)), "name");
4367 1.1 christos mapSet(def, createInt(option->code), "code");
4368 1.1 christos
4369 1.1 christos datatype = convert_format(option->format, &is_array, &encapsulate);
4370 1.1 christos
4371 1.1 christos if ((datatype == NULL) && (strchr(datatype->content, '?') != NULL))
4372 1.1 christos parse_error(cfile, "failed to convert format \"%s\" for "
4373 1.1 christos "option %s.%s code %u",
4374 1.1 christos option->format, option->space->name,
4375 1.1 christos option->name, option->code);
4376 1.1 christos /* todo */
4377 1.1 christos if (encapsulate)
4378 1.1 christos parse_error(cfile, "option %s.%s code %u encapsulate?",
4379 1.1 christos option->space->name, option->name, option->code);
4380 1.1 christos
4381 1.1 christos if (strchr(datatype->content, ',') == NULL)
4382 1.1 christos mapSet(def, createString(datatype), "type");
4383 1.1 christos else {
4384 1.1 christos mapSet(def, createString(datatype), "record-types");
4385 1.1 christos mapSet(def, createString(makeString(-1, "record")), "type");
4386 1.1 christos }
4387 1.1 christos if (is_array)
4388 1.1 christos mapSet(def, createBool(ISC_TRUE), "array");
4389 1.1 christos
4390 1.1 christos listPush(optdef, def);
4391 1.1 christos
4392 1.1 christos return;
4393 1.1 christos }
4394 1.1 christos
4395 1.1 christos /*
4396 1.1 christos * Push new interface on the interface list when it is not already.
4397 1.1 christos */
4398 1.1 christos
4399 1.1 christos static void
4400 1.1 christos new_network_interface(struct parse *cfile, struct element *iface)
4401 1.1 christos {
4402 1.1 christos struct element *ifconf;
4403 1.1 christos struct element *iflist;
4404 1.1 christos struct string *name = stringValue(iface);
4405 1.1 christos int i;
4406 1.1 christos
4407 1.1 christos ifconf = mapGet(cfile->stack[1], "interfaces-config");
4408 1.1 christos if (ifconf == NULL) {
4409 1.1 christos ifconf = createMap();
4410 1.1 christos mapSet(cfile->stack[1], ifconf, "interfaces-config");
4411 1.1 christos }
4412 1.1 christos
4413 1.1 christos iflist = mapGet(ifconf, "interfaces");
4414 1.1 christos if (iflist == NULL) {
4415 1.1 christos iflist = createList();
4416 1.1 christos mapSet(ifconf, iflist, "interfaces");
4417 1.1 christos }
4418 1.1 christos
4419 1.1 christos for (i = 0; i < listSize(iflist); i++) {
4420 1.1 christos struct element *item;
4421 1.1 christos
4422 1.1 christos item = listGet(iflist, i);
4423 1.1 christos if ((item != NULL) &&
4424 1.1 christos (item->type == ELEMENT_STRING) &&
4425 1.1 christos eqString(stringValue(item), name))
4426 1.1 christos return;
4427 1.1 christos }
4428 1.1 christos
4429 1.1 christos listPush(iflist, createString(name));
4430 1.1 christos }
4431 1.1 christos
4432 1.1 christos /* Convert address and mask in binary into address/len text */
4433 1.1 christos
4434 1.1 christos static const uint32_t bitmasks[32 + 1] = {
4435 1.1 christos 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
4436 1.1 christos 0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
4437 1.1 christos 0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
4438 1.1 christos 0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
4439 1.1 christos 0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
4440 1.1 christos 0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
4441 1.1 christos 0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
4442 1.1 christos 0x0000000f, 0x00000007, 0x00000003, 0x00000001,
4443 1.1 christos 0x00000000 };
4444 1.1 christos
4445 1.1 christos static struct string *
4446 1.1 christos addrmask(const struct string *address, const struct string *netmask)
4447 1.1 christos {
4448 1.1 christos struct string *result;
4449 1.1 christos uint8_t plen;
4450 1.1 christos uint32_t mask;
4451 1.1 christos
4452 1.1 christos result = makeStringExt(address->length, address->content, 'I');
4453 1.1 christos
4454 1.1 christos memcpy(&mask, netmask->content, 4);
4455 1.1 christos mask = ntohl(mask);
4456 1.1 christos for (plen = 0; plen <= 32; ++plen)
4457 1.1 christos if (~mask == bitmasks[plen])
4458 1.1 christos break;
4459 1.1 christos if (plen > 32)
4460 1.1 christos return NULL;
4461 1.1 christos
4462 1.1 christos appendString(result, "/");
4463 1.1 christos concatString(result, makeStringExt(1, (char *)&plen, 'B'));
4464 1.1 christos return result;
4465 1.1 christos }
4466 1.1 christos
4467 1.1 christos /*
4468 1.1 christos * find a place where to put a reservation
4469 1.1 christos * (reservations aka hosts must be in a subnet in Kea < 1.5)
4470 1.1 christos * (defaulting to the last defined subnet (e.g. for reservations
4471 1.1 christos * without any address).
4472 1.1 christos * (first step is to find an enclosing group).
4473 1.1 christos */
4474 1.1 christos
4475 1.1 christos static struct element *
4476 1.1 christos find_match(struct parse *cfile, struct element *host,
4477 1.1 christos isc_boolean_t *used_heuristicp)
4478 1.1 christos {
4479 1.1 christos struct element *address;
4480 1.1 christos struct subnet *subnet;
4481 1.1 christos char addr[16];
4482 1.1 christos size_t group;
4483 1.1 christos size_t i, len;
4484 1.1 christos int kind;
4485 1.1 christos
4486 1.1 christos if (global_hr) {
4487 1.1 christos struct element *hosts;
4488 1.1 christos
4489 1.1 christos hosts = mapGet(cfile->stack[1], "reservations");
4490 1.1 christos if (!hosts) {
4491 1.1 christos mapSet(cfile->stack[1],
4492 1.1 christos createString(makeString(-1, "global")),
4493 1.1 christos "reservation-mode");
4494 1.1 christos hosts = createList();
4495 1.1 christos mapSet(cfile->stack[1], hosts, "reservations");
4496 1.1 christos }
4497 1.1 christos *used_heuristicp = ISC_FALSE;
4498 1.1 christos return cfile->stack[1];
4499 1.1 christos }
4500 1.1 christos
4501 1.1 christos for (group = cfile->stack_top; group > 0; --group) {
4502 1.1 christos kind = cfile->stack[group]->kind;
4503 1.1 christos if ((kind == GROUP_DECL) || (kind == ROOT_GROUP))
4504 1.1 christos break;
4505 1.1 christos }
4506 1.1 christos if (!group)
4507 1.1 christos parse_error(cfile, "can't find root group");
4508 1.1 christos if (kind == GROUP_DECL)
4509 1.1 christos return cfile->stack[group];
4510 1.1 christos
4511 1.1 christos if (local_family == AF_INET) {
4512 1.1 christos address = mapGet(host, "ip-address");
4513 1.1 christos if (address == NULL) {
4514 1.1 christos if (TAILQ_EMPTY(&known_subnets))
4515 1.1 christos return cfile->stack[1];
4516 1.1 christos if (used_heuristicp)
4517 1.1 christos *used_heuristicp = ISC_TRUE;
4518 1.1 christos return TAILQ_LAST(&known_subnets, subnets)->subnet;
4519 1.1 christos }
4520 1.1 christos len = 4;
4521 1.1 christos } else {
4522 1.1 christos address = mapGet(host, "ip-addresses");
4523 1.1 christos if (address == NULL) {
4524 1.1 christos if (TAILQ_EMPTY(&known_subnets))
4525 1.1 christos return cfile->stack[1];
4526 1.1 christos if (used_heuristicp)
4527 1.1 christos *used_heuristicp = ISC_TRUE;
4528 1.1 christos return TAILQ_LAST(&known_subnets, subnets)->subnet;
4529 1.1 christos }
4530 1.1 christos address = listGet(address, 0);
4531 1.1 christos if (address == NULL)
4532 1.1 christos return TAILQ_LAST(&known_subnets, subnets)->subnet;
4533 1.1 christos len = 16;
4534 1.1 christos }
4535 1.1 christos
4536 1.1 christos if (inet_pton(local_family, stringValue(address)->content, addr) != 1)
4537 1.1 christos parse_error(cfile, "bad address %s",
4538 1.1 christos stringValue(address)->content);
4539 1.1 christos TAILQ_FOREACH(subnet, &known_subnets) {
4540 1.1 christos isc_boolean_t matching = ISC_TRUE;
4541 1.1 christos
4542 1.1 christos if (subnet->mask->length != len)
4543 1.1 christos continue;
4544 1.1 christos for (i = 0; i < len; i++)
4545 1.1 christos if ((addr[i] & subnet->mask->content[i]) !=
4546 1.1 christos subnet->addr->content[i]) {
4547 1.1 christos matching = ISC_FALSE;
4548 1.1 christos break;
4549 1.1 christos }
4550 1.1 christos if (matching)
4551 1.1 christos return subnet->subnet;
4552 1.1 christos }
4553 1.1 christos return cfile->stack[1];
4554 1.1 christos }
4555 1.1 christos
4556 1.1 christos /*
4557 1.1 christos * find a subnet where to put a pool
4558 1.1 christos * (pools are not allowed at shared-network level in Kea)
4559 1.1 christos */
4560 1.1 christos
4561 1.1 christos static struct element *
4562 1.1 christos find_location(struct element *share, struct range *range)
4563 1.1 christos {
4564 1.1 christos struct subnet *subnet;
4565 1.1 christos size_t i;
4566 1.1 christos
4567 1.1 christos TAILQ_FOREACH(subnet, &known_subnets) {
4568 1.1 christos isc_boolean_t matching = ISC_TRUE;
4569 1.1 christos
4570 1.1 christos if (subnet->share != share)
4571 1.1 christos continue;
4572 1.1 christos if (subnet->mask->length != range->low->length)
4573 1.1 christos continue;
4574 1.1 christos for (i = 0; i < range->low->length; i++)
4575 1.1 christos if ((range->low->content[i] &
4576 1.1 christos subnet->mask->content[i]) !=
4577 1.1 christos subnet->addr->content[i]) {
4578 1.1 christos matching = ISC_FALSE;
4579 1.1 christos break;
4580 1.1 christos }
4581 1.1 christos if (matching)
4582 1.1 christos return subnet->subnet;
4583 1.1 christos }
4584 1.1 christos return NULL;
4585 1.1 christos }
4586 1.1 christos
4587 1.1 christos /*
4588 1.1 christos * Compute a prefix length from lower - higher IPv6 addresses.
4589 1.1 christos */
4590 1.1 christos
4591 1.1 christos static const uint8_t bytemasks[8] = {
4592 1.1 christos 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
4593 1.1 christos };
4594 1.1 christos
4595 1.1 christos static int
4596 1.1 christos get_prefix_length(const char *low, const char *high)
4597 1.1 christos {
4598 1.1 christos uint8_t lo[16];
4599 1.1 christos uint8_t hi[16];
4600 1.1 christos uint8_t xor[16];
4601 1.1 christos int i, plen;
4602 1.1 christos
4603 1.1 christos if ((inet_pton(AF_INET6, low, lo) != 1) ||
4604 1.1 christos (inet_pton(AF_INET6, high, hi) != 1))
4605 1.1 christos return -100;
4606 1.1 christos
4607 1.1 christos for (i = 0; i < 16; i++)
4608 1.1 christos xor[i] = lo[i] ^ hi[i];
4609 1.1 christos for (plen = 0; plen < 128; plen += 8)
4610 1.1 christos if (xor[plen / 8] != 0)
4611 1.1 christos break;
4612 1.1 christos if (plen == 128)
4613 1.1 christos return plen;
4614 1.1 christos for (i = (plen / 8) + 1; i < 16; i++)
4615 1.1 christos if (xor[i] != 0)
4616 1.1 christos return -2;
4617 1.1 christos for (i = 0; i < 8; i++) {
4618 1.1 christos uint8_t msk = ~xor[plen / 8];
4619 1.1 christos
4620 1.1 christos if (msk == bytemasks[i])
4621 1.1 christos return plen + i + 1;
4622 1.1 christos }
4623 1.1 christos return -1;
4624 1.1 christos }
4625 1.1 christos
4626 1.1 christos /*
4627 1.1 christos * Get a (global) class from its reference, i.e.:
4628 1.1 christos * - name for a (super)class
4629 1.1 christos * - super, and binary or string for a subclass
4630 1.1 christos */
4631 1.1 christos static struct element *
4632 1.1 christos get_class(struct parse *cfile, struct element *ref)
4633 1.1 christos {
4634 1.1 christos struct element *classes;
4635 1.1 christos struct element *class;
4636 1.1 christos struct element *name;
4637 1.1 christos struct element *selector;
4638 1.1 christos struct element *param;
4639 1.1 christos size_t i;
4640 1.1 christos
4641 1.1 christos classes = mapGet(cfile->stack[1], "client-classes");
4642 1.1 christos if ((classes == NULL) || (listSize(classes) == 0))
4643 1.1 christos return NULL;
4644 1.1 christos
4645 1.1 christos name = mapGet(ref, "super");
4646 1.1 christos if (name == NULL) {
4647 1.1 christos name = mapGet(ref, "name");
4648 1.1 christos if (name == NULL)
4649 1.1 christos return NULL;
4650 1.1 christos for (i = 0; i < listSize(classes); i++) {
4651 1.1 christos class = listGet(classes, i);
4652 1.1 christos if (mapContains(ref, "super"))
4653 1.1 christos continue;
4654 1.1 christos param = mapGet(class, "name");
4655 1.1 christos if (param == NULL)
4656 1.1 christos continue;
4657 1.1 christos if (eqString(stringValue(name), stringValue(param)))
4658 1.1 christos return class;
4659 1.1 christos }
4660 1.1 christos return NULL;
4661 1.1 christos }
4662 1.1 christos selector = mapGet(ref, "string");
4663 1.1 christos if (selector == NULL) {
4664 1.1 christos selector = mapGet(ref, "binary");
4665 1.1 christos if (selector == NULL)
4666 1.1 christos return NULL;
4667 1.1 christos for (i = 0; i <listSize(classes); i++) {
4668 1.1 christos class = listGet(classes, i);
4669 1.1 christos param = mapGet(class, "super");
4670 1.1 christos if (param == NULL)
4671 1.1 christos continue;
4672 1.1 christos if (!eqString(stringValue(name), stringValue(param)))
4673 1.1 christos continue;
4674 1.1 christos param = mapGet(class, "binary");
4675 1.1 christos if (param == NULL)
4676 1.1 christos continue;
4677 1.1 christos if (eqString(stringValue(selector),
4678 1.1 christos stringValue(param)))
4679 1.1 christos return class;
4680 1.1 christos }
4681 1.1 christos return NULL;
4682 1.1 christos }
4683 1.1 christos for (i = 0; i <listSize(classes); i++) {
4684 1.1 christos class = listGet(classes, i);
4685 1.1 christos param = mapGet(class, "super");
4686 1.1 christos if (param == NULL)
4687 1.1 christos continue;
4688 1.1 christos if (!eqString(stringValue(name), stringValue(param)))
4689 1.1 christos continue;
4690 1.1 christos param = mapGet(class, "string");
4691 1.1 christos if (param == NULL)
4692 1.1 christos continue;
4693 1.1 christos if (eqString(stringValue(selector), stringValue(param)))
4694 1.1 christos return class;
4695 1.1 christos }
4696 1.1 christos return NULL;
4697 1.1 christos }
4698 1.1 christos
4699 1.1 christos /*
4700 1.1 christos * Concatenate two class reference lists eliminating duplicates
4701 1.1 christos * (complexity is bad: if this becomes a performance pig, use a hash table)
4702 1.1 christos */
4703 1.1 christos
4704 1.1 christos static void
4705 1.1 christos concat_classes(struct parse *cfile, struct element *dst, struct element *src)
4706 1.1 christos {
4707 1.1 christos struct element *class;
4708 1.1 christos struct element *sitem;
4709 1.1 christos struct element *ditem;
4710 1.1 christos size_t i;
4711 1.1 christos isc_boolean_t dup;
4712 1.1 christos
4713 1.1 christos while (listSize(src) > 0) {
4714 1.1 christos sitem = listGet(src, 0);
4715 1.1 christos listRemove(src, 0);
4716 1.1 christos class = get_class(cfile, sitem);
4717 1.1 christos if (class == NULL)
4718 1.1 christos /* just ignore */
4719 1.1 christos continue;
4720 1.1 christos dup = ISC_FALSE;
4721 1.1 christos for (i = 0; i < listSize(dst); i++) {
4722 1.1 christos ditem = listGet(dst, i);
4723 1.1 christos if (class == get_class(cfile, ditem)) {
4724 1.1 christos dup = ISC_TRUE;
4725 1.1 christos break;
4726 1.1 christos }
4727 1.1 christos }
4728 1.1 christos if (dup)
4729 1.1 christos continue;
4730 1.1 christos listPush(dst, sitem);
4731 1.1 christos }
4732 1.1 christos }
4733 1.1 christos
4734 1.1 christos /* Generate a class from allow/deny member lists */
4735 1.1 christos
4736 1.1 christos static void
4737 1.1 christos generate_class(struct parse *cfile, struct element *pool,
4738 1.1 christos struct element *allow, struct element *deny)
4739 1.1 christos {
4740 1.1 christos struct element *classes;
4741 1.1 christos struct element *class;
4742 1.1 christos struct element *elem;
4743 1.1 christos struct element *prop;
4744 1.1 christos struct element *depend;
4745 1.1 christos struct element *result = NULL;
4746 1.1 christos struct string *name;
4747 1.1 christos struct string *expr;
4748 1.1 christos struct string *msg;
4749 1.1 christos struct comments comments;
4750 1.1 christos struct comment *comment;
4751 1.1 christos isc_boolean_t rescan;
4752 1.1 christos size_t i;
4753 1.1 christos
4754 1.1 christos if ((listSize(allow) == 0) && (listSize(deny) == 0))
4755 1.1 christos return;
4756 1.1 christos
4757 1.1 christos classes = mapGet(cfile->stack[1], "generated-classes");
4758 1.1 christos if (classes == NULL) {
4759 1.1 christos classes = createList();
4760 1.1 christos mapSet(cfile->stack[1], classes, "generated-classes");
4761 1.1 christos }
4762 1.1 christos
4763 1.1 christos /* Create comments */
4764 1.1 christos TAILQ_INIT(&comments);
4765 1.1 christos comment = createComment("/// From:");
4766 1.1 christos TAILQ_INSERT_TAIL(&comments, comment);
4767 1.1 christos for (i = 0; i < listSize(allow); i++) {
4768 1.1 christos struct element *alias;
4769 1.1 christos
4770 1.1 christos elem = listGet(allow, i);
4771 1.1 christos assert(elem != NULL);
4772 1.1 christos prop = mapGet(elem, "class");
4773 1.1 christos assert(prop != NULL);
4774 1.1 christos assert(prop->type == ELEMENT_STRING);
4775 1.1 christos alias = mapGet(elem, "real");
4776 1.1 christos msg = makeString(-1, "/// allow ");
4777 1.1 christos concatString(msg, stringValue(alias ? alias : prop));
4778 1.1 christos comment = createComment(msg->content);
4779 1.1 christos TAILQ_INSERT_TAIL(&comments, comment);
4780 1.1 christos }
4781 1.1 christos for (i = 0; i < listSize(deny); i++) {
4782 1.1 christos struct element *alias;
4783 1.1 christos
4784 1.1 christos elem = listGet(deny, i);
4785 1.1 christos assert(elem != NULL);
4786 1.1 christos prop = mapGet(elem, "class");
4787 1.1 christos assert(prop != NULL);
4788 1.1 christos assert(prop->type == ELEMENT_STRING);
4789 1.1 christos alias = mapGet(elem, "real");
4790 1.1 christos msg = makeString(-1, "/// deny ");
4791 1.1 christos concatString(msg, stringValue(alias ? alias : prop));
4792 1.1 christos comment = createComment(msg->content);
4793 1.1 christos TAILQ_INSERT_TAIL(&comments, comment);
4794 1.1 christos }
4795 1.1 christos TAILQ_CONCAT(&comments, &allow->comments);
4796 1.1 christos TAILQ_CONCAT(&comments, &deny->comments);
4797 1.1 christos
4798 1.1 christos /* Deal with special cases */
4799 1.1 christos for (;;) {
4800 1.1 christos rescan = ISC_FALSE;
4801 1.1 christos for (i = 0; i < listSize(allow); i++) {
4802 1.1 christos elem = listGet(allow, i);
4803 1.1 christos assert(elem != NULL);
4804 1.1 christos prop = mapGet(elem, "way");
4805 1.1 christos assert(prop != NULL);
4806 1.1 christos assert(prop->type == ELEMENT_BOOLEAN);
4807 1.1 christos class = mapGet(elem, "class");
4808 1.1 christos assert(class != NULL);
4809 1.1 christos assert(class->type == ELEMENT_STRING);
4810 1.1 christos /* allow !ALL and other */
4811 1.1 christos if (eqString(stringValue(class), CLASS_ALL) &&
4812 1.1 christos !boolValue(prop) && (listSize(allow) > 1)) {
4813 1.1 christos listRemove(allow, i);
4814 1.1 christos rescan = ISC_TRUE;
4815 1.1 christos break;
4816 1.1 christos }
4817 1.1 christos /* allow ALL alone */
4818 1.1 christos if (eqString(stringValue(class), CLASS_ALL) &&
4819 1.1 christos boolValue(prop) && (listSize(allow) == 1)) {
4820 1.1 christos resetList(allow);
4821 1.1 christos rescan = ISC_TRUE;
4822 1.1 christos break;
4823 1.1 christos }
4824 1.1 christos }
4825 1.1 christos if (!rescan)
4826 1.1 christos break;
4827 1.1 christos }
4828 1.1 christos
4829 1.1 christos for (;;) {
4830 1.1 christos rescan = ISC_FALSE;
4831 1.1 christos for (i = 0; i < listSize(deny); i++) {
4832 1.1 christos elem = listGet(deny, i);
4833 1.1 christos assert(elem != NULL);
4834 1.1 christos prop = mapGet(elem, "way");
4835 1.1 christos assert(prop != NULL);
4836 1.1 christos assert(prop->type == ELEMENT_BOOLEAN);
4837 1.1 christos class = mapGet(elem, "class");
4838 1.1 christos assert(class != NULL);
4839 1.1 christos assert(class->type == ELEMENT_STRING);
4840 1.1 christos /* DENY !ALL */
4841 1.1 christos if (eqString(stringValue(class), CLASS_ALL) &&
4842 1.1 christos !boolValue(prop)) {
4843 1.1 christos listRemove(deny, i);
4844 1.1 christos rescan = ISC_TRUE;
4845 1.1 christos break;
4846 1.1 christos }
4847 1.1 christos /* DENY ALL */
4848 1.1 christos if (eqString(stringValue(class), CLASS_ALL) &&
4849 1.1 christos boolValue(prop)) {
4850 1.1 christos resetList(allow);
4851 1.1 christos if (listSize(deny) > 1) {
4852 1.1 christos listRemove(deny, i);
4853 1.1 christos resetList(deny);
4854 1.1 christos listPush(deny, elem);
4855 1.1 christos }
4856 1.1 christos break;
4857 1.1 christos }
4858 1.1 christos }
4859 1.1 christos if (!rescan)
4860 1.1 christos break;
4861 1.1 christos }
4862 1.1 christos
4863 1.1 christos /* Fully cleaned? */
4864 1.1 christos if ((listSize(allow) == 0) && (listSize(deny) == 0)) {
4865 1.1 christos if (result != NULL)
4866 1.1 christos TAILQ_CONCAT(&result->comments, &comments);
4867 1.1 christos else
4868 1.1 christos TAILQ_CONCAT(&pool->comments, &comments);
4869 1.1 christos return;
4870 1.1 christos }
4871 1.1 christos
4872 1.1 christos /* Unique allow member short cut */
4873 1.1 christos if ((listSize(allow) == 1) && (listSize(deny) == 0) &&
4874 1.1 christos !allow->skip && !deny->skip) {
4875 1.1 christos elem = listGet(allow, 0);
4876 1.1 christos assert(elem != NULL);
4877 1.1 christos prop = mapGet(elem, "way");
4878 1.1 christos assert(prop != NULL);
4879 1.1 christos assert(prop->type == ELEMENT_BOOLEAN);
4880 1.1 christos class = mapGet(elem, "class");
4881 1.1 christos assert(class != NULL);
4882 1.1 christos assert(class->type == ELEMENT_STRING);
4883 1.1 christos if (boolValue(prop)) {
4884 1.1 christos result = createString(stringValue(class));
4885 1.1 christos TAILQ_CONCAT(&result->comments, &comments);
4886 1.1 christos mapSet(pool, result, "client-class");
4887 1.1 christos return;
4888 1.1 christos }
4889 1.1 christos }
4890 1.1 christos
4891 1.1 christos /* Build name */
4892 1.1 christos name = makeString(-1, "gen#");
4893 1.1 christos for (i = 0; i < listSize(allow); i++) {
4894 1.1 christos elem = listGet(allow, i);
4895 1.1 christos assert(elem != NULL);
4896 1.1 christos prop = mapGet(elem, "way");
4897 1.1 christos assert(prop != NULL);
4898 1.1 christos assert(prop->type == ELEMENT_BOOLEAN);
4899 1.1 christos if (!boolValue(prop))
4900 1.1 christos appendString(name, "!");
4901 1.1 christos prop = mapGet(elem, "class");
4902 1.1 christos assert(prop != NULL);
4903 1.1 christos assert(prop->type == ELEMENT_STRING);
4904 1.1 christos concatString(name, stringValue(prop));
4905 1.1 christos appendString(name, "#");
4906 1.1 christos }
4907 1.1 christos if (listSize(deny) > 0) {
4908 1.1 christos appendString(name, "_AND_#");
4909 1.1 christos for (i = 0; i < listSize(deny); i++) {
4910 1.1 christos elem = listGet(deny, i);
4911 1.1 christos assert(elem != NULL);
4912 1.1 christos prop = mapGet(elem, "way");
4913 1.1 christos assert(prop != NULL);
4914 1.1 christos assert(prop->type == ELEMENT_BOOLEAN);
4915 1.1 christos if (boolValue(prop))
4916 1.1 christos appendString(name, "!");
4917 1.1 christos prop = mapGet(elem, "class");
4918 1.1 christos assert(prop != NULL);
4919 1.1 christos assert(prop->type == ELEMENT_STRING);
4920 1.1 christos concatString(name, stringValue(prop));
4921 1.1 christos appendString(name, "#");
4922 1.1 christos }
4923 1.1 christos }
4924 1.1 christos
4925 1.1 christos /* Check if it already exists */
4926 1.1 christos for (i = 0; i < listSize(classes); i++) {
4927 1.1 christos struct element *descr;
4928 1.1 christos
4929 1.1 christos class = listGet(classes, i);
4930 1.1 christos assert(class != NULL);
4931 1.1 christos descr = mapGet(class, "name");
4932 1.1 christos assert(descr != NULL);
4933 1.1 christos assert(descr->type == ELEMENT_STRING);
4934 1.1 christos if (!eqString(name, stringValue(descr)))
4935 1.1 christos continue;
4936 1.1 christos result = createString(name);
4937 1.1 christos TAILQ_CONCAT(&result->comments, &comments);
4938 1.1 christos mapSet(pool, result, "client-class");
4939 1.1 christos return;
4940 1.1 christos }
4941 1.1 christos
4942 1.1 christos /* Create expression */
4943 1.1 christos class = createMap();
4944 1.1 christos depend = createList();
4945 1.1 christos expr = allocString();
4946 1.1 christos
4947 1.1 christos if ((listSize(allow) > 0) && (listSize(deny) > 0))
4948 1.1 christos appendString(expr, "(");
4949 1.1 christos
4950 1.1 christos for (i = 0; i < listSize(allow); i++) {
4951 1.1 christos isc_boolean_t negative;
4952 1.1 christos
4953 1.1 christos if (i > 0)
4954 1.1 christos appendString(expr, " or ");
4955 1.1 christos elem = listGet(allow, i);
4956 1.1 christos prop = mapGet(elem, "way");
4957 1.1 christos negative = !boolValue(prop);
4958 1.1 christos prop = mapGet(elem, "class");
4959 1.1 christos if (negative)
4960 1.1 christos appendString(expr, "not ");
4961 1.1 christos appendString(expr, "member('");
4962 1.1 christos concatString(expr, stringValue(prop));
4963 1.1 christos appendString(expr, "')");
4964 1.1 christos listPush(depend, createString(stringValue(prop)));
4965 1.1 christos }
4966 1.1 christos
4967 1.1 christos if ((listSize(allow) > 0) && (listSize(deny) > 0))
4968 1.1 christos appendString(expr, ") and ");
4969 1.1 christos
4970 1.1 christos for (i = 0; i < listSize(deny); i++) {
4971 1.1 christos isc_boolean_t negative;
4972 1.1 christos
4973 1.1 christos if (i > 0)
4974 1.1 christos appendString(expr, " and ");
4975 1.1 christos elem = listGet(deny, i);
4976 1.1 christos prop = mapGet(elem, "way");
4977 1.1 christos negative = boolValue(prop);
4978 1.1 christos prop = mapGet(elem, "class");
4979 1.1 christos if (negative)
4980 1.1 christos appendString(expr, "not ");
4981 1.1 christos appendString(expr, "member('");
4982 1.1 christos concatString(expr, stringValue(prop));
4983 1.1 christos appendString(expr, "')");
4984 1.1 christos listPush(depend, createString(stringValue(prop)));
4985 1.1 christos }
4986 1.1 christos
4987 1.1 christos mapSet(class, createString(name), "name");
4988 1.1 christos mapSet(class, createString(expr), "test");
4989 1.1 christos mapSet(class, depend, "depend");
4990 1.1 christos /* inherit untranslatable cases */
4991 1.1 christos class->skip |= allow->skip || deny->skip;
4992 1.1 christos listPush(classes, class);
4993 1.1 christos
4994 1.1 christos result = createString(name);
4995 1.1 christos TAILQ_CONCAT(&result->comments, &comments);
4996 1.1 christos mapSet(pool, result, "client-class");
4997 1.1 christos }
4998