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