2019-11-22 13:39:45 -05:00
|
|
|
/*
|
2022-01-25 16:24:16 +01:00
|
|
|
* Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
|
2019-11-22 13:39:45 -05:00
|
|
|
*
|
|
|
|
* 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.
|
2022-01-19 20:13:19 +01:00
|
|
|
* PO Box 360
|
|
|
|
* Newmarket, NH 03857 USA
|
2019-11-22 13:39:45 -05:00
|
|
|
* <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}
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
...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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
/* 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
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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');
|
|
|
|
}
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
/*
|
|
|
|
* 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);
|
2022-01-19 20:14:16 +01:00
|
|
|
}
|
2019-11-22 13:39:45 -05:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
2022-01-19 20:14:16 +01:00
|
|
|
* We are lazy for this. We pull numbers, names, colons, and dots
|
2019-11-22 13:39:45 -05:00
|
|
|
* 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;
|
|
|
|
|
|
|
|
/*
|
2022-01-19 20:14:16 +01:00
|
|
|
* First token is non-raw. This way we eat any whitespace before
|
2019-11-22 13:39:45 -05:00
|
|
|
* 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.");
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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);
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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);
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
/* 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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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. */
|
2022-01-19 20:14:16 +01:00
|
|
|
else
|
2019-11-22 13:39:45 -05:00
|
|
|
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:
|
2022-01-19 20:14:16 +01:00
|
|
|
appendString(cond, val);
|
2019-11-22 13:39:45 -05:00
|
|
|
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);
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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");
|
|
|
|
}
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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
|
|
|
|
*/
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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 |
|
2022-01-19 20:14:16 +01:00
|
|
|
* CONCAT LPAREN data-expression COMMA
|
2019-11-22 13:39:45 -05:00
|
|
|
* 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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
case STRING:
|
|
|
|
skip_token(&val, &len, cfile);
|
|
|
|
resetString(expr, makeString(len, val));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXTRACT_INT:
|
2022-01-19 20:14:16 +01:00
|
|
|
skip_token(&val, NULL, cfile);
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
case ENCODE_INT:
|
2022-01-19 20:14:16 +01:00
|
|
|
skip_token(&val, NULL, cfile);
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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");
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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;
|
|
|
|
}
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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);
|
|
|
|
|
2022-01-19 20:14:16 +01:00
|
|
|
if ((rhs_context != context_any) &&
|
2019-11-22 13:39:45 -05:00
|
|
|
(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;
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
/* Store the LHS and RHS. */
|
|
|
|
mapSet(tmp, lhs, "left");
|
|
|
|
mapSet(tmp, rhs, "right");
|
|
|
|
|
|
|
|
lhs = createMap();
|
|
|
|
mapSet(lhs, tmp, binop_name);
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
tmp = NULL;
|
|
|
|
rhs = NULL;
|
|
|
|
|
|
|
|
binop = next_op;
|
|
|
|
goto new_rhs;
|
2022-01-19 20:14:16 +01:00
|
|
|
}
|
2019-11-22 13:39:45 -05:00
|
|
|
|
|
|
|
/* 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... */
|
|
|
|
/*
|
2022-01-19 20:14:16 +01:00
|
|
|
* XXXSK: I'm not sure why we should ever get here, but we
|
2019-11-22 13:39:45 -05:00
|
|
|
* 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);
|
2022-01-19 20:14:16 +01:00
|
|
|
mapSet(opt_data, data, "data");
|
2019-11-22 13:39:45 -05:00
|
|
|
} 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]);
|
|
|
|
}
|
2022-01-19 20:14:16 +01:00
|
|
|
|
2019-11-22 13:39:45 -05:00
|
|
|
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... */
|
|
|
|
/*
|
2022-01-19 20:14:16 +01:00
|
|
|
* XXXSK: I'm not sure why we should ever get here, but we
|
2019-11-22 13:39:45 -05:00
|
|
|
* 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));
|
|
|
|
}
|