aboutsummaryrefslogtreecommitdiff
path: root/server/confpars.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/confpars.c')
-rw-r--r--server/confpars.c5504
1 files changed, 5504 insertions, 0 deletions
diff --git a/server/confpars.c b/server/confpars.c
new file mode 100644
index 0000000..c0742d4
--- /dev/null
+++ b/server/confpars.c
@@ -0,0 +1,5504 @@
+/* confpars.c
+
+ Parser for dhcpd config file... */
+
+/*
+ * Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1995-2003 by Internet Software Consortium
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internet Systems Consortium, Inc.
+ * 950 Charter Street
+ * Redwood City, CA 94063
+ * <info@isc.org>
+ * https://www.isc.org/
+ *
+ * This software has been written for Internet Systems Consortium
+ * by Ted Lemon in cooperation with Vixie Enterprises and Nominum, Inc.
+ * To learn more about Internet Systems Consortium, see
+ * ``https://www.isc.org/''. To learn more about Vixie Enterprises,
+ * see ``http://www.vix.com''. To learn more about Nominum, Inc., see
+ * ``http://www.nominum.com''.
+ */
+
+#include "dhcpd.h"
+
+static unsigned char global_host_once = 1;
+static unsigned char dhcpv6_class_once = 1;
+
+static int parse_binding_value(struct parse *cfile,
+ struct binding_value *value);
+
+#if defined (TRACING)
+trace_type_t *trace_readconf_type;
+trace_type_t *trace_readleases_type;
+
+void parse_trace_setup ()
+{
+ trace_readconf_type = trace_type_register ("readconf", (void *)0,
+ trace_conf_input,
+ trace_conf_stop, MDL);
+ trace_readleases_type = trace_type_register ("readleases", (void *)0,
+ trace_conf_input,
+ trace_conf_stop, MDL);
+}
+#endif
+
+/* conf-file :== parameters declarations END_OF_FILE
+ parameters :== <nil> | parameter | parameters parameter
+ declarations :== <nil> | declaration | declarations declaration */
+
+isc_result_t readconf ()
+{
+ isc_result_t res;
+
+ res = read_conf_file (path_dhcpd_conf, root_group, ROOT_GROUP, 0);
+#if defined(LDAP_CONFIGURATION)
+ if (res != ISC_R_SUCCESS)
+ return (res);
+
+ return ldap_read_config ();
+#else
+ return (res);
+#endif
+}
+
+isc_result_t read_conf_file (const char *filename, struct group *group,
+ int group_type, int leasep)
+{
+ int file;
+ struct parse *cfile;
+ isc_result_t status;
+#if defined (TRACING)
+ char *fbuf, *dbuf;
+ off_t flen;
+ int result;
+ unsigned tflen, ulen;
+ trace_type_t *ttype;
+
+ if (leasep)
+ ttype = trace_readleases_type;
+ else
+ ttype = trace_readconf_type;
+
+ /* If we're in playback, we need to snarf the contents of the
+ named file out of the playback file rather than trying to
+ open and read it. */
+ if (trace_playback ()) {
+ dbuf = (char *)0;
+ tflen = 0;
+ status = trace_get_file (ttype, filename, &tflen, &dbuf);
+ if (status != ISC_R_SUCCESS)
+ return status;
+ ulen = tflen;
+
+ /* What we get back is filename\0contents, where contents is
+ terminated just by the length. So we figure out the length
+ of the filename, and subtract that and the NUL from the
+ total length to get the length of the contents of the file.
+ We make fbuf a pointer to the contents of the file, and
+ leave dbuf as it is so we can free it later. */
+ tflen = strlen (dbuf);
+ ulen = ulen - tflen - 1;
+ fbuf = dbuf + tflen + 1;
+ goto memfile;
+ }
+#endif
+
+ if ((file = open (filename, O_RDONLY)) < 0) {
+ if (leasep) {
+ log_error ("Can't open lease database %s: %m --",
+ path_dhcpd_db);
+ log_error (" check for failed database %s!",
+ "rewrite attempt");
+ log_error ("Please read the dhcpd.leases manual%s",
+ " page if you");
+ log_fatal ("don't know what to do about this.");
+ } else {
+ log_fatal ("Can't open %s: %m", filename);
+ }
+ }
+
+ cfile = (struct parse *)0;
+#if defined (TRACING)
+ flen = lseek (file, (off_t)0, SEEK_END);
+ if (flen < 0) {
+ boom:
+ log_fatal ("Can't lseek on %s: %m", filename);
+ }
+ if (lseek (file, (off_t)0, SEEK_SET) < 0)
+ goto boom;
+ /* Can't handle files greater than 2^31-1. */
+ if (flen > 0x7FFFFFFFUL)
+ log_fatal ("%s: file is too long to buffer.", filename);
+ ulen = flen;
+
+ /* Allocate a buffer that will be what's written to the tracefile,
+ and also will be what we parse from. */
+ tflen = strlen (filename);
+ dbuf = dmalloc (ulen + tflen + 1, MDL);
+ if (!dbuf)
+ log_fatal ("No memory for %s (%d bytes)",
+ filename, ulen);
+
+ /* Copy the name into the beginning, nul-terminated. */
+ strcpy (dbuf, filename);
+
+ /* Load the file in after the NUL. */
+ fbuf = dbuf + tflen + 1;
+ result = read (file, fbuf, ulen);
+ if (result < 0)
+ log_fatal ("Can't read in %s: %m", filename);
+ if (result != ulen)
+ log_fatal ("%s: short read of %d bytes instead of %d.",
+ filename, ulen, result);
+ close (file);
+ memfile:
+ /* If we're recording, write out the filename and file contents. */
+ if (trace_record ())
+ trace_write_packet (ttype, ulen + tflen + 1, dbuf, MDL);
+ status = new_parse(&cfile, -1, fbuf, ulen, filename, 0); /* XXX */
+#else
+ status = new_parse(&cfile, file, NULL, 0, filename, 0);
+#endif
+ if (status != ISC_R_SUCCESS || cfile == NULL)
+ return status;
+
+ if (leasep)
+ status = lease_file_subparse (cfile);
+ else
+ status = conf_file_subparse (cfile, group, group_type);
+ end_parse (&cfile);
+#if defined (TRACING)
+ dfree (dbuf, MDL);
+#endif
+ return status;
+}
+
+#if defined (TRACING)
+void trace_conf_input (trace_type_t *ttype, unsigned len, char *data)
+{
+ char *fbuf;
+ unsigned flen;
+ unsigned tflen;
+ struct parse *cfile = (struct parse *)0;
+ static int postconf_initialized;
+ static int leaseconf_initialized;
+ isc_result_t status;
+
+ /* Do what's done above, except that we don't have to read in the
+ data, because it's already been read for us. */
+ tflen = strlen (data);
+ flen = len - tflen - 1;
+ fbuf = data + tflen + 1;
+
+ /* If we're recording, write out the filename and file contents. */
+ if (trace_record ())
+ trace_write_packet (ttype, len, data, MDL);
+
+ status = new_parse(&cfile, -1, fbuf, flen, data, 0);
+ if (status == ISC_R_SUCCESS || cfile != NULL) {
+ if (ttype == trace_readleases_type)
+ lease_file_subparse (cfile);
+ else
+ conf_file_subparse (cfile, root_group, ROOT_GROUP);
+ end_parse (&cfile);
+ }
+
+ /* Postconfiguration needs to be done after the config file
+ has been loaded. */
+ if (!postconf_initialized && ttype == trace_readconf_type) {
+ postconf_initialization (0);
+ postconf_initialized = 1;
+ }
+
+ if (!leaseconf_initialized && ttype == trace_readleases_type) {
+ db_startup (0);
+ leaseconf_initialized = 1;
+ postdb_startup ();
+ }
+}
+
+void trace_conf_stop (trace_type_t *ttype) { }
+#endif
+
+/* conf-file :== parameters declarations END_OF_FILE
+ parameters :== <nil> | parameter | parameters parameter
+ declarations :== <nil> | declaration | declarations declaration */
+
+isc_result_t conf_file_subparse (struct parse *cfile, struct group *group,
+ int group_type)
+{
+ const char *val;
+ enum dhcp_token token;
+ int declaration = 0;
+ int status;
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE)
+ break;
+ declaration = parse_statement (cfile, group, group_type,
+ (struct host_decl *)0,
+ declaration);
+ } while (1);
+ token = next_token (&val, (unsigned *)0, cfile);
+
+ status = cfile->warnings_occurred ? DHCP_R_BADPARSE : ISC_R_SUCCESS;
+ return status;
+}
+
+/* lease-file :== lease-declarations END_OF_FILE
+ lease-statements :== <nil>
+ | lease-declaration
+ | lease-declarations lease-declaration */
+
+isc_result_t lease_file_subparse (struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ isc_result_t status;
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == END_OF_FILE)
+ break;
+ if (token == LEASE) {
+ struct lease *lease = (struct lease *)0;
+ if (parse_lease_declaration (&lease, cfile)) {
+ enter_lease (lease);
+ lease_dereference (&lease, MDL);
+ } else
+ parse_warn (cfile,
+ "possibly corrupt lease file");
+ } else if (token == IA_NA) {
+ parse_ia_na_declaration(cfile);
+ } else if (token == IA_TA) {
+ parse_ia_ta_declaration(cfile);
+ } else if (token == IA_PD) {
+ parse_ia_pd_declaration(cfile);
+ } else if (token == CLASS) {
+ parse_class_declaration(0, cfile, root_group,
+ CLASS_TYPE_CLASS);
+ } else if (token == SUBCLASS) {
+ parse_class_declaration(0, cfile, root_group,
+ CLASS_TYPE_SUBCLASS);
+ } else if (token == HOST) {
+ parse_host_declaration (cfile, root_group);
+ } else if (token == GROUP) {
+ parse_group_declaration (cfile, root_group);
+#if defined (FAILOVER_PROTOCOL)
+ } else if (token == FAILOVER) {
+ parse_failover_state_declaration
+ (cfile, (dhcp_failover_state_t *)0);
+#endif
+#ifdef DHCPv6
+ } else if (token == SERVER_DUID) {
+ parse_server_duid(cfile);
+#endif /* DHCPv6 */
+ } else {
+ log_error ("Corrupt lease file - possible data loss!");
+ skip_to_semi (cfile);
+ }
+
+ } while (1);
+
+ status = cfile->warnings_occurred ? DHCP_R_BADPARSE : ISC_R_SUCCESS;
+ return status;
+}
+
+/* statement :== parameter | declaration
+
+ parameter :== DEFAULT_LEASE_TIME lease_time
+ | MAX_LEASE_TIME lease_time
+ | DYNAMIC_BOOTP_LEASE_CUTOFF date
+ | DYNAMIC_BOOTP_LEASE_LENGTH lease_time
+ | BOOT_UNKNOWN_CLIENTS boolean
+ | ONE_LEASE_PER_CLIENT boolean
+ | GET_LEASE_HOSTNAMES boolean
+ | USE_HOST_DECL_NAME boolean
+ | NEXT_SERVER ip-addr-or-hostname SEMI
+ | option_parameter
+ | SERVER-IDENTIFIER ip-addr-or-hostname SEMI
+ | FILENAME string-parameter
+ | SERVER_NAME string-parameter
+ | hardware-parameter
+ | fixed-address-parameter
+ | ALLOW allow-deny-keyword
+ | DENY allow-deny-keyword
+ | USE_LEASE_ADDR_FOR_DEFAULT_ROUTE boolean
+ | AUTHORITATIVE
+ | NOT AUTHORITATIVE
+
+ declaration :== host-declaration
+ | group-declaration
+ | shared-network-declaration
+ | subnet-declaration
+ | VENDOR_CLASS class-declaration
+ | USER_CLASS class-declaration
+ | RANGE address-range-declaration */
+
+int parse_statement (cfile, group, type, host_decl, declaration)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+ struct host_decl *host_decl;
+ int declaration;
+{
+ enum dhcp_token token;
+ const char *val;
+ struct shared_network *share;
+ char *n;
+ struct hardware hardware;
+ struct executable_statement *et, *ep;
+ struct option *option = NULL;
+ struct option_cache *cache;
+ int lose;
+ int known;
+ isc_result_t status;
+ unsigned code;
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+
+ switch (token) {
+ case INCLUDE:
+ next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "filename string expected.");
+ skip_to_semi (cfile);
+ } else {
+ status = read_conf_file (val, group, type, 0);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile, "%s: bad parse.", val);
+ parse_semi (cfile);
+ }
+ return 1;
+
+ case HOST:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type != HOST_DECL && type != CLASS_DECL) {
+ if (global_host_once &&
+ (type == SUBNET_DECL || type == SHARED_NET_DECL)) {
+ global_host_once = 0;
+ log_error("WARNING: Host declarations are "
+ "global. They are not limited to "
+ "the scope you declared them in.");
+ }
+
+ parse_host_declaration (cfile, group);
+ } else {
+ parse_warn (cfile,
+ "host declarations not allowed here.");
+ skip_to_semi (cfile);
+ }
+ return 1;
+
+ case GROUP:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type != HOST_DECL && type != CLASS_DECL)
+ parse_group_declaration (cfile, group);
+ else {
+ parse_warn (cfile,
+ "group declarations not allowed here.");
+ skip_to_semi (cfile);
+ }
+ return 1;
+
+ case SHARED_NETWORK:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type == SHARED_NET_DECL ||
+ type == HOST_DECL ||
+ type == SUBNET_DECL ||
+ type == CLASS_DECL) {
+ parse_warn (cfile, "shared-network parameters not %s.",
+ "allowed here");
+ skip_to_semi (cfile);
+ break;
+ }
+
+ parse_shared_net_declaration (cfile, group);
+ return 1;
+
+ case SUBNET:
+ case SUBNET6:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type == HOST_DECL || type == SUBNET_DECL ||
+ type == CLASS_DECL) {
+ parse_warn (cfile,
+ "subnet declarations not allowed here.");
+ skip_to_semi (cfile);
+ return 1;
+ }
+
+ /* If we're in a subnet declaration, just do the parse. */
+ if (group->shared_network != NULL) {
+ if (token == SUBNET) {
+ parse_subnet_declaration(cfile,
+ group->shared_network);
+ } else {
+ parse_subnet6_declaration(cfile,
+ group->shared_network);
+ }
+ break;
+ }
+
+ /*
+ * Otherwise, cons up a fake shared network structure
+ * and populate it with the lone subnet...because the
+ * intention most likely is to refer to the entire link
+ * by shorthand, any configuration inside the subnet is
+ * actually placed in the shared-network's group.
+ */
+
+ share = NULL;
+ status = shared_network_allocate (&share, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't allocate shared subnet: %s",
+ isc_result_totext (status));
+ if (!clone_group (&share -> group, group, MDL))
+ log_fatal ("Can't allocate group for shared net");
+ shared_network_reference (&share -> group -> shared_network,
+ share, MDL);
+
+ /*
+ * This is an implicit shared network, not explicit in
+ * the config.
+ */
+ share->flags |= SHARED_IMPLICIT;
+
+ if (token == SUBNET) {
+ parse_subnet_declaration(cfile, share);
+ } else {
+ parse_subnet6_declaration(cfile, share);
+ }
+
+ /* share -> subnets is the subnet we just parsed. */
+ if (share->subnets) {
+ interface_reference(&share->interface,
+ share->subnets->interface,
+ MDL);
+
+ /* Make the shared network name from network number. */
+ if (token == SUBNET) {
+ n = piaddrmask(&share->subnets->net,
+ &share->subnets->netmask);
+ } else {
+ n = piaddrcidr(&share->subnets->net,
+ share->subnets->prefix_len);
+ }
+
+ share->name = strdup(n);
+
+ if (share->name == NULL)
+ log_fatal("Out of memory allocating default "
+ "shared network name (\"%s\").", n);
+
+ /* Copy the authoritative parameter from the subnet,
+ since there is no opportunity to declare it here. */
+ share->group->authoritative =
+ share->subnets->group->authoritative;
+ enter_shared_network(share);
+ }
+ shared_network_dereference(&share, MDL);
+ return 1;
+
+ case VENDOR_CLASS:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group, CLASS_TYPE_VENDOR);
+ return 1;
+
+ case USER_CLASS:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group, CLASS_TYPE_USER);
+ return 1;
+
+ case CLASS:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group, CLASS_TYPE_CLASS);
+ return 1;
+
+ case SUBCLASS:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type == CLASS_DECL) {
+ parse_warn (cfile,
+ "class declarations not allowed here.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_class_declaration(NULL, cfile, group,
+ CLASS_TYPE_SUBCLASS);
+ return 1;
+
+ case HARDWARE:
+ next_token (&val, (unsigned *)0, cfile);
+ memset (&hardware, 0, sizeof hardware);
+ if (host_decl && memcmp(&hardware, &(host_decl->interface),
+ sizeof(hardware)) != 0) {
+ parse_warn(cfile, "Host %s hardware address already "
+ "configured.", host_decl->name);
+ break;
+ }
+
+ parse_hardware_param (cfile, &hardware);
+ if (host_decl)
+ host_decl -> interface = hardware;
+ else
+ parse_warn (cfile, "hardware address parameter %s",
+ "not allowed here.");
+ break;
+
+ case FIXED_ADDR:
+ case FIXED_ADDR6:
+ next_token(&val, NULL, cfile);
+ cache = NULL;
+ if (parse_fixed_addr_param(&cache, cfile, token)) {
+ if (host_decl) {
+ if (host_decl->fixed_addr) {
+ option_cache_dereference(&cache, MDL);
+ parse_warn(cfile,
+ "Only one fixed address "
+ "declaration per host.");
+ } else {
+ host_decl->fixed_addr = cache;
+ }
+ } else {
+ parse_warn(cfile,
+ "fixed-address parameter not "
+ "allowed here.");
+ option_cache_dereference(&cache, MDL);
+ }
+ }
+ break;
+
+ case POOL:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type == POOL_DECL) {
+ parse_warn (cfile, "pool declared within pool.");
+ skip_to_semi(cfile);
+ } else if (type != SUBNET_DECL && type != SHARED_NET_DECL) {
+ parse_warn (cfile, "pool declared outside of network");
+ skip_to_semi(cfile);
+ } else
+ parse_pool_statement (cfile, group, type);
+
+ return declaration;
+
+ case RANGE:
+ next_token (&val, (unsigned *)0, cfile);
+ if (type != SUBNET_DECL || !group -> subnet) {
+ parse_warn (cfile,
+ "range declaration not allowed here.");
+ skip_to_semi (cfile);
+ return declaration;
+ }
+ parse_address_range (cfile, group, type, (struct pool *)0,
+ (struct lease **)0);
+ return declaration;
+
+#ifdef DHCPv6
+ case RANGE6:
+ next_token(NULL, NULL, cfile);
+ if ((type != SUBNET_DECL) || (group->subnet == NULL)) {
+ parse_warn (cfile,
+ "range6 declaration not allowed here.");
+ skip_to_semi(cfile);
+ return declaration;
+ }
+ parse_address_range6(cfile, group);
+ return declaration;
+
+ case PREFIX6:
+ next_token(NULL, NULL, cfile);
+ if ((type != SUBNET_DECL) || (group->subnet == NULL)) {
+ parse_warn (cfile,
+ "prefix6 declaration not allowed here.");
+ skip_to_semi(cfile);
+ return declaration;
+ }
+ parse_prefix6(cfile, group);
+ return declaration;
+
+ case FIXED_PREFIX6:
+ next_token(&val, NULL, cfile);
+ if (!host_decl) {
+ parse_warn (cfile,
+ "fixed-prefix6 declaration not "
+ "allowed here.");
+ skip_to_semi(cfile);
+ break;
+ }
+ parse_fixed_prefix6(cfile, host_decl);
+ break;
+
+#endif /* DHCPv6 */
+
+ case TOKEN_NOT:
+ token = next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case AUTHORITATIVE:
+ group -> authoritative = 0;
+ goto authoritative;
+ default:
+ parse_warn (cfile, "expecting assertion");
+ skip_to_semi (cfile);
+ break;
+ }
+ break;
+ case AUTHORITATIVE:
+ token = next_token (&val, (unsigned *)0, cfile);
+ group -> authoritative = 1;
+ authoritative:
+ if (type == HOST_DECL)
+ parse_warn (cfile, "authority makes no sense here.");
+ parse_semi (cfile);
+ break;
+
+ /* "server-identifier" is a special hack, equivalent to
+ "option dhcp-server-identifier". */
+ case SERVER_IDENTIFIER:
+ code = DHO_DHCP_SERVER_IDENTIFIER;
+ if (!option_code_hash_lookup(&option, dhcp_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Server identifier not in hash (%s:%d).",
+ MDL);
+ token = next_token (&val, (unsigned *)0, cfile);
+ goto finish_option;
+
+ case OPTION:
+ token = next_token (&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SPACE) {
+ if (type != ROOT_GROUP) {
+ parse_warn (cfile,
+ "option space definitions %s",
+ "may not be scoped.");
+ skip_to_semi (cfile);
+ break;
+ }
+ parse_option_space_decl (cfile);
+ return declaration;
+ }
+
+ known = 0;
+ status = parse_option_name(cfile, 1, &known, &option);
+ if (status == ISC_R_SUCCESS) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == CODE) {
+ if (type != ROOT_GROUP) {
+ parse_warn (cfile,
+ "option definitions%s",
+ " may not be scoped.");
+ skip_to_semi (cfile);
+ option_dereference(&option, MDL);
+ break;
+ }
+ next_token (&val, (unsigned *)0, cfile);
+
+ /*
+ * If the option was known, remove it from the
+ * code and name hashes before redefining it.
+ */
+ if (known) {
+ option_name_hash_delete(
+ option->universe->name_hash,
+ option->name, 0, MDL);
+ option_code_hash_delete(
+ option->universe->code_hash,
+ &option->code, 0, MDL);
+ }
+
+ parse_option_code_definition(cfile, option);
+ option_dereference(&option, MDL);
+ return declaration;
+ }
+
+ /* If this wasn't an option code definition, don't
+ allow an unknown option. */
+ if (!known) {
+ parse_warn (cfile, "unknown option %s.%s",
+ option -> universe -> name,
+ option -> name);
+ skip_to_semi (cfile);
+ option_dereference(&option, MDL);
+ return declaration;
+ }
+
+ finish_option:
+ et = (struct executable_statement *)0;
+ if (!parse_option_statement
+ (&et, cfile, 1, option,
+ supersede_option_statement))
+ return declaration;
+ option_dereference(&option, MDL);
+ goto insert_statement;
+ } else
+ return declaration;
+
+ break;
+
+ case FAILOVER:
+ if (type != ROOT_GROUP && type != SHARED_NET_DECL) {
+ parse_warn (cfile, "failover peers may only be %s",
+ "defined in shared-network");
+ log_error ("declarations and the outer scope.");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+#if defined (FAILOVER_PROTOCOL)
+ parse_failover_peer (cfile, group, type);
+#else
+ parse_warn (cfile, "No failover support.");
+ skip_to_semi (cfile);
+#endif
+ break;
+
+#ifdef DHCPv6
+ case SERVER_DUID:
+ parse_server_duid_conf(cfile);
+ break;
+#endif /* DHCPv6 */
+
+ default:
+ et = (struct executable_statement *)0;
+ lose = 0;
+ if (!parse_executable_statement (&et, cfile, &lose,
+ context_any)) {
+ if (!lose) {
+ if (declaration)
+ parse_warn (cfile,
+ "expecting a declaration");
+ else
+ parse_warn (cfile,
+ "expecting a parameter %s",
+ "or declaration");
+ skip_to_semi (cfile);
+ }
+ return declaration;
+ }
+ if (!et)
+ return declaration;
+ insert_statement:
+ if (group -> statements) {
+ int multi = 0;
+
+ /* If this set of statements is only referenced
+ by this group, just add the current statement
+ to the end of the chain. */
+ for (ep = group -> statements; ep -> next;
+ ep = ep -> next)
+ if (ep -> refcnt > 1) /* XXX */
+ multi = 1;
+ if (!multi) {
+ executable_statement_reference (&ep -> next,
+ et, MDL);
+ executable_statement_dereference (&et, MDL);
+ return declaration;
+ }
+
+ /* Otherwise, make a parent chain, and put the
+ current group statements first and the new
+ statement in the next pointer. */
+ ep = (struct executable_statement *)0;
+ if (!executable_statement_allocate (&ep, MDL))
+ log_fatal ("No memory for statements.");
+ ep -> op = statements_statement;
+ executable_statement_reference (&ep -> data.statements,
+ group -> statements,
+ MDL);
+ executable_statement_reference (&ep -> next, et, MDL);
+ executable_statement_dereference (&group -> statements,
+ MDL);
+ executable_statement_reference (&group -> statements,
+ ep, MDL);
+ executable_statement_dereference (&ep, MDL);
+ } else {
+ executable_statement_reference (&group -> statements,
+ et, MDL);
+ }
+ executable_statement_dereference (&et, MDL);
+ return declaration;
+ }
+
+ return 0;
+}
+
+#if defined (FAILOVER_PROTOCOL)
+void parse_failover_peer (cfile, group, type)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+{
+ enum dhcp_token token;
+ const char *val;
+ dhcp_failover_state_t *peer;
+ u_int32_t *tp;
+ char *name;
+ u_int32_t split;
+ u_int8_t hba [32];
+ unsigned hba_len = sizeof hba;
+ int i;
+ struct expression *expr;
+ isc_result_t status;
+ dhcp_failover_config_t *cp;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != PEER) {
+ parse_warn (cfile, "expecting \"peer\"");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (is_identifier (token) || token == STRING) {
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("no memory for peer name %s", name);
+ strcpy (name, val);
+ } else {
+ parse_warn (cfile, "expecting failover peer name.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ /* See if there's a peer declaration by this name. */
+ peer = (dhcp_failover_state_t *)0;
+ find_failover_peer (&peer, name, MDL);
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ dfree (name, MDL);
+ if (type != SHARED_NET_DECL)
+ parse_warn (cfile, "failover peer reference not %s",
+ "in shared-network declaration");
+ else {
+ if (!peer) {
+ parse_warn (cfile, "reference to unknown%s%s",
+ " failover peer ", name);
+ return;
+ }
+ dhcp_failover_state_reference
+ (&group -> shared_network -> failover_peer,
+ peer, MDL);
+ }
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ } else if (token == STATE) {
+ if (!peer) {
+ parse_warn (cfile, "state declaration for unknown%s%s",
+ " failover peer ", name);
+ return;
+ }
+ parse_failover_state_declaration (cfile, peer);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ } else if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace");
+ skip_to_semi (cfile);
+ }
+
+ /* Make sure this isn't a redeclaration. */
+ if (peer) {
+ parse_warn (cfile, "redeclaration of failover peer %s", name);
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+
+ status = dhcp_failover_state_allocate (&peer, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't allocate failover peer %s: %s",
+ name, isc_result_totext (status));
+
+ /* Save the name. */
+ peer -> name = name;
+
+ do {
+ cp = &peer -> me;
+ peer:
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case RBRACE:
+ break;
+
+ case PRIMARY:
+ peer -> i_am = primary;
+ break;
+
+ case SECONDARY:
+ peer -> i_am = secondary;
+ if (peer -> hba)
+ parse_warn (cfile,
+ "secondary may not define %s",
+ "load balance settings.");
+ break;
+
+ case PEER:
+ cp = &peer -> partner;
+ goto peer;
+
+ case ADDRESS:
+ expr = (struct expression *)0;
+ if (!parse_ip_addr_or_hostname (&expr, cfile, 0)) {
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ option_cache (&cp -> address,
+ (struct data_string *)0, expr,
+ (struct option *)0, MDL);
+ expression_dereference (&expr, MDL);
+ break;
+
+ case PORT:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number");
+ skip_to_rbrace (cfile, 1);
+ }
+ cp -> port = atoi (val);
+ break;
+
+ case MAX_LEASE_MISBALANCE:
+ tp = &peer->max_lease_misbalance;
+ goto parse_idle;
+
+ case MAX_LEASE_OWNERSHIP:
+ tp = &peer->max_lease_ownership;
+ goto parse_idle;
+
+ case MAX_BALANCE:
+ tp = &peer->max_balance;
+ goto parse_idle;
+
+ case MIN_BALANCE:
+ tp = &peer->min_balance;
+ goto parse_idle;
+
+ case AUTO_PARTNER_DOWN:
+ tp = &peer->auto_partner_down;
+ goto parse_idle;
+
+ case MAX_RESPONSE_DELAY:
+ tp = &cp -> max_response_delay;
+ parse_idle:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number.");
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ *tp = atoi (val);
+ break;
+
+ case MAX_UNACKED_UPDATES:
+ tp = &cp -> max_flying_updates;
+ goto parse_idle;
+
+ case MCLT:
+ tp = &peer -> mclt;
+ goto parse_idle;
+
+ case HBA:
+ hba_len = 32;
+ if (peer -> i_am == secondary)
+ parse_warn (cfile,
+ "secondary may not define %s",
+ "load balance settings.");
+ if (!parse_numeric_aggregate (cfile, hba, &hba_len,
+ COLON, 16, 8)) {
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ if (hba_len != 32) {
+ parse_warn (cfile,
+ "HBA must be exactly 32 bytes.");
+ dfree (hba, MDL);
+ break;
+ }
+ make_hba:
+ peer -> hba = dmalloc (32, MDL);
+ if (!peer -> hba) {
+ dfree (peer -> name, MDL);
+ dfree (peer, MDL);
+ }
+ memcpy (peer -> hba, hba, 32);
+ break;
+
+ case SPLIT:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (peer -> i_am == secondary)
+ parse_warn (cfile,
+ "secondary may not define %s",
+ "load balance settings.");
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number");
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ split = atoi (val);
+ if (split > 255) {
+ parse_warn (cfile, "split must be < 256");
+ } else {
+ memset (hba, 0, sizeof hba);
+ for (i = 0; i < split; i++) {
+ if (i < split)
+ hba [i / 8] |= (1 << (i & 7));
+ }
+ goto make_hba;
+ }
+ break;
+
+ case LOAD:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != BALANCE) {
+ parse_warn (cfile, "expecting 'balance'");
+ badload:
+ skip_to_rbrace (cfile, 1);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != TOKEN_MAX) {
+ parse_warn (cfile, "expecting 'max'");
+ goto badload;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SECONDS) {
+ parse_warn (cfile, "expecting 'secs'");
+ goto badload;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting number");
+ goto badload;
+ }
+ peer -> load_balance_max_secs = atoi (val);
+ break;
+
+ default:
+ parse_warn (cfile,
+ "invalid statement in peer declaration");
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ if (token != RBRACE && !parse_semi (cfile)) {
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&peer, MDL);
+ return;
+ }
+ } while (token != RBRACE);
+
+ /* me.address can be null; the failover link initiate code tries to
+ * derive a reasonable address to use.
+ */
+ if (!peer -> partner.address)
+ parse_warn (cfile, "peer address may not be omitted");
+
+ if (!peer->me.port)
+ peer->me.port = DEFAULT_FAILOVER_PORT;
+ if (!peer->partner.port)
+ peer->partner.port = DEFAULT_FAILOVER_PORT;
+
+ if (peer -> i_am == primary) {
+ if (!peer -> hba) {
+ parse_warn (cfile,
+ "primary failover server must have hba or split.");
+ } else if (!peer -> mclt) {
+ parse_warn (cfile,
+ "primary failover server must have mclt.");
+ }
+ }
+
+ if (!peer->max_lease_misbalance)
+ peer->max_lease_misbalance = DEFAULT_MAX_LEASE_MISBALANCE;
+ if (!peer->max_lease_ownership)
+ peer->max_lease_ownership = DEFAULT_MAX_LEASE_OWNERSHIP;
+ if (!peer->max_balance)
+ peer->max_balance = DEFAULT_MAX_BALANCE_TIME;
+ if (!peer->min_balance)
+ peer->min_balance = DEFAULT_MIN_BALANCE_TIME;
+ if (!peer->me.max_flying_updates)
+ peer->me.max_flying_updates = DEFAULT_MAX_FLYING_UPDATES;
+ if (!peer->me.max_response_delay)
+ peer->me.max_response_delay = DEFAULT_MAX_RESPONSE_DELAY;
+
+ if (type == SHARED_NET_DECL)
+ group->shared_network->failover_peer = peer;
+
+ /* Set the initial state. */
+ if (peer -> i_am == primary) {
+ peer -> me.state = recover;
+ peer -> me.stos = cur_time;
+ peer -> partner.state = unknown_state;
+ peer -> partner.stos = cur_time;
+ } else {
+ peer -> me.state = recover;
+ peer -> me.stos = cur_time;
+ peer -> partner.state = unknown_state;
+ peer -> partner.stos = cur_time;
+ }
+
+ status = enter_failover_peer (peer);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile, "failover peer %s: %s",
+ peer -> name, isc_result_totext (status));
+ dhcp_failover_state_dereference (&peer, MDL);
+}
+
+void parse_failover_state_declaration (struct parse *cfile,
+ dhcp_failover_state_t *peer)
+{
+ enum dhcp_token token;
+ const char *val;
+ char *name;
+ dhcp_failover_state_t *state;
+ dhcp_failover_config_t *cp;
+
+ if (!peer) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != PEER) {
+ parse_warn (cfile, "expecting \"peer\"");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (is_identifier (token) || token == STRING) {
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("failover peer name %s: no memory",
+ name);
+ strcpy (name, val);
+ } else {
+ parse_warn (cfile, "expecting failover peer name.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ /* See if there's a peer declaration by this name. */
+ state = (dhcp_failover_state_t *)0;
+ find_failover_peer (&state, name, MDL);
+ if (!state) {
+ parse_warn (cfile, "unknown failover peer: %s", name);
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STATE) {
+ parse_warn (cfile, "expecting 'state'");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ return;
+ }
+ } else {
+ state = (dhcp_failover_state_t *)0;
+ dhcp_failover_state_reference (&state, peer, MDL);
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ dhcp_failover_state_dereference (&state, MDL);
+ return;
+ }
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case RBRACE:
+ break;
+ case MY:
+ cp = &state -> me;
+ do_state:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STATE) {
+ parse_warn (cfile, "expecting 'state'");
+ goto bogus;
+ }
+ parse_failover_state (cfile,
+ &cp -> state, &cp -> stos);
+ break;
+
+ case PARTNER:
+ cp = &state -> partner;
+ goto do_state;
+
+ case MCLT:
+ if (state -> i_am == primary) {
+ parse_warn (cfile,
+ "mclt not valid for primary");
+ goto bogus;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting a number.");
+ goto bogus;
+ }
+ state -> mclt = atoi (val);
+ parse_semi (cfile);
+ break;
+
+ default:
+ parse_warn (cfile, "expecting state setting.");
+ bogus:
+ skip_to_rbrace (cfile, 1);
+ dhcp_failover_state_dereference (&state, MDL);
+ return;
+ }
+ } while (token != RBRACE);
+ dhcp_failover_state_dereference (&state, MDL);
+}
+
+void parse_failover_state (cfile, state, stos)
+ struct parse *cfile;
+ enum failover_state *state;
+ TIME *stos;
+{
+ enum dhcp_token token;
+ const char *val;
+ enum failover_state state_in;
+ TIME stos_in;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case UNKNOWN_STATE:
+ state_in = unknown_state;
+ break;
+
+ case PARTNER_DOWN:
+ state_in = partner_down;
+ break;
+
+ case NORMAL:
+ state_in = normal;
+ break;
+
+ case COMMUNICATIONS_INTERRUPTED:
+ state_in = communications_interrupted;
+ break;
+
+ case CONFLICT_DONE:
+ state_in = conflict_done;
+ break;
+
+ case RESOLUTION_INTERRUPTED:
+ state_in = resolution_interrupted;
+ break;
+
+ case POTENTIAL_CONFLICT:
+ state_in = potential_conflict;
+ break;
+
+ case RECOVER:
+ state_in = recover;
+ break;
+
+ case RECOVER_WAIT:
+ state_in = recover_wait;
+ break;
+
+ case RECOVER_DONE:
+ state_in = recover_done;
+ break;
+
+ case SHUTDOWN:
+ state_in = shut_down;
+ break;
+
+ case PAUSED:
+ state_in = paused;
+ break;
+
+ case STARTUP:
+ state_in = startup;
+ break;
+
+ default:
+ parse_warn (cfile, "unknown failover state");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ stos_in = cur_time;
+ } else {
+ if (token != AT) {
+ parse_warn (cfile, "expecting \"at\"");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ stos_in = parse_date (cfile);
+ if (!stos_in)
+ return;
+ }
+
+ /* Now that we've apparently gotten a clean parse, we
+ can trust that this is a state that was fully committed to
+ disk, so we can install it. */
+ *stos = stos_in;
+ *state = state_in;
+}
+#endif /* defined (FAILOVER_PROTOCOL) */
+
+/* Permit_list_match returns 1 if every element of the permit list in lhs
+ also appears in rhs. Note that this doesn't by itself mean that the
+ two lists are equal - to check for equality, permit_list_match has to
+ return 1 with (list1, list2) and with (list2, list1). */
+
+int permit_list_match (struct permit *lhs, struct permit *rhs)
+{
+ struct permit *plp, *prp;
+ int matched;
+
+ if (!lhs)
+ return 1;
+ if (!rhs)
+ return 0;
+ for (plp = lhs; plp; plp = plp -> next) {
+ matched = 0;
+ for (prp = rhs; prp; prp = prp -> next) {
+ if (prp -> type == plp -> type &&
+ (prp -> type != permit_class ||
+ prp -> class == plp -> class)) {
+ matched = 1;
+ break;
+ }
+ }
+ if (!matched)
+ return 0;
+ }
+ return 1;
+}
+
+void parse_pool_statement (cfile, group, type)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+{
+ enum dhcp_token token;
+ const char *val;
+ int done = 0;
+ struct pool *pool, **p, *pp;
+ struct permit *permit;
+ struct permit **permit_head;
+ int declaration = 0;
+ isc_result_t status;
+ struct lease *lpchain = (struct lease *)0, *lp;
+ TIME t;
+ int is_allow = 0;
+
+ pool = (struct pool *)0;
+ status = pool_allocate (&pool, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("no memory for pool: %s",
+ isc_result_totext (status));
+
+ if (type == SUBNET_DECL)
+ shared_network_reference (&pool -> shared_network,
+ group -> subnet -> shared_network,
+ MDL);
+ else if (type == SHARED_NET_DECL)
+ shared_network_reference (&pool -> shared_network,
+ group -> shared_network, MDL);
+ else {
+ parse_warn(cfile, "Dynamic pools are only valid inside "
+ "subnet or shared-network statements.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (pool->shared_network == NULL ||
+ !clone_group(&pool->group, pool->shared_network->group, MDL))
+ log_fatal("can't clone pool group.");
+
+#if defined (FAILOVER_PROTOCOL)
+ /* Inherit the failover peer from the shared network. */
+ if (pool -> shared_network -> failover_peer)
+ dhcp_failover_state_reference
+ (&pool -> failover_peer,
+ pool -> shared_network -> failover_peer, MDL);
+#endif
+
+ if (!parse_lbrace (cfile)) {
+ pool_dereference (&pool, MDL);
+ return;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case TOKEN_NO:
+ next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != FAILOVER ||
+ (token = next_token (&val, (unsigned *)0,
+ cfile)) != PEER) {
+ parse_warn (cfile,
+ "expecting \"failover peer\".");
+ skip_to_semi (cfile);
+ continue;
+ }
+#if defined (FAILOVER_PROTOCOL)
+ if (pool -> failover_peer)
+ dhcp_failover_state_dereference
+ (&pool -> failover_peer, MDL);
+#endif
+ break;
+
+#if defined (FAILOVER_PROTOCOL)
+ case FAILOVER:
+ next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != PEER) {
+ parse_warn (cfile, "expecting 'peer'.");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting string.");
+ skip_to_semi (cfile);
+ break;
+ }
+ if (pool -> failover_peer)
+ dhcp_failover_state_dereference
+ (&pool -> failover_peer, MDL);
+ status = find_failover_peer (&pool -> failover_peer,
+ val, MDL);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile,
+ "failover peer %s: %s", val,
+ isc_result_totext (status));
+ else
+ pool -> failover_peer -> pool_count++;
+ parse_semi (cfile);
+ break;
+#endif
+
+ case RANGE:
+ next_token (&val, (unsigned *)0, cfile);
+ parse_address_range (cfile, group, type,
+ pool, &lpchain);
+ break;
+ case ALLOW:
+ permit_head = &pool -> permit_list;
+ /* remember the clause which leads to get_permit */
+ is_allow = 1;
+ get_permit:
+ permit = new_permit (MDL);
+ if (!permit)
+ log_fatal ("no memory for permit");
+ next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case UNKNOWN:
+ permit -> type = permit_unknown_clients;
+ get_clients:
+ if (next_token (&val, (unsigned *)0,
+ cfile) != CLIENTS) {
+ parse_warn (cfile,
+ "expecting \"clients\"");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ break;
+
+ case KNOWN_CLIENTS:
+ permit -> type = permit_known_clients;
+ break;
+
+ case UNKNOWN_CLIENTS:
+ permit -> type = permit_unknown_clients;
+ break;
+
+ case KNOWN:
+ permit -> type = permit_known_clients;
+ goto get_clients;
+
+ case AUTHENTICATED:
+ permit -> type = permit_authenticated_clients;
+ goto get_clients;
+
+ case UNAUTHENTICATED:
+ permit -> type =
+ permit_unauthenticated_clients;
+ goto get_clients;
+
+ case ALL:
+ permit -> type = permit_all_clients;
+ goto get_clients;
+ break;
+
+ case DYNAMIC:
+ permit -> type = permit_dynamic_bootp_clients;
+ if (next_token (&val, (unsigned *)0,
+ cfile) != TOKEN_BOOTP) {
+ parse_warn (cfile,
+ "expecting \"bootp\"");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ goto get_clients;
+
+ case MEMBERS:
+ if (next_token (&val, (unsigned *)0,
+ cfile) != OF) {
+ parse_warn (cfile, "expecting \"of\"");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ if (next_token (&val, (unsigned *)0,
+ cfile) != STRING) {
+ parse_warn (cfile,
+ "expecting class name.");
+ skip_to_semi (cfile);
+ free_permit (permit, MDL);
+ continue;
+ }
+ permit -> type = permit_class;
+ permit -> class = (struct class *)0;
+ find_class (&permit -> class, val, MDL);
+ if (!permit -> class)
+ parse_warn (cfile,
+ "no such class: %s", val);
+ break;
+
+ case AFTER:
+ if (pool->valid_from || pool->valid_until) {
+ parse_warn(cfile,
+ "duplicate \"after\" clause.");
+ skip_to_semi(cfile);
+ free_permit(permit, MDL);
+ continue;
+ }
+ t = parse_date_core(cfile);
+ permit->type = permit_after;
+ permit->after = t;
+ if (is_allow) {
+ pool->valid_from = t;
+ } else {
+ pool->valid_until = t;
+ }
+ break;
+
+ default:
+ parse_warn (cfile, "expecting permit type.");
+ skip_to_semi (cfile);
+ break;
+ }
+ while (*permit_head)
+ permit_head = &((*permit_head) -> next);
+ *permit_head = permit;
+ parse_semi (cfile);
+ break;
+
+ case DENY:
+ permit_head = &pool -> prohibit_list;
+ /* remember the clause which leads to get_permit */
+ is_allow = 0;
+ goto get_permit;
+
+ case RBRACE:
+ next_token (&val, (unsigned *)0, cfile);
+ done = 1;
+ break;
+
+ case END_OF_FILE:
+ /*
+ * We can get to END_OF_FILE if, for instance,
+ * the parse_statement() reads all available tokens
+ * and leaves us at the end.
+ */
+ parse_warn(cfile, "unexpected end of file");
+ goto cleanup;
+
+ default:
+ declaration = parse_statement (cfile, pool -> group,
+ POOL_DECL,
+ (struct host_decl *)0,
+ declaration);
+ break;
+ }
+ } while (!done);
+
+ /* See if there's already a pool into which we can merge this one. */
+ for (pp = pool -> shared_network -> pools; pp; pp = pp -> next) {
+ if (pp -> group -> statements != pool -> group -> statements)
+ continue;
+#if defined (FAILOVER_PROTOCOL)
+ if (pool -> failover_peer != pp -> failover_peer)
+ continue;
+#endif
+ if (!permit_list_match (pp -> permit_list,
+ pool -> permit_list) ||
+ !permit_list_match (pool -> permit_list,
+ pp -> permit_list) ||
+ !permit_list_match (pp -> prohibit_list,
+ pool -> prohibit_list) ||
+ !permit_list_match (pool -> prohibit_list,
+ pp -> prohibit_list))
+ continue;
+
+ /* Okay, we can merge these two pools. All we have to
+ do is fix up the leases, which all point to their pool. */
+ for (lp = lpchain; lp; lp = lp -> next) {
+ pool_dereference (&lp -> pool, MDL);
+ pool_reference (&lp -> pool, pp, MDL);
+ }
+ break;
+ }
+
+ /* If we didn't succeed in merging this pool into another, put
+ it on the list. */
+ if (!pp) {
+ p = &pool -> shared_network -> pools;
+ for (; *p; p = &((*p) -> next))
+ ;
+ pool_reference (p, pool, MDL);
+ }
+
+ /* Don't allow a pool declaration with no addresses, since it is
+ probably a configuration error. */
+ if (!lpchain) {
+ parse_warn (cfile, "Pool declaration with no address range.");
+ log_error ("Pool declarations must always contain at least");
+ log_error ("one range statement.");
+ }
+
+cleanup:
+ /* Dereference the lease chain. */
+ lp = (struct lease *)0;
+ while (lpchain) {
+ lease_reference (&lp, lpchain, MDL);
+ lease_dereference (&lpchain, MDL);
+ if (lp -> next) {
+ lease_reference (&lpchain, lp -> next, MDL);
+ lease_dereference (&lp -> next, MDL);
+ lease_dereference (&lp, MDL);
+ }
+ }
+ pool_dereference (&pool, MDL);
+}
+
+/* Expect a left brace; if there isn't one, skip over the rest of the
+ statement and return zero; otherwise, return 1. */
+
+int parse_lbrace (cfile)
+ struct parse *cfile;
+{
+ enum dhcp_token token;
+ const char *val;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LBRACE) {
+ parse_warn (cfile, "expecting left brace.");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ return 1;
+}
+
+
+/* host-declaration :== hostname RBRACE parameters declarations LBRACE */
+
+void parse_host_declaration (cfile, group)
+ struct parse *cfile;
+ struct group *group;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct host_decl *host;
+ char *name;
+ int declaration = 0;
+ int dynamicp = 0;
+ int deleted = 0;
+ isc_result_t status;
+ int known;
+ struct option *option;
+ struct expression *expr = NULL;
+
+ name = parse_host_name (cfile);
+ if (!name) {
+ parse_warn (cfile, "expecting a name for host declaration.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ host = (struct host_decl *)0;
+ status = host_allocate (&host, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("can't allocate host decl struct %s: %s",
+ name, isc_result_totext (status));
+ host -> name = name;
+ if (!clone_group (&host -> group, group, MDL)) {
+ log_fatal ("can't clone group for host %s", name);
+ boom:
+ host_dereference (&host, MDL);
+ return;
+ }
+
+ if (!parse_lbrace (cfile))
+ goto boom;
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ break;
+ }
+ if (token == END_OF_FILE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ }
+ /* If the host declaration was created by the server,
+ remember to save it. */
+ if (token == DYNAMIC) {
+ dynamicp = 1;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+ /* If the host declaration was created by the server,
+ remember to save it. */
+ if (token == TOKEN_DELETED) {
+ deleted = 1;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ if (token == GROUP) {
+ struct group_object *go;
+ token = next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING && !is_identifier (token)) {
+ parse_warn (cfile,
+ "expecting string or identifier.");
+ skip_to_rbrace (cfile, 1);
+ break;
+ }
+ go = (struct group_object *)0;
+ if (!group_hash_lookup (&go, group_name_hash,
+ val, strlen (val), MDL)) {
+ parse_warn (cfile, "unknown group %s in host %s",
+ val, host -> name);
+ } else {
+ if (host -> named_group)
+ group_object_dereference
+ (&host -> named_group, MDL);
+ group_object_reference (&host -> named_group,
+ go, MDL);
+ group_object_dereference (&go, MDL);
+ }
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ if (token == UID) {
+ const char *s;
+ unsigned char *t = 0;
+ unsigned len;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ data_string_forget (&host -> client_identifier, MDL);
+
+ if (host->client_identifier.len != 0) {
+ parse_warn(cfile, "Host %s already has a "
+ "client identifier.",
+ host->name);
+ break;
+ }
+
+ /* See if it's a string or a cshl. */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ token = next_token (&val, &len, cfile);
+ s = val;
+ host -> client_identifier.terminated = 1;
+ } else {
+ len = 0;
+ t = parse_numeric_aggregate
+ (cfile,
+ (unsigned char *)0, &len, ':', 16, 8);
+ if (!t) {
+ parse_warn (cfile,
+ "expecting hex list.");
+ skip_to_semi (cfile);
+ }
+ s = (const char *)t;
+ }
+ if (!buffer_allocate
+ (&host -> client_identifier.buffer,
+ len + host -> client_identifier.terminated, MDL))
+ log_fatal ("no memory for uid for host %s.",
+ host -> name);
+ host -> client_identifier.data =
+ host -> client_identifier.buffer -> data;
+ host -> client_identifier.len = len;
+ memcpy (host -> client_identifier.buffer -> data, s,
+ len + host -> client_identifier.terminated);
+ if (t)
+ dfree (t, MDL);
+
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ if (token == HOST_IDENTIFIER) {
+ if (host->host_id_option != NULL) {
+ parse_warn(cfile,
+ "only one host-identifier allowed "
+ "per host");
+ skip_to_rbrace(cfile, 1);
+ break;
+ }
+ next_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != OPTION) {
+ parse_warn(cfile,
+ "host-identifier must be an option");
+ skip_to_rbrace(cfile, 1);
+ break;
+ }
+ known = 0;
+ option = NULL;
+ status = parse_option_name(cfile, 1, &known, &option);
+ if ((status != ISC_R_SUCCESS) || (option == NULL)) {
+ break;
+ }
+ if (!known) {
+ parse_warn(cfile, "unknown option %s.%s",
+ option->universe->name,
+ option->name);
+ skip_to_rbrace(cfile, 1);
+ break;
+ }
+
+ if (! parse_option_data(&expr, cfile, 1, option)) {
+ skip_to_rbrace(cfile, 1);
+ option_dereference(&option, MDL);
+ break;
+ }
+
+ if (!parse_semi(cfile)) {
+ skip_to_rbrace(cfile, 1);
+ expression_dereference(&expr, MDL);
+ option_dereference(&option, MDL);
+ break;
+ }
+
+ option_reference(&host->host_id_option, option, MDL);
+ option_dereference(&option, MDL);
+ data_string_copy(&host->host_id,
+ &expr->data.const_data, MDL);
+ expression_dereference(&expr, MDL);
+ continue;
+ }
+
+ declaration = parse_statement(cfile, host->group, HOST_DECL,
+ host, declaration);
+ } while (1);
+
+ if (deleted) {
+ struct host_decl *hp = (struct host_decl *)0;
+ if (host_hash_lookup (&hp, host_name_hash,
+ (unsigned char *)host -> name,
+ strlen (host -> name), MDL)) {
+ delete_host (hp, 0);
+ host_dereference (&hp, MDL);
+ }
+ } else {
+ if (host -> named_group && host -> named_group -> group) {
+ if (host -> group -> statements ||
+ (host -> group -> authoritative !=
+ host -> named_group -> group -> authoritative)) {
+ if (host -> group -> next)
+ group_dereference (&host -> group -> next,
+ MDL);
+ group_reference (&host -> group -> next,
+ host -> named_group -> group,
+ MDL);
+ } else {
+ group_dereference (&host -> group, MDL);
+ group_reference (&host -> group,
+ host -> named_group -> group,
+ MDL);
+ }
+ }
+
+ if (dynamicp)
+ host -> flags |= HOST_DECL_DYNAMIC;
+ else
+ host -> flags |= HOST_DECL_STATIC;
+
+ status = enter_host (host, dynamicp, 0);
+ if (status != ISC_R_SUCCESS)
+ parse_warn (cfile, "host %s: %s", host -> name,
+ isc_result_totext (status));
+ }
+ host_dereference (&host, MDL);
+}
+
+/* class-declaration :== STRING LBRACE parameters declarations RBRACE
+*/
+
+int parse_class_declaration (cp, cfile, group, type)
+ struct class **cp;
+ struct parse *cfile;
+ struct group *group;
+ int type;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct class *class = (struct class *)0, *pc = (struct class *)0;
+ int declaration = 0;
+ int lose = 0;
+ struct data_string data;
+ char *name;
+ const char *tname;
+ struct executable_statement *stmt = (struct executable_statement *)0;
+ int new = 1;
+ isc_result_t status = ISC_R_FAILURE;
+ int matchedonce = 0;
+ int submatchedonce = 0;
+ unsigned code;
+
+ if (dhcpv6_class_once && local_family == AF_INET6) {
+ dhcpv6_class_once = 0;
+ log_error("WARNING: class declarations are not supported "
+ "for DHCPv6.");
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "Expecting class name");
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ /* See if there's already a class with the specified name. */
+ find_class (&pc, val, MDL);
+
+ /* If it is a class, we're updating it. If it's any of the other
+ * types (subclass, vendor or user class), the named class is a
+ * reference to the parent class so its mandatory.
+ */
+ if (pc && (type == CLASS_TYPE_CLASS)) {
+ class_reference(&class, pc, MDL);
+ new = 0;
+ class_dereference(&pc, MDL);
+ } else if (!pc && (type != CLASS_TYPE_CLASS)) {
+ parse_warn(cfile, "no class named %s", val);
+ skip_to_semi(cfile);
+ return 0;
+ }
+
+ /* The old vendor-class and user-class declarations had an implicit
+ match. We don't do the implicit match anymore. Instead, for
+ backward compatibility, we have an implicit-vendor-class and an
+ implicit-user-class. vendor-class and user-class declarations
+ are turned into subclasses of the implicit classes, and the
+ submatch expression of the implicit classes extracts the contents of
+ the vendor class or user class. */
+ if ((type == CLASS_TYPE_VENDOR) || (type == CLASS_TYPE_USER)) {
+ data.len = strlen (val);
+ data.buffer = (struct buffer *)0;
+ if (!buffer_allocate (&data.buffer, data.len + 1, MDL))
+ log_fatal ("no memory for class name.");
+ data.data = &data.buffer -> data [0];
+ data.terminated = 1;
+
+ tname = type ? "implicit-vendor-class" : "implicit-user-class";
+ } else if (type == CLASS_TYPE_CLASS) {
+ tname = val;
+ } else {
+ tname = (const char *)0;
+ }
+
+ if (tname) {
+ name = dmalloc (strlen (tname) + 1, MDL);
+ if (!name)
+ log_fatal ("No memory for class name %s.", tname);
+ strcpy (name, val);
+ } else
+ name = (char *)0;
+
+ /* If this is a straight subclass, parse the hash string. */
+ if (type == CLASS_TYPE_SUBCLASS) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ token = next_token (&val, &data.len, cfile);
+ data.buffer = (struct buffer *)0;
+ if (!buffer_allocate (&data.buffer,
+ data.len + 1, MDL)) {
+ if (pc)
+ class_dereference (&pc, MDL);
+
+ return 0;
+ }
+ data.terminated = 1;
+ data.data = &data.buffer -> data [0];
+ memcpy ((char *)data.buffer -> data, val,
+ data.len + 1);
+ } else if (token == NUMBER_OR_NAME || token == NUMBER) {
+ memset (&data, 0, sizeof data);
+ if (!parse_cshl (&data, cfile)) {
+ if (pc)
+ class_dereference (&pc, MDL);
+ return 0;
+ }
+ } else {
+ parse_warn (cfile, "Expecting string or hex list.");
+ if (pc)
+ class_dereference (&pc, MDL);
+ return 0;
+ }
+ }
+
+ /* See if there's already a class in the hash table matching the
+ hash data. */
+ if (type != CLASS_TYPE_CLASS)
+ class_hash_lookup (&class, pc -> hash,
+ (const char *)data.data, data.len, MDL);
+
+ /* If we didn't find an existing class, allocate a new one. */
+ if (!class) {
+ /* Allocate the class structure... */
+ status = class_allocate (&class, MDL);
+ if (pc) {
+ group_reference (&class -> group, pc -> group, MDL);
+ class_reference (&class -> superclass, pc, MDL);
+ class -> lease_limit = pc -> lease_limit;
+ if (class -> lease_limit) {
+ class -> billed_leases =
+ dmalloc (class -> lease_limit *
+ sizeof (struct lease *), MDL);
+ if (!class -> billed_leases)
+ log_fatal ("no memory for billing");
+ memset (class -> billed_leases, 0,
+ (class -> lease_limit *
+ sizeof class -> billed_leases));
+ }
+ data_string_copy (&class -> hash_string, &data, MDL);
+ if (!pc -> hash &&
+ !class_new_hash (&pc->hash, SCLASS_HASH_SIZE, MDL))
+ log_fatal ("No memory for subclass hash.");
+ class_hash_add (pc -> hash,
+ (const char *)class -> hash_string.data,
+ class -> hash_string.len,
+ (void *)class, MDL);
+ } else {
+ if (class->group)
+ group_dereference(&class->group, MDL);
+ if (!clone_group (&class -> group, group, MDL))
+ log_fatal ("no memory to clone class group.");
+ }
+
+ /* If this is an implicit vendor or user class, add a
+ statement that causes the vendor or user class ID to
+ be sent back in the reply. */
+ if (type == CLASS_TYPE_VENDOR || type == CLASS_TYPE_USER) {
+ stmt = (struct executable_statement *)0;
+ if (!executable_statement_allocate (&stmt, MDL))
+ log_fatal ("no memory for class statement.");
+ stmt -> op = supersede_option_statement;
+ if (option_cache_allocate (&stmt -> data.option,
+ MDL)) {
+ stmt -> data.option -> data = data;
+ code = (type == CLASS_TYPE_VENDOR)
+ ? DHO_VENDOR_CLASS_IDENTIFIER
+ : DHO_USER_CLASS;
+ option_code_hash_lookup(
+ &stmt->data.option->option,
+ dhcp_universe.code_hash,
+ &code, 0, MDL);
+ }
+ class -> statements = stmt;
+ }
+
+ /* Save the name, if there is one. */
+ if (class->name != NULL)
+ dfree(class->name, MDL);
+ class->name = name;
+ }
+
+ if (type != CLASS_TYPE_CLASS)
+ data_string_forget(&data, MDL);
+
+ /* Spawned classes don't have to have their own settings. */
+ if (class -> superclass) {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI) {
+ next_token (&val, (unsigned *)0, cfile);
+ if (cp)
+ status = class_reference (cp, class, MDL);
+ class_dereference (&class, MDL);
+ if (pc)
+ class_dereference (&pc, MDL);
+ return cp ? (status == ISC_R_SUCCESS) : 1;
+ }
+ /* Give the subclass its own group. */
+ if (!clone_group (&class -> group, class -> group, MDL))
+ log_fatal ("can't clone class group.");
+
+ }
+
+ if (!parse_lbrace (cfile)) {
+ class_dereference (&class, MDL);
+ if (pc)
+ class_dereference (&pc, MDL);
+ return 0;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ } else if (token == DYNAMIC) {
+ class->flags |= CLASS_DECL_DYNAMIC;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ } else if (token == TOKEN_DELETED) {
+ class->flags |= CLASS_DECL_DELETED;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ } else if (token == MATCH) {
+ if (pc) {
+ parse_warn (cfile,
+ "invalid match in subclass.");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token != IF)
+ goto submatch;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (matchedonce) {
+ parse_warn(cfile, "A class may only have "
+ "one 'match if' clause.");
+ skip_to_semi(cfile);
+ break;
+ }
+ matchedonce = 1;
+ if (class->expr)
+ expression_dereference(&class->expr, MDL);
+ if (!parse_boolean_expression (&class->expr, cfile,
+ &lose)) {
+ if (!lose) {
+ parse_warn (cfile,
+ "expecting boolean expr.");
+ skip_to_semi (cfile);
+ }
+ } else {
+#if defined (DEBUG_EXPRESSION_PARSE)
+ print_expression ("class match",
+ class -> expr);
+#endif
+ parse_semi (cfile);
+ }
+ } else if (token == SPAWN) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (pc) {
+ parse_warn (cfile,
+ "invalid spawn in subclass.");
+ skip_to_semi (cfile);
+ break;
+ }
+ class -> spawning = 1;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != WITH) {
+ parse_warn (cfile,
+ "expecting with after spawn");
+ skip_to_semi (cfile);
+ break;
+ }
+ submatch:
+ if (submatchedonce) {
+ parse_warn (cfile,
+ "can't override existing %s.",
+ "submatch/spawn");
+ skip_to_semi (cfile);
+ break;
+ }
+ submatchedonce = 1;
+ if (class->submatch)
+ expression_dereference(&class->submatch, MDL);
+ if (!parse_data_expression (&class -> submatch,
+ cfile, &lose)) {
+ if (!lose) {
+ parse_warn (cfile,
+ "expecting data expr.");
+ skip_to_semi (cfile);
+ }
+ } else {
+#if defined (DEBUG_EXPRESSION_PARSE)
+ print_expression ("class submatch",
+ class -> submatch);
+#endif
+ parse_semi (cfile);
+ }
+ } else if (token == LEASE) {
+ next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != LIMIT) {
+ parse_warn (cfile, "expecting \"limit\"");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NUMBER) {
+ parse_warn (cfile, "expecting a number");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ break;
+ }
+ class -> lease_limit = atoi (val);
+ if (class->billed_leases)
+ dfree(class->billed_leases, MDL);
+ class -> billed_leases =
+ dmalloc (class -> lease_limit *
+ sizeof (struct lease *), MDL);
+ if (!class -> billed_leases)
+ log_fatal ("no memory for billed leases.");
+ memset (class -> billed_leases, 0,
+ (class -> lease_limit *
+ sizeof class -> billed_leases));
+ have_billing_classes = 1;
+ parse_semi (cfile);
+ } else {
+ declaration = parse_statement (cfile, class -> group,
+ CLASS_DECL,
+ (struct host_decl *)0,
+ declaration);
+ }
+ } while (1);
+
+ if (class->flags & CLASS_DECL_DELETED) {
+ if (type == CLASS_TYPE_CLASS) {
+ struct class *theclass = NULL;
+
+ status = find_class(&theclass, class->name, MDL);
+ if (status == ISC_R_SUCCESS) {
+ delete_class(theclass, 0);
+ class_dereference(&theclass, MDL);
+ }
+ } else {
+ class_hash_delete(pc->hash,
+ (char *)class->hash_string.data,
+ class->hash_string.len, MDL);
+ }
+ } else if (type == CLASS_TYPE_CLASS && new) {
+ if (!collections -> classes)
+ class_reference (&collections -> classes, class, MDL);
+ else {
+ struct class *c;
+ for (c = collections -> classes;
+ c -> nic; c = c -> nic)
+ ;
+ class_reference (&c -> nic, class, MDL);
+ }
+ }
+
+ if (cp) /* should always be 0??? */
+ status = class_reference (cp, class, MDL);
+ class_dereference (&class, MDL);
+ if (pc)
+ class_dereference (&pc, MDL);
+ return cp ? (status == ISC_R_SUCCESS) : 1;
+}
+
+/* shared-network-declaration :==
+ hostname LBRACE declarations parameters RBRACE */
+
+void parse_shared_net_declaration (cfile, group)
+ struct parse *cfile;
+ struct group *group;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct shared_network *share;
+ char *name;
+ int declaration = 0;
+ isc_result_t status;
+
+ share = (struct shared_network *)0;
+ status = shared_network_allocate (&share, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Can't allocate shared subnet: %s",
+ isc_result_totext (status));
+ clone_group (&share -> group, group, MDL);
+ shared_network_reference (&share -> group -> shared_network,
+ share, MDL);
+
+ /* Get the name of the shared network... */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ token = next_token (&val, (unsigned *)0, cfile);
+
+ if (val [0] == 0) {
+ parse_warn (cfile, "zero-length shared network name");
+ val = "<no-name-given>";
+ }
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("no memory for shared network name");
+ strcpy (name, val);
+ } else {
+ name = parse_host_name (cfile);
+ if (!name) {
+ parse_warn (cfile,
+ "expecting a name for shared-network");
+ skip_to_semi (cfile);
+ shared_network_dereference (&share, MDL);
+ return;
+ }
+ }
+ share -> name = name;
+
+ if (!parse_lbrace (cfile)) {
+ shared_network_dereference (&share, MDL);
+ return;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!share -> subnets)
+ parse_warn (cfile,
+ "empty shared-network decl");
+ else
+ enter_shared_network (share);
+ shared_network_dereference (&share, MDL);
+ return;
+ } else if (token == END_OF_FILE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ } else if (token == INTERFACE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ token = next_token (&val, (unsigned *)0, cfile);
+ new_shared_network_interface (cfile, share, val);
+ if (!parse_semi (cfile))
+ break;
+ continue;
+ }
+
+ declaration = parse_statement (cfile, share -> group,
+ SHARED_NET_DECL,
+ (struct host_decl *)0,
+ declaration);
+ } while (1);
+ shared_network_dereference (&share, MDL);
+}
+
+
+static int
+common_subnet_parsing(struct parse *cfile,
+ struct shared_network *share,
+ struct subnet *subnet) {
+ enum dhcp_token token;
+ struct subnet *t, *u;
+ const char *val;
+ int declaration = 0;
+
+ enter_subnet(subnet);
+
+ if (!parse_lbrace(cfile)) {
+ subnet_dereference(&subnet, MDL);
+ return 0;
+ }
+
+ do {
+ token = peek_token(&val, NULL, cfile);
+ if (token == RBRACE) {
+ token = next_token(&val, NULL, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ token = next_token(&val, NULL, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ } else if (token == INTERFACE) {
+ token = next_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ new_shared_network_interface(cfile, share, val);
+ if (!parse_semi(cfile))
+ break;
+ continue;
+ }
+ declaration = parse_statement(cfile, subnet->group,
+ SUBNET_DECL,
+ NULL,
+ declaration);
+ } while (1);
+
+ /* Add the subnet to the list of subnets in this shared net. */
+ if (share->subnets == NULL) {
+ subnet_reference(&share->subnets, subnet, MDL);
+ } else {
+ u = NULL;
+ for (t = share->subnets; t->next_sibling; t = t->next_sibling) {
+ if (subnet_inner_than(subnet, t, 0)) {
+ subnet_reference(&subnet->next_sibling, t, MDL);
+ if (u) {
+ subnet_dereference(&u->next_sibling,
+ MDL);
+ subnet_reference(&u->next_sibling,
+ subnet, MDL);
+ } else {
+ subnet_dereference(&share->subnets,
+ MDL);
+ subnet_reference(&share->subnets,
+ subnet, MDL);
+ }
+ subnet_dereference(&subnet, MDL);
+ return 1;
+ }
+ u = t;
+ }
+ subnet_reference(&t->next_sibling, subnet, MDL);
+ }
+ subnet_dereference(&subnet, MDL);
+ return 1;
+}
+
+/* subnet-declaration :==
+ net NETMASK netmask RBRACE parameters declarations LBRACE */
+
+void parse_subnet_declaration (cfile, share)
+ struct parse *cfile;
+ struct shared_network *share;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct subnet *subnet;
+ struct iaddr iaddr;
+ unsigned char addr [4];
+ unsigned len = sizeof addr;
+ isc_result_t status;
+
+ subnet = (struct subnet *)0;
+ status = subnet_allocate (&subnet, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("Allocation of new subnet failed: %s",
+ isc_result_totext (status));
+ shared_network_reference (&subnet -> shared_network, share, MDL);
+
+ /*
+ * If our parent shared network was implicitly created by the software,
+ * and not explicitly configured by the user, then we actually put all
+ * configuration scope in the parent (the shared network and subnet
+ * share the same {}-level scope).
+ *
+ * Otherwise, we clone the parent group and continue as normal.
+ */
+ if (share->flags & SHARED_IMPLICIT) {
+ group_reference(&subnet->group, share->group, MDL);
+ } else {
+ if (!clone_group(&subnet->group, share->group, MDL)) {
+ log_fatal("Allocation of group for new subnet failed.");
+ }
+ }
+ subnet_reference (&subnet -> group -> subnet, subnet, MDL);
+
+ /* Get the network number... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8)) {
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+ memcpy (iaddr.iabuf, addr, len);
+ iaddr.len = len;
+ subnet -> net = iaddr;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NETMASK) {
+ parse_warn (cfile, "Expecting netmask");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ /* Get the netmask... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8)) {
+ subnet_dereference (&subnet, MDL);
+ return;
+ }
+ memcpy (iaddr.iabuf, addr, len);
+ iaddr.len = len;
+ subnet -> netmask = iaddr;
+
+ /* Validate the network number/netmask pair. */
+ if (host_addr (subnet -> net, subnet -> netmask)) {
+ char *maskstr;
+
+ maskstr = strdup (piaddr (subnet -> netmask));
+ parse_warn (cfile,
+ "subnet %s netmask %s: bad subnet number/mask combination.",
+ piaddr (subnet -> net), maskstr);
+ free(maskstr);
+ subnet_dereference (&subnet, MDL);
+ skip_to_semi (cfile);
+ return;
+ }
+
+ common_subnet_parsing(cfile, share, subnet);
+}
+
+/* subnet6-declaration :==
+ net / bits RBRACE parameters declarations LBRACE */
+
+void
+parse_subnet6_declaration(struct parse *cfile, struct shared_network *share) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ struct subnet *subnet;
+ isc_result_t status;
+ enum dhcp_token token;
+ const char *val;
+ char *endp;
+ int ofs;
+ const static int mask[] = { 0x00, 0x80, 0xC0, 0xE0,
+ 0xF0, 0xF8, 0xFC, 0xFE };
+ struct iaddr iaddr;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "subnet6 statement is only supported "
+ "in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ subnet = NULL;
+ status = subnet_allocate(&subnet, MDL);
+ if (status != ISC_R_SUCCESS) {
+ log_fatal("Allocation of new subnet failed: %s",
+ isc_result_totext(status));
+ }
+ shared_network_reference(&subnet->shared_network, share, MDL);
+
+ /*
+ * If our parent shared network was implicitly created by the software,
+ * and not explicitly configured by the user, then we actually put all
+ * configuration scope in the parent (the shared network and subnet
+ * share the same {}-level scope).
+ *
+ * Otherwise, we clone the parent group and continue as normal.
+ */
+ if (share->flags & SHARED_IMPLICIT) {
+ group_reference(&subnet->group, share->group, MDL);
+ } else {
+ if (!clone_group(&subnet->group, share->group, MDL)) {
+ log_fatal("Allocation of group for new subnet failed.");
+ }
+ }
+ subnet_reference(&subnet->group->subnet, subnet, MDL);
+
+ if (!parse_ip6_addr(cfile, &subnet->net)) {
+ subnet_dereference(&subnet, MDL);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SLASH) {
+ parse_warn(cfile, "Expecting a '/'.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "Expecting a number.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ subnet->prefix_len = strtol(val, &endp, 10);
+ if ((subnet->prefix_len < 0) ||
+ (subnet->prefix_len > 128) ||
+ (*endp != '\0')) {
+ parse_warn(cfile, "Expecting a number between 0 and 128.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!is_cidr_mask_valid(&subnet->net, subnet->prefix_len)) {
+ parse_warn(cfile, "New subnet mask too short.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Create a netmask.
+ */
+ subnet->netmask.len = 16;
+ ofs = subnet->prefix_len / 8;
+ if (ofs < subnet->netmask.len) {
+ subnet->netmask.iabuf[ofs] = mask[subnet->prefix_len % 8];
+ }
+ while (--ofs >= 0) {
+ subnet->netmask.iabuf[ofs] = 0xFF;
+ }
+
+ /* Validate the network number/netmask pair. */
+ iaddr = subnet_number(subnet->net, subnet->netmask);
+ if (memcmp(&iaddr, &subnet->net, 16) != 0) {
+ parse_warn(cfile,
+ "subnet %s/%d: prefix not long enough for address.",
+ piaddr(subnet->net), subnet->prefix_len);
+ subnet_dereference(&subnet, MDL);
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!common_subnet_parsing(cfile, share, subnet)) {
+ return;
+ }
+#endif /* defined(DHCPv6) */
+}
+
+/* group-declaration :== RBRACE parameters declarations LBRACE */
+
+void parse_group_declaration (cfile, group)
+ struct parse *cfile;
+ struct group *group;
+{
+ const char *val;
+ enum dhcp_token token;
+ struct group *g;
+ int declaration = 0;
+ struct group_object *t;
+ isc_result_t status;
+ char *name = NULL;
+ int deletedp = 0;
+ int dynamicp = 0;
+ int staticp = 0;
+
+ g = (struct group *)0;
+ if (!clone_group (&g, group, MDL))
+ log_fatal ("no memory for explicit group.");
+
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (is_identifier (token) || token == STRING) {
+ next_token (&val, (unsigned *)0, cfile);
+
+ name = dmalloc (strlen (val) + 1, MDL);
+ if (!name)
+ log_fatal ("no memory for group decl name %s", val);
+ strcpy (name, val);
+ }
+
+ if (!parse_lbrace (cfile)) {
+ group_dereference (&g, MDL);
+ return;
+ }
+
+ do {
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ break;
+ } else if (token == END_OF_FILE) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ } else if (token == TOKEN_DELETED) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_semi (cfile);
+ deletedp = 1;
+ } else if (token == DYNAMIC) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_semi (cfile);
+ dynamicp = 1;
+ } else if (token == STATIC) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ parse_semi (cfile);
+ staticp = 1;
+ }
+ declaration = parse_statement (cfile, g, GROUP_DECL,
+ (struct host_decl *)0,
+ declaration);
+ } while (1);
+
+ if (name) {
+ if (deletedp) {
+ if (group_name_hash) {
+ t = (struct group_object *)0;
+ if (group_hash_lookup (&t, group_name_hash,
+ name,
+ strlen (name), MDL)) {
+ delete_group (t, 0);
+ }
+ }
+ } else {
+ t = (struct group_object *)0;
+ status = group_object_allocate (&t, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("no memory for group decl %s: %s",
+ val, isc_result_totext (status));
+ group_reference (&t -> group, g, MDL);
+ t -> name = name;
+ t -> flags = ((staticp ? GROUP_OBJECT_STATIC : 0) |
+ (dynamicp ? GROUP_OBJECT_DYNAMIC : 0) |
+ (deletedp ? GROUP_OBJECT_DELETED : 0));
+ supersede_group (t, 0);
+ }
+ if (t)
+ group_object_dereference (&t, MDL);
+ }
+}
+
+/* fixed-addr-parameter :== ip-addrs-or-hostnames SEMI
+ ip-addrs-or-hostnames :== ip-addr-or-hostname
+ | ip-addrs-or-hostnames ip-addr-or-hostname */
+
+int
+parse_fixed_addr_param(struct option_cache **oc,
+ struct parse *cfile,
+ enum dhcp_token type) {
+ int parse_ok;
+ const char *val;
+ enum dhcp_token token;
+ struct expression *expr = NULL;
+ struct expression *tmp, *new;
+ int status;
+
+ do {
+ tmp = NULL;
+ if (type == FIXED_ADDR) {
+ parse_ok = parse_ip_addr_or_hostname(&tmp, cfile, 1);
+ } else {
+ /* INSIST(type == FIXED_ADDR6); */
+ parse_ok = parse_ip6_addr_expr(&tmp, cfile);
+ }
+ if (parse_ok) {
+ if (expr != NULL) {
+ new = NULL;
+ status = make_concat(&new, expr, tmp);
+ expression_dereference(&expr, MDL);
+ expression_dereference(&tmp, MDL);
+ if (!status) {
+ return 0;
+ }
+ expr = new;
+ } else {
+ expr = tmp;
+ }
+ } else {
+ if (expr != NULL) {
+ expression_dereference (&expr, MDL);
+ }
+ return 0;
+ }
+ token = peek_token(&val, NULL, cfile);
+ if (token == COMMA) {
+ token = next_token(&val, NULL, cfile);
+ }
+ } while (token == COMMA);
+
+ if (!parse_semi(cfile)) {
+ if (expr) {
+ expression_dereference (&expr, MDL);
+ }
+ return 0;
+ }
+
+ status = option_cache(oc, NULL, expr, NULL, MDL);
+ expression_dereference(&expr, MDL);
+ return status;
+}
+
+/* lease_declaration :== LEASE ip_address LBRACE lease_parameters RBRACE
+
+ lease_parameters :== <nil>
+ | lease_parameter
+ | lease_parameters lease_parameter
+
+ lease_parameter :== STARTS date
+ | ENDS date
+ | TIMESTAMP date
+ | HARDWARE hardware-parameter
+ | UID hex_numbers SEMI
+ | HOSTNAME hostname SEMI
+ | CLIENT_HOSTNAME hostname SEMI
+ | CLASS identifier SEMI
+ | DYNAMIC_BOOTP SEMI */
+
+int parse_lease_declaration (struct lease **lp, struct parse *cfile)
+{
+ const char *val;
+ enum dhcp_token token;
+ unsigned char addr [4];
+ unsigned len = sizeof addr;
+ int seenmask = 0;
+ int seenbit;
+ char tbuf [32];
+ struct lease *lease;
+ struct executable_statement *on;
+ int lose;
+ TIME t;
+ int noequal, newbinding;
+ struct binding *binding;
+ struct binding_value *nv;
+ isc_result_t status;
+ struct option_cache *oc;
+ pair *p;
+ binding_state_t new_state;
+ unsigned buflen = 0;
+ struct class *class;
+
+ lease = (struct lease *)0;
+ status = lease_allocate (&lease, MDL);
+ if (status != ISC_R_SUCCESS)
+ return 0;
+
+ /* Get the address for which the lease has been issued. */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8)) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ memcpy (lease -> ip_addr.iabuf, addr, len);
+ lease -> ip_addr.len = len;
+
+ if (!parse_lbrace (cfile)) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+
+ do {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == RBRACE)
+ break;
+ else if (token == END_OF_FILE) {
+ parse_warn (cfile, "unexpected end of file");
+ break;
+ }
+ strncpy (tbuf, val, sizeof tbuf);
+ tbuf [(sizeof tbuf) - 1] = 0;
+
+ /* Parse any of the times associated with the lease. */
+ switch (token) {
+ case STARTS:
+ case ENDS:
+ case TIMESTAMP:
+ case TSTP:
+ case TSFP:
+ case ATSFP:
+ case CLTT:
+ t = parse_date (cfile);
+ switch (token) {
+ case STARTS:
+ seenbit = 1;
+ lease -> starts = t;
+ break;
+
+ case ENDS:
+ seenbit = 2;
+ lease -> ends = t;
+ break;
+
+ case TSTP:
+ seenbit = 65536;
+ lease -> tstp = t;
+ break;
+
+ case TSFP:
+ seenbit = 131072;
+ lease -> tsfp = t;
+ break;
+
+ case ATSFP:
+ seenbit = 262144;
+ lease->atsfp = t;
+ break;
+
+ case CLTT:
+ seenbit = 524288;
+ lease -> cltt = t;
+ break;
+
+ default: /* for gcc, we'll never get here. */
+ log_fatal ("Impossible error at %s:%d.", MDL);
+ return 0;
+ }
+ break;
+
+ /* Colon-separated hexadecimal octets... */
+ case UID:
+ seenbit = 8;
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ unsigned char *tuid;
+ token = next_token (&val, &buflen, cfile);
+ if (buflen < sizeof lease -> uid_buf) {
+ tuid = lease -> uid_buf;
+ lease -> uid_max =
+ sizeof lease -> uid_buf;
+ } else {
+ tuid = ((unsigned char *)
+ dmalloc (buflen, MDL));
+ if (!tuid) {
+ log_error ("no space for uid");
+ lease_dereference (&lease,
+ MDL);
+ return 0;
+ }
+ lease -> uid_max = buflen;
+ }
+ lease -> uid_len = buflen;
+ memcpy (tuid, val, lease -> uid_len);
+ lease -> uid = tuid;
+ } else {
+ buflen = 0;
+ lease -> uid = (parse_numeric_aggregate
+ (cfile, (unsigned char *)0,
+ &buflen, ':', 16, 8));
+ if (!lease -> uid) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ lease -> uid_len = buflen;
+ lease -> uid_max = buflen;
+ if (lease -> uid_len == 0) {
+ lease -> uid = (unsigned char *)0;
+ parse_warn (cfile, "zero-length uid");
+ seenbit = 0;
+ parse_semi (cfile);
+ break;
+ }
+ }
+ parse_semi (cfile);
+ if (!lease -> uid) {
+ log_fatal ("No memory for lease uid");
+ }
+ break;
+
+ case CLASS:
+ seenbit = 32;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (!is_identifier (token)) {
+ if (token != SEMI)
+ skip_to_rbrace (cfile, 1);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ parse_semi (cfile);
+ /* for now, we aren't using this. */
+ break;
+
+ case HARDWARE:
+ seenbit = 64;
+ parse_hardware_param (cfile,
+ &lease -> hardware_addr);
+ break;
+
+ case TOKEN_RESERVED:
+ seenbit = 0;
+ lease->flags |= RESERVED_LEASE;
+ parse_semi(cfile);
+ break;
+
+ case DYNAMIC_BOOTP:
+ seenbit = 0;
+ lease -> flags |= BOOTP_LEASE;
+ parse_semi (cfile);
+ break;
+
+ /* XXX: Reverse compatibility? */
+ case TOKEN_ABANDONED:
+ seenbit = 256;
+ lease -> binding_state = FTS_ABANDONED;
+ lease -> next_binding_state = FTS_ABANDONED;
+ parse_semi (cfile);
+ break;
+
+ case TOKEN_NEXT:
+ seenbit = 128;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != BINDING) {
+ parse_warn (cfile, "expecting 'binding'");
+ skip_to_semi (cfile);
+ break;
+ }
+ goto do_binding_state;
+
+ case REWIND:
+ seenbit = 512;
+ token = next_token(&val, NULL, cfile);
+ if (token != BINDING) {
+ parse_warn(cfile, "expecting 'binding'");
+ skip_to_semi(cfile);
+ break;
+ }
+ goto do_binding_state;
+
+ case BINDING:
+ seenbit = 256;
+
+ do_binding_state:
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != STATE) {
+ parse_warn (cfile, "expecting 'state'");
+ skip_to_semi (cfile);
+ break;
+ }
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ new_state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ new_state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ new_state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ new_state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ new_state = FTS_RELEASED;
+ break;
+ case TOKEN_RESET:
+ new_state = FTS_RESET;
+ break;
+ case TOKEN_BACKUP:
+ new_state = FTS_BACKUP;
+ break;
+
+ /* RESERVED and BOOTP states preserved for
+ * compatibleness with older versions.
+ */
+ case TOKEN_RESERVED:
+ new_state = FTS_ACTIVE;
+ lease->flags |= RESERVED_LEASE;
+ break;
+ case TOKEN_BOOTP:
+ new_state = FTS_ACTIVE;
+ lease->flags |= BOOTP_LEASE;
+ break;
+
+ default:
+ parse_warn (cfile,
+ "%s: expecting a binding state.",
+ val);
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ if (seenbit == 256) {
+ lease -> binding_state = new_state;
+
+ /*
+ * Apply default/conservative next/rewind
+ * binding states if they haven't been set
+ * yet. These defaults will be over-ridden if
+ * they are set later in parsing.
+ */
+ if (!(seenmask & 128))
+ lease->next_binding_state = new_state;
+
+ /* The most conservative rewind state. */
+ if (!(seenmask & 512))
+ lease->rewind_binding_state = new_state;
+ } else if (seenbit == 128)
+ lease -> next_binding_state = new_state;
+ else if (seenbit == 512)
+ lease->rewind_binding_state = new_state;
+ else
+ log_fatal("Impossible condition at %s:%d.",
+ MDL);
+
+ parse_semi (cfile);
+ break;
+
+ case CLIENT_HOSTNAME:
+ seenbit = 1024;
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == STRING) {
+ if (!parse_string (cfile,
+ &lease -> client_hostname,
+ (unsigned *)0)) {
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ } else {
+ lease -> client_hostname =
+ parse_host_name (cfile);
+ if (lease -> client_hostname)
+ parse_semi (cfile);
+ else {
+ parse_warn (cfile,
+ "expecting a hostname.");
+ skip_to_semi (cfile);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ }
+ break;
+
+ case BILLING:
+ seenbit = 2048;
+ class = (struct class *)0;
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token == CLASS) {
+ token = next_token (&val,
+ (unsigned *)0, cfile);
+ if (token != STRING) {
+ parse_warn (cfile, "expecting string");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ token = BILLING;
+ break;
+ }
+ if (lease -> billing_class)
+ class_dereference (&lease -> billing_class,
+ MDL);
+ find_class (&class, val, MDL);
+ if (!class)
+ parse_warn (cfile,
+ "unknown class %s", val);
+ parse_semi (cfile);
+ } else if (token == SUBCLASS) {
+ if (lease -> billing_class)
+ class_dereference (&lease -> billing_class,
+ MDL);
+ parse_class_declaration(&class, cfile, NULL,
+ CLASS_TYPE_SUBCLASS);
+ } else {
+ parse_warn (cfile, "expecting \"class\"");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ }
+ if (class) {
+ class_reference (&lease -> billing_class,
+ class, MDL);
+ class_dereference (&class, MDL);
+ }
+ break;
+
+ case ON:
+ on = (struct executable_statement *)0;
+ lose = 0;
+ if (!parse_on_statement (&on, cfile, &lose)) {
+ skip_to_rbrace (cfile, 1);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+ seenbit = 0;
+ if ((on -> data.on.evtypes & ON_EXPIRY) &&
+ on -> data.on.statements) {
+ seenbit |= 16384;
+ executable_statement_reference
+ (&lease -> on_expiry,
+ on -> data.on.statements, MDL);
+ }
+ if ((on -> data.on.evtypes & ON_RELEASE) &&
+ on -> data.on.statements) {
+ seenbit |= 32768;
+ executable_statement_reference
+ (&lease -> on_release,
+ on -> data.on.statements, MDL);
+ }
+ executable_statement_dereference (&on, MDL);
+ break;
+
+ case OPTION:
+ case SUPERSEDE:
+ noequal = 0;
+ seenbit = 0;
+ oc = (struct option_cache *)0;
+ if (parse_option_decl (&oc, cfile)) {
+ if (oc -> option -> universe !=
+ &agent_universe) {
+ parse_warn (cfile,
+ "agent option expected.");
+ option_cache_dereference (&oc, MDL);
+ break;
+ }
+ if (!lease -> agent_options &&
+ !(option_chain_head_allocate
+ (&lease -> agent_options, MDL))) {
+ log_error ("no memory to stash agent option");
+ break;
+ }
+ for (p = &lease -> agent_options -> first;
+ *p; p = &((*p) -> cdr))
+ ;
+ *p = cons (0, 0);
+ option_cache_reference (((struct option_cache **)
+ &((*p) -> car)), oc, MDL);
+ option_cache_dereference (&oc, MDL);
+ }
+ break;
+
+ case TOKEN_SET:
+ noequal = 0;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != NAME && token != NUMBER_OR_NAME) {
+ parse_warn (cfile,
+ "%s can't be a variable name",
+ val);
+ badset:
+ skip_to_semi (cfile);
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+
+ seenbit = 0;
+ special_set:
+ if (lease -> scope)
+ binding = find_binding (lease -> scope, val);
+ else
+ binding = (struct binding *)0;
+
+ if (!binding) {
+ if (!lease -> scope)
+ if (!(binding_scope_allocate
+ (&lease -> scope, MDL)))
+ log_fatal ("no memory for scope");
+ binding = dmalloc (sizeof *binding, MDL);
+ if (!binding)
+ log_fatal ("No memory for lease %s.",
+ "binding");
+ memset (binding, 0, sizeof *binding);
+ binding -> name =
+ dmalloc (strlen (val) + 1, MDL);
+ if (!binding -> name)
+ log_fatal ("No memory for binding %s.",
+ "name");
+ strcpy (binding -> name, val);
+ newbinding = 1;
+ } else {
+ newbinding = 0;
+ }
+
+ nv = NULL;
+ if (!binding_value_allocate(&nv, MDL))
+ log_fatal("no memory for binding value.");
+
+ if (!noequal) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != EQUAL) {
+ parse_warn (cfile,
+ "expecting '=' in set statement.");
+ goto badset;
+ }
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_value_dereference(&nv, MDL);
+ lease_dereference(&lease, MDL);
+ return 0;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&binding->value,
+ nv, MDL);
+ binding->next = lease->scope->bindings;
+ lease->scope->bindings = binding;
+ } else {
+ binding_value_dereference(&binding->value, MDL);
+ binding_value_reference(&binding->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ /* case NAME: */
+ default:
+ if (!strcasecmp (val, "ddns-fwd-name")) {
+ seenbit = 4096;
+ noequal = 1;
+ goto special_set;
+ } else if (!strcasecmp (val, "ddns-rev-name")) {
+ seenbit = 8192;
+ noequal = 1;
+ goto special_set;
+ } else
+ parse_warn(cfile, "Unexpected configuration "
+ "directive.");
+ skip_to_semi (cfile);
+ seenbit = 0;
+ lease_dereference (&lease, MDL);
+ return 0;
+ }
+
+ if (seenmask & seenbit) {
+ parse_warn (cfile,
+ "Too many %s parameters in lease %s\n",
+ tbuf, piaddr (lease -> ip_addr));
+ } else
+ seenmask |= seenbit;
+
+ } while (1);
+
+ /* If no binding state is specified, make one up. */
+ if (!(seenmask & 256)) {
+ if (lease -> ends > cur_time ||
+ lease -> on_expiry || lease -> on_release)
+ lease -> binding_state = FTS_ACTIVE;
+#if defined (FAILOVER_PROTOCOL)
+ else if (lease -> pool && lease -> pool -> failover_peer)
+ lease -> binding_state = FTS_EXPIRED;
+#endif
+ else
+ lease -> binding_state = FTS_FREE;
+ if (lease -> binding_state == FTS_ACTIVE) {
+#if defined (FAILOVER_PROTOCOL)
+ if (lease -> pool && lease -> pool -> failover_peer)
+ lease -> next_binding_state = FTS_EXPIRED;
+ else
+#endif
+ lease -> next_binding_state = FTS_FREE;
+ } else
+ lease -> next_binding_state = lease -> binding_state;
+
+ /* The most conservative rewind state implies no rewind. */
+ lease->rewind_binding_state = lease->binding_state;
+ }
+
+ if (!(seenmask & 65536))
+ lease -> tstp = lease -> ends;
+
+ lease_reference (lp, lease, MDL);
+ lease_dereference (&lease, MDL);
+ return 1;
+}
+
+/* Parse the right side of a 'binding value'.
+ *
+ * set foo = "bar"; is a string
+ * set foo = false; is a boolean
+ * set foo = %31; is a numeric value.
+ */
+static int
+parse_binding_value(struct parse *cfile, struct binding_value *value)
+{
+ struct data_string *data;
+ unsigned char *s;
+ const char *val;
+ unsigned buflen;
+ int token;
+
+ if ((cfile == NULL) || (value == NULL))
+ log_fatal("Invalid arguments at %s:%d.", MDL);
+
+ token = peek_token(&val, NULL, cfile);
+ if (token == STRING) {
+ token = next_token(&val, &buflen, cfile);
+
+ value->type = binding_data;
+ value->value.data.len = buflen;
+
+ data = &value->value.data;
+
+ if (!buffer_allocate(&data->buffer, buflen + 1, MDL))
+ log_fatal ("No memory for binding.");
+
+ memcpy(data->buffer->data, val, buflen + 1);
+
+ data->data = data->buffer->data;
+ data->terminated = 1;
+ } else if (token == NUMBER_OR_NAME) {
+ value->type = binding_data;
+
+ data = &value->value.data;
+ s = parse_numeric_aggregate(cfile, NULL, &data->len,
+ ':', 16, 8);
+ if (s == NULL) {
+ skip_to_semi(cfile);
+ return 0;
+ }
+
+ if (data->len) {
+ if (!buffer_allocate(&data->buffer, data->len + 1,
+ MDL))
+ log_fatal("No memory for binding.");
+
+ memcpy(data->buffer->data, s, data->len);
+ data->data = data->buffer->data;
+
+ dfree (s, MDL);
+ }
+ } else if (token == PERCENT) {
+ token = next_token(&val, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting decimal number.");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return 0;
+ }
+ value->type = binding_numeric;
+ value->value.intval = atol(val);
+ } else if (token == NAME) {
+ token = next_token(&val, NULL, cfile);
+ value->type = binding_boolean;
+ if (!strcasecmp(val, "true"))
+ value->value.boolean = 1;
+ else if (!strcasecmp(val, "false"))
+ value->value.boolean = 0;
+ else {
+ parse_warn(cfile, "expecting true or false");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return 0;
+ }
+ } else {
+ parse_warn (cfile, "expecting a constant value.");
+ if (token != SEMI)
+ skip_to_semi (cfile);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* address-range-declaration :== ip-address ip-address SEMI
+ | DYNAMIC_BOOTP ip-address ip-address SEMI */
+
+void parse_address_range (cfile, group, type, inpool, lpchain)
+ struct parse *cfile;
+ struct group *group;
+ int type;
+ struct pool *inpool;
+ struct lease **lpchain;
+{
+ struct iaddr low, high, net;
+ unsigned char addr [4];
+ unsigned len = sizeof addr;
+ enum dhcp_token token;
+ const char *val;
+ int dynamic = 0;
+ struct subnet *subnet;
+ struct shared_network *share;
+ struct pool *pool;
+ isc_result_t status;
+
+ if ((token = peek_token (&val,
+ (unsigned *)0, cfile)) == DYNAMIC_BOOTP) {
+ token = next_token (&val, (unsigned *)0, cfile);
+ dynamic = 1;
+ }
+
+ /* Get the bottom address in the range... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8))
+ return;
+ memcpy (low.iabuf, addr, len);
+ low.len = len;
+
+ /* Only one address? */
+ token = peek_token (&val, (unsigned *)0, cfile);
+ if (token == SEMI)
+ high = low;
+ else {
+ /* Get the top address in the range... */
+ if (!parse_numeric_aggregate (cfile, addr, &len, DOT, 10, 8))
+ return;
+ memcpy (high.iabuf, addr, len);
+ high.len = len;
+ }
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ if (token != SEMI) {
+ parse_warn (cfile, "semicolon expected.");
+ skip_to_semi (cfile);
+ return;
+ }
+
+ if (type == SUBNET_DECL) {
+ subnet = group -> subnet;
+ share = subnet -> shared_network;
+ } else {
+ share = group -> shared_network;
+ for (subnet = share -> subnets;
+ subnet; subnet = subnet -> next_sibling) {
+ net = subnet_number (low, subnet -> netmask);
+ if (addr_eq (net, subnet -> net))
+ break;
+ }
+ if (!subnet) {
+ parse_warn (cfile, "address range not on network %s",
+ group -> shared_network -> name);
+ log_error ("Be sure to place pool statement after %s",
+ "related subnet declarations.");
+ return;
+ }
+ }
+
+ if (!inpool) {
+ struct pool *last = (struct pool *)0;
+
+ /* If we're permitting dynamic bootp for this range,
+ then look for a pool with an empty prohibit list and
+ a permit list with one entry that permits all clients. */
+ for (pool = share -> pools; pool; pool = pool -> next) {
+ if ((!dynamic && !pool -> permit_list &&
+ pool -> prohibit_list &&
+ !pool -> prohibit_list -> next &&
+ (pool -> prohibit_list -> type ==
+ permit_dynamic_bootp_clients)) ||
+ (dynamic && !pool -> prohibit_list &&
+ pool -> permit_list &&
+ !pool -> permit_list -> next &&
+ (pool -> permit_list -> type ==
+ permit_all_clients))) {
+ break;
+ }
+ last = pool;
+ }
+
+ /* If we didn't get a pool, make one. */
+ if (!pool) {
+ struct permit *p;
+ status = pool_allocate (&pool, MDL);
+ if (status != ISC_R_SUCCESS)
+ log_fatal ("no memory for ad-hoc pool: %s",
+ isc_result_totext (status));
+ p = new_permit (MDL);
+ if (!p)
+ log_fatal ("no memory for ad-hoc permit.");
+
+ /* Dynamic pools permit all clients. Otherwise
+ we prohibit BOOTP clients. */
+ if (dynamic) {
+ p -> type = permit_all_clients;
+ pool -> permit_list = p;
+ } else {
+ p -> type = permit_dynamic_bootp_clients;
+ pool -> prohibit_list = p;
+ }
+
+ if (share -> pools)
+ pool_reference (&last -> next, pool, MDL);
+ else
+ pool_reference (&share -> pools, pool, MDL);
+ shared_network_reference (&pool -> shared_network,
+ share, MDL);
+ if (!clone_group (&pool -> group, share -> group, MDL))
+ log_fatal ("no memory for anon pool group.");
+ } else {
+ pool = (struct pool *)0;
+ if (last)
+ pool_reference (&pool, last, MDL);
+ else
+ pool_reference (&pool, share -> pools, MDL);
+ }
+ } else {
+ pool = (struct pool *)0;
+ pool_reference (&pool, inpool, MDL);
+ }
+
+#if defined (FAILOVER_PROTOCOL)
+ if (pool -> failover_peer && dynamic) {
+ /* Doctor, do you think I'm overly sensitive
+ about getting bug reports I can't fix? */
+ parse_warn (cfile, "dynamic-bootp flag is %s",
+ "not permitted for address");
+ log_error ("range declarations where there is a failover");
+ log_error ("peer in scope. If you wish to declare an");
+ log_error ("address range from which dynamic bootp leases");
+ log_error ("can be allocated, please declare it within a");
+ log_error ("pool declaration that also contains the \"no");
+ log_error ("failover\" statement. The failover protocol");
+ log_error ("itself does not permit dynamic bootp - this");
+ log_error ("is not a limitation specific to the ISC DHCP");
+ log_error ("server. Please don't ask me to defend this");
+ log_error ("until you have read and really tried %s",
+ "to understand");
+ log_error ("the failover protocol specification.");
+
+ /* We don't actually bomb at this point - instead,
+ we let parse_lease_file notice the error and
+ bomb at that point - it's easier. */
+ }
+#endif /* FAILOVER_PROTOCOL */
+
+ /* Create the new address range... */
+ new_address_range (cfile, low, high, subnet, pool, lpchain);
+ pool_dereference (&pool, MDL);
+}
+
+#ifdef DHCPv6
+static void
+add_ipv6_pool_to_subnet(struct subnet *subnet, u_int16_t type,
+ struct iaddr *lo_addr, int bits, int units) {
+ struct ipv6_pool *pool;
+ struct shared_network *share;
+ struct in6_addr tmp_in6_addr;
+ int num_pools;
+ struct ipv6_pool **tmp;
+
+ share = subnet->shared_network;
+
+ /*
+ * Create our pool.
+ */
+ if (lo_addr->len != sizeof(tmp_in6_addr)) {
+ log_fatal("Internal error: Attempt to add non-IPv6 address "
+ "to IPv6 shared network.");
+ }
+ memcpy(&tmp_in6_addr, lo_addr->iabuf, sizeof(tmp_in6_addr));
+ pool = NULL;
+ if (ipv6_pool_allocate(&pool, type, &tmp_in6_addr,
+ bits, units, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory");
+ }
+
+ /*
+ * Add to our global IPv6 pool set.
+ */
+ if (add_ipv6_pool(pool) != ISC_R_SUCCESS) {
+ log_fatal ("Out of memory");
+ }
+
+ /*
+ * Link the pool to its network.
+ */
+ pool->subnet = NULL;
+ subnet_reference(&pool->subnet, subnet, MDL);
+ pool->shared_network = NULL;
+ shared_network_reference(&pool->shared_network, share, MDL);
+
+ /*
+ * Increase our array size for ipv6_pools in the shared_network.
+ */
+ if (share->ipv6_pools == NULL) {
+ num_pools = 0;
+ } else {
+ num_pools = 0;
+ while (share->ipv6_pools[num_pools] != NULL) {
+ num_pools++;
+ }
+ }
+ tmp = dmalloc(sizeof(struct ipv6_pool *) * (num_pools + 2), MDL);
+ if (tmp == NULL) {
+ log_fatal("Out of memory");
+ }
+ if (num_pools > 0) {
+ memcpy(tmp, share->ipv6_pools,
+ sizeof(struct ipv6_pool *) * num_pools);
+ }
+ if (share->ipv6_pools != NULL) {
+ dfree(share->ipv6_pools, MDL);
+ }
+ share->ipv6_pools = tmp;
+
+ /*
+ * Record this pool in our array of pools for this shared network.
+ */
+ ipv6_pool_reference(&share->ipv6_pools[num_pools], pool, MDL);
+ share->ipv6_pools[num_pools+1] = NULL;
+}
+
+/* address-range6-declaration :== ip-address6 ip-address6 SEMI
+ | ip-address6 SLASH number SEMI
+ | ip-address6 [SLASH number] TEMPORARY SEMI */
+
+void
+parse_address_range6(struct parse *cfile, struct group *group) {
+ struct iaddr lo, hi;
+ int bits;
+ enum dhcp_token token;
+ const char *val;
+ struct iaddrcidrnetlist *nets;
+ struct iaddrcidrnetlist *p;
+ u_int16_t type = D6O_IA_NA;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "range6 statement is only supported "
+ "in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /* This is enforced by the caller, this is just a sanity check. */
+ if (group->subnet == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /*
+ * Read starting address.
+ */
+ if (!parse_ip6_addr(cfile, &lo)) {
+ return;
+ }
+
+ /*
+ * See if we we're using range or CIDR notation or TEMPORARY
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SLASH) {
+ /*
+ * '/' means CIDR notation, so read the bits we want.
+ */
+ next_token(NULL, NULL, cfile);
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting number");
+ skip_to_semi(cfile);
+ return;
+ }
+ bits = atoi(val);
+ if ((bits < 0) || (bits > 128)) {
+ parse_warn(cfile, "networks have 0 to 128 bits");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (!is_cidr_mask_valid(&lo, bits)) {
+ parse_warn(cfile, "network mask too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * can be temporary (RFC 4941 like)
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == TEMPORARY) {
+ if (bits < 64)
+ parse_warn(cfile, "temporary mask too short");
+ if (bits == 128)
+ parse_warn(cfile, "temporary singleton?");
+ token = next_token(NULL, NULL, cfile);
+ type = D6O_IA_TA;
+ }
+
+ add_ipv6_pool_to_subnet(group->subnet, type, &lo,
+ bits, 128);
+
+ } else if (token == TEMPORARY) {
+ /*
+ * temporary (RFC 4941)
+ */
+ type = D6O_IA_TA;
+ next_token(NULL, NULL, cfile);
+ bits = 64;
+ if (!is_cidr_mask_valid(&lo, bits)) {
+ parse_warn(cfile, "network mask too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ add_ipv6_pool_to_subnet(group->subnet, type, &lo,
+ bits, 128);
+ } else {
+ /*
+ * No '/', so we are looking for the end address of
+ * the IPv6 pool.
+ */
+ if (!parse_ip6_addr(cfile, &hi)) {
+ return;
+ }
+
+ /*
+ * Convert our range to a set of CIDR networks.
+ */
+ nets = NULL;
+ if (range2cidr(&nets, &lo, &hi) != ISC_R_SUCCESS) {
+ log_fatal("Error converting range to CIDR networks");
+ }
+
+ for (p=nets; p != NULL; p=p->next) {
+ add_ipv6_pool_to_subnet(group->subnet, type,
+ &p->cidrnet.lo_addr,
+ p->cidrnet.bits, 128);
+ }
+
+ free_iaddrcidrnetlist(&nets);
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "semicolon expected.");
+ skip_to_semi(cfile);
+ return;
+ }
+}
+
+/* prefix6-declaration :== ip-address6 ip-address6 SLASH number SEMI */
+
+void
+parse_prefix6(struct parse *cfile, struct group *group) {
+ struct iaddr lo, hi;
+ int bits;
+ enum dhcp_token token;
+ const char *val;
+ struct iaddrcidrnetlist *nets;
+ struct iaddrcidrnetlist *p;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "prefix6 statement is only supported "
+ "in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /* This is enforced by the caller, so it's just a sanity check. */
+ if (group->subnet == NULL)
+ log_fatal("Impossible condition at %s:%d.", MDL);
+
+ /*
+ * Read starting and ending address.
+ */
+ if (!parse_ip6_addr(cfile, &lo)) {
+ return;
+ }
+ if (!parse_ip6_addr(cfile, &hi)) {
+ return;
+ }
+
+ /*
+ * Next is '/' number ';'.
+ */
+ token = next_token(NULL, NULL, cfile);
+ if (token != SLASH) {
+ parse_warn(cfile, "expecting '/'");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "expecting number");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ bits = atoi(val);
+ if ((bits <= 0) || (bits >= 128)) {
+ parse_warn(cfile, "networks have 0 to 128 bits (exclusive)");
+ return;
+ }
+ if (!is_cidr_mask_valid(&lo, bits) ||
+ !is_cidr_mask_valid(&hi, bits)) {
+ parse_warn(cfile, "network mask too short");
+ return;
+ }
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "semicolon expected.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Convert our range to a set of CIDR networks.
+ */
+ nets = NULL;
+ if (range2cidr(&nets, &lo, &hi) != ISC_R_SUCCESS) {
+ log_fatal("Error converting prefix to CIDR");
+ }
+
+ for (p = nets; p != NULL; p = p->next) {
+ /* Normalize and check. */
+ if (p->cidrnet.bits == 128) {
+ p->cidrnet.bits = bits;
+ }
+ if (p->cidrnet.bits > bits) {
+ parse_warn(cfile, "impossible mask length");
+ continue;
+ }
+ add_ipv6_pool_to_subnet(group->subnet, D6O_IA_PD,
+ &p->cidrnet.lo_addr,
+ p->cidrnet.bits, bits);
+ }
+
+ free_iaddrcidrnetlist(&nets);
+}
+
+/* fixed-prefix6 :== ip6-address SLASH number SEMI */
+
+void
+parse_fixed_prefix6(struct parse *cfile, struct host_decl *host_decl) {
+ struct iaddrcidrnetlist *ia, **h;
+ enum dhcp_token token;
+ const char *val;
+
+ /*
+ * Get the head of the fixed-prefix list.
+ */
+ h = &host_decl->fixed_prefix;
+
+ /*
+ * Walk to the end.
+ */
+ while (*h != NULL) {
+ h = &((*h)->next);
+ }
+
+ /*
+ * Allocate a new iaddrcidrnetlist structure.
+ */
+ ia = dmalloc(sizeof(*ia), MDL);
+ if (!ia) {
+ log_fatal("Out of memory");
+ }
+
+ /*
+ * Parse it.
+ */
+ if (!parse_ip6_addr(cfile, &ia->cidrnet.lo_addr)) {
+ dfree(ia, MDL);
+ return;
+ }
+ token = next_token(NULL, NULL, cfile);
+ if (token != SLASH) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "expecting '/'");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "expecting number");
+ if (token != SEMI)
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "semicolon expected.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Fill it.
+ */
+ ia->cidrnet.bits = atoi(val);
+ if ((ia->cidrnet.bits < 0) || (ia->cidrnet.bits > 128)) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "networks have 0 to 128 bits");
+ return;
+ }
+ if (!is_cidr_mask_valid(&ia->cidrnet.lo_addr, ia->cidrnet.bits)) {
+ dfree(ia, MDL);
+ parse_warn(cfile, "network mask too short");
+ return;
+ }
+
+ /*
+ * Store it.
+ */
+ *h = ia;
+ return;
+}
+#endif /* DHCPv6 */
+
+/* allow-deny-keyword :== BOOTP
+ | BOOTING
+ | DYNAMIC_BOOTP
+ | UNKNOWN_CLIENTS */
+
+int parse_allow_deny (oc, cfile, flag)
+ struct option_cache **oc;
+ struct parse *cfile;
+ int flag;
+{
+ enum dhcp_token token;
+ const char *val;
+ unsigned char rf = flag;
+ unsigned code;
+ struct option *option = NULL;
+ struct expression *data = (struct expression *)0;
+ int status;
+
+ if (!make_const_data (&data, &rf, 1, 0, 1, MDL))
+ return 0;
+
+ token = next_token (&val, (unsigned *)0, cfile);
+ switch (token) {
+ case TOKEN_BOOTP:
+ code = SV_ALLOW_BOOTP;
+ break;
+
+ case BOOTING:
+ code = SV_ALLOW_BOOTING;
+ break;
+
+ case DYNAMIC_BOOTP:
+ code = SV_DYNAMIC_BOOTP;
+ break;
+
+ case UNKNOWN_CLIENTS:
+ code = SV_BOOT_UNKNOWN_CLIENTS;
+ break;
+
+ case DUPLICATES:
+ code = SV_DUPLICATES;
+ break;
+
+ case DECLINES:
+ code= SV_DECLINES;
+ break;
+
+ case CLIENT_UPDATES:
+ code = SV_CLIENT_UPDATES;
+ break;
+
+ case LEASEQUERY:
+ code = SV_LEASEQUERY;
+ break;
+
+ default:
+ parse_warn (cfile, "expecting allow/deny key");
+ skip_to_semi (cfile);
+ return 0;
+ }
+ /* Reference on option is passed to option cache. */
+ if (!option_code_hash_lookup(&option, server_universe.code_hash,
+ &code, 0, MDL))
+ log_fatal("Unable to find server option %u (%s:%d).",
+ code, MDL);
+ status = option_cache(oc, NULL, data, option, MDL);
+ expression_dereference (&data, MDL);
+ parse_semi (cfile);
+ return status;
+}
+
+void
+parse_ia_na_declaration(struct parse *cfile) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ enum dhcp_token token;
+ struct ia_xx *ia;
+ const char *val;
+ struct ia_xx *old_ia;
+ unsigned int len;
+ u_int32_t iaid;
+ struct iaddr iaddr;
+ binding_state_t state;
+ u_int32_t prefer;
+ u_int32_t valid;
+ TIME end_time;
+ struct iasubopt *iaaddr;
+ struct ipv6_pool *pool;
+ char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ isc_boolean_t newbinding;
+ struct binding_scope *scope=NULL;
+ struct binding *bnd;
+ struct binding_value *nv=NULL;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "IA_NA is only supported in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting an iaid+ia_na string");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (len < 5) {
+ parse_warn(cfile, "corrupt lease file; "
+ "iaid+ia_na string too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memcpy(&iaid, val, 4);
+ ia = NULL;
+ if (ia_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ ia->ia_type = D6O_IA_NA;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ if (token == CLTT) {
+ ia->cltt = parse_date (cfile);
+ continue;
+ }
+
+ if (token != IAADDR) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IAADDR or right brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!parse_ip6_addr(cfile, &iaddr)) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IPv6 address");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ state = FTS_LAST+1;
+ prefer = valid = 0;
+ end_time = -1;
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ switch(token) {
+ /* Lease binding state. */
+ case BINDING:
+ token = next_token(&val, NULL, cfile);
+ if (token != STATE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting state");
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ state = FTS_RELEASED;
+ break;
+ default:
+ parse_warn(cfile,
+ "corrupt lease "
+ "file; "
+ "expecting a "
+ "binding state.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting "
+ "semicolon.");
+ }
+ break;
+
+ /* Lease preferred lifetime. */
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "preferred time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ prefer = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ token = next_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease valid lifetime. */
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "max time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ valid = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ token = next_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease expiration time. */
+ case ENDS:
+ end_time = parse_date(cfile);
+ break;
+
+ /* Lease binding scopes. */
+ case TOKEN_SET:
+ token = next_token(&val, NULL, cfile);
+ if ((token != NAME) &&
+ (token != NUMBER_OR_NAME)) {
+ parse_warn(cfile, "%s is not a valid "
+ "variable name",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+
+ if (scope != NULL)
+ bnd = find_binding(scope, val);
+ else {
+ if (!binding_scope_allocate(&scope,
+ MDL)) {
+ log_fatal("Out of memory for "
+ "lease binding "
+ "scope.");
+ }
+
+ bnd = NULL;
+ }
+
+ if (bnd == NULL) {
+ bnd = dmalloc(sizeof(*bnd),
+ MDL);
+ if (bnd == NULL) {
+ log_fatal("No memory for "
+ "lease binding.");
+ }
+
+ bnd->name = dmalloc(strlen(val) + 1,
+ MDL);
+ if (bnd->name == NULL) {
+ log_fatal("No memory for "
+ "binding name.");
+ }
+ strcpy(bnd->name, val);
+
+ newbinding = ISC_TRUE;
+ } else {
+ newbinding = ISC_FALSE;
+ }
+
+ if (!binding_value_allocate(&nv, MDL)) {
+ log_fatal("no memory for binding "
+ "value.");
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != EQUAL) {
+ parse_warn(cfile, "expecting '=' in "
+ "set statement.");
+ goto binding_err;
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_err:
+ binding_value_dereference(&nv, MDL);
+ binding_scope_dereference(&scope, MDL);
+ return;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ bnd->next = scope->bindings;
+ scope->bindings = bnd;
+ } else {
+ binding_value_dereference(&bnd->value,
+ MDL);
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ default:
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting ia_na contents, "
+ "got '%s'", val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ }
+
+ if (state == FTS_LAST+1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing state in iaaddr");
+ return;
+ }
+ if (end_time == -1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing end time in iaaddr");
+ return;
+ }
+
+ iaaddr = NULL;
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ memcpy(&iaaddr->addr, iaddr.iabuf, sizeof(iaaddr->addr));
+ iaaddr->plen = 0;
+ iaaddr->state = state;
+ iaaddr->prefer = prefer;
+ iaaddr->valid = valid;
+ if (iaaddr->state == FTS_RELEASED)
+ iaaddr->hard_lifetime_end_time = end_time;
+
+ if (scope != NULL) {
+ binding_scope_reference(&iaaddr->scope, scope, MDL);
+ binding_scope_dereference(&scope, MDL);
+ }
+
+ /* add to our various structures */
+ ia_add_iasubopt(ia, iaaddr, MDL);
+ ia_reference(&iaaddr->ia, ia, MDL);
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_NA,
+ &iaaddr->addr) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iaaddr->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "no pool found for address %s",
+ addr_buf);
+ return;
+ }
+ add_lease6(pool, iaaddr, end_time);
+ ipv6_pool_dereference(&pool, MDL);
+ iasubopt_dereference(&iaaddr, MDL);
+ }
+
+ /*
+ * If we have an existing record for this IA_NA, remove it.
+ */
+ old_ia = NULL;
+ if (ia_hash_lookup(&old_ia, ia_na_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL)) {
+ ia_hash_delete(ia_na_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL);
+ ia_dereference(&old_ia, MDL);
+ }
+
+ /*
+ * If we have addresses, add this, otherwise don't bother.
+ */
+ if (ia->num_iasubopt > 0) {
+ ia_hash_add(ia_na_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, ia, MDL);
+ }
+ ia_dereference(&ia, MDL);
+#endif /* defined(DHCPv6) */
+}
+
+void
+parse_ia_ta_declaration(struct parse *cfile) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ enum dhcp_token token;
+ struct ia_xx *ia;
+ const char *val;
+ struct ia_xx *old_ia;
+ unsigned int len;
+ u_int32_t iaid;
+ struct iaddr iaddr;
+ binding_state_t state;
+ u_int32_t prefer;
+ u_int32_t valid;
+ TIME end_time;
+ struct iasubopt *iaaddr;
+ struct ipv6_pool *pool;
+ char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ isc_boolean_t newbinding;
+ struct binding_scope *scope=NULL;
+ struct binding *bnd;
+ struct binding_value *nv=NULL;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "IA_TA is only supported in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting an iaid+ia_ta string");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (len < 5) {
+ parse_warn(cfile, "corrupt lease file; "
+ "iaid+ia_ta string too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memcpy(&iaid, val, 4);
+ ia = NULL;
+ if (ia_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ ia->ia_type = D6O_IA_TA;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ if (token == CLTT) {
+ ia->cltt = parse_date (cfile);
+ continue;
+ }
+
+ if (token != IAADDR) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IAADDR or right brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!parse_ip6_addr(cfile, &iaddr)) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IPv6 address");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ state = FTS_LAST+1;
+ prefer = valid = 0;
+ end_time = -1;
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ switch(token) {
+ /* Lease binding state. */
+ case BINDING:
+ token = next_token(&val, NULL, cfile);
+ if (token != STATE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting state");
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ state = FTS_RELEASED;
+ break;
+ default:
+ parse_warn(cfile,
+ "corrupt lease "
+ "file; "
+ "expecting a "
+ "binding state.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting "
+ "semicolon.");
+ }
+ break;
+
+ /* Lease preferred lifetime. */
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "preferred time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ prefer = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ token = next_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease valid lifetime. */
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "max time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ valid = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ token = next_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease expiration time. */
+ case ENDS:
+ end_time = parse_date(cfile);
+ break;
+
+ /* Lease binding scopes. */
+ case TOKEN_SET:
+ token = next_token(&val, NULL, cfile);
+ if ((token != NAME) &&
+ (token != NUMBER_OR_NAME)) {
+ parse_warn(cfile, "%s is not a valid "
+ "variable name",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+
+ if (scope != NULL)
+ bnd = find_binding(scope, val);
+ else {
+ if (!binding_scope_allocate(&scope,
+ MDL)) {
+ log_fatal("Out of memory for "
+ "lease binding "
+ "scope.");
+ }
+
+ bnd = NULL;
+ }
+
+ if (bnd == NULL) {
+ bnd = dmalloc(sizeof(*bnd),
+ MDL);
+ if (bnd == NULL) {
+ log_fatal("No memory for "
+ "lease binding.");
+ }
+
+ bnd->name = dmalloc(strlen(val) + 1,
+ MDL);
+ if (bnd->name == NULL) {
+ log_fatal("No memory for "
+ "binding name.");
+ }
+ strcpy(bnd->name, val);
+
+ newbinding = ISC_TRUE;
+ } else {
+ newbinding = ISC_FALSE;
+ }
+
+ if (!binding_value_allocate(&nv, MDL)) {
+ log_fatal("no memory for binding "
+ "value.");
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != EQUAL) {
+ parse_warn(cfile, "expecting '=' in "
+ "set statement.");
+ goto binding_err;
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_err:
+ binding_value_dereference(&nv, MDL);
+ binding_scope_dereference(&scope, MDL);
+ return;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ bnd->next = scope->bindings;
+ scope->bindings = bnd;
+ } else {
+ binding_value_dereference(&bnd->value,
+ MDL);
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ default:
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting ia_ta contents, "
+ "got '%s'", val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ }
+
+ if (state == FTS_LAST+1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing state in iaaddr");
+ return;
+ }
+ if (end_time == -1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing end time in iaaddr");
+ return;
+ }
+
+ iaaddr = NULL;
+ if (iasubopt_allocate(&iaaddr, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ memcpy(&iaaddr->addr, iaddr.iabuf, sizeof(iaaddr->addr));
+ iaaddr->plen = 0;
+ iaaddr->state = state;
+ iaaddr->prefer = prefer;
+ iaaddr->valid = valid;
+ if (iaaddr->state == FTS_RELEASED)
+ iaaddr->hard_lifetime_end_time = end_time;
+
+ if (scope != NULL) {
+ binding_scope_reference(&iaaddr->scope, scope, MDL);
+ binding_scope_dereference(&scope, MDL);
+ }
+
+ /* add to our various structures */
+ ia_add_iasubopt(ia, iaaddr, MDL);
+ ia_reference(&iaaddr->ia, ia, MDL);
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_TA,
+ &iaaddr->addr) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iaaddr->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "no pool found for address %s",
+ addr_buf);
+ return;
+ }
+ add_lease6(pool, iaaddr, end_time);
+ ipv6_pool_dereference(&pool, MDL);
+ iasubopt_dereference(&iaaddr, MDL);
+ }
+
+ /*
+ * If we have an existing record for this IA_TA, remove it.
+ */
+ old_ia = NULL;
+ if (ia_hash_lookup(&old_ia, ia_ta_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL)) {
+ ia_hash_delete(ia_ta_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL);
+ ia_dereference(&old_ia, MDL);
+ }
+
+ /*
+ * If we have addresses, add this, otherwise don't bother.
+ */
+ if (ia->num_iasubopt > 0) {
+ ia_hash_add(ia_ta_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, ia, MDL);
+ }
+ ia_dereference(&ia, MDL);
+#endif /* defined(DHCPv6) */
+}
+
+void
+parse_ia_pd_declaration(struct parse *cfile) {
+#if !defined(DHCPv6)
+ parse_warn(cfile, "No DHCPv6 support.");
+ skip_to_semi(cfile);
+#else /* defined(DHCPv6) */
+ enum dhcp_token token;
+ struct ia_xx *ia;
+ const char *val;
+ struct ia_xx *old_ia;
+ unsigned int len;
+ u_int32_t iaid;
+ struct iaddr iaddr;
+ u_int8_t plen;
+ binding_state_t state;
+ u_int32_t prefer;
+ u_int32_t valid;
+ TIME end_time;
+ struct iasubopt *iapref;
+ struct ipv6_pool *pool;
+ char addr_buf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ isc_boolean_t newbinding;
+ struct binding_scope *scope=NULL;
+ struct binding *bnd;
+ struct binding_value *nv=NULL;
+
+ if (local_family != AF_INET6) {
+ parse_warn(cfile, "IA_PD is only supported in DHCPv6 mode.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting an iaid+ia_pd string");
+ skip_to_semi(cfile);
+ return;
+ }
+ if (len < 5) {
+ parse_warn(cfile, "corrupt lease file; "
+ "iaid+ia_pd string too short");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memcpy(&iaid, val, 4);
+ ia = NULL;
+ if (ia_allocate(&ia, iaid, val+4, len-4, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ ia->ia_type = D6O_IA_PD;
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ if (token == CLTT) {
+ ia->cltt = parse_date (cfile);
+ continue;
+ }
+
+ if (token != IAPREFIX) {
+ parse_warn(cfile, "corrupt lease file; expecting "
+ "IAPREFIX or right brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ if (!parse_ip6_prefix(cfile, &iaddr, &plen)) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting IPv6 prefix");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != LBRACE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting left brace");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ state = FTS_LAST+1;
+ prefer = valid = 0;
+ end_time = -1;
+ for (;;) {
+ token = next_token(&val, NULL, cfile);
+ if (token == RBRACE) break;
+
+ switch(token) {
+ /* Prefix binding state. */
+ case BINDING:
+ token = next_token(&val, NULL, cfile);
+ if (token != STATE) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting state");
+ skip_to_semi(cfile);
+ return;
+ }
+ token = next_token(&val, NULL, cfile);
+ switch (token) {
+ case TOKEN_ABANDONED:
+ state = FTS_ABANDONED;
+ break;
+ case TOKEN_FREE:
+ state = FTS_FREE;
+ break;
+ case TOKEN_ACTIVE:
+ state = FTS_ACTIVE;
+ break;
+ case TOKEN_EXPIRED:
+ state = FTS_EXPIRED;
+ break;
+ case TOKEN_RELEASED:
+ state = FTS_RELEASED;
+ break;
+ default:
+ parse_warn(cfile,
+ "corrupt lease "
+ "file; "
+ "expecting a "
+ "binding state.");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting "
+ "semicolon.");
+ }
+ break;
+
+ /* Lease preferred lifetime. */
+ case PREFERRED_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "preferred time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ prefer = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ token = next_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Lease valid lifetime. */
+ case MAX_LIFE:
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "%s is not a valid "
+ "max time",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ valid = atoi (val);
+
+ /*
+ * Currently we peek for the semi-colon to
+ * allow processing of older lease files that
+ * don't have the semi-colon. Eventually we
+ * should remove the peeking code.
+ */
+ token = peek_token(&val, NULL, cfile);
+ if (token == SEMI) {
+ token = next_token(&val, NULL, cfile);
+ } else {
+ parse_warn(cfile,
+ "corrupt lease file; "
+ "expecting semicolon.");
+ }
+ break;
+
+ /* Prefix expiration time. */
+ case ENDS:
+ end_time = parse_date(cfile);
+ break;
+
+ /* Prefix binding scopes. */
+ case TOKEN_SET:
+ token = next_token(&val, NULL, cfile);
+ if ((token != NAME) &&
+ (token != NUMBER_OR_NAME)) {
+ parse_warn(cfile, "%s is not a valid "
+ "variable name",
+ val);
+ skip_to_semi(cfile);
+ continue;
+ }
+
+ if (scope != NULL)
+ bnd = find_binding(scope, val);
+ else {
+ if (!binding_scope_allocate(&scope,
+ MDL)) {
+ log_fatal("Out of memory for "
+ "lease binding "
+ "scope.");
+ }
+
+ bnd = NULL;
+ }
+
+ if (bnd == NULL) {
+ bnd = dmalloc(sizeof(*bnd),
+ MDL);
+ if (bnd == NULL) {
+ log_fatal("No memory for "
+ "prefix binding.");
+ }
+
+ bnd->name = dmalloc(strlen(val) + 1,
+ MDL);
+ if (bnd->name == NULL) {
+ log_fatal("No memory for "
+ "binding name.");
+ }
+ strcpy(bnd->name, val);
+
+ newbinding = ISC_TRUE;
+ } else {
+ newbinding = ISC_FALSE;
+ }
+
+ if (!binding_value_allocate(&nv, MDL)) {
+ log_fatal("no memory for binding "
+ "value.");
+ }
+
+ token = next_token(NULL, NULL, cfile);
+ if (token != EQUAL) {
+ parse_warn(cfile, "expecting '=' in "
+ "set statement.");
+ goto binding_err;
+ }
+
+ if (!parse_binding_value(cfile, nv)) {
+ binding_err:
+ binding_value_dereference(&nv, MDL);
+ binding_scope_dereference(&scope, MDL);
+ return;
+ }
+
+ if (newbinding) {
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ bnd->next = scope->bindings;
+ scope->bindings = bnd;
+ } else {
+ binding_value_dereference(&bnd->value,
+ MDL);
+ binding_value_reference(&bnd->value,
+ nv, MDL);
+ }
+
+ binding_value_dereference(&nv, MDL);
+ parse_semi(cfile);
+ break;
+
+ default:
+ parse_warn(cfile, "corrupt lease file; "
+ "expecting ia_pd contents, "
+ "got '%s'", val);
+ skip_to_semi(cfile);
+ continue;
+ }
+ }
+
+ if (state == FTS_LAST+1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing state in iaprefix");
+ return;
+ }
+ if (end_time == -1) {
+ parse_warn(cfile, "corrupt lease file; "
+ "missing end time in iaprefix");
+ return;
+ }
+
+ iapref = NULL;
+ if (iasubopt_allocate(&iapref, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Out of memory.");
+ }
+ memcpy(&iapref->addr, iaddr.iabuf, sizeof(iapref->addr));
+ iapref->plen = plen;
+ iapref->state = state;
+ iapref->prefer = prefer;
+ iapref->valid = valid;
+ if (iapref->state == FTS_RELEASED)
+ iapref->hard_lifetime_end_time = end_time;
+
+ if (scope != NULL) {
+ binding_scope_reference(&iapref->scope, scope, MDL);
+ binding_scope_dereference(&scope, MDL);
+ }
+
+ /* add to our various structures */
+ ia_add_iasubopt(ia, iapref, MDL);
+ ia_reference(&iapref->ia, ia, MDL);
+ pool = NULL;
+ if (find_ipv6_pool(&pool, D6O_IA_PD,
+ &iapref->addr) != ISC_R_SUCCESS) {
+ inet_ntop(AF_INET6, &iapref->addr,
+ addr_buf, sizeof(addr_buf));
+ parse_warn(cfile, "no pool found for address %s",
+ addr_buf);
+ return;
+ }
+ add_lease6(pool, iapref, end_time);
+ ipv6_pool_dereference(&pool, MDL);
+ iasubopt_dereference(&iapref, MDL);
+ }
+
+ /*
+ * If we have an existing record for this IA_PD, remove it.
+ */
+ old_ia = NULL;
+ if (ia_hash_lookup(&old_ia, ia_pd_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL)) {
+ ia_hash_delete(ia_pd_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, MDL);
+ ia_dereference(&old_ia, MDL);
+ }
+
+ /*
+ * If we have prefixes, add this, otherwise don't bother.
+ */
+ if (ia->num_iasubopt > 0) {
+ ia_hash_add(ia_pd_active,
+ (unsigned char *)ia->iaid_duid.data,
+ ia->iaid_duid.len, ia, MDL);
+ }
+ ia_dereference(&ia, MDL);
+#endif /* defined(DHCPv6) */
+}
+
+#ifdef DHCPv6
+/*
+ * When we parse a server-duid statement in a lease file, we are
+ * looking at the saved server DUID from a previous run. In this case
+ * we expect it to be followed by the binary representation of the
+ * DUID stored in a string:
+ *
+ * server-duid "\000\001\000\001\015\221\034JRT\000\0224Y";
+ */
+void
+parse_server_duid(struct parse *cfile) {
+ enum dhcp_token token;
+ const char *val;
+ unsigned int len;
+ struct data_string duid;
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "corrupt lease file; expecting a DUID");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ memset(&duid, 0, sizeof(duid));
+ duid.len = len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ memcpy(duid.buffer->data, val, len);
+
+ set_server_duid(&duid);
+
+ data_string_forget(&duid, MDL);
+
+ token = next_token(&val, &len, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "corrupt lease file; expecting a semicolon");
+ skip_to_semi(cfile);
+ return;
+ }
+}
+
+/*
+ * When we parse a server-duid statement in a config file, we will
+ * have the type of the server DUID to generate, and possibly the
+ * actual value defined.
+ *
+ * server-duid llt;
+ * server-duid llt ethernet|ieee802|fddi 213982198 00:16:6F:49:7D:9B;
+ * server-duid ll;
+ * server-duid ll ethernet|ieee802|fddi 00:16:6F:49:7D:9B;
+ * server-duid en 2495 "enterprise-specific-identifier-1234";
+ */
+void
+parse_server_duid_conf(struct parse *cfile) {
+ enum dhcp_token token;
+ const char *val;
+ unsigned int len;
+ u_int32_t enterprise_number;
+ int ll_type;
+ struct data_string ll_addr;
+ u_int32_t llt_time;
+ struct data_string duid;
+ int duid_type_num;
+
+ /*
+ * Consume the SERVER_DUID token.
+ */
+ token = next_token(NULL, NULL, cfile);
+
+ /*
+ * Obtain the DUID type.
+ */
+ token = next_token(&val, NULL, cfile);
+
+ /*
+ * Enterprise is the easiest - enterprise number and raw data
+ * are required.
+ */
+ if (token == EN) {
+ /*
+ * Get enterprise number and identifier.
+ */
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "enterprise number expected");
+ skip_to_semi(cfile);
+ return;
+ }
+ enterprise_number = atoi(val);
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "identifier expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + 4 + len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, DUID_EN);
+ putULong(duid.buffer->data + 2, enterprise_number);
+ memcpy(duid.buffer->data + 6, val, len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ }
+
+ /*
+ * Next easiest is the link-layer DUID. It consists only of
+ * the LL directive, or optionally the specific value to use.
+ *
+ * If we have LL only, then we set the type. If we have the
+ * value, then we set the actual DUID.
+ */
+ else if (token == LL) {
+ if (peek_token(NULL, NULL, cfile) == SEMI) {
+ set_server_duid_type(DUID_LL);
+ } else {
+ /*
+ * Get our hardware type and address.
+ */
+ token = next_token(NULL, NULL, cfile);
+ switch (token) {
+ case ETHERNET:
+ ll_type = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ ll_type = HTYPE_IEEE802;
+ break;
+ case TOKEN_FDDI:
+ ll_type = HTYPE_FDDI;
+ break;
+ default:
+ parse_warn(cfile, "hardware type expected");
+ skip_to_semi(cfile);
+ return;
+ }
+ memset(&ll_addr, 0, sizeof(ll_addr));
+ if (!parse_cshl(&ll_addr, cfile)) {
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + 2 + ll_addr.len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, DUID_LL);
+ putULong(duid.buffer->data + 2, ll_type);
+ memcpy(duid.buffer->data + 4,
+ ll_addr.data, ll_addr.len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ data_string_forget(&ll_addr, MDL);
+ }
+ }
+
+ /*
+ * Finally the link-layer DUID plus time. It consists only of
+ * the LLT directive, or optionally the specific value to use.
+ *
+ * If we have LLT only, then we set the type. If we have the
+ * value, then we set the actual DUID.
+ */
+ else if (token == LLT) {
+ if (peek_token(NULL, NULL, cfile) == SEMI) {
+ set_server_duid_type(DUID_LLT);
+ } else {
+ /*
+ * Get our hardware type, timestamp, and address.
+ */
+ token = next_token(NULL, NULL, cfile);
+ switch (token) {
+ case ETHERNET:
+ ll_type = HTYPE_ETHER;
+ break;
+ case TOKEN_RING:
+ ll_type = HTYPE_IEEE802;
+ break;
+ case TOKEN_FDDI:
+ ll_type = HTYPE_FDDI;
+ break;
+ default:
+ parse_warn(cfile, "hardware type expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ token = next_token(&val, NULL, cfile);
+ if (token != NUMBER) {
+ parse_warn(cfile, "timestamp expected");
+ skip_to_semi(cfile);
+ return;
+ }
+ llt_time = atoi(val);
+
+ memset(&ll_addr, 0, sizeof(ll_addr));
+ if (!parse_cshl(&ll_addr, cfile)) {
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + 2 + 4 + ll_addr.len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, DUID_LLT);
+ putULong(duid.buffer->data + 2, ll_type);
+ putULong(duid.buffer->data + 4, llt_time);
+ memcpy(duid.buffer->data + 8,
+ ll_addr.data, ll_addr.len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ data_string_forget(&ll_addr, MDL);
+ }
+ }
+
+ /*
+ * If users want they can use a number for DUID types.
+ * This is useful for supporting future, not-yet-defined
+ * DUID types.
+ *
+ * In this case, they have to put in the complete value.
+ *
+ * This also works for existing DUID types of course.
+ */
+ else if (token == NUMBER) {
+ duid_type_num = atoi(val);
+
+ token = next_token(&val, &len, cfile);
+ if (token != STRING) {
+ parse_warn(cfile, "identifier expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Save the DUID.
+ */
+ memset(&duid, 0, sizeof(duid));
+ duid.len = 2 + len;
+ if (!buffer_allocate(&duid.buffer, duid.len, MDL)) {
+ log_fatal("Out of memory storing DUID");
+ }
+ duid.data = (unsigned char *)duid.buffer->data;
+ putUShort(duid.buffer->data, duid_type_num);
+ memcpy(duid.buffer->data + 2, val, len);
+
+ set_server_duid(&duid);
+ data_string_forget(&duid, MDL);
+ }
+
+ /*
+ * Anything else is an error.
+ */
+ else {
+ parse_warn(cfile, "DUID type of LLT, EN, or LL expected");
+ skip_to_semi(cfile);
+ return;
+ }
+
+ /*
+ * Finally consume our trailing semicolon.
+ */
+ token = next_token(NULL, NULL, cfile);
+ if (token != SEMI) {
+ parse_warn(cfile, "semicolon expected");
+ skip_to_semi(cfile);
+ }
+}
+
+#endif /* DHCPv6 */
+