diff options
Diffstat (limited to 'keama/parse.c')
-rw-r--r-- | keama/parse.c | 6140 |
1 files changed, 6140 insertions, 0 deletions
diff --git a/keama/parse.c b/keama/parse.c new file mode 100644 index 00000000..3596d5d5 --- /dev/null +++ b/keama/parse.c @@ -0,0 +1,6140 @@ +/* + * Copyright (c) 2017, 2018 by Internet Systems Consortium, Inc. ("ISC") + * + * 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/ + * + */ + +#include "keama.h" + +#include <sys/types.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> + +static void config_min_valid_lifetime(struct element *, struct parse *); +static void config_def_valid_lifetime(struct element *, struct parse *); +static void config_max_valid_lifetime(struct element *, struct parse *); +static void config_file(struct element *, struct parse *); +static void config_sname(struct element *, struct parse *); +static void config_next_server(struct element *, struct parse *); +static void config_vendor_option_space(struct element *, struct parse *); +static void config_site_option_space(struct element *, struct parse *); +static struct element *default_qualifying_suffix(void); +static void config_qualifying_suffix(struct element *, struct parse *); +static void config_enable_updates(struct element *, struct parse *); +static void config_ddns_update_style(struct element *, struct parse *); +static void config_preferred_lifetime(struct element *, struct parse *); +static void config_match_client_id(struct element *, struct parse *); +static void config_echo_client_id(struct element *, struct parse *); + +/* +static uint32_t getULong(const unsigned char *buf); +static int32_t getLong(const unsigned char *buf); +static uint32_t getUShort(const unsigned char *buf); +static int32_t getShort(const unsigned char *buf); +static uint32_t getUChar(const unsigned char *obuf); +*/ +static void putULong(unsigned char *obuf, uint32_t val); +static void putLong(unsigned char *obuf, int32_t val); +static void putUShort(unsigned char *obuf, uint32_t val); +static void putShort(unsigned char *obuf, int32_t val); +/* +static void putUChar(unsigned char *obuf, uint32_t val); +*/ + +/* +static isc_boolean_t is_compound_expression(struct element *); +*/ +static enum expression_context op_context(enum expr_op); +static int op_val(enum expr_op); +static int op_precedence(enum expr_op, enum expr_op); +static enum expression_context expression_context(struct element *); +static enum expr_op expression(struct element *); + +/* Skip to the semicolon ending the current statement. If we encounter + braces, the matching closing brace terminates the statement. +*/ +void +skip_to_semi(struct parse *cfile) +{ + skip_to_rbrace(cfile, 0); +} + +/* Skips everything from the current point upto (and including) the given + number of right braces. If we encounter a semicolon but haven't seen a + left brace, consume it and return. + This lets us skip over: + + statement; + statement foo bar { } + statement foo bar { statement { } } + statement} + + ...et cetera. */ +void +skip_to_rbrace(struct parse *cfile, int brace_count) +{ + enum dhcp_token token; + const char *val; + + do { + token = peek_token(&val, NULL, cfile); + if (token == RBRACE) { + if (brace_count > 0) { + --brace_count; + } + + if (brace_count == 0) { + /* Eat the brace and return. */ + skip_token(&val, NULL, cfile); + return; + } + } else if (token == LBRACE) { + brace_count++; + } else if (token == SEMI && (brace_count == 0)) { + /* Eat the semicolon and return. */ + skip_token(&val, NULL, cfile); + return; + } else if (token == EOL) { + /* EOL only happens when parsing /etc/resolv.conf, + and we treat it like a semicolon because the + resolv.conf file is line-oriented. */ + skip_token(&val, NULL, cfile); + return; + } + + /* Eat the current token */ + token = next_token(&val, NULL, cfile); + } while (token != END_OF_FILE); +} + +void +parse_semi(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); +} + +/* string-parameter :== STRING SEMI */ + +void +parse_string(struct parse *cfile, char **sptr, unsigned *lptr) +{ + const char *val; + enum dhcp_token token; + char *s; + unsigned len; + + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "expecting a string"); + s = (char *)malloc(len + 1); + parse_error(cfile, "no memory for string %s.", val); + memcpy(s, val, len + 1); + + parse_semi(cfile); + if (sptr) + *sptr = s; + else + free(s); + if (lptr) + *lptr = len; +} + +/* + * hostname :== IDENTIFIER + * | IDENTIFIER DOT + * | hostname DOT IDENTIFIER + */ + +struct string * +parse_host_name(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + struct string *s = NULL; + + /* Read a dotted hostname... */ + do { + /* Read a token, which should be an identifier. */ + token = peek_token(&val, NULL, cfile); + if (!is_identifier(token) && token != NUMBER) + break; + skip_token(&val, NULL, cfile); + + /* Store this identifier... */ + if (s == NULL) + s = makeString(-1, val); + else + appendString(s, val); + /* Look for a dot; if it's there, keep going, otherwise + we're done. */ + token = peek_token(&val, NULL, cfile); + if (token == DOT) { + token = next_token(&val, NULL, cfile); + appendString(s, val); + } + } while (token == DOT); + + return s; +} + +/* ip-addr-or-hostname :== ip-address | hostname + ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER + + Parse an ip address or a hostname. + + Note that RFC1123 permits hostnames to consist of all digits, + making it difficult to quickly disambiguate them from ip addresses. +*/ + +struct string * +parse_ip_addr_or_hostname(struct parse *cfile, isc_boolean_t check_multi) +{ + const char *val; + enum dhcp_token token; + unsigned char addr[4]; + unsigned len = sizeof(addr); + isc_boolean_t ipaddr = ISC_FALSE; + struct string *bin = NULL; + + token = peek_token(&val, NULL, cfile); + if (token == NUMBER) { + /* + * a hostname may be numeric, but domain names must + * start with a letter, so we can disambiguate by + * looking ahead a few tokens. we save the parse + * context first, and restore it after we know what + * we're dealing with. + */ + save_parse_state(cfile); + skip_token(NULL, NULL, cfile); + if (next_token(NULL, NULL, cfile) == DOT && + next_token(NULL, NULL, cfile) == NUMBER) + ipaddr = ISC_TRUE; + restore_parse_state(cfile); + + if (ipaddr) + bin = parse_numeric_aggregate(cfile, addr, &len, + DOT, 10, 8); + } + + if ((bin == NULL) && (is_identifier(token) || token == NUMBER)) { + struct string *name; + struct hostent *h; + + name = parse_host_name(cfile); + if (name == NULL) + return NULL; + + if (resolve == fatal) + parse_error(cfile, "expected IPv4 address. got " + "hostname %s", name->content); + else if (resolve == pass) + return name; + + /* from do_host_lookup */ + h = gethostbyname(name->content); + if ((h == NULL) || (h->h_addr_list[0] == NULL)) + parse_error(cfile, "%s: host unknown.", name->content); + if (check_multi && h->h_addr_list[1]) { + struct comment *comment; + char msg[128]; + + snprintf(msg, sizeof(msg), + "/// %s resolves into multiple addresses", + name->content); + comment = createComment(msg); + TAILQ_INSERT_TAIL(&cfile->comments, comment); + } + bin = makeString(4, h->h_addr_list[0]); + } + + if (bin == NULL) { + if (token != RBRACE && token != LBRACE) + token = next_token(&val, NULL, cfile); + parse_error(cfile, "%s (%d): expecting IP address or hostname", + val, token); + } + + return makeStringExt(bin->length, bin->content, 'I'); +} + +/* + * ip-address :== NUMBER DOT NUMBER DOT NUMBER DOT NUMBER + */ + +struct string * +parse_ip_addr(struct parse *cfile) +{ + unsigned char addr[4]; + unsigned len = sizeof(addr); + + return parse_numeric_aggregate(cfile, addr, &len, DOT, 10, 8); +} + +/* + * Return true if every character in the string is hexadecimal. + */ +static isc_boolean_t +is_hex_string(const char *s) +{ + while (*s != '\0') { + if (!isxdigit((int)*s)) { + return ISC_FALSE; + } + s++; + } + return ISC_TRUE; +} + +/* + * ip-address6 :== (complicated set of rules) + * + * See section 2.2 of RFC 1884 for details. + * + * We are lazy for this. We pull numbers, names, colons, and dots + * together and then throw the resulting string at the inet_pton() + * function. + */ + +struct string * +parse_ip6_addr(struct parse *cfile) +{ + enum dhcp_token token; + const char *val; + char addr[16]; + int val_len; + char v6[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + int v6_len; + + /* + * First token is non-raw. This way we eat any whitespace before + * our IPv6 address begins, like one would expect. + */ + token = peek_token(&val, NULL, cfile); + + /* + * Gather symbols. + */ + v6_len = 0; + for (;;) { + if ((((token == NAME) || (token == NUMBER_OR_NAME)) && + is_hex_string(val)) || + (token == NUMBER) || + (token == TOKEN_ADD) || + (token == DOT) || + (token == COLON)) { + + next_raw_token(&val, NULL, cfile); + val_len = strlen(val); + if ((v6_len + val_len) >= sizeof(v6)) + parse_error(cfile, "Invalid IPv6 address."); + memcpy(v6+v6_len, val, val_len); + v6_len += val_len; + + } else { + break; + } + token = peek_raw_token(&val, NULL, cfile); + } + v6[v6_len] = '\0'; + + /* + * Use inet_pton() for actual work. + */ + if (inet_pton(AF_INET6, v6, addr) <= 0) + parse_error(cfile, "Invalid IPv6 address."); + return makeString(16, addr); +} + +/* + * Same as parse_ip6_addr() above, but returns the value as a text + * rather than in an address binary structure. + */ +struct string * +parse_ip6_addr_txt(struct parse *cfile) +{ + const struct string *bin; + + bin = parse_ip6_addr(cfile); + return makeStringExt(bin->length, bin->content, '6'); +} + +/* + * hardware-parameter :== HARDWARE hardware-type colon-separated-hex-list SEMI + * hardware-type :== ETHERNET | TOKEN_RING | TOKEN_FDDI | INFINIBAND + * Note that INFINIBAND may not be useful for some items, such as classification + * as the hardware address won't always be available. + */ + +struct element * +parse_hardware_param(struct parse *cfile) +{ + const char *val; + enum dhcp_token token; + isc_boolean_t ether = ISC_FALSE; + unsigned hlen; + struct string *t, *r; + struct element *hw; + + token = next_token(&val, NULL, cfile); + if (token == ETHERNET) + ether = ISC_TRUE; + else { + r = makeString(-1, val); + appendString(r, " "); + } + + /* Parse the hardware address information. Technically, + it would make a lot of sense to restrict the length of the + data we'll accept here to the length of a particular hardware + address type. Unfortunately, there are some broken clients + out there that put bogus data in the chaddr buffer, and we accept + that data in the lease file rather than simply failing on such + clients. Yuck. */ + hlen = 0; + token = peek_token(&val, NULL, cfile); + if (token == SEMI) + parse_error(cfile, "empty hardware address"); + t = parse_numeric_aggregate(cfile, NULL, &hlen, COLON, 16, 8); + if (t == NULL) + parse_error(cfile, "can't get hardware address"); + if (hlen > HARDWARE_ADDR_LEN) + parse_error(cfile, "hardware address too long"); + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "expecting semicolon."); + if (ether) + r = makeStringExt(hlen, t->content, 'H'); + else + concatString(r, makeStringExt(hlen,t->content, 'H')); + hw = createString(r); + TAILQ_CONCAT(&hw->comments, &cfile->comments); + if (!ether || (hlen != 6)) { + hw->skip = ISC_TRUE; + cfile->issue_counter++; + } + return hw; +} + +/* No BNF for numeric aggregates - that's defined by the caller. What + this function does is to parse a sequence of numbers separated by + the token specified in separator. If max is zero, any number of + numbers will be parsed; otherwise, exactly max numbers are + expected. Base and size tell us how to internalize the numbers + once they've been tokenized. + + buf - A pointer to space to return the parsed value, if it is null + then the function will allocate space for the return. + + max - The maximum number of items to store. If zero there is no + maximum. When buf is null and the function needs to allocate space + it will do an allocation of max size at the beginning if max is non + zero. If max is zero then the allocation will be done later, after + the function has determined the size necessary for the incoming + string. + + returns NULL on errors or a pointer to the string structure on success. + */ + +struct string * +parse_numeric_aggregate(struct parse *cfile, unsigned char *buf, + unsigned *max, int separator, + int base, unsigned size) +{ + const char *val; + enum dhcp_token token; + unsigned char *bufp = buf, *s; + unsigned count = 0; + struct string *r = NULL, *t = NULL; + + if (!bufp && *max) { + bufp = (unsigned char *)malloc(*max * size / 8); + if (!bufp) + parse_error(cfile, "no space for numeric aggregate"); + } + s = bufp; + if (!s) { + r = allocString(); + t = makeString(size / 8, "bigger than needed"); + } + + do { + if (count) { + token = peek_token(&val, NULL, cfile); + if (token != separator) { + if (!*max) + break; + if (token != RBRACE && token != LBRACE) + token = next_token(&val, NULL, cfile); + parse_error(cfile, "too few numbers."); + } + skip_token(&val, NULL, cfile); + } + token = next_token(&val, NULL, cfile); + + if (token == END_OF_FILE) + parse_error(cfile, "unexpected end of file"); + + /* Allow NUMBER_OR_NAME if base is 16. */ + if (token != NUMBER && + (base != 16 || token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting numeric value."); + /* If we can, convert the number now; otherwise, build + a linked list of all the numbers. */ + if (s) { + convert_num(cfile, s, val, base, size); + s += size / 8; + } else { + convert_num(cfile, (unsigned char *)t->content, + val, base, size); + concatString(r, t); + } + } while (++count != *max); + + *max = count; + if (bufp) + r = makeString(count * size / 8, (char *)bufp); + + return r; +} + +void +convert_num(struct parse *cfile, unsigned char *buf, const char *str, + int base, unsigned size) +{ + const unsigned char *ptr = (const unsigned char *)str; + int negative = 0; + uint32_t val = 0; + int tval; + int max; + + if (*ptr == '-') { + negative = 1; + ++ptr; + } + + /* If base wasn't specified, figure it out from the data. */ + if (!base) { + if (ptr[0] == '0') { + if (ptr[1] == 'x') { + base = 16; + ptr += 2; + } else if (isascii(ptr[1]) && isdigit(ptr[1])) { + base = 8; + ptr += 1; + } else { + base = 10; + } + } else { + base = 10; + } + } + + do { + tval = *ptr++; + /* XXX assumes ASCII... */ + if (tval >= 'a') + tval = tval - 'a' + 10; + else if (tval >= 'A') + tval = tval - 'A' + 10; + else if (tval >= '0') + tval -= '0'; + else + parse_error(cfile, "Bogus number: %s.", str); + if (tval >= base) + parse_error(cfile, + "Bogus number %s: digit %d not in base %d", + str, tval, base); + val = val * base + tval; + } while (*ptr); + + if (negative) + max = (1 << (size - 1)); + else + max = (1 << (size - 1)) + ((1 << (size - 1)) - 1); + if (val > max) { + switch (base) { + case 8: + parse_error(cfile, + "%s%lo exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + case 16: + parse_error(cfile, + "%s%lx exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + default: + parse_error(cfile, + "%s%lu exceeds max (%d) for precision.", + negative ? "-" : "", + (unsigned long)val, max); + break; + } + } + + if (negative) { + switch (size) { + case 8: + *buf = -(unsigned long)val; + break; + case 16: + putShort(buf, -(long)val); + break; + case 32: + putLong(buf, -(long)val); + break; + default: + parse_error(cfile, + "Unexpected integer size: %d\n", size); + break; + } + } else { + switch (size) { + case 8: + *buf = (uint8_t)val; + break; + case 16: + putUShort (buf, (uint16_t)val); + break; + case 32: + putULong (buf, val); + break; + default: + parse_error(cfile, + "Unexpected integer size: %d\n", size); + } + } +} + +/* + * option-name :== IDENTIFIER | + IDENTIFIER . IDENTIFIER + */ + +struct option * +parse_option_name(struct parse *cfile, + isc_boolean_t allocate, + isc_boolean_t *known) +{ + const char *val; + enum dhcp_token token; + const char *uname; + struct space *space; + struct option *option = NULL; + unsigned code; + + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, + "expecting identifier after option keyword."); + + uname = strdup(val); + if (!uname) + parse_error(cfile, "no memory for uname information."); + token = peek_token(&val, NULL, cfile); + if (token == DOT) { + /* Go ahead and take the DOT token... */ + skip_token(&val, NULL, cfile); + + /* The next token should be an identifier... */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier after '.'"); + + /* Look up the option name hash table for the specified + uname. */ + space = space_lookup(uname); + if (space == NULL) + parse_error(cfile, "no option space named %s.", uname); + } else { + /* Use the default hash table, which contains all the + standard dhcp option names. */ + val = uname; + space = space_lookup("dhcp"); + } + + option = option_lookup_name(space->old, val); + + if (option) { + if (known && (option->status != isc_dhcp_unknown)) + *known = ISC_TRUE; + } else if (space == space_lookup("server")) + parse_error(cfile, "unknown server option %s.", val); + + /* If the option name is of the form unknown-[decimal], use + * the trailing decimal value to find the option definition. + * If there is no definition, construct one. This is to + * support legacy use of unknown options in config files or + * lease databases. + */ + else if (strncasecmp(val, "unknown-", 8) == 0) { + code = atoi(val + 8); + + /* Option code 0 is always illegal for us, thanks + * to the option decoder. + */ + if (code == 0) + parse_error(cfile, "Option code 0 is illegal " + "in the %s space.", space->old); + if ((local_family == AF_INET) && (code == 255)) + parse_error(cfile, "Option code 255 is illegal " + "in the %s space.", space->old); + + /* It's odd to think of unknown option codes as + * being known, but this means we know what the + * parsed name is talking about. + */ + if (known) + *known = ISC_TRUE; + option = option_lookup_code(space->old, code); + + /* If we did not find an option of that code, + * manufacture an unknown-xxx option definition. + */ + if (option == NULL) { + option = (struct option *)malloc(sizeof(*option)); + /* DHCP code does not check allocation failure? */ + memset(option, 0, sizeof(*option)); + option->name = strdup(val); + option->space = space; + option->code = code; + /* Mark format as undefined */ + option->format = "u"; + push_option(option); + } else { + struct comment *comment; + char msg[256]; + + snprintf(msg, sizeof(msg), + "/// option %s.%s redefinition", + space->name, val); + comment = createComment(msg); + TAILQ_INSERT_TAIL(&cfile->comments, comment); + } + /* If we've been told to allocate, that means that this + * (might) be an option code definition, so we'll create + * an option structure and return it for the parent to + * decide. + */ + } else if (allocate) { + option = (struct option *)malloc(sizeof(*option)); + /* DHCP code does not check allocation failure? */ + memset(option, 0, sizeof(*option)); + option->name = strdup(val); + option->space = space; + /* Mark format as undefined */ + option->format = "u"; + push_option(option); + } else + parse_error(cfile, "no option named %s in space %s", + val, space->old); + + return option; +} + +/* IDENTIFIER[WIDTHS] SEMI + * WIDTHS ~= LENGTH WIDTH NUMBER + * CODE WIDTH NUMBER + */ + +void +parse_option_space_decl(struct parse *cfile) +{ + int token; + const char *val; + struct element *nu; + struct element *p; + struct space *universe; + int tsize = 1, lsize = 1; + + skip_token(&val, NULL, cfile); /* Discard the SPACE token, + which was checked by the + caller. */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + nu = createMap(); + nu->skip = ISC_TRUE; + + /* Expect it will be usable in Kea */ + universe = (struct space *)malloc(sizeof(*universe)); + if (universe == NULL) + parse_error(cfile, "No memory for new option space."); + memset(universe, 0, sizeof(*universe)); + universe->old = strdup(val); + universe->name = universe->old; + push_space(universe); + + do { + token = next_token(&val, NULL, cfile); + switch(token) { + case SEMI: + break; + + case CODE: + if (mapSize(nu) == 0) { + cfile->issue_counter++; + mapSet(nu, + createString( + makeString(-1, universe->old)), + "name"); + } + token = next_token(&val, NULL, cfile); + if (token != WIDTH) + parse_error(cfile, "expecting width token."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, + "expecting number 1, 2, 4."); + + tsize = atoi(val); + p = NULL; + if ((local_family == AF_INET) && (tsize != 1)) { + struct comment *comment; + + comment = createComment("/// Only code width " + "1 is supported"); + p = createInt(tsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } else if ((local_family == AF_INET6) && + (tsize != 2)) { + struct comment *comment; + + comment = createComment("/// Only code width " + "2 is supported"); + p = createInt(tsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } + if (p != NULL) + mapSet(nu, p, "code-width"); + break; + + case LENGTH: + if (mapSize(nu) == 0) { + cfile->issue_counter++; + mapSet(nu, + createString( + makeString(-1, universe->old)), + "name"); + } + token = next_token(&val, NULL, cfile); + if (token != WIDTH) + parse_error(cfile, "expecting width token."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting number 1 or 2."); + + lsize = atoi(val); + p = NULL; + if ((local_family == AF_INET) && (lsize != 1)) { + struct comment *comment; + + comment = createComment("/// Only length " + "width 1 is " + "supported"); + p = createInt(lsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } else if ((local_family == AF_INET6) && + (lsize != 2)) { + struct comment *comment; + + comment = createComment("/// Only length " + "width 2 is " + "supported"); + p = createInt(lsize); + TAILQ_INSERT_TAIL(&p->comments, comment); + } + if (p != NULL) + mapSet(nu, p, "length-width"); + break; + + case HASH: + token = next_token(&val, NULL, cfile); + if (token != SIZE) + parse_error(cfile, "expecting size token."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, + "expecting a 10base number"); + break; + + default: + parse_error(cfile, "Unexpected token."); + } + } while (token != SEMI); + + if (mapSize(nu) > 1) + mapSet(cfile->stack[1], nu, "option-space"); +} + +/* This is faked up to look good right now. Ideally, this should do a + recursive parse and allow arbitrary data structure definitions, but for + now it just allows you to specify a single type, an array of single types, + a sequence of types, or an array of sequences of types. + + ocd :== NUMBER EQUALS ocsd SEMI + + ocsd :== ocsd_type | + ocsd_type_sequence | + ARRAY OF ocsd_simple_type_sequence + + ocsd_type_sequence :== LBRACE ocsd_types RBRACE + + ocsd_simple_type_sequence :== LBRACE ocsd_simple_types RBRACE + + ocsd_types :== ocsd_type | + ocsd_types ocsd_type + + ocsd_type :== ocsd_simple_type | + ARRAY OF ocsd_simple_type + + ocsd_simple_types :== ocsd_simple_type | + ocsd_simple_types ocsd_simple_type + + ocsd_simple_type :== BOOLEAN | + INTEGER NUMBER | + SIGNED INTEGER NUMBER | + UNSIGNED INTEGER NUMBER | + IP-ADDRESS | + TEXT | + STRING | + ENCAPSULATE identifier */ + +void +parse_option_code_definition(struct parse *cfile, struct option *option) +{ + const char *val; + enum dhcp_token token; + struct element *def; + unsigned code; + unsigned arrayp = 0; + isc_boolean_t is_array = ISC_FALSE; + int recordp = 0; + isc_boolean_t no_more_in_record = ISC_FALSE; + char *type; + isc_boolean_t is_signed; + isc_boolean_t has_encapsulation = ISC_FALSE; + isc_boolean_t not_supported = ISC_FALSE; + struct string *encapsulated; + struct string *datatype; + struct string *saved; + struct string *format; + struct element *optdef; + + if (option->space->status == special) { + parse_vendor_code_definition(cfile, option); + return; + } + + /* Put the option in the definition */ + def = createMap(); + mapSet(def, + createString(makeString(-1, option->space->name)), + "space"); + mapSet(def, createString(makeString(-1, option->name)), "name"); + TAILQ_CONCAT(&def->comments, &cfile->comments); + + /* Parse the option code. */ + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting option code number."); + TAILQ_CONCAT(&def->comments, &cfile->comments); + code = atoi(val); + mapSet(def, createInt(code), "code"); + + /* We have the code so we can get the real option now */ + if (option->code == 0) { + struct option *from_code = NULL; + + option->code = code; + from_code = option_lookup_code(option->space->old, code); + if (from_code != NULL) { + option->status = from_code->status; + option->format = from_code->format; + } + } + + /* Redefinitions are not allowed */ + if ((option->status != dynamic) || + (strcmp(option->format, "u") != 0)) { + struct comment *comment; + + comment = createComment("/// Kea does not allow redefinition " + "of options"); + TAILQ_INSERT_TAIL(&def->comments, comment); + def->skip = ISC_TRUE; + cfile->issue_counter++; + /* Avoid option-data per name */ + option->status = kea_unknown; + } + + token = next_token(&val, NULL, cfile); + if (token != EQUAL) + parse_error(cfile, "expecting \"=\""); + saved = allocString(); + + /* See if this is an array. */ + token = next_token(&val, NULL, cfile); + if (token == ARRAY) { + token = next_token(&val, NULL, cfile); + if (token != OF) + parse_error(cfile, "expecting \"of\"."); + arrayp = 1; + token = next_token(&val, NULL, cfile); + appendString(saved, "array of"); + } + + if (token == LBRACE) { + recordp = 1; + token = next_token(&val, NULL, cfile); + if (arrayp) + appendString(saved, " "); + appendString(saved, "{"); + } + + /* At this point we're expecting a data type. */ + datatype = allocString(); + /* We record the format essentially for the binary one */ + format = allocString(); + next_type: + if (saved->length > 0) + appendString(saved, " "); + type = NULL; + if (has_encapsulation) + parse_error(cfile, + "encapsulate must always be the last item."); + + switch (token) { + case ARRAY: + if (arrayp) + parse_error(cfile, "no nested arrays."); + if (recordp) { + struct comment *comment; + + comment = createComment("/// unsupported array " + "inside a record"); + TAILQ_INSERT_TAIL(&def->comments, comment); + not_supported = ISC_TRUE; + cfile->issue_counter++; + } + token = next_token(&val, NULL, cfile); + if (token != OF) + parse_error(cfile, "expecting \"of\"."); + arrayp = recordp + 1; + token = next_token(&val, NULL, cfile); + if ((recordp) && (token == LBRACE)) + parse_error(cfile, + "only uniform array inside record."); + appendString(saved, "array of"); + if (token == LBRACE) { + struct comment *comment; + + comment = createComment("/// unsupported record " + "inside an array"); + TAILQ_INSERT_TAIL(&def->comments, comment); + not_supported = ISC_TRUE; + cfile->issue_counter++; + appendString(saved, " {"); + } + goto next_type; + case BOOLEAN: + type = "boolean"; + appendString(format, "f"); + break; + case INTEGER: + is_signed = ISC_TRUE; + parse_integer: + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting number."); + switch (atoi(val)) { + case 8: + if (is_signed) { + type = "int8"; + appendString(format, "b"); + } else { + type = "uint8"; + appendString(format, "B"); + } + break; + case 16: + if (is_signed) { + type = "int16"; + appendString(format, "s"); + } else { + type = "uint16"; + appendString(format, "S"); + } + break; + case 32: + if (is_signed) { + type = "int32"; + appendString(format, "l"); + } else { + type = "uint32"; + appendString(format, "L"); + } + break; + default: + parse_error(cfile, + "%s bit precision is not supported.", val); + } + break; + case SIGNED: + is_signed = ISC_TRUE; + parse_signed: + token = next_token(&val, NULL, cfile); + if (token != INTEGER) + parse_error(cfile, "expecting \"integer\" keyword."); + goto parse_integer; + case UNSIGNED: + is_signed = ISC_FALSE; + goto parse_signed; + + case IP_ADDRESS: + type = "ipv4-address"; + appendString(format, "I"); + break; + case IP6_ADDRESS: + type = "ipv6-address"; + appendString(format, "6"); + break; + case DOMAIN_NAME: + type = "fqdn"; + appendString(format, "d"); + goto no_arrays; + case DOMAIN_LIST: + /* Consume optional compression indicator. */ + token = peek_token(&val, NULL, cfile); + appendString(format, "D"); + type = "fqdn"; + is_array = ISC_TRUE; + if (token == COMPRESSED) { + if (local_family == AF_INET6) + parse_error(cfile, "domain list in DHCPv6 " + "MUST NOT be compressed"); + skip_token(&val, NULL, cfile); + appendString(format, "c"); + appendString(saved, "compressed "); + } + appendString(saved, "list of "); + goto no_arrays; + case TEXT: + type = "string"; + appendString(format, "t"); + no_arrays: + if (arrayp) + parse_error(cfile, "arrays of text strings not %s", + "yet supported."); + no_more_in_record = ISC_TRUE; + break; + case STRING_TOKEN: + /* can be binary too */ + type = "string"; + appendString(format, "x"); + goto no_arrays; + + case ENCAPSULATE: + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, + "expecting option space identifier"); + encapsulated = makeString(-1, val); + has_encapsulation = ISC_TRUE; + appendString(format, "E"); + appendString(format, val); + appendString(format, "."); + appendString(saved, "encapsulate "); + appendString(saved, val); + if (datatype->length == 0) + type = "empty"; + break; + + case ZEROLEN: + type = "empty"; + appendString(format, "Z"); + if (arrayp) + parse_error(cfile, "array incompatible with zerolen."); + no_more_in_record = ISC_TRUE; + break; + + default: + parse_error(cfile, "unknown data type %s", val); + } + appendString(saved, type); + appendString(datatype, type); + + if (recordp) { + token = next_token(&val, NULL, cfile); + if (arrayp > recordp) { + is_array = ISC_TRUE; + arrayp = 0; + appendString(format, "a"); + } + if (token == COMMA) { + if (no_more_in_record) { + char last; + + last = format->content[format->length - 1]; + parse_error(cfile, + "%s must be at end of record.", + last == 't' ? "text" : "string"); + } + token = next_token(&val, NULL, cfile); + appendString(saved, ","); + appendString(datatype, ", "); + goto next_type; + } + if (token != RBRACE) + parse_error(cfile, "expecting right brace."); + appendString(saved, "}"); + } + parse_semi(cfile); + if (has_encapsulation && arrayp) + parse_error(cfile, + "Arrays of encapsulations don't make sense."); + if (arrayp) + appendString(format, (arrayp > recordp) ? "a" : "A"); + if (is_array || arrayp) { + struct element *array_def; + + array_def = createBool(ISC_TRUE); + if (not_supported) + array_def->skip = ISC_TRUE; + mapSet(def, array_def, "array"); + } + + if (not_supported) { + struct element *type_def; + struct element *saved_def; + struct comment *comment; + + saved_def = createString(saved); + saved_def->skip = ISC_TRUE; + mapSet(def, saved_def, "definition"); + type_def = createString(makeString(-1, "binary")); + comment = createComment("/// Option definition is not " + "compatible with Kea"); + TAILQ_INSERT_TAIL(&type_def->comments, comment); + comment = createComment("/// Fallback to full binary"); + TAILQ_INSERT_TAIL(&type_def->comments, comment); + mapSet(def, type_def, "type"); + } else if (recordp) { + mapSet(def, createString(datatype), "record-types"); + mapSet(def, createString(makeString(-1, "record")), "type"); + } else + mapSet(def, createString(datatype), "type"); + + /* Force full binary when the format is not supported by Kea */ + if (not_supported) + appendString(format, "Y"); + option->format = format->content; + + if (has_encapsulation) + mapSet(def, createString(encapsulated), "encapsulate"); + + optdef = mapGet(cfile->stack[1], "option-def"); + if (optdef == NULL) { + optdef = createList(); + mapSet(cfile->stack[1], optdef, "option-def"); + } + listPush(optdef, def); +} + +/* + * Specialized version of parse_option_code_definition for vendor options + * DHCPv4 vivso (code 125, space vendor) and DHCPv6 vendor-opts (17, + * space vsio). The syntax is a subnet: + * vcd :== NUMBER EQUALS ENCAPSULATE identifier SEMI + */ + +void +parse_vendor_code_definition(struct parse *cfile, struct option *option) +{ + const char *val; + enum dhcp_token token; + struct string *id; + struct string *space; + struct space *universe; + struct string *name; + unsigned code; + struct element *vendor; + + space = makeString(-1, "vendor-"); + + /* Parse the option code / vendor id. */ + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "expecting option code number."); + id = makeString(-1, val); + appendString(space, val); + + + token = next_token(&val, NULL, cfile); + if (token != EQUAL) + parse_error(cfile, "expecting \"=\""); + token = next_token(&val, NULL, cfile); + if (token != ENCAPSULATE) + parse_error(cfile, "expecting encapsulate"); + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting option space identifier"); + universe = space_lookup(val); + if (universe == NULL) + parse_error(cfile, "unknown option space %s", val); + /* Map the universe to vendor-<code> */ + universe->name = space->content; + /* Create the vendor option */ + vendor = createMap(); + if (local_family == AF_INET) { + space = makeString(-1, "dhcp4"); + name = makeString(-1, "vivso-suboptions"); + code = DHO_VIVSO_SUBOPTIONS; + } else { + space =makeString(-1, "dhcp6"); + name = makeString(-1, "vendor-opts"); + code = D6O_VENDOR_OPTS; + } + mapSet(vendor, createString(space), "space"); + mapSet(vendor, createString(name), "name"); + mapSet(vendor, createInt(code), "code"); + mapSet(vendor, createString(id), "data"); + universe->vendor = vendor; + parse_semi(cfile); +} + +struct string * +convert_format(const char *fmt, isc_boolean_t *is_array, + isc_boolean_t *encapsulate) +{ + struct string *datatype; + const char *g; + + if ((strchr(fmt, 'A') != NULL) || (strchr(fmt, 'a') != NULL) || + (strchr(fmt, 'D') != NULL)) + *is_array = ISC_TRUE; + + if (strchr(fmt, 'E') != NULL) + *encapsulate = ISC_TRUE; + + if ((strchr(fmt, 'Y') != NULL) || (strchr(fmt, 'A') != NULL) || + (strchr(fmt, 'E') != NULL) || (strchr(fmt, 'o') != NULL) || + (*fmt == 'X') || (*fmt == 'u')) + return makeString(-1, "binary"); + + datatype = allocString(); + + do { + if (datatype->length != 0) + appendString(datatype, ", "); + + switch (*fmt) { + case 'U': + case 't': + case 'x': + appendString(datatype, "string"); + break; + case 'I': + appendString(datatype, "ipv4-address"); + break; + case '6': + appendString(datatype, "ipv6-address"); + break; + case 'l': + appendString(datatype, "int32"); + break; + case 'L': + case 'T': + appendString(datatype, "uint32"); + break; + case 's': + appendString(datatype, "int16"); + break; + case 'S': + appendString(datatype, "uint16"); + break; + case 'b': + appendString(datatype, "int8"); + break; + case 'B': + appendString(datatype, "uint8"); + break; + case 'f': + appendString(datatype, "boolean"); + break; + case 'E': + case 'N': + g = strchr(fmt, '.'); + if (g == NULL) + return makeString(-1, "bad?!"); + if (*fmt == 'N') + return makeString(-1, "unsupported?!"); + fmt = g; + break; + case 'X': + appendString(datatype, "binary"); + break; + case 'd': + case 'D': + appendString(datatype, "fqdn"); + break; + case 'Z': + appendString(datatype, "empty"); + break; + case 'A': + case 'a': + case 'c': + /* ignored */ + break; + default: + return makeString(-1, "unknown?!"); + } + fmt++; + } while (*fmt != '\0'); + + return datatype; +} + +/* + * base64 :== NUMBER_OR_STRING + */ + +struct string * +parse_base64(struct parse *cfile) +{ + const char *val; + unsigned i; + static unsigned char + from64[] = {64, 64, 64, 64, 64, 64, 64, 64, /* \"#$%&' */ + 64, 64, 64, 62, 64, 64, 64, 63, /* ()*+,-./ */ + 52, 53, 54, 55, 56, 57, 58, 59, /* 01234567 */ + 60, 61, 64, 64, 64, 64, 64, 64, /* 89:;<=>? */ + 64, 0, 1, 2, 3, 4, 5, 6, /* @ABCDEFG */ + 7, 8, 9, 10, 11, 12, 13, 14, /* HIJKLMNO */ + 15, 16, 17, 18, 19, 20, 21, 22, /* PQRSTUVW */ + 23, 24, 25, 64, 64, 64, 64, 64, /* XYZ[\]^_ */ + 64, 26, 27, 28, 29, 30, 31, 32, /* 'abcdefg */ + 33, 34, 35, 36, 37, 38, 39, 40, /* hijklmno */ + 41, 42, 43, 44, 45, 46, 47, 48, /* pqrstuvw */ + 49, 50, 51, 64, 64, 64, 64, 64}; /* xyz{|}~ */ + struct string *t; + struct string *r; + isc_boolean_t valid_base64; + + r = allocString(); + + /* It's possible for a + or a / to cause a base64 quantity to be + tokenized into more than one token, so we have to parse them all + in before decoding. */ + do { + unsigned l; + + (void)next_token(&val, &l, cfile); + t = makeString(l, val); + concatString(r, t); + (void)peek_token(&val, NULL, cfile); + valid_base64 = ISC_TRUE; + for (i = 0; val[i]; i++) { + /* Check to see if the character is valid. It + may be out of range or within the right range + but not used in the mapping */ + if (((val[i] < ' ') || (val[i] > 'z')) || + ((from64[val[i] - ' '] > 63) && (val[i] != '='))) { + valid_base64 = ISC_FALSE; + break; /* no need to continue for loop */ + } + } + } while (valid_base64); + + return r; +} + +/* + * colon-separated-hex-list :== NUMBER | + * NUMBER COLON colon-separated-hex-list + */ + +struct string * +parse_cshl(struct parse *cfile) +{ + uint8_t ibuf; + char tbuf[4]; + isc_boolean_t first = ISC_TRUE; + struct string *data; + enum dhcp_token token; + const char *val; + + data = allocString(); + + for (;;) { + token = next_token(&val, NULL, cfile); + if (token != NUMBER && token != NUMBER_OR_NAME) + parse_error(cfile, "expecting hexadecimal number."); + convert_num(cfile, &ibuf, val, 16, 8); + if (first) + snprintf(tbuf, sizeof(tbuf), "%02hhx", ibuf); + else + snprintf(tbuf, sizeof(tbuf), ":%02hhx", ibuf); + first = ISC_FALSE; + appendString(data, tbuf); + + token = peek_token(&val, NULL, cfile); + if (token != COLON) + break; + skip_token(&val, NULL, cfile); + } + + return data; +} + +/* Same but without colons in output */ + +struct string * +parse_hexa(struct parse *cfile) +{ + uint8_t ibuf; + char tbuf[4]; + struct string *data; + enum dhcp_token token; + const char *val; + + data = allocString(); + + for (;;) { + token = next_token(&val, NULL, cfile); + if (token != NUMBER && token != NUMBER_OR_NAME) + parse_error(cfile, "expecting hexadecimal number."); + convert_num(cfile, &ibuf, val, 16, 8); + snprintf(tbuf, sizeof(tbuf), "%02hhx", ibuf); + appendString(data, tbuf); + + token = peek_token(&val, NULL, cfile); + if (token != COLON) + break; + skip_token(&val, NULL, cfile); + } + + return data; +} + +/* + * executable-statements :== executable-statement executable-statements | + * executable-statement + * + * executable-statement :== + * IF if-statement | + * ADD class-name SEMI | + * BREAK SEMI | + * OPTION option-parameter SEMI | + * SUPERSEDE option-parameter SEMI | + * PREPEND option-parameter SEMI | + * APPEND option-parameter SEMI + */ + +isc_boolean_t +parse_executable_statements(struct element *statements, + struct parse *cfile, isc_boolean_t *lose, + enum expression_context case_context) +{ + if (statements->type != ELEMENT_LIST) + parse_error(cfile, "statements is not a list?"); + for (;;) { + struct element *statement; + + statement = createMap(); + TAILQ_CONCAT(&statement->comments, &cfile->comments); + if (!parse_executable_statement(statement, cfile, lose, + case_context, ISC_FALSE)) + break; + TAILQ_CONCAT(&statement->comments, &cfile->comments); + listPush(statements, statement); + } + if (!*lose) + return ISC_TRUE; + + return ISC_FALSE; +} + +isc_boolean_t +parse_executable_statement(struct element *result, + struct parse *cfile, isc_boolean_t *lose, + enum expression_context case_context, + isc_boolean_t direct) +{ + unsigned len; + enum dhcp_token token; + const char *val; + struct element *st; + struct option *option; + struct element *var; + struct element *pri; + struct element *expr; + isc_boolean_t known; + int flag; + int i; + struct element *zone; + struct string *s; + static isc_boolean_t log_warning = ISC_TRUE; + + token = peek_token(&val, NULL, cfile); + switch (token) { + case DB_TIME_FORMAT: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token == DEFAULT) + s = makeString(-1, val); + else if (token == LOCAL) + s = makeString(-1, val); + else + parse_error(cfile, "Expecting 'local' or 'default'."); + + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "Expecting a semicolon."); + st = createString(s); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "db-time-format"); + + /* We're done here. */ + return ISC_TRUE; + + case IF: + skip_token(&val, NULL, cfile); + return parse_if_statement(result, cfile, lose); + + case TOKEN_ADD: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "expecting class name."); + s = makeString(-1, val); + parse_semi(cfile); + st = createString(s); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "add-class"); + break; + + case BREAK: + skip_token(&val, NULL, cfile); + s = makeString(-1, val); + parse_semi(cfile); + st = createNull(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "break"); + break; + + case SEND: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + send_option_statement); + + case SUPERSEDE: + case OPTION: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + supersede_option_statement); + + case ALLOW: + flag = 1; + goto pad; + case DENY: + flag = 0; + goto pad; + case IGNORE: + flag = 2; + pad: + skip_token(&val, NULL, cfile); + st = parse_allow_deny(cfile, flag); + mapSet(result, st, "config"); + break; + + case DEFAULT: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == COLON) + goto switch_default; + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + default_option_statement); + case PREPEND: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + prepend_option_statement); + case APPEND: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + return parse_option_statement(result, cfile, option, + append_option_statement); + + case ON: + skip_token(&val, NULL, cfile); + return parse_on_statement(result, cfile, lose); + + case SWITCH: + skip_token(&val, NULL, cfile); + return parse_switch_statement(result, cfile, lose); + + case CASE: + skip_token(&val, NULL, cfile); + if (case_context == context_any) + parse_error(cfile, + "case statement in inappropriate scope."); + return parse_case_statement(result, + cfile, lose, case_context); + + switch_default: + skip_token(&val, NULL, cfile); + if (case_context == context_any) + parse_error(cfile, "switch default statement in %s", + "inappropriate scope."); + s = makeString(-1, "default"); + st = createNull(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "default"); + return ISC_TRUE; + + case DEFINE: + case TOKEN_SET: + skip_token(&val, NULL, cfile); + if (token == DEFINE) + flag = 1; + else + flag = 0; + + token = next_token(&val, NULL, cfile); + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, + "%s can't be a variable name", val); + st = createMap(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, flag ? "define" : "set"); + var = createString(makeString(-1, val)); + mapSet(st, var, "name"); + token = next_token(&val, NULL, cfile); + + if (token == LPAREN) { + struct element *func; + struct string *args; + + func = createMap(); + args = allocString(); + do { + token = next_token(&val, NULL, cfile); + if (token == RPAREN) + break; + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, + "expecting argument name"); + if (args->length > 0) + appendString(args, ", "); + appendString(args, val); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + + if (token != RPAREN) { + parse_error(cfile, "expecting right paren."); + badx: + skip_to_semi(cfile); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(func, createString(args), "arguments"); + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "expecting left brace."); + + expr = createList(); + if (!parse_executable_statements(expr, cfile, + lose, case_context)) { + if (*lose) + goto badx; + } + mapSet(func, expr, "body"); + mapSet(st, func, "function"); + + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "expecting rigt brace."); + } else { + if (token != EQUAL) + parse_error(cfile, + "expecting '=' in %s statement.", + flag ? "define" : "set"); + + expr = createMap(); + if (!parse_expression(expr, cfile, lose, context_any, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting expression."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(st, expr, "value"); + parse_semi(cfile); + } + break; + + case UNSET: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, "%s can't be a variable name", val); + + st = createMap(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "unset"); + var = createString(makeString(-1, val)); + mapSet(st, var, "name"); + parse_semi(cfile); + break; + + case EVAL: + skip_token(&val, NULL, cfile); + expr = createMap(); + + if (!parse_expression(expr, cfile, lose, + context_data, /* XXX */ + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting data expression."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(result, expr, "eval"); + parse_semi(cfile); + break; + + case EXECUTE: + skip_token(&val, NULL, cfile); + expr = createMap(); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "Expecting a quoted string."); + mapSet(expr, createString(makeString(len, val)), "command"); + + st = createList(); + + while ((token = next_token(&val, NULL, cfile)) == COMMA) { + var = createMap(); + if (!parse_data_expression(var, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting expression."); + skip_to_semi(cfile); + *lose = ISC_TRUE; + return ISC_FALSE; + } + listPush(st, var); + } + mapSet(expr, st, "arguments"); + + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + parse_semi(cfile); + mapSet(result, expr, "execute"); + break; + + case RETURN: + skip_token(&val, NULL, cfile); + + expr = createMap(); + + if (!parse_expression(expr, cfile, lose, context_data, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting data expression."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(result, expr, "return"); + parse_semi(cfile); + break; + + case LOG: + skip_token(&val, NULL, cfile); + + st = createMap(); + st->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, st, "log"); + if (log_warning) { + struct comment *comment; + + comment = createComment("/// Kea does not support " + "yet log statements"); + TAILQ_INSERT_TAIL(&st->comments, comment); + comment= createComment("/// Reference Kea #234"); + TAILQ_INSERT_TAIL(&st->comments, comment); + log_warning = ISC_FALSE; + } + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + token = peek_token(&val, NULL, cfile); + i = 1; + if (token == FATAL) + s = makeString(-1, val); + else if (token == ERROR) + s = makeString(-1, val); + else if (token == TOKEN_DEBUG) + s = makeString(-1, val); + else if (token == INFO) + s = makeString(-1, val); + else { + s = makeString(-1, "DEBUG"); + i = 0; + } + if (i) { + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != COMMA) + parse_error(cfile, "comma expected."); + } + pri = createString(s); + mapSet(st, pri, "priority"); + + expr = createMap(); + if (!parse_data_expression(expr, cfile, lose)) { + skip_to_semi(cfile); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(st, expr, "message"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + + token = next_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + break; + + case PARSE_VENDOR_OPT: + /* The parse-vendor-option; The statement has no arguments. + * We simply set up the statement and when it gets executed it + * will find all information it needs in the packet and options. + */ + skip_token(&val, NULL, cfile); + parse_semi(cfile); + + /* Done by Kea after classification so this statement + * silently does not translate */ + break; + + /* Not really a statement, but we parse it here anyway + because it's appropriate for all DHCP agents with + parsers. */ + case ZONE: + skip_token(&val, NULL, cfile); + zone = createMap(); + zone->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, zone, "zone"); + + s = parse_host_name(cfile); + if (s == NULL) { + parse_error(cfile, "expecting hostname."); + badzone: + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + if (s->content[s->length - 1] != '.') + appendString(s, "."); + mapSet(zone, createString(s), "name"); + if (!parse_zone(zone, cfile)) + goto badzone; + return ISC_TRUE; + + /* Also not really a statement, but same idea as above. */ + case KEY: + skip_token(&val, NULL, cfile); + if (!parse_key(result, cfile)) { + /* Kea TODO */ + *lose = ISC_TRUE; + return ISC_FALSE; + } + return ISC_TRUE; + + default: + if (is_identifier(token)) { + /* the config universe is the server one */ + option = option_lookup_name("server", val); + if (option) { + skip_token(&val, NULL, cfile); + result->skip = ISC_TRUE; + cfile->issue_counter++; + return parse_config_statement + (direct ? NULL : result, + cfile, option, + supersede_option_statement); + } + } + + if (token == NUMBER_OR_NAME || token == NAME) { + /* This is rather ugly. Since function calls are + data expressions, fake up an eval statement. */ + expr = createMap(); + + if (!parse_expression(expr, cfile, lose, context_data, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, "expecting " + "function call."); + else + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + mapSet(result, expr, "eval"); + parse_semi(cfile); + break; + } + + *lose = ISC_FALSE; + return ISC_FALSE; + } + + return ISC_TRUE; +} + +/* zone-statements :== zone-statement | + zone-statement zone-statements + zone-statement :== + PRIMARY ip-addresses SEMI | + SECONDARY ip-addresses SEMI | + PRIMARY6 ip-address6 SEMI | + SECONDARY6 ip-address6 SEMI | + key-reference SEMI + ip-addresses :== ip-addr-or-hostname | + ip-addr-or-hostname COMMA ip-addresses + key-reference :== KEY STRING | + KEY identifier */ + +isc_boolean_t +parse_zone(struct element *zone, struct parse *cfile) +{ + int token; + const char *val; + struct element *values; + struct string *key_name; + isc_boolean_t done = ISC_FALSE; + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "expecting left brace"); + + do { + token = peek_token(&val, NULL, cfile); + switch (token) { + case PRIMARY: + if (mapContains(zone, "primary")) + parse_error(cfile, "more than one primary."); + values = createList(); + mapSet(zone, values, "primary"); + goto consemup; + + case SECONDARY: + if (mapContains(zone, "secondary")) + parse_error(cfile, "more than one secondary."); + values = createList(); + mapSet(zone, values, "secondary"); + consemup: + skip_token(&val, NULL, cfile); + do { + struct string *value; + + value = parse_ip_addr_or_hostname(cfile, + ISC_FALSE); + if (value == NULL) + parse_error(cfile, + "expecting IP addr or " + "hostname."); + listPush(values, createString(value)); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + if (token != SEMI) + parse_error(cfile, "expecting semicolon."); + break; + + case PRIMARY6: + if (mapContains(zone, "primary6")) + parse_error(cfile, "more than one primary6."); + values = createList(); + mapSet(zone, values, "primary6"); + goto consemup6; + + case SECONDARY6: + if (mapContains(zone, "secondary6")) + parse_error(cfile, "more than one secondary6."); + values = createList(); + mapSet(zone, values, "secondary6"); + consemup6: + skip_token(&val, NULL, cfile); + do { + struct string *addr; + + addr = parse_ip6_addr_txt(cfile); + if (addr == NULL) + parse_error(cfile, "expecting IPv6 addr."); + listPush(values, createString(addr)); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + if (token != SEMI) + parse_error(cfile, "expecting semicolon."); + break; + + case KEY: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, NULL, cfile); + key_name = makeString(-1, val); + } else { + key_name = parse_host_name(cfile); + if (!key_name) + parse_error(cfile, "expecting key name."); + } + if (mapContains(zone, "key")) + parse_error(cfile, "Multiple key definitions"); + mapSet(zone, createString(key_name), "key"); + parse_semi(cfile); + break; + + default: + done = 1; + break; + } + } while (!done); + + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "expecting right brace."); + return (1); +} + +/* key-statements :== key-statement | + key-statement key-statements + key-statement :== + ALGORITHM host-name SEMI | + secret-definition SEMI + secret-definition :== SECRET base64val | + SECRET STRING + + Kea: where to put this? It is a D2 value */ + +isc_boolean_t +parse_key(struct element* result, struct parse *cfile) +{ + int token; + const char *val; + isc_boolean_t done = ISC_FALSE; + struct element *key; + struct string *alg; + struct string *sec; + struct element *keys; + char *s; + + key = createMap(); + key->skip = ISC_TRUE; + cfile->issue_counter++; + + token = peek_token(&val, NULL, cfile); + if (token == STRING) { + skip_token(&val, NULL, cfile); + mapSet(key, createString(makeString(-1, val)), "name"); + } else { + struct string *name; + + name = parse_host_name(cfile); + if (name == NULL) + parse_error(cfile, "expecting key name."); + mapSet(key, createString(name), "name"); + } + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "expecting left brace"); + + do { + token = next_token(&val, NULL, cfile); + switch (token) { + case ALGORITHM: + if (mapContains(key, "algorithm")) + parse_error(cfile, "key: too many algorithms"); + alg = parse_host_name(cfile); + if (alg == NULL) + parse_error(cfile, + "expecting key algorithm name."); + parse_semi(cfile); + /* If the algorithm name isn't an FQDN, tack on + the .SIG-ALG.REG.NET. domain. */ + s = strrchr(alg->content, '.'); + if (!s) + appendString(alg, ".SIG-ALG.REG.INT."); + /* If there is no trailing '.', hack one in. */ + else + appendString(alg, "."); + mapSet(key, createString(alg), "algorithm"); + break; + + case SECRET: + if (mapContains(key, "secret")) + parse_error(cfile, "key: too many secrets"); + + sec = parse_base64(cfile); + if (sec == NULL) { + skip_to_rbrace(cfile, 1); + return ISC_FALSE; + } + mapSet(key, createString(sec), "secret"); + + parse_semi(cfile); + break; + + default: + done = ISC_TRUE; + break; + } + } while (!done); + if (token != RBRACE) + parse_error(cfile, "expecting right brace."); + /* Allow the BIND 8 syntax, which has a semicolon after each + closing brace. */ + token = peek_token(&val, NULL, cfile); + if (token == SEMI) + skip_token(&val, NULL, cfile); + + /* Remember the key. */ + keys = mapGet(result, "tsig-keys"); + if (keys == NULL) { + keys = createList(); + mapSet(result, keys, "tsig-keys"); + } + listPush(keys, key); + return ISC_TRUE; +} + +/* + * on-statement :== event-types LBRACE executable-statements RBRACE + * event-types :== event-type OR event-types | + * event-type + * event-type :== EXPIRY | COMMIT | RELEASE + */ + +isc_boolean_t +parse_on_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose) +{ + enum dhcp_token token; + const char *val; + struct element *statement; + struct string *cond; + struct element *body; + + statement = createMap(); + statement->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, statement, "on"); + + cond = allocString(); + do { + token = next_token(&val, NULL, cfile); + switch (token) { + case EXPIRY: + case COMMIT: + case RELEASE: + case TRANSMISSION: + appendString(cond, val); + break; + + default: + parse_error(cfile, "expecting a lease event type"); + } + token = next_token(&val, NULL, cfile); + if (token == OR) + appendString(cond, " or "); + } while (token == OR); + + mapSet(statement, createString(cond), "condition"); + + /* Semicolon means no statements. */ + if (token == SEMI) + return ISC_TRUE; + + if (token != LBRACE) + parse_error(cfile, "left brace expected."); + + body = createList(); + if (!parse_executable_statements(body, cfile, lose, context_any)) { + if (*lose) { + /* Try to even things up. */ + do { + token = next_token(&val, NULL, cfile); + } while (token != END_OF_FILE && token != RBRACE); + return ISC_FALSE; + } + } + mapSet(statement, body, "body"); + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + return ISC_TRUE; +} + +/* + * switch-statement :== LPAREN expr RPAREN LBRACE executable-statements RBRACE + * + */ + +isc_boolean_t +parse_switch_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose) +{ + enum dhcp_token token; + const char *val; + struct element *statement; + struct element *cond; + struct element *body; + + statement = createMap(); + statement->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(result, statement, "switch"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) { + parse_error(cfile, "expecting left brace."); + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + + cond = createMap(); + if (!parse_expression(cond, cfile, lose, context_data_or_numeric, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting data or numeric expression."); + return ISC_FALSE; + } + mapSet(statement, cond, "condition"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right paren expected."); + + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "left brace expected."); + + body = createList(); + if (!parse_executable_statements(body, cfile, lose, + (is_data_expression(cond) ? context_data : context_numeric))) { + if (*lose) { + skip_to_rbrace(cfile, 1); + return ISC_FALSE; + } + } + mapSet(statement, body, "body"); + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + return ISC_TRUE; +} + +/* + * case-statement :== CASE expr COLON + * + */ + +isc_boolean_t +parse_case_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose, + enum expression_context case_context) +{ + enum dhcp_token token; + const char *val; + struct element *expr; + + expr = createMap(); + if (!parse_expression(expr, cfile, lose, case_context, + NULL, expr_none)) + { + if (!*lose) + parse_error(cfile, "expecting %s expression.", + (case_context == context_data + ? "data" : "numeric")); + *lose = ISC_TRUE; + skip_to_semi(cfile); + return ISC_FALSE; + } + + token = next_token(&val, NULL, cfile); + if (token != COLON) + parse_error(cfile, "colon expected."); + mapSet(result, expr, "case"); + return ISC_TRUE; +} + +/* + * if-statement :== boolean-expression LBRACE executable-statements RBRACE + * else-statement + * + * else-statement :== <null> | + * ELSE LBRACE executable-statements RBRACE | + * ELSE IF if-statement | + * ELSIF if-statement + */ + +isc_boolean_t +parse_if_statement(struct element *result, + struct parse *cfile, + isc_boolean_t *lose) +{ + enum dhcp_token token; + const char *val; + isc_boolean_t parenp; + struct element *statement; + struct element *cond; + struct element *branch; + + statement = createMap(); + statement->skip = ISC_TRUE; + cfile->issue_counter++; + + mapSet(result, statement, "if"); + + token = peek_token(&val, NULL, cfile); + if (token == LPAREN) { + parenp = ISC_TRUE; + skip_token(&val, NULL, cfile); + } else + parenp = ISC_FALSE; + + cond = createMap(); + if (!parse_boolean_expression(cond, cfile, lose)) { + if (!*lose) + parse_error(cfile, "boolean expression expected."); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(statement, cond, "condition"); + if (parenp) { + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "expecting right paren."); + } + token = next_token(&val, NULL, cfile); + if (token != LBRACE) + parse_error(cfile, "left brace expected."); + branch = createList(); + if (!parse_executable_statements(branch, cfile, lose, context_any)) { + if (*lose) { + /* Try to even things up. */ + do { + token = next_token(&val, NULL, cfile); + } while (token != END_OF_FILE && token != RBRACE); + return ISC_FALSE; + } + } + mapSet(statement, branch, "then"); + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + token = peek_token(&val, NULL, cfile); + if (token == ELSE) { + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token == IF) { + skip_token(&val, NULL, cfile); + branch = createMap(); + if (!parse_if_statement(branch, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting if statement"); + *lose = ISC_TRUE; + return ISC_FALSE; + } + } else if (token != LBRACE) + parse_error(cfile, "left brace or if expected."); + else { + skip_token(&val, NULL, cfile); + branch = createList(); + if (!parse_executable_statements(branch, cfile, + lose, context_any)) + return ISC_FALSE; + token = next_token(&val, NULL, cfile); + if (token != RBRACE) + parse_error(cfile, "right brace expected."); + } + mapSet(statement, branch, "else"); + } else if (token == ELSIF) { + skip_token(&val, NULL, cfile); + branch = createMap(); + if (!parse_if_statement(branch, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting conditional."); + *lose = ISC_TRUE; + return ISC_FALSE; + } + mapSet(statement, branch, "else"); + } + + return ISC_TRUE; +} + +/* + * boolean_expression :== CHECK STRING | + * NOT boolean-expression | + * data-expression EQUAL data-expression | + * data-expression BANG EQUAL data-expression | + * data-expression REGEX_MATCH data-expression | + * boolean-expression AND boolean-expression | + * boolean-expression OR boolean-expression + * EXISTS OPTION-NAME + */ + +isc_boolean_t +parse_boolean_expression(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose) +{ + /* Parse an expression... */ + if (!parse_expression(expr, cfile, lose, context_boolean, + NULL, expr_none)) + return ISC_FALSE; + + if (!is_boolean_expression(expr) && + !mapContains(expr, "variable-reference") && + !mapContains(expr, "funcall")) + parse_error(cfile, "Expecting a boolean expression."); + return ISC_TRUE; +} + +/* boolean :== ON SEMI | OFF SEMI | TRUE SEMI | FALSE SEMI */ + +isc_boolean_t +parse_boolean(struct parse *cfile) +{ + const char *val; + isc_boolean_t rv; + + (void)next_token(&val, NULL, cfile); + if (!strcasecmp (val, "true") + || !strcasecmp (val, "on")) + rv = ISC_TRUE; + else if (!strcasecmp (val, "false") + || !strcasecmp (val, "off")) + rv = ISC_FALSE; + else + parse_error(cfile, + "boolean value (true/false/on/off) expected"); + parse_semi(cfile); + return rv; +} + +/* + * data_expression :== SUBSTRING LPAREN data-expression COMMA + * numeric-expression COMMA + * numeric-expression RPAREN | + * CONCAT LPAREN data-expression COMMA + * data-expression RPAREN + * SUFFIX LPAREN data_expression COMMA + * numeric-expression RPAREN | + * LCASE LPAREN data_expression RPAREN | + * UCASE LPAREN data_expression RPAREN | + * OPTION option_name | + * HARDWARE | + * PACKET LPAREN numeric-expression COMMA + * numeric-expression RPAREN | + * V6RELAY LPAREN numeric-expression COMMA + * data-expression RPAREN | + * STRING | + * colon_separated_hex_list + */ + +isc_boolean_t +parse_data_expression(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose) +{ + /* Parse an expression... */ + if (!parse_expression(expr, cfile, lose, context_data, + NULL, expr_none)) + return ISC_FALSE; + + if (!is_data_expression(expr) && + !mapContains(expr, "variable-reference") && + !mapContains(expr, "funcall")) + parse_error(cfile, "Expecting a data expression."); + return ISC_TRUE; +} + +/* + * numeric-expression :== EXTRACT_INT LPAREN data-expression + * COMMA number RPAREN | + * NUMBER + */ + +isc_boolean_t +parse_numeric_expression(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose) +{ + /* Parse an expression... */ + if (!parse_expression(expr, cfile, lose, context_numeric, + NULL, expr_none)) + return ISC_FALSE; + + if (!is_numeric_expression(expr) && + !mapContains(expr, "variable-reference") && + !mapContains(expr, "funcall")) + parse_error(cfile, "Expecting a numeric expression."); + return ISC_TRUE; +} + +/* Parse a subexpression that does not contain a binary operator. */ + +isc_boolean_t +parse_non_binary(struct element *expr, + struct parse *cfile, + isc_boolean_t *lose, + enum expression_context context) +{ + enum dhcp_token token; + const char *val; + struct element *nexp; + struct element *arg; + struct element *chain; + struct string *data; + struct comment *comment; + struct option *option; + isc_boolean_t known; + unsigned len; + + token = peek_token(&val, NULL, cfile); + + /* Check for unary operators... */ + switch (token) { + case CHECK: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "string expected."); + nexp = createString(makeString(-1, val)); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "check"); + break; + + case TOKEN_NOT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + + if (!parse_non_binary(nexp, cfile, lose, context_boolean)) { + if (!*lose) + parse_error(cfile, "expression expected"); + *lose = ISC_TRUE; + return ISC_FALSE; + } + if (!is_boolean_expression(nexp)) + parse_error(cfile, "boolean expression expected"); + if (!nexp->skip) { + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, nexp, "not"); + break; + + case LPAREN: + skip_token(&val, NULL, cfile); + if (!parse_expression(expr, cfile, lose, context, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, "expression expected"); + *lose = ISC_TRUE; + return ISC_FALSE; + } + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right paren expected"); + break; + + case EXISTS: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE;; + } + nexp = createMap(); + /* push infos to get it back trying to reduce it */ + mapSet(nexp, + createString(makeString(-1, option->space->old)), + "universe"); + mapSet(nexp, + createString(makeString(-1, option->name)), + "name"); + mapSet(nexp, createInt(option->code), "code"); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "exists"); + break; + + case STATIC: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "static"); + break; + + case KNOWN: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "known"); + break; + + case SUBSTRING: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "substring"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) { + nolparen: + parse_error(cfile, "left parenthesis expected."); + } + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) { + nodata: + if (!*lose) + parse_error(cfile, + "expecting data expression."); + return ISC_FALSE; + } + mapSet(nexp, arg, "expression"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) { + nocomma: + parse_error(cfile, "comma expected."); + } + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) { + nonum: + if (!*lose) + parse_error(cfile, + "expecting numeric expression."); + return ISC_FALSE; + } + mapSet(nexp, arg, "offset"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "length"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) { + norparen: + parse_error(cfile, "right parenthesis expected."); + } + break; + + case SUFFIX: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "suffix"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "expression"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "length"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case LCASE: + skip_token(&val, NULL, cfile); + nexp = createMap(); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression(nexp, cfile, lose)) + goto nodata; + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + if (!nexp->skip) { + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, nexp, "lowercase"); + break; + + case UCASE: + skip_token(&val, NULL, cfile); + nexp = createMap(); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + if (!parse_data_expression(nexp, cfile, lose)) + goto nodata; + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + if (!nexp->skip) { + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, nexp, "uppercase"); + break; + + case CONCAT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "concat"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "left"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + concat_another: + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + + token = next_token(&val, NULL, cfile); + + if (token == COMMA) { + chain = createMap(); + mapSet(nexp, chain, "right"); + nexp = createMap(); + mapSet(chain, nexp, "concat"); + mapSet(nexp, arg, "left"); + goto concat_another; + } + mapSet(nexp, arg, "right"); + + if (token != RPAREN) + goto norparen; + break; + + case BINARY_TO_ASCII: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "binary-to-ascii"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "base"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "width"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "separator"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "buffer"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case REVERSE: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "reverse"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!(parse_numeric_expression(arg, cfile, lose))) + goto nodata; + mapSet(nexp, arg, "width"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!(parse_data_expression(arg, cfile, lose))) + goto nodata; + mapSet(nexp, arg, "buffer"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case PICK: + /* pick (a, b, c) actually produces an internal representation + that looks like pick (a, pick (b, pick (c, nil))). */ + skip_token(&val, NULL, cfile); + nexp = createList(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "pick-first-value"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + do { + arg = createMap(); + if (!(parse_data_expression(arg, cfile, lose))) + goto nodata; + listPush(nexp, arg); + + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + + if (token != RPAREN) + goto norparen; + break; + + case OPTION: + case CONFIG_OPTION: + skip_token(&val, NULL, cfile); + known = ISC_FALSE; + option = parse_option_name(cfile, ISC_FALSE, &known); + if (option == NULL) { + *lose = ISC_TRUE; + return ISC_FALSE; + } + nexp = createMap(); + mapSet(nexp, + createString(makeString(-1, option->space->old)), + "universe"); + mapSet(nexp, + createString(makeString(-1, option->name)), + "name"); + mapSet(nexp, createInt(option->code), "code"); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + if (token == OPTION) + mapSet(expr, nexp, "option"); + else { + createComment("/// config-option is " + "not supported by Kea"); + TAILQ_CONCAT(&nexp->comments, &cfile->comments); + mapSet(expr, nexp, "config-option"); + } + break; + + case HARDWARE: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "hardware"); + break; + + case LEASED_ADDRESS: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "leased-address"); + break; + + case CLIENT_STATE: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "client-state"); + break; + + case FILENAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "filename"); + break; + + case SERVER_NAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "server-name"); + break; + + case LEASE_TIME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "lease-time"); + break; + + case TOKEN_NULL: + skip_token(&val, NULL, cfile); + /* can look at context to return directly ""? */ + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "null"); + break; + + case HOST_DECL_NAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "host-decl-name"); + break; + + case PACKET: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "packet"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "offset"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nonum; + mapSet(nexp, arg, "length"); + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case STRING: + skip_token(&val, &len, cfile); + resetString(expr, makeString(len, val)); + break; + + case EXTRACT_INT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + if (!parse_data_expression(nexp, cfile, lose)) { + if (!*lose) + parse_error(cfile, + "expecting data expression."); + return ISC_FALSE; + } + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + parse_error(cfile, "comma expected."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "number expected."); + switch (atoi(val)) { + case 8: + mapSet(expr, nexp, "extract-int8"); + break; + + case 16: + mapSet(expr, nexp, "extract-int16"); + break; + + case 32: + mapSet(expr, nexp, "extract-int32"); + break; + + default: + parse_error(cfile, "unsupported integer size %s", val); + } + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + break; + + case ENCODE_INT: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + parse_error(cfile, "left parenthesis expected."); + + if (!parse_numeric_expression(nexp, cfile, lose)) + parse_error(cfile, "expecting numeric expression."); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + parse_error(cfile, "comma expected."); + + token = next_token(&val, NULL, cfile); + if (token != NUMBER) + parse_error(cfile, "number expected."); + switch (atoi(val)) { + case 8: + mapSet(expr, nexp, "encode-int8"); + break; + + case 16: + mapSet(expr, nexp, "encode-int16"); + break; + + case 32: + mapSet(expr, nexp, "encode-int32"); + break; + + default: + parse_error(cfile, "unsupported integer size %s", val); + } + + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + parse_error(cfile, "right parenthesis expected."); + break; + + case NUMBER: + /* If we're in a numeric context, this should just be a + number, by itself. */ + if (context == context_numeric || + context == context_data_or_numeric) { + skip_token(&val, NULL, cfile); + /* can also return a const-int */ + resetInt(expr, atoi(val)); + break; + } + + case NUMBER_OR_NAME: + /* Return a const-data to make a difference with + a string literal. createHexa() adds 0x */ + mapSet(expr, createHexa(parse_hexa(cfile)), "const-data"); + break; + + case NS_FORMERR: + skip_token(&val, NULL, cfile); +#ifndef FORMERR +#define FORMERR 1 +#endif + resetInt(expr, FORMERR); + comment = createComment("/// constant FORMERR(1)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOERROR: + skip_token(&val, NULL, cfile); +#ifndef ISC_R_SUCCESS +#define ISC_R_SUCCESS 0 +#endif + resetInt(expr, ISC_R_SUCCESS); + comment = createComment("/// constant ISC_R_SUCCESS(0)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOTAUTH: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NOTAUTH +#define DHCP_R_NOTAUTH ((6 << 16) + 21) +#endif + resetInt(expr, DHCP_R_NOTAUTH); + comment = createComment("/// constant DHCP_R_NOTAUTH(393237)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOTIMP: + skip_token(&val, NULL, cfile); +#ifndef ISC_R_NOTIMPLEMENTED +#define ISC_R_NOTIMPLEMENTED 27 +#endif + resetInt(expr, ISC_R_NOTIMPLEMENTED); + comment = createComment("/// constant ISC_R_NOTIMPLEMENTED(27)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NOTZONE: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NOTZONE +#define DHCP_R_NOTZONE ((6 << 16) + 22) +#endif + resetInt(expr, DHCP_R_NOTZONE); + comment = createComment("/// constant DHCP_R_NOTZONE(393238)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NXDOMAIN: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NXDOMAIN +#define DHCP_R_NXDOMAIN ((6 << 16) + 15) +#endif + resetInt(expr, DHCP_R_NXDOMAIN); + comment = createComment("/// constant DHCP_R_NXDOMAIN(393231)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_NXRRSET: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_NXRRSET +#define DHCP_R_NXRRSET ((6 << 16) + 20) +#endif + resetInt(expr, DHCP_R_NXRRSET); + comment = createComment("/// constant DHCP_R_NXRRSET(393236)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_REFUSED: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_REFUSED +#define DHCP_R_REFUSED ((6 << 16) + 17) +#endif + resetInt(expr, DHCP_R_REFUSED); + comment = createComment("/// constant DHCP_R_REFUSED(393233)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_SERVFAIL: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_SERVFAIL +#define DHCP_R_SERVFAIL ((6 << 16) + 14) +#endif + resetInt(expr, DHCP_R_SERVFAIL); + comment = createComment("/// constant DHCP_R_SERVFAIL(393230)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_YXDOMAIN: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_YXDOMAIN +#define DHCP_R_YXDOMAIN ((6 << 16) + 18) +#endif + resetInt(expr, DHCP_R_YXDOMAIN); + comment = createComment("/// constant DHCP_R_YXDOMAIN(393234)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case NS_YXRRSET: + skip_token(&val, NULL, cfile); +#ifndef DHCP_R_YXRRSET +#define DHCP_R_YXRRSET ((6 << 16) + 19) +#endif + resetInt(expr, DHCP_R_YXRRSET); + comment = createComment("/// constant DHCP_R_YXRRSET(393235)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case BOOTING: + skip_token(&val, NULL, cfile); +#ifndef S_INIT +#define S_INIT 2 +#endif + resetInt(expr, S_INIT); + comment = createComment("/// constant S_INIT(2)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case REBOOT: + skip_token(&val, NULL, cfile); +#ifndef S_REBOOTING +#define S_REBOOTING 1 +#endif + resetInt(expr, S_REBOOTING); + comment = createComment("/// constant S_REBOOTING(1)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case SELECT: + skip_token(&val, NULL, cfile); +#ifndef S_SELECTING +#define S_SELECTING 3 +#endif + resetInt(expr, S_SELECTING); + comment = createComment("/// constant S_SELECTING(3)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case REQUEST: + skip_token(&val, NULL, cfile); +#ifndef S_REQUESTING +#define S_REQUESTING 4 +#endif + resetInt(expr, S_REQUESTING); + comment = createComment("/// constant S_REQUESTING(4)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case BOUND: + skip_token(&val, NULL, cfile); +#ifndef S_BOUND +#define S_BOUND 5 +#endif + resetInt(expr, S_BOUND); + comment = createComment("/// constant S_BOUND(5)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case RENEW: + skip_token(&val, NULL, cfile); +#ifndef S_RENEWING +#define S_RENEWING 6 +#endif + resetInt(expr, S_RENEWING); + comment = createComment("/// constant S_RENEWING(6)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case REBIND: + skip_token(&val, NULL, cfile); +#ifndef S_REBINDING +#define S_REBINDING 7 +#endif + resetInt(expr, S_REBINDING); + comment = createComment("/// constant S_REBINDING(7)"); + TAILQ_INSERT_TAIL(&expr->comments, comment); + break; + + case DEFINED: + skip_token(&val, NULL, cfile); + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token(&val, NULL, cfile); + if (token != NAME && token != NUMBER_OR_NAME) + parse_error(cfile, "%s can't be a variable name", val); + + nexp = createString(makeString(-1, val)); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "variable-exists"); + token = next_token(&val, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + /* This parses 'gethostname()'. */ + case GETHOSTNAME: + skip_token(&val, NULL, cfile); + nexp = createNull(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "gethostname"); + + token = next_token(NULL, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + token = next_token(NULL, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case GETHOSTBYNAME: + skip_token(&val, NULL, cfile); + token = next_token(NULL, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + /* The argument is a quoted string. */ + token = next_token(&val, NULL, cfile); + if (token != STRING) + parse_error(cfile, "Expecting quoted literal: " + "\"foo.example.com\""); + nexp = createString(makeString(-1, val)); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "gethostbyname"); + + token = next_token(NULL, NULL, cfile); + if (token != RPAREN) + goto norparen; + break; + + case V6RELAY: + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "v6relay"); + + token = next_token(&val, NULL, cfile); + if (token != LPAREN) + goto nolparen; + + arg = createMap(); + if (!parse_numeric_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "relay"); + + token = next_token(&val, NULL, cfile); + if (token != COMMA) + goto nocomma; + + arg = createMap(); + if (!parse_data_expression(arg, cfile, lose)) + goto nodata; + mapSet(nexp, arg, "relay-option"); + + token = next_token(&val, NULL, cfile); + + if (token != RPAREN) + goto norparen; + break; + + /* Not a valid start to an expression... */ + default: + if (token != NAME && token != NUMBER_OR_NAME) + return ISC_FALSE; + + skip_token(&val, NULL, cfile); + + /* Save the name of the variable being referenced. */ + data = makeString(-1, val); + + /* Simple variable reference, as far as we can tell. */ + token = peek_token(&val, NULL, cfile); + if (token != LPAREN) { + nexp = createString(data); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "variable-reference"); + break; + } + + skip_token(&val, NULL, cfile); + nexp = createMap(); + nexp->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(expr, nexp, "funcall"); + chain = createString(data); + mapSet(nexp, chain, "name"); + + /* Now parse the argument list. */ + chain = createList(); + do { + arg = createMap(); + if (!parse_expression(arg, cfile, lose, context_any, + NULL, expr_none)) { + if (!*lose) + parse_error(cfile, + "expecting expression."); + skip_to_semi(cfile); + return ISC_FALSE; + } + listPush(chain, arg); + token = next_token(&val, NULL, cfile); + } while (token == COMMA); + if (token != RPAREN) + parse_error(cfile, "Right parenthesis expected."); + mapSet(nexp, chain, "arguments"); + break; + } + return ISC_TRUE; +} + +/* Parse an expression. */ + +isc_boolean_t +parse_expression(struct element *expr, struct parse *cfile, + isc_boolean_t *lose, enum expression_context context, + struct element *lhs, enum expr_op binop) +{ + enum dhcp_token token; + const char *val; + struct element *rhs, *tmp; + enum expr_op next_op; + enum expression_context + lhs_context = context_any, + rhs_context = context_any; + const char *binop_name; + +new_rhs: + rhs = createMap(); + if (!parse_non_binary(rhs, cfile, lose, context)) { + /* If we already have a left-hand side, then it's not + okay for there not to be a right-hand side here, so + we need to flag it as an error. */ + if (lhs) + if (!*lose) + parse_error(cfile, + "expecting right-hand side."); + return ISC_FALSE; + } + + /* At this point, rhs contains either an entire subexpression, + or at least a left-hand-side. If we do not see a binary token + as the next token, we're done with the expression. */ + + token = peek_token(&val, NULL, cfile); + switch (token) { + case BANG: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + if (token != EQUAL) + parse_error(cfile, "! in boolean context without ="); + next_op = expr_not_equal; + context = expression_context(rhs); + break; + + case EQUAL: + next_op = expr_equal; + context = expression_context(rhs); + break; + + case TILDE: + skip_token(&val, NULL, cfile); + token = peek_token(&val, NULL, cfile); + + if (token == TILDE) + next_op = expr_iregex_match; + else if (token == EQUAL) + next_op = expr_regex_match; + else + parse_error(cfile, "expecting ~= or ~~ operator"); + + context = expression_context(rhs); + break; + + case AND: + next_op = expr_and; + context = expression_context(rhs); + break; + + case OR: + next_op = expr_or; + context = expression_context(rhs); + break; + + case PLUS: + next_op = expr_add; + context = expression_context(rhs); + break; + + case MINUS: + next_op = expr_subtract; + context = expression_context(rhs); + break; + + case SLASH: + next_op = expr_divide; + context = expression_context(rhs); + break; + + case ASTERISK: + next_op = expr_multiply; + context = expression_context(rhs); + break; + + case PERCENT: + next_op = expr_remainder; + context = expression_context(rhs); + break; + + case AMPERSAND: + next_op = expr_binary_and; + context = expression_context(rhs); + break; + + case PIPE: + next_op = expr_binary_or; + context = expression_context(rhs); + break; + + case CARET: + next_op = expr_binary_xor; + context = expression_context(rhs); + break; + + default: + next_op = expr_none; + } + + /* If we have no lhs yet, we just parsed it. */ + if (!lhs) { + /* If there was no operator following what we just parsed, + then we're done - return it. */ + if (next_op == expr_none) { + resetBy(expr, rhs); + return ISC_TRUE; + } + + lhs = rhs; + rhs = NULL; + binop = next_op; + skip_token(&val, NULL, cfile); + goto new_rhs; + } + + /* If the next binary operator is of greater precedence than the + * current operator, then rhs we have parsed so far is actually + * the lhs of the next operator. To get this value, we have to + * recurse. + */ + if (binop != expr_none && next_op != expr_none && + op_precedence(binop, next_op) < 0) { + + /* Eat the subexpression operator token, which we pass to + * parse_expression...we only peek()'d earlier. + */ + skip_token(&val, NULL, cfile); + + /* Continue parsing of the right hand side with that token. */ + tmp = rhs; + rhs = createMap(); + if (!parse_expression(rhs, cfile, lose, op_context(next_op), + tmp, next_op)) { + if (!*lose) + parse_error(cfile, + "expecting a subexpression"); + return ISC_FALSE; + } + next_op = expr_none; + } + + binop_name = "none"; + if (binop != expr_none) { + rhs_context = expression_context(rhs); + lhs_context = expression_context(lhs); + + if ((rhs_context != context_any) && + (lhs_context != context_any) && + (rhs_context != lhs_context)) + parse_error(cfile, "illegal expression relating " + "different types"); + + switch (binop) { + case expr_not_equal: + binop_name = "not-equal"; + goto data_numeric; + case expr_equal: + binop_name = "equal"; + data_numeric: + if ((rhs_context != context_data_or_numeric) && + (rhs_context != context_data) && + (rhs_context != context_numeric) && + (rhs_context != context_any)) + parse_error(cfile, "expecting data/numeric " + "expression"); + break; + + case expr_iregex_match: + binop_name = "iregex-match"; + break; + + case expr_regex_match: + binop_name = "regex-match"; + if (expression_context(rhs) != context_data) + parse_error(cfile, + "expecting data expression"); + break; + + case expr_and: + binop_name = "and"; + goto boolean; + case expr_or: + binop_name = "or"; + boolean: + if ((rhs_context != context_boolean) && + (rhs_context != context_any)) { + parse_error(cfile, + "expecting boolean expressions"); + } + break; + + case expr_add: + binop_name = "add"; + goto numeric; + case expr_subtract: + binop_name = "subtract"; + goto numeric; + case expr_divide: + binop_name = "divide"; + goto numeric; + case expr_multiply: + binop_name = "multiply"; + goto numeric; + case expr_remainder: + binop_name = "remainder"; + goto numeric; + case expr_binary_and: + binop_name = "binary-and"; + goto numeric; + case expr_binary_or: + binop_name = "binary-or"; + goto numeric; + case expr_binary_xor: + binop_name = "binary-xor"; + numeric: + if ((rhs_context != context_numeric) && + (rhs_context != context_any)) + parse_error(cfile, + "expecting numeric expressions"); + break; + + default: + break; + } + } + + /* Now, if we didn't find a binary operator, we're done parsing + this subexpression, so combine it with the preceding binary + operator and return the result. */ + if (next_op == expr_none) { + tmp = createMap(); + tmp->skip = ISC_TRUE; + mapSet(expr, tmp, binop_name); + /* All the binary operators' data union members + are the same, so we'll cheat and use the member + for the equals operator. */ + mapSet(tmp, lhs, "left"); + mapSet(tmp, rhs, "right"); + return ISC_TRUE;; + } + + /* Eat the operator token - we now know it was a binary operator... */ + skip_token(&val, NULL, cfile); + + /* Now combine the LHS and the RHS using binop. */ + tmp = createMap(); + tmp->skip = ISC_TRUE; + + /* Store the LHS and RHS. */ + mapSet(tmp, lhs, "left"); + mapSet(tmp, rhs, "right"); + + lhs = createMap(); + mapSet(lhs, tmp, binop_name); + + tmp = NULL; + rhs = NULL; + + binop = next_op; + goto new_rhs; +} + +/* Escape embedded commas, detected heading and leading space */ +struct string * +escape_option_string(unsigned len, const char *val, + isc_boolean_t *require_binary, + isc_boolean_t *modified) +{ + struct string *result; + struct string *add; + unsigned i; + char s[2]; + + result = allocString(); + add = allocString(); + if ((len > 0) && (isspace(val[0]) || isspace(val[len - 1]))) { + *require_binary = ISC_TRUE; + return result; + } + for (i = 0; i < len; i++) { + if (val[i] == ',') { + add->length = 2; + add->content = "\\,"; + *modified = ISC_TRUE; + } else { + add->length = 1; + s[0] = val[i]; + s[1] = 0; + add->content = s; + } + concatString(result, add); + } + free(add); + return result; +} + +isc_boolean_t +parse_option_data(struct element *expr, + struct parse *cfile, + struct option *option) +{ + const char *val; + const char *fmt; + enum dhcp_token token; + unsigned len; + struct string *data; + struct string *saved; + struct string *item; + struct element *elem; + struct comment *comment; + isc_boolean_t require_binary = ISC_FALSE; + isc_boolean_t canon_bool = ISC_FALSE; + isc_boolean_t modified = ISC_FALSE; + + /* Save the initial content */ + saved = allocString(); + save_parse_state(cfile); + for (;;) { + token = next_raw_token(&val, &len, cfile); + if ((token == SEMI) || (token == END_OF_FILE)) + break; + item = makeString(len, val); + if (token == STRING) { + appendString(saved, "\""); + concatString(saved, item); + appendString(saved, "\""); + } else + concatString(saved, item); + } + restore_parse_state(cfile); + + elem = createString(saved); + elem->skip = ISC_TRUE; + mapSet(expr, elem, "original-data"); + + /* Check for binary case */ + + fmt = option->format; + + if ((fmt == NULL) || (*fmt == 0)) + parse_error(cfile, "unknown format for option %s.%s\n", + option->space->name, option->name); + + if ((strchr(fmt, 'Y') != NULL) || (strchr(fmt, 'A') != NULL) || + (strchr(fmt, 'E') != NULL) || (strchr(fmt, 'o') != NULL) || + (*fmt == 'X') || (*fmt == 'u')) + return parse_option_binary(expr, cfile, option, ISC_FALSE); + + if (strchr(fmt, 'N') != NULL) + parse_error(cfile, "unsupported format %s for option %s.%s\n", + fmt, option->space->name, option->name); + + data = allocString(); + + save_parse_state(cfile); + /* Just collect data expecting ISC DHCP and Kea are compatible */ + do { + /* Set fmt one char back for 'a'. */ + if ((fmt != option->format) && (*fmt == 'a')) + fmt -= 1; + + do { + if (*fmt == 'a') + break; + if (data->length != 0) + appendString(data, ", "); + item = parse_option_token(cfile, fmt, &require_binary, + &canon_bool, &modified); + if ((*fmt == 'D') && (fmt[1] == 'c')) + fmt++; + if (require_binary) { + restore_parse_state(cfile); + return parse_option_binary(expr, cfile, option, + item == NULL); + } + if (item == NULL) + parse_error(cfile, "parse_option_data failed"); + concatString(data, item); + fmt++; + } while (*fmt != '\0'); + + if (*fmt == 'a') { + token = peek_token(&val, NULL, cfile); + /* Comma means: continue with next element in array */ + if (token == COMMA) { + skip_token(&val, NULL, cfile); + continue; + } + /* no comma: end of array. + end of string means: leave the loop */ + if (fmt[1] == '\0') + break; + /* 'a' means: go on with next char */ + if (*fmt == 'a') { + fmt++; + continue; + } + } + } while (*fmt == 'a'); + + if (!modified || eqString(saved, data)) + mapRemove(expr, "original-data"); + + elem = createString(data); + if (canon_bool) { + comment = createComment("/// canonized booleans to " + "lowercase true or false"); + TAILQ_INSERT_TAIL(&elem->comments, comment); + } + mapSet(expr, elem, "data"); + + return ISC_TRUE; +} + +isc_boolean_t +parse_option_binary(struct element *expr, struct parse *cfile, + struct option *option, isc_boolean_t ambiguous) +{ + const char *val; + const char *fmt; + enum dhcp_token token; + struct string *data; + struct string *item; + struct element *elem; + struct comment *comment; + const char *g; + + data = allocString(); + fmt = option->format; + + mapSet(expr, createBool(ISC_FALSE), "csv-format"); + + /* Just collect data expecting ISC DHCP and Kea are compatible */ + do { + /* Set fmt to start of format for 'A' and one char back + * for 'a'. + */ + if ((fmt != option->format) && (*fmt == 'a')) + fmt -= 1; + else if (*fmt == 'A') + fmt = option->format; + + do { + if ((*fmt == 'A') || (*fmt == 'a')) + break; + if (*fmt == 'o') { + /* consume the optional flag */ + fmt++; + continue; + } + + if (fmt[1] == 'o') { + /* + * A value for the current format is + * optional - check to see if the next + * token is a semi-colon if so we don't + * need to parse it and doing so would + * consume the semi-colon which our + * caller is expecting to parse + */ + token = peek_token(&val, NULL, cfile); + if (token == SEMI) { + fmt++; + continue; + } + } + + item = parse_option_token_binary(cfile, fmt); + switch (*fmt) { + case 'E': + g = strchr(fmt, '.'); + if (g == NULL) + parse_error(cfile, + "malformed encapsulation " + "format (bug!)"); + fmt = g; + break; + case 'D': + if (fmt[1] == 'c') + fmt++; + break; + case 'N': + g = strchr(fmt, '.'); + if (g == NULL) + parse_error(cfile, + "malformed enumeration " + "format (bug!)"); + fmt = g; + break; + } + if (item != NULL) + concatString(data, item); + else if (fmt[1] != 'o') + parse_error(cfile, "parse_option_token_binary " + "failed"); + fmt++; + } while (*fmt != '\0'); + + if ((*fmt == 'A') || (*fmt == 'a')) { + token = peek_token(&val, NULL, cfile); + /* Comma means: continue with next element in array */ + if (token == COMMA) { + skip_token(&val, NULL, cfile); + continue; + } + /* no comma: end of array. + 'A' or end of string means: leave the loop */ + if ((*fmt == 'A') || (fmt[1] == '\0')) + break; + /* 'a' means: go on with next char */ + if (*fmt == 'a') { + fmt++; + continue; + } + } + } while ((*fmt == 'A') || (*fmt == 'a')); + + elem = mapGet(expr, "original-data"); + if ((elem != NULL) && eqString(stringValue(elem), data)) + mapRemove(expr, "original-data"); + + elem = createString(data); + if (ambiguous) { + comment = createComment("/// Please consider to change " + "last type in the record to binary"); + TAILQ_INSERT_TAIL(&elem->comments, comment); + comment = createComment("/// Reference Kea #246"); + TAILQ_INSERT_TAIL(&elem->comments, comment); + expr->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(expr, elem, "data"); + + return ISC_TRUE; +} + +struct string * +parse_option_textbin(struct parse *cfile, struct option *option) +{ + struct element *expr; + struct element *data; + const char *fmt; + + expr = createMap(); + fmt = option->format; + + if ((fmt == NULL) || (*fmt == 0)) + parse_error(cfile, "unknown format for option %s.%s\n", + option->space->name, option->name); + + if (strcmp(fmt, "t") != 0) { + if (!parse_option_binary(expr, cfile, option, ISC_FALSE)) + parse_error(cfile, "can't parse binary option data"); + data = mapGet(expr, "data"); + if (data == NULL) + parse_error(cfile, "can't get binary option data"); + if (data->type != ELEMENT_STRING) + parse_error(cfile, "option data must be binary"); + return stringValue(data); + } + + if (!parse_option_data(expr, cfile, option)) + parse_error(cfile, "can't parse text option data"); + data = mapGet(expr, "data"); + if (data == NULL) + parse_error(cfile, "can't get test option data"); + if (data->type != ELEMENT_STRING) + parse_error(cfile, "option data must be a string"); + return quote(stringValue(data)); +} + +/* option-statement :== identifier DOT identifier <syntax> SEMI + | identifier <syntax> SEMI + + Option syntax is handled specially through format strings, so it + would be painful to come up with BNF for it. However, it always + starts as above and ends in a SEMI. */ + +isc_boolean_t +parse_option_statement(struct element *result, + struct parse *cfile, + struct option *option, + enum statement_op op) +{ + const char *val; + enum dhcp_token token; + struct element *expr; + struct element *opt_data; + struct element *opt_data_list; + isc_boolean_t lose; + size_t where; + + if (option->space == space_lookup("server")) + return parse_config_statement(result, cfile, option, op); + + opt_data = createMap(); + TAILQ_CONCAT(&opt_data->comments, &cfile->comments); + mapSet(opt_data, + createString(makeString(-1, option->space->name)), "space"); + mapSet(opt_data, createString(makeString(-1, option->name)), "name"); + mapSet(opt_data, createInt(option->code), "code"); + if (option->status == kea_unknown) { + opt_data->skip = ISC_TRUE; + cfile->issue_counter++; + } + if (op != supersede_option_statement) { + struct string *msg; + struct comment *comment; + + msg = makeString(-1, "/// Kea does not support option data "); + appendString(msg, "set variants ("); + switch (op) { + case send_option_statement: + appendString(msg, "send"); + break; + case supersede_option_statement: + appendString(msg, "supersede"); + break; + case default_option_statement: + appendString(msg, "default"); + break; + case prepend_option_statement: + appendString(msg, "prepend"); + break; + case append_option_statement: + appendString(msg, "append"); + break; + default: + appendString(msg, "???"); + break; + } + appendString(msg, ")"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + } + + /* Setting PRL is a standard hack */ + if ((option->space == space_lookup("dhcp")) && + (option->code == 55)) { + struct comment *comment; + + comment = createComment("/// Possible PRL hack"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + comment = createComment("/// Consider setting \"always-send\" " + "to true when setting data " + "for relevant options, cf Kea #250"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + } + + /* Setting ORO is a standard hack */ + if ((option->space == space_lookup("dhcp6")) && + (option->code == 6)) { + struct comment *comment; + + comment = createComment("/// Possible ORO hack"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + comment = createComment("/// Consider setting \"always-send\" " + "to true when setting data " + "for relevant options, cf Kea #250"); + TAILQ_INSERT_TAIL(&opt_data->comments, comment); + } + + token = peek_token(&val, NULL, cfile); + /* We should keep a list of defined empty options */ + if ((token == SEMI) && (option->format[0] != 'Z')) { + /* Eat the semicolon... */ + /* + * XXXSK: I'm not sure why we should ever get here, but we + * do during our startup. This confuses things if + * we are parsing a zero-length option, so don't + * eat the semicolon token in that case. + */ + skip_token(&val, NULL, cfile); + } else if (token == EQUAL) { + struct element *data; + isc_boolean_t modified = ISC_FALSE; + + /* Eat the equals sign. */ + skip_token(&val, NULL, cfile); + + /* Parse a data expression and use its value for the data. */ + expr = createMap(); + if (!parse_data_expression(expr, cfile, &lose)) { + /* In this context, we must have an executable + statement, so if we found something else, it's + still an error. */ + if (!lose) + parse_error(cfile, + "expecting a data expression."); + return ISC_FALSE; + } + /* evaluate the expression */ + expr = eval_data_expression(expr, &modified); + + mapSet(opt_data, createBool(ISC_FALSE), "csv-format"); + + if (expr->type == ELEMENT_STRING) { + struct string *s; + struct string *r; + + s = stringValue(expr); + expr->skip = ISC_TRUE; + mapSet(opt_data, expr, "original-data"); + + r = makeStringExt(s->length, s->content, 'X'); + data = createString(r); + mapSet(opt_data, data, "data"); + } else if ((expr->type == ELEMENT_MAP) && + mapContains(expr, "const-data")) { + struct element *value; + struct string *r; + + value = mapGet(expr, "const-data"); + if ((value == NULL) || (value->type != ELEMENT_STRING)) + parse_error(cfile, "can't get const-data"); + r = hexaValue(value); + data = createString(r); + mapSet(opt_data, data, "data"); + } else { + opt_data->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(opt_data, expr, "expression"); + } + } else { + if (!parse_option_data(opt_data, cfile, option)) + return ISC_FALSE; + } + + parse_semi(cfile); + + if (result != NULL) { + opt_data->skip = ISC_TRUE; + mapSet(result, opt_data, "option"); + return ISC_TRUE; + } + + for (where = cfile->stack_top; where > 0; --where) { + if (cfile->stack[where]->kind != PARAMETER) + break; + } + + opt_data_list = mapGet(cfile->stack[where], "option-data"); + if (opt_data_list == NULL) { + opt_data_list = createList(); + mapSet(cfile->stack[where], opt_data_list, "option-data"); + } + if (!opt_data->skip && (option->space->vendor != NULL)) + add_option_data(option->space->vendor, opt_data_list); + listPush(opt_data_list, opt_data); + + return ISC_TRUE; +} + +/* Text version of parse_option_token */ + +struct string * +parse_option_token(struct parse *cfile, const char *fmt, + isc_boolean_t *require_binary, + isc_boolean_t *canon_bool, + isc_boolean_t *modified) +{ + const char *val; + enum dhcp_token token; + unsigned len; + struct string *item; + + switch (*fmt) { + case 'U': + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + return makeString(len, val); + case 'x': + token = peek_token(&val, NULL, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) { + *require_binary = ISC_TRUE; + return NULL; + } + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "expecting string " + "or hexadecimal data."); + /* STRING can return embedded unexpected characters */ + return escape_option_string(len, val, require_binary, + modified); + case 'X': + token = peek_token(&val, NULL, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) { + return parse_hexa(cfile); + } + token = next_token(&val, &len, cfile); + if (token != STRING) + parse_error(cfile, "expecting string " + "or hexadecimal data."); + return makeStringExt(len, val, 'X'); + + case 'D': /* Domain list... */ + *modified = ISC_TRUE; + return parse_domain_list(cfile, ISC_FALSE); + + case 'd': /* Domain name... */ + *modified = ISC_TRUE; + item = parse_host_name(cfile); + if (item == NULL) + parse_error(cfile, "not a valid domain name."); + return item; + + case 't': /* Text string... */ + token = next_token(&val, &len, cfile); + if (token != STRING && !is_identifier(token)) + parse_error(cfile, "expecting string."); + /* STRING can return embedded unexpected characters */ + return escape_option_string(len, val, require_binary, + modified); + + case 'I': /* IP address or hostname. */ + *modified = ISC_TRUE; + return parse_ip_addr_or_hostname(cfile, ISC_FALSE); + + case '6': /* IPv6 address. */ + *modified = ISC_TRUE; + return parse_ip6_addr_txt(cfile); + + case 'T': /* Lease interval. */ + token = next_token(&val, NULL, cfile); + if (token == INFINITE) + return makeString(-1, "0xffffffff"); + goto check_number; + + case 'L': /* Unsigned 32-bit integer... */ + case 'l': + case 's': /* Signed 16-bit integer. */ + case 'S': /* Unsigned 16-bit integer. */ + case 'b': /* Signed 8-bit integer. */ + case 'B': /* Unsigned 8-bit integer. */ + token = next_token(&val, NULL, cfile); + check_number: + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + /* check octal */ + if (val[0] == '0' && isascii(val[1]) && isdigit(val[1])) + *require_binary = ISC_TRUE; + return makeString(-1, val); + + case 'f': /* Boolean flag. */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + if (strcasecmp(val, "true") == 0) + return makeString(-1, "true"); + if (strcasecmp(val, "on") == 0) { + *canon_bool = ISC_TRUE; + *modified = ISC_TRUE; + return makeString(-1, "true"); + } + if (strcasecmp(val, "false") == 0) + return makeString(-1, "false"); + if (strcasecmp(val, "off") == 0) { + *canon_bool = ISC_TRUE; + *modified = ISC_TRUE; + return makeString(-1, "false"); + } + parse_error(cfile, "expecting boolean."); + + case 'Z': /* Zero-length option. */ + token = peek_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + return allocString(); + + default: + parse_error(cfile, "Bad format '%c' in parse_option_token.", + *fmt); + } +} + +/* Binary (aka hexadecimal) version of parse_option_token */ + +struct string * +parse_option_token_binary(struct parse *cfile, const char *fmt) +{ + const char *val; + enum dhcp_token token; + unsigned len; + struct string *item; + uint8_t buf[4]; + + switch (*fmt) { + case 'U': + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting identifier."); + } + return makeStringExt(len, val, 'X'); + case 'E': + case 'X': + case 'x': + case 'u': + token = peek_token(&val, NULL, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) + return parse_hexa(cfile); + token = next_token(&val, &len, cfile); + if (token != STRING) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting string " + "or hexadecimal data."); + } + return makeStringExt(len, val, 'X'); + + case 'D': /* Domain list... */ + item = parse_domain_list(cfile, ISC_TRUE); + if (item == NULL) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "parse_domain_list failed"); + } + return NULL; + + case 'd': /* Domain name... */ + item = parse_host_name(cfile); + if (item == NULL) + parse_error(cfile, "not a valid domain name."); + item = makeStringExt(item->length, item->content, 'd'); + if (item == NULL) + parse_error(cfile, "too long domain name."); + return makeStringExt(item->length, item->content, 'X'); + + case 't': /* Text string... */ + token = next_token(&val, &len, cfile); + if (token != STRING && !is_identifier(token)) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting string."); + } + return makeStringExt(len, val, 'X'); + + case 'I': /* IP address or hostname. */ + item = parse_ip_addr_or_hostname(cfile, ISC_FALSE); + return makeStringExt(item->length, item->content, 'i'); + + case '6': /* IPv6 address. */ + item = parse_ip6_addr(cfile); + return makeStringExt(item->length, item->content, 'X'); + + case 'T': /* Lease interval. */ + token = next_token(&val, NULL, cfile); + if (token == INFINITE) + return makeString(-1, "ffffffff"); + goto check_number; + + case 'L': /* Unsigned 32-bit integer... */ + case 'l': /* Signed 32-bit integer... */ + token = next_token(&val, NULL, cfile); + check_number: + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) { + need_number: + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting number."); + } + convert_num(cfile, buf, val, 0, 32); + return makeStringExt(4, (const char *)buf, 'X'); + + case 's': /* Signed 16-bit integer. */ + case 'S': /* Unsigned 16-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + goto need_number; + convert_num(cfile, buf, val, 0, 16); + return makeStringExt(2, (const char *)buf, 'X'); + + case 'b': /* Signed 8-bit integer. */ + case 'B': /* Unsigned 8-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + goto need_number; + convert_num(cfile, buf, val, 0, 8); + return makeStringExt(1, (const char *)buf, 'X'); + + case 'f': /* Boolean flag. */ + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) { + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting identifier."); + } + if ((strcasecmp(val, "true") == 0) || + (strcasecmp(val, "on") == 0)) + return makeString(-1, "01"); + if ((strcasecmp(val, "false") == 0) || + (strcasecmp(val, "off") == 0)) + return makeString(-1, "00"); + if (strcasecmp(val, "ignore") == 0) + return makeString(-1, "02"); + if (fmt[1] == 'o') + return NULL; + parse_error(cfile, "expecting boolean."); + + case 'Z': /* Zero-length option. */ + token = peek_token(&val, NULL, cfile); + if (token != SEMI) + parse_error(cfile, "semicolon expected."); + return allocString(); + + default: + parse_error(cfile, "Bad format '%c' in parse_option_token.", + *fmt); + } +} + +struct string * +parse_domain_list(struct parse *cfile, isc_boolean_t binary) +{ + const char *val; + enum dhcp_token token; + unsigned len; + struct string *result; + + token = SEMI; + result = allocString(); + + do { + /* Consume the COMMA token if peeked. */ + if (token == COMMA) { + skip_token(&val, NULL, cfile); + if (!binary) + appendString(result, ", "); + } + + /* Get next (or first) value. */ + token = next_token(&val, &len, cfile); + + if (token != STRING) + parse_error(cfile, "Expecting a domain string."); + + /* Just pack the names in series into the buffer. */ + if (binary) { + struct string *item; + + item = makeStringExt(len, val, 'd'); + if (item == NULL) + parse_error(cfile, "not a valid domain name."); + item = makeStringExt(item->length, item->content, 'X'); + concatString(result, item); + } else + concatString(result, makeString(len, val)); + + token = peek_token(&val, NULL, cfile); + } while (token == COMMA); + + return result; +} + +/* Specialized version of parse_option_data working on config + * options which are scalar (I6LSBtTfUXdNxxx.) only. */ + +isc_boolean_t +parse_config_data(struct element *expr, + struct parse *cfile, + struct option *option) +{ + const char *val; + enum dhcp_token token; + struct string *data; + struct element *elem; + unsigned len; + uint32_t u32; + uint16_t u16; + uint8_t u8; + + token = peek_token(&val, NULL, cfile); + + if (token == END_OF_FILE) + parse_error(cfile, "unexpected end of file"); + if (token == SEMI) + parse_error(cfile, "empty config option"); + if (token == COMMA) + parse_error(cfile, "multiple value config option"); + + /* from parse_option_token */ + + switch (option->format[0]) { + case 'U': /* universe */ + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting identifier."); + elem = createString(makeString(len, val)); + break; + + case 'X': /* string or binary */ + token = next_token(&val, &len, cfile); + if (token == NUMBER_OR_NAME || token == NUMBER) + data = parse_cshl(cfile); + else if (token == STRING) + data = makeString(len, val); + else + parse_error(cfile, "expecting string " + "or hexadecimal data."); + elem = createString(data); + break; + + case 'd': /* FQDN */ + data = parse_host_name(cfile); + if (data == NULL) + parse_error(cfile, "not a valid domain name."); + elem = createString(data); + break; + + case 't': /* text */ + token = next_token(&val, &len, cfile); + elem = createString(makeString(len, val)); + break; + + case 'N': /* enumeration */ + token = next_token(&val, &len, cfile); + if (!is_identifier(token)) + parse_error(cfile, "identifier expected"); + elem = createString(makeString(len, val)); + break; + + case 'I': /* IP address or hostname. */ + data = parse_ip_addr_or_hostname(cfile, ISC_FALSE); + if (data == NULL) + parse_error(cfile, "expecting IP address of hostname"); + elem = createString(data); + break; + + case '6': /* IPv6 address. */ + data = parse_ip6_addr_txt(cfile); + if (data == NULL) + parse_error(cfile, "expecting IPv6 address"); + elem = createString(data); + break; + + case 'T': /* Lease interval. */ + token = next_token(&val, NULL, cfile); + if (token != INFINITE) + goto check_number; + elem = createInt(-1); + break; + + case 'L': /* Unsigned 32-bit integer... */ + token = next_token(&val, NULL, cfile); + check_number: + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + convert_num(cfile, (unsigned char *)&u32, val, 0, 32); + elem = createInt(ntohl(u32)); + break; + + case 'S': /* Unsigned 16-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + convert_num(cfile, (unsigned char *)&u16, val, 0, 16); + elem = createInt(ntohs(u16)); + break; + + case 'B': /* Unsigned 8-bit integer. */ + token = next_token(&val, NULL, cfile); + if ((token != NUMBER) && (token != NUMBER_OR_NAME)) + parse_error(cfile, "expecting number."); + convert_num(cfile, (unsigned char *)&u8, val, 0, 8); + elem = createInt(ntohs(u8)); + break; + + case 'f': + token = next_token(&val, NULL, cfile); + if (!is_identifier(token)) + parse_error(cfile, "expecting boolean."); + if ((strcasecmp(val, "true") == 0) || + (strcasecmp(val, "on") == 0)) + elem = createBool(ISC_TRUE); + else if ((strcasecmp(val, "false") == 0) || + (strcasecmp(val, "off") == 0)) + elem = createBool(ISC_FALSE); + else if (strcasecmp(val, "ignore") == 0) { + elem = createNull(); + elem->skip = ISC_TRUE; + } else + parse_error(cfile, "expecting boolean."); + break; + + default: + parse_error(cfile, "Bad format '%c' in parse_config_data.", + option->format[0]); + } + + mapSet(expr, elem, "value"); + + return ISC_TRUE; +} + +/* Specialized version of parse_option_statement for config options */ + +isc_boolean_t +parse_config_statement(struct element *result, + struct parse *cfile, + struct option *option, + enum statement_op op) +{ + const char *val; + enum dhcp_token token; + struct comments *comments; + struct element *expr; + struct element *config; + struct element *config_list; + isc_boolean_t lose; + size_t where; + + config = createMap(); + TAILQ_CONCAT(&config->comments, &cfile->comments); + comments = get_config_comments(option->code); + TAILQ_CONCAT(&config->comments, comments); + mapSet(config, createString(makeString(-1, option->name)), "name"); + mapSet(config, createInt(option->code), "code"); + if (option->status == kea_unknown) { + config->skip = ISC_TRUE; + cfile->issue_counter++; + } + if (op != supersede_option_statement) { + struct string *msg; + struct comment *comment; + + msg = makeString(-1, "/// Kea does not support option data "); + appendString(msg, "set variants ("); + switch (op) { + case send_option_statement: + appendString(msg, "send"); + break; + case supersede_option_statement: + appendString(msg, "supersede"); + break; + case default_option_statement: + appendString(msg, "default"); + break; + case prepend_option_statement: + appendString(msg, "prepend"); + break; + case append_option_statement: + appendString(msg, "append"); + break; + default: + appendString(msg, "???"); + break; + } + appendString(msg, ")"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&config->comments, comment); + } + + token = peek_token(&val, NULL, cfile); + /* We should keep a list of defined empty options */ + if ((token == SEMI) && (option->format[0] != 'Z')) { + /* Eat the semicolon... */ + /* + * XXXSK: I'm not sure why we should ever get here, but we + * do during our startup. This confuses things if + * we are parsing a zero-length option, so don't + * eat the semicolon token in that case. + */ + skip_token(&val, NULL, cfile); + } else if (token == EQUAL) { + /* Eat the equals sign. */ + skip_token(&val, NULL, cfile); + + /* Parse a data expression and use its value for the data. */ + expr = createMap(); + if (!parse_data_expression(expr, cfile, &lose)) { + /* In this context, we must have an executable + statement, so if we found something else, it's + still an error. */ + if (!lose) + parse_error(cfile, + "expecting a data expression."); + return ISC_FALSE; + } + mapSet(config, expr, "value"); + } else { + if (!parse_config_data(config, cfile, option)) + return ISC_FALSE; + } + + parse_semi(cfile); + + if (result != NULL) { + config->skip = ISC_TRUE; + mapSet(result, config, "config"); + return ISC_TRUE; + } + + for (where = cfile->stack_top; where > 0; --where) { + if ((cfile->stack[where]->kind == PARAMETER) || + (cfile->stack[where]->kind == POOL_DECL)) + continue; + break; + } + + if (option->status != special) { + config_list = mapGet(cfile->stack[where], "config"); + if (config_list == NULL) { + config_list = createList(); + config_list->skip = ISC_TRUE; + mapSet(cfile->stack[where], config_list, "config"); + } + listPush(config_list, config); + return ISC_TRUE; + } + + /* deal with all special cases */ + + switch (option->code) { + case 1: /* default-lease-time */ + config_def_valid_lifetime(config, cfile); + break; + case 2: /* max-lease-time */ + config_max_valid_lifetime(config, cfile); + break; + case 3: /* min-lease-time */ + config_min_valid_lifetime(config, cfile); + break; + case 15: /* filename */ + config_file(config, cfile); + break; + case 16: /* server-name */ + config_sname(config, cfile); + break; + case 17: /* next-server */ + config_next_server(config, cfile); + break; + case 18: /* authoritative */ + parse_error(cfile, "authoritative is a statement, " + "here it is used as a config option"); + case 19: /* vendor-option-space */ + config_vendor_option_space(config, cfile); + break; + case 21: /* site-option-space */ + config_site_option_space(config, cfile); + break; + case 23: /* ddns-domainname */ + config_qualifying_suffix(config, cfile); + break; + case 30: /* ddns-updates */ + config_enable_updates(config, cfile); + break; + case 39: /* ddns-update-style */ + config_ddns_update_style(config, cfile); + break; + case 53: /* preferred-lifetime */ + config_preferred_lifetime(config, cfile); + break; + case 82: /* ignore-client-uids */ + config_match_client_id(config, cfile); + break; + case 85: /* echo-client-id */ + config_echo_client_id(config, cfile); + break; + default: + parse_error(cfile, "unsupported config option %s (%u)", + option->name, option->code); + } + + return ISC_TRUE; +} + +static void +config_def_valid_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// default-valid-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// default-valid-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "valid-lifetime"); +} + +static void +config_min_valid_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// min-valid-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// min-valid-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "min-valid-lifetime"); +} + +static void +config_max_valid_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// max-valid-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// max-valid-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "max-valid-lifetime"); +} + +static void +config_file(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t popped = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "boot-file-name is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == HOST_DECL) || + (kind == CLASS_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == ROOT_GROUP) { + popped = ISC_TRUE; + break; + } + } + if (popped) { + comment = createComment("/// boot-file-name was defined in " + "an unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(cfile->stack[scope], value, "boot-file-name"); +} + +static void +config_sname(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t popped = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "server-hostname is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == HOST_DECL) || + (kind == CLASS_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == ROOT_GROUP) { + popped = ISC_TRUE; + break; + } + } + if (popped) { + comment = createComment("/// server-hostname was defined in " + "an unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(cfile->stack[scope], value, "server-hostname"); +} + +static void +config_next_server(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t popped = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "next-server is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == HOST_DECL) || + (kind == CLASS_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + popped = ISC_TRUE; + } + if (popped) { + comment = createComment("/// next-server moved from " + "an internal unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "next-server"); +} + +static void +config_vendor_option_space(struct element *config, struct parse *cfile) +{ + struct element *defs; + struct element *def; + struct element *opts; + struct element *opt; + struct element *space; + + if (local_family != AF_INET) + parse_error(cfile, "vendor-option-space is DHCPv4 only"); + + /* create local option definition */ + def = createMap(); + mapSet(def, + createString(makeString(-1, "vendor-encapsulated-options")), + "name"); + mapSet(def, createInt(43), "code"); + mapSet(def, createString(makeString(-1, "empty")), "type"); + space = mapGet(config, "value"); + if (space == NULL) + parse_error(cfile, "vendor-option-space has no value"); + if (space->type != ELEMENT_STRING) + parse_error(cfile, + "vendor-option-space value is not a string"); + mapSet(def, space, "encapsulate"); + + /* add it */ + defs = mapGet(cfile->stack[cfile->stack_top], "option-def"); + if (defs == NULL) { + defs = createList(); + mapSet(cfile->stack[cfile->stack_top], defs, "option-def"); + } else { + size_t i; + + /* Look for duplicate */ + for (i = 0; i < listSize(defs); i++) { + struct element *item; + struct element *code; + struct element *old; + + item = listGet(defs, i); + if ((item == NULL) || (item->type != ELEMENT_MAP)) + continue; + code = mapGet(item, "code"); + if ((code == NULL) || + (code->type != ELEMENT_INTEGER) || + (intValue(code) != 43)) + continue; + old = mapGet(item, "encapsulate"); + if ((old == NULL) || (old->type != ELEMENT_STRING)) + continue; + if (eqString(stringValue(space), stringValue(old))) + return; + } + } + listPush(defs, def); + + /* add a data too assuming at least one suboption exists */ + opt = createMap(); + mapSet(opt, + createString(makeString(-1, "vendor-encapsulated-options")), + "name"); + mapSet(opt, createInt(43), "code"); + opts = mapGet(cfile->stack[cfile->stack_top], "option-data"); + if (opts == NULL) { + opts = createList(); + mapSet(cfile->stack[cfile->stack_top], opts, "option-data"); + } + listPush(opts, opt); +} + +static void +config_site_option_space(struct element *config, struct parse *cfile) +{ + struct element *defs; + struct element *space; + struct string *msg; + struct comment *comment; + + if (local_family != AF_INET) + parse_error(cfile, "site-option-space is DHCPv4 only"); + + space = mapGet(config, "value"); + if (space == NULL) + parse_error(cfile, "site-option-space has no value"); + if (space->type != ELEMENT_STRING) + parse_error(cfile, "site-option-space value is not a string"); + + defs = mapGet(cfile->stack[cfile->stack_top], "option-def"); + if (defs == NULL) { + defs = createList(); + mapSet(cfile->stack[cfile->stack_top], defs, "option-def"); + } + + msg = makeString(-1, "/// site-option-space '"); + concatString(msg, stringValue(space)); + appendString(msg, "'"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&defs->comments, comment); + msg = makeString(-1, "/// Please to move private (code 224..254)"); + appendString(msg, " option definitions from '"); + concatString(msg, stringValue(space)); + appendString(msg, "' to 'dhcp4' space"); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&defs->comments, comment); +} + +static struct element * +default_qualifying_suffix(void) +{ + struct element *qs; + struct comment *comment; + + qs = createString(allocString()); + comment = createComment("/// Unspecified ddns-domainname (default " + "domain-name option value)"); + TAILQ_INSERT_TAIL(&qs->comments, comment); + comment = createComment("/// Kea requires a qualifying-suffix"); + TAILQ_INSERT_TAIL(&qs->comments, comment); + comment = createComment("/// Initialized to \"\": please put a value"); + TAILQ_INSERT_TAIL(&qs->comments, comment); + return qs; +} + +static void +config_qualifying_suffix(struct element *config, struct parse *cfile) +{ + struct element *value; + size_t scope; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + if (cfile->stack[scope]->kind != ROOT_GROUP) { + struct comment *comment; + + comment = createComment("/// Only global qualifying-suffix " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "qualifying-suffix"); + } else { + struct element *d2; + + d2 = mapGet(cfile->stack[1], "dhcp-ddns"); + if (d2 == NULL) { + d2 = createMap(); + mapSet(d2, createBool(ISC_FALSE), "enable-updates"); + mapSet(cfile->stack[1], d2, "dhcp-ddns"); + } else if (mapContains(d2, "qualifying-suffix")) + mapRemove(d2, "qualifying-suffix"); + mapSet(d2, value, "qualifying-suffix"); + } +} + +static void +config_enable_updates(struct element *config, struct parse *cfile) +{ + struct element *value; + size_t scope; + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + if (cfile->stack[scope]->kind != ROOT_GROUP) { + struct comment *comment; + + comment = createComment("/// Only global enable-updates " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "enable-updates"); + } else { + struct element *d2; + + d2 = mapGet(cfile->stack[1], "dhcp-ddns"); + if (d2 == NULL) { + d2 = createMap(); + mapSet(cfile->stack[1], d2, "dhcp-ddns"); + if (boolValue(value)) { + struct element *qs; + + qs = default_qualifying_suffix(); + mapSet(d2, qs, "qualifying-suffix"); + } + } else if (mapContains(d2, "enable-updates")) + mapRemove(d2, "enable-updates"); + mapSet(d2, value, "enable-updates"); + } +} + +static void +config_ddns_update_style(struct element *config, struct parse *cfile) +{ + struct element *value; + isc_boolean_t enable = ISC_TRUE; + size_t scope; + + value = mapGet(config, "value"); + if (strcmp(stringValue(value)->content, "standard") == 0) + enable = ISC_TRUE; + else if (strcmp(stringValue(value)->content, "none") == 0) + enable = ISC_FALSE; + else { + struct string *msg; + struct comment *comment; + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + msg = makeString(-1, "/// Unsupported ddns-update-style "); + concatString(msg, stringValue(value)); + comment = createComment(msg->content); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "ddns-update-style"); + } + + for (scope = cfile->stack_top; scope > 0; --scope) + if ((cfile->stack[scope]->kind != PARAMETER) || + (cfile->stack[scope]->kind != POOL_DECL)) + break; + if (cfile->stack[scope]->kind != ROOT_GROUP) { + struct comment *comment; + + comment = createComment("/// Only global ddns-update-style " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + mapSet(cfile->stack[scope], value, "ddns-update-style"); + } else { + struct element *d2; + + /* map ddns-update-style into enable-updates */ + value = createBool(enable); + d2 = mapGet(cfile->stack[1], "dhcp-ddns"); + if (d2 == NULL) { + d2 = createMap(); + mapSet(cfile->stack[1], d2, "dhcp-ddns"); + if (boolValue(value)) { + struct element *qs; + + qs = default_qualifying_suffix(); + mapSet(d2, qs, "qualifying-suffix"); + } + } else if (mapContains(d2, "enable-updates")) + mapRemove(d2, "enable-updates"); + mapSet(d2, value, "enable-updates"); + } +} + +static void +config_preferred_lifetime(struct element *config, struct parse *cfile) +{ + struct element *value; + struct element *child; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + if (local_family != AF_INET6) + parse_error(cfile, "preferred-lifetime is DHCPv6 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// preferred-lifetime in " + "unsupported scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment = createComment("/// preferred-lifetime moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + /* if there is another specified value and we are + * enough lucky to have already got it... */ + if (mapContains(cfile->stack[scope], "preferred-lifetime")) { + comment = createComment("/// Avoid to overwrite " + "current value..."); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + } + } + mapSet(cfile->stack[scope], value, "preferred-lifetime"); + /* derive T1 and T2 */ + child = createInt(intValue(value) / 2); + child->skip = value->skip; + mapSet(cfile->stack[scope], child, "renew-timer"); + child = createInt(intValue(value) * 4 / 5); + child->skip = value->skip; + mapSet(cfile->stack[scope], child, "rebind-timer"); +} + +static void +config_match_client_id(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + isc_boolean_t pop_from_pool = ISC_FALSE; + + if (local_family != AF_INET) + parse_error(cfile, "ignore-client-uids is DHCPv4 only"); + + value = mapGet(config, "value"); + /* match-client-id is !ignore-client-uids */ + value = createBool(!boolValue(value)); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if ((kind == ROOT_GROUP) || + (kind == SHARED_NET_DECL) || + (kind == SUBNET_DECL) || + (kind == GROUP_DECL)) + break; + if (kind == POOL_DECL) { + pop_from_pool = ISC_TRUE; + continue; + } + comment = createComment("/// match-client-id in unsupported " + "scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + break; + } + if (pop_from_pool) { + comment= createComment("/// match-client-id moved from " + "an internal pool scope"); + TAILQ_INSERT_TAIL(&value->comments, comment); + } + mapSet(cfile->stack[scope], value, "match-client-id"); +} + +static void +config_echo_client_id(struct element *config, struct parse *cfile) +{ + struct element *value; + struct comment *comment; + size_t scope; + + if (local_family != AF_INET) + parse_error(cfile, "echo-client-id is DHCPv4 only"); + + value = mapGet(config, "value"); + + for (scope = cfile->stack_top; scope > 0; --scope) { + int kind = cfile->stack[scope]->kind; + + if (kind == PARAMETER) + continue; + if (kind == ROOT_GROUP) + break; + comment = createComment("/// Only global echo-client-id " + "is supported"); + TAILQ_INSERT_TAIL(&value->comments, comment); + value->skip = ISC_TRUE; + cfile->issue_counter++; + } + mapSet(cfile->stack[scope], value, "echo-client-id"); +} + +/* parse_error moved to keama.c */ + +/* From omapi/convert.c */ +/* +static uint32_t +getULong(const unsigned char *buf) +{ + uint32_t ibuf; + + memcpy(&ibuf, buf, sizeof(uint32_t)); + return ntohl(ibuf); +} + +static int32_t +getLong(const unsigned char *buf) +{ + int32_t ibuf; + + memcpy(&ibuf, buf, sizeof(int32_t)); + return ntohl(ibuf); +} + +static uint32_t +getUShort(const unsigned char *buf) +{ + unsigned short ibuf; + + memcpy(&ibuf, buf, sizeof(uint16_t)); + return ntohs(ibuf); +} + +static int32_t +getShort(const unsigned char *buf) +{ + short ibuf; + + memcpy(&ibuf, buf, sizeof(int16_t)); + return ntohs(ibuf); +} + +static uint32_t +getUChar(const unsigned char *obuf) +{ + return obuf[0]; +} +*/ +static void +putULong(unsigned char *obuf, uint32_t val) +{ + uint32_t tmp = htonl(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} + +static void +putLong(unsigned char *obuf, int32_t val) +{ + int32_t tmp = htonl(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} + +static void +putUShort(unsigned char *obuf, uint32_t val) +{ + uint16_t tmp = htons(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} + +static void +putShort(unsigned char *obuf, int32_t val) +{ + int16_t tmp = htons(val); + memcpy(obuf, &tmp, sizeof(tmp)); +} +/* +static void +putUChar(unsigned char *obuf, uint32_t val) +{ + *obuf = val; +} +*/ +/* From common/tree.c */ + +isc_boolean_t +is_boolean_expression(struct element *expr) +{ + if (expr->type == ELEMENT_BOOLEAN) + return ISC_TRUE; + if (expr->type != ELEMENT_MAP) + return ISC_FALSE; + return (mapContains(expr, "check") || + mapContains(expr, "exists") || + mapContains(expr, "variable-exists") || + mapContains(expr, "equal") || + mapContains(expr, "not-equal") || + mapContains(expr, "regex-match") || + mapContains(expr, "iregex-match") || + mapContains(expr, "and") || + mapContains(expr, "or") || + mapContains(expr, "not") || + mapContains(expr, "known") || + mapContains(expr, "static")); +} + +isc_boolean_t +is_data_expression(struct element *expr) +{ + if (expr->type == ELEMENT_STRING) + return ISC_TRUE; + if (expr->type != ELEMENT_MAP) + return ISC_FALSE; + return (mapContains(expr, "substring") || + mapContains(expr, "suffix") || + mapContains(expr, "lowercase") || + mapContains(expr, "uppercase") || + mapContains(expr, "option") || + mapContains(expr, "hardware") || + mapContains(expr, "hw-type") || + mapContains(expr, "hw-address") || + mapContains(expr, "const-data") || + mapContains(expr, "packet") || + mapContains(expr, "concat") || + mapContains(expr, "encapsulate") || + mapContains(expr, "encode-int8") || + mapContains(expr, "encode-int16") || + mapContains(expr, "encode-int32") || + mapContains(expr, "gethostbyname") || + mapContains(expr, "binary-to-ascii") || + mapContains(expr, "filename") || + mapContains(expr, "server-name") || + mapContains(expr, "reverse") || + mapContains(expr, "pick-first-value") || + mapContains(expr, "host-decl-name") || + mapContains(expr, "leased-address") || + mapContains(expr, "config-option") || + mapContains(expr, "null") || + mapContains(expr, "gethostname") || + mapContains(expr, "v6relay")); +} + +isc_boolean_t +is_numeric_expression(struct element *expr) +{ + if (expr->type == ELEMENT_INTEGER) + return ISC_TRUE; + if (expr->type != ELEMENT_MAP) + return ISC_FALSE; + return (mapContains(expr, "extract-int8") || + mapContains(expr, "extract-int16") || + mapContains(expr, "extract-int32") || + mapContains(expr, "const-int") || + mapContains(expr, "lease-time") || + mapContains(expr, "add") || + mapContains(expr, "subtract") || + mapContains(expr, "multiply") || + mapContains(expr, "divide") || + mapContains(expr, "remainder") || + mapContains(expr, "binary-and") || + mapContains(expr, "binary-or") || + mapContains(expr, "binary-xor") || + mapContains(expr, "client-state")); +} +/* +static isc_boolean_t +is_compound_expression(struct element *expr) +{ + return (mapContains(expr, "substring") || + mapContains(expr, "suffix") || + mapContains(expr, "option") || + mapContains(expr, "concat") || + mapContains(expr, "encode-int8") || + mapContains(expr, "encode-int16") || + mapContains(expr, "encode-int32") || + mapContains(expr, "binary-to-ascii") || + mapContains(expr, "reverse") || + mapContains(expr, "pick-first-value") || + mapContains(expr, "config-option") || + mapContains(expr, "extract-int8") || + mapContains(expr, "extract-int16") || + mapContains(expr, "extract-int32") || + mapContains(expr, "v6relay")); +} +*/ +static enum expression_context +op_context(enum expr_op op) +{ + switch (op) { +/* XXX Why aren't these specific? */ + case expr_none: + case expr_match: + case expr_static: + case expr_check: + case expr_substring: + case expr_suffix: + case expr_lcase: + case expr_ucase: + case expr_concat: + case expr_encapsulate: + case expr_host_lookup: + case expr_not: + case expr_option: + case expr_hardware: + case expr_hw_type: + case expr_hw_address: + case expr_packet: + case expr_const_data: + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_const_int: + case expr_exists: + case expr_variable_exists: + case expr_known: + case expr_binary_to_ascii: + case expr_reverse: + case expr_filename: + case expr_sname: + case expr_pick_first_value: + case expr_host_decl_name: + case expr_config_option: + case expr_leased_address: + case expr_lease_time: + case expr_null: + case expr_variable_reference: + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + case expr_dns_transaction: + case expr_arg: + case expr_funcall: + case expr_function: + case expr_gethostname: + case expr_v6relay: + case expr_concat_dclist: + return context_any; + + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + return context_data; + + case expr_and: + return context_boolean; + + case expr_or: + return context_boolean; + + case expr_add: + case expr_subtract: + case expr_multiply: + case expr_divide: + case expr_remainder: + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + return context_numeric; + } + return context_any; +} + +static int +op_val(enum expr_op op) +{ + switch (op) { + case expr_none: + case expr_match: + case expr_static: + case expr_check: + case expr_substring: + case expr_suffix: + case expr_lcase: + case expr_ucase: + case expr_concat: + case expr_encapsulate: + case expr_host_lookup: + case expr_not: + case expr_option: + case expr_hardware: + case expr_hw_type: + case expr_hw_address: + case expr_packet: +#ifdef keep_expr_const_data_precedence + case expr_const_data: +#endif + case expr_extract_int8: + case expr_extract_int16: + case expr_extract_int32: + case expr_encode_int8: + case expr_encode_int16: + case expr_encode_int32: + case expr_const_int: + case expr_exists: + case expr_variable_exists: + case expr_known: + case expr_binary_to_ascii: + case expr_reverse: + case expr_filename: + case expr_sname: + case expr_pick_first_value: + case expr_host_decl_name: + case expr_config_option: + case expr_leased_address: + case expr_lease_time: + case expr_dns_transaction: + case expr_null: + case expr_variable_reference: + case expr_ns_add: + case expr_ns_delete: + case expr_ns_exists: + case expr_ns_not_exists: + case expr_arg: + case expr_funcall: + case expr_function: + /* XXXDPN: Need to assign sane precedences to these. */ + case expr_binary_and: + case expr_binary_or: + case expr_binary_xor: + case expr_client_state: + case expr_gethostname: + case expr_v6relay: + case expr_concat_dclist: + return 100; + + case expr_equal: + case expr_not_equal: + case expr_regex_match: + case expr_iregex_match: + return 4; + + case expr_or: + case expr_and: + return 3; + + case expr_add: + case expr_subtract: + return 2; + + case expr_multiply: + case expr_divide: + case expr_remainder: + return 1; +#ifndef keep_expr_const_data_precedence + case expr_const_data: + return 0; +#endif + } + return 100; +} + +static int +op_precedence(enum expr_op op1, enum expr_op op2) +{ + return op_val(op1) - op_val(op2); +} + +static enum expression_context +expression_context(struct element *expr) +{ + if (is_data_expression(expr)) + return context_data; + if (is_numeric_expression(expr)) + return context_numeric; + if (is_boolean_expression(expr)) + return context_boolean; + return context_any; +} + +static enum expr_op +expression(struct element *expr) +{ + if (expr->type != ELEMENT_MAP) + return expr_none; + if (mapContains(expr, "match")) + return expr_match; + if (mapContains(expr, "check")) + return expr_check; + if (mapContains(expr, "equal")) + return expr_equal; + if (mapContains(expr, "substring")) + return expr_substring; + if (mapContains(expr, "suffix")) + return expr_suffix; + if (mapContains(expr, "concat")) + return expr_concat; + if (mapContains(expr, "and")) + return expr_and; + if (mapContains(expr, "or")) + return expr_or; + if (mapContains(expr, "not")) + return expr_not; + if (mapContains(expr, "option")) + return expr_option; + if (mapContains(expr, "hardware")) + return expr_hardware; + if (mapContains(expr, "hw-type")) + return expr_hw_type; + if (mapContains(expr, "hw-address")) + return expr_hw_address; + if (mapContains(expr, "packet")) + return expr_packet; + if (mapContains(expr, "const-data")) + return expr_const_data; + if (mapContains(expr, "extract-int8")) + return expr_extract_int8; + if (mapContains(expr, "extract-int16")) + return expr_extract_int16; + if (mapContains(expr, "extract-int32")) + return expr_extract_int32; + if (mapContains(expr, "encode-int8")) + return expr_encode_int8; + if (mapContains(expr, "encode-int16")) + return expr_encode_int16; + if (mapContains(expr, "encode-int32")) + return expr_encode_int32; + if (mapContains(expr, "const-int")) + return expr_const_int; + if (mapContains(expr, "exists")) + return expr_exists; + if (mapContains(expr, "encapsulate")) + return expr_encapsulate; + if (mapContains(expr, "known")) + return expr_known; + if (mapContains(expr, "reverse")) + return expr_reverse; + if (mapContains(expr, "leased-address")) + return expr_leased_address; + if (mapContains(expr, "binary-to-ascii")) + return expr_binary_to_ascii; + if (mapContains(expr, "config-option")) + return expr_config_option; + if (mapContains(expr, "host-decl-name")) + return expr_host_decl_name; + if (mapContains(expr, "pick-first-value")) + return expr_pick_first_value; + if (mapContains(expr, "lease-time")) + return expr_lease_time; + if (mapContains(expr, "static")) + return expr_static; + if (mapContains(expr, "not-equal")) + return expr_not_equal; + if (mapContains(expr, "null")) + return expr_null; + if (mapContains(expr, "variable-exists")) + return expr_variable_exists; + if (mapContains(expr, "variable-reference")) + return expr_variable_reference; + if (mapContains(expr, "filename")) + return expr_filename; + if (mapContains(expr, "server-name")) + return expr_sname; + if (mapContains(expr, "arguments")) + return expr_arg; + if (mapContains(expr, "funcall")) + return expr_funcall; + if (mapContains(expr, "function")) + return expr_function; + if (mapContains(expr, "add")) + return expr_add; + if (mapContains(expr, "subtract")) + return expr_subtract; + if (mapContains(expr, "multiply")) + return expr_multiply; + if (mapContains(expr, "divide")) + return expr_divide; + if (mapContains(expr, "remainder")) + return expr_remainder; + if (mapContains(expr, "binary-and")) + return expr_binary_and; + if (mapContains(expr, "binary-or")) + return expr_binary_or; + if (mapContains(expr, "binary-xor")) + return expr_binary_xor; + if (mapContains(expr, "client-state")) + return expr_client_state; + if (mapContains(expr, "uppercase")) + return expr_ucase; + if (mapContains(expr, "lowercase")) + return expr_lcase; + if (mapContains(expr, "regex-match")) + return expr_regex_match; + if (mapContains(expr, "iregex-match")) + return expr_iregex_match; + if (mapContains(expr, "gethostname")) + return expr_gethostname; + if (mapContains(expr, "v6relay")) + return expr_v6relay; + if (TAILQ_EMPTY(&expr->value.map_value)) { + fprintf(stderr, "empty expression"); + if (expr->key != NULL) + fprintf(stderr, " for %s", expr->key); + } else { + struct element *item; + isc_boolean_t first = ISC_TRUE; + + TAILQ_FOREACH(item, &expr->value.map_value) { + const char *key; + + key = item->key; + if (key == NULL) + continue; + if (first) + fprintf(stderr, ": %s", key); + else + fprintf(stderr, ", %s", key); + first = ISC_FALSE; + } + } + fputs("\n", stderr); + return expr_none; +} + +int +expr_precedence(enum expr_op op, struct element *expr) +{ + if (expr->type != ELEMENT_MAP) + return op_val(op); + return op_val(op) - op_val(expression(expr)); +} |