summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--keama/confparse.c247
-rw-r--r--keama/data.c22
-rw-r--r--keama/data.h1
-rw-r--r--keama/doc.txt8
-rw-r--r--keama/keama.88
-rw-r--r--keama/keama.c23
-rw-r--r--keama/keama.h16
-rw-r--r--keama/options.c87
-rw-r--r--keama/parse.c164
-rw-r--r--keama/tests/empty.err0
-rw-r--r--keama/tests/empty.msg1
-rw-r--r--keama/tests/minimal4.in47
-rw-r--r--keama/tests/minimal4.out5
-rw-r--r--keama/tests/minimal6.in67
-rw-r--r--keama/tests/minimal6.out5
-rw-r--r--keama/tests/runall.sh11
-rw-r--r--keama/tests/runone.sh99
17 files changed, 588 insertions, 123 deletions
diff --git a/keama/confparse.c b/keama/confparse.c
index b35168a4..1ed60dfe 100644
--- a/keama/confparse.c
+++ b/keama/confparse.c
@@ -45,11 +45,14 @@ struct subnet {
struct string *mask;
TAILQ_ENTRY(subnet) next;
};
+
TAILQ_HEAD(subnets, subnet) known_subnets;
static void add_host_reservation_identifiers(struct parse *, const char *);
static void add_match_class(struct parse *, struct element *,
struct element *);
+static void option_data_derive(struct parse *, struct handle *,
+ struct handle *, isc_boolean_t);
static void new_network_interface(struct parse *, struct element *);
static struct string *addrmask(const struct string *, const struct string *);
static struct element *find_match(struct parse *, struct element *);
@@ -830,6 +833,23 @@ parse_pool_statement(struct parse *cfile, int type)
}
} while (!done);
+ if (local_family == AF_INET) {
+ struct element *opt_list;
+
+ opt_list = mapGet(pool, "option-data");
+ if (opt_list != NULL) {
+ struct comment *comment;
+
+ comment = createComment("/// Kea doesn't support "
+ "option-data in DHCPv4 pools");
+ TAILQ_INSERT_TAIL(&opt_list->comments, comment);
+ if (!opt_list->skip) {
+ opt_list->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ }
+ }
+
cfile->stack_top--;
}
@@ -1409,7 +1429,6 @@ parse_shared_net_declaration(struct parse *cfile)
int declaration = 0;
share = createMap();
- share->skip = ISC_TRUE;
share->kind = SHARED_NET_DECL;
TAILQ_CONCAT(&share->comments, &cfile->comments);
@@ -1467,10 +1486,20 @@ parse_shared_net_declaration(struct parse *cfile)
if (listSize(subnets) == 0)
parse_error(cfile, "empty shared-network decl");
if (listSize(subnets) > 1) {
+ struct element *shares;
+
share->skip = ISC_TRUE;
cfile->issue_counter++;
- mapSet(cfile->stack[cfile->stack_top],
- share, "shared-network");
+ shares = mapGet(cfile->stack[cfile->stack_top],
+ "shared-networks");
+ if (shares == NULL) {
+ shares = createList();
+ shares->skip = ISC_TRUE;
+ shares->kind = SHARED_NET_DECL;
+ mapSet(cfile->stack[cfile->stack_top],
+ shares, "shared-networks");
+ }
+ listPush(shares, share);
return;
}
@@ -1794,31 +1823,36 @@ void
dissolve_group(struct parse *cfile, struct element *group)
{
struct handle *handle;
+ struct handle *nh;
struct element *parent;
struct element *item;
- /*struct element *decl;*/
struct element *param;
struct handle *hosts = NULL;
struct handle *shares = NULL;
struct handle *subnets = NULL;
struct handle *classes = NULL;
- struct handle *pools = NULL;
struct handle *pdpools = NULL;
- struct handle *downs = NULL;
+ struct handle *pools = NULL;
+ struct handles downs;
struct comment *comment;
const char *key;
const char *name = NULL;
unsigned order = 0;
isc_boolean_t marked = ISC_FALSE;
- /* check that group is on its parent */
+ TAILQ_INIT(&downs);
+ /* check that group is in its parent */
parent = cfile->stack[cfile->stack_top];
+ if (parent->kind == PARAMETER)
+ parse_error(cfile, "unexpected kind for group parent %d",
+ parent->kind);
item = mapGet(parent, "group");
if (item == NULL)
parse_error(cfile, "no group in parent");
if (item != group)
parse_error(cfile, "got a different group from parent");
+ mapRemove(parent, "group");
/* classify content */
while (mapSize(group) > 0) {
@@ -1846,19 +1880,26 @@ dissolve_group(struct parse *cfile, struct element *group)
parse_error(cfile, "got reservations twice "
"at %u and %u",
hosts->order, order);
+ if ((parent->kind == HOST_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "host declarations not "
+ "allowed here.");
hosts = handle;
handle = NULL;
break;
case SHARED_NET_DECL:
- if (strcmp(handle->key, "shared-network") != 0)
- parse_error(cfile, "expected shared-network "
+ if (strcmp(handle->key, "shared-networks") != 0)
+ parse_error(cfile, "expected shared-networks "
"got %s at %u",
handle->key, order);
- if (shares == NULL)
- shares = handle;
- else
- TAILQ_INSERT_TAIL(&shares->values, handle);
+ if ((parent->kind == SHARED_NET_DECL) ||
+ (parent->kind == HOST_DECL) ||
+ (parent->kind == SUBNET_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "shared-network parameters "
+ "not allowed here.");
+ shares = handle;
handle = NULL;
break;
@@ -1870,6 +1911,11 @@ dissolve_group(struct parse *cfile, struct element *group)
if (subnets != NULL)
parse_error(cfile, "got %s twice at %u and %u",
key, subnets->order, order);
+ if ((parent->kind == HOST_DECL) ||
+ (parent->kind == SUBNET_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "subnet declarations not "
+ "allowed here.");
subnets = handle;
handle = NULL;
break;
@@ -1879,6 +1925,9 @@ dissolve_group(struct parse *cfile, struct element *group)
parse_error(cfile, "expected client-classes "
"got %s at %u",
handle->key, order);
+ if (parent->kind == CLASS_DECL)
+ parse_error(cfile, "class declarations not "
+ "allowed here.");
if (classes == NULL)
classes = handle;
else
@@ -1887,20 +1936,29 @@ dissolve_group(struct parse *cfile, struct element *group)
break;
case POOL_DECL:
- if (strcmp(handle->key, "pools") == 0) {
- if (pools != NULL)
- parse_error(cfile, "got pools twice "
- "at %u and %u",
- pools->order, order);
- pools = handle;
- } else if (strcmp(handle->key, "pd-pools") == 0) {
+ if (strcmp(handle->key, "pd-pools") == 0) {
if (pdpools != NULL)
parse_error(cfile, "got pd-pools "
"twice at %u and %u",
pdpools->order, order);
pdpools = handle;
+ } else if (strcmp(handle->key, "pools") == 0) {
+ if (pools != NULL)
+ parse_error(cfile, "got pools twice "
+ "at %u and %u",
+ pools->order, order);
+ pools = handle;
} else
-
+ parse_error(cfile, "expecyed [pd-]pools got "
+ "%s at %u",
+ handle->key, order);
+ if (parent->kind == POOL_DECL)
+ parse_error(cfile, "pool declared within "
+ "pool.");
+ if ((parent->kind == HOST_DECL) ||
+ (parent->kind == CLASS_DECL))
+ parse_error(cfile, "pool declared outside "
+ "of network");
handle = NULL;
break;
default:
@@ -1920,7 +1978,7 @@ dissolve_group(struct parse *cfile, struct element *group)
/* unexpected values */
if ((strcmp(handle->key, "reservations") == 0) ||
(strcmp(handle->key, "group") == 0) ||
- (strcmp(handle->key, "shared-network") == 0) ||
+ (strcmp(handle->key, "shared-networks") == 0) ||
(strcmp(handle->key, "subnets") == 0) ||
(strcmp(handle->key, "subnet4") == 0) ||
(strcmp(handle->key, "subnet6") == 0) ||
@@ -1966,8 +2024,8 @@ dissolve_group(struct parse *cfile, struct element *group)
comment = createComment(msg->content);
TAILQ_INSERT_TAIL(&param->comments, comment);
}
- TAILQ_INSERT_AFTER(&parent->value.map_value,
- group, param);
+ mapSet(parent, param, handle->key);
+ free(handle);
continue;
}
/* To reconsider: qualifying-suffix, enable-updates */
@@ -1976,15 +2034,14 @@ dissolve_group(struct parse *cfile, struct element *group)
(strcmp(handle->key, "deny") == 0) ||
(strcmp(handle->key, "interface") == 0) ||
(strcmp(handle->key, "valid-lifetime") == 0) ||
+ (strcmp(handle->key, "preferred-lifetime") == 0) ||
+ (strcmp(handle->key, "renew-timer") == 0) ||
+ (strcmp(handle->key, "rebind-timer") == 0) ||
(strcmp(handle->key, "boot-file-name") == 0) ||
(strcmp(handle->key, "server-hostname") == 0) ||
(strcmp(handle->key, "next-server") == 0) ||
- (strcmp(handle->key, "preferred-lifetime") == 0) ||
(strcmp(handle->key, "match-client-id") == 0)) {
- if (downs == NULL)
- downs = handle;
- else
- TAILQ_INSERT_TAIL(&downs->values, handle);
+ TAILQ_INSERT_TAIL(&downs, handle);
continue;
}
/* unknown */
@@ -2003,10 +2060,136 @@ dissolve_group(struct parse *cfile, struct element *group)
TAILQ_INSERT_TAIL(&param->comments, comment);
param->skip = ISC_TRUE;
cfile->issue_counter++;
- TAILQ_INSERT_AFTER(&parent->value.map_value, group, param);
+ mapSet(parent, param, handle->key);
+ free(handle);
+ }
+ TAILQ_FOREACH_SAFE(handle, &downs, nh) {
+ if (strcmp(handle->key, "option-data") == 0) {
+ option_data_derive(cfile, handle, hosts, ISC_FALSE);
+ option_data_derive(cfile, handle, shares, ISC_FALSE);
+ option_data_derive(cfile, handle, subnets, ISC_FALSE);
+ option_data_derive(cfile, handle, classes, ISC_FALSE);
+ option_data_derive(cfile, handle, pdpools, ISC_FALSE);
+ option_data_derive(cfile, handle, pools, ISC_TRUE);
+ } else if ((strcmp(handle->key, "allow") == 0) ||
+ (strcmp(handle->key, "deny") == 0)) {
+ derive(handle, pdpools);
+ derive(handle, pools);
+ } else if ((strcmp(handle->key, "interface") == 0) ||
+ (strcmp(handle->key, "valid-lifetime") == 0) ||
+ (strcmp(handle->key, "preferred-lifetime") == 0) ||
+ (strcmp(handle->key, "renew-timer") == 0) ||
+ (strcmp(handle->key, "rebind-timer") == 0) ||
+ (strcmp(handle->key, "match-client-id") == 0)) {
+ derive(handle, shares);
+ derive(handle, subnets);
+ } else if ((strcmp(handle->key, "boot-file-name") == 0) ||
+ (strcmp(handle->key, "server-hostname") == 0)) {
+ derive(handle, hosts);
+ derive(handle, classes);
+ } else if (strcmp(handle->key, "next-server") == 0) {
+ derive(handle, hosts);
+ derive(handle, subnets);
+ derive(handle, classes);
+ } else
+ parse_error(cfile, "unexpected parameter %s to derive",
+ handle->key);
+ }
+ if (hosts != NULL) {
+ struct element *root;
+
+ root = mapGet(cfile->stack[1], "reservations");
+ if (root == NULL)
+ mapSet(cfile->stack[1], hosts->value, "reservations");
+ else
+ concat(root, hosts->value);
+ }
+ if (shares != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, "shared-networks");
+ if (upper == NULL)
+ mapSet(parent, shares->value, "shared-networks");
+ else
+ concat(upper, shares->value);
+ }
+ key = local_family == AF_INET ? "subnet4" : "subnet6";
+ if (subnets != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, key);
+ if (upper == NULL)
+ mapSet(parent, subnets->value, key);
+ else
+ concat(upper, subnets->value);
+ }
+ if (classes != NULL) {
+ struct element *root;
+
+ root = mapGet(cfile->stack[1], "client-classes");
+ if (root == NULL)
+ mapSet(cfile->stack[1], classes->value,
+ "client-classes");
+ else
+ concat(root, classes->value);
+ }
+ if (pdpools != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, "pd-pools");
+ if (upper == NULL)
+ mapSet(parent, pdpools->value, "pools");
+ else
+ concat(upper, pdpools->value);
+ }
+ if (pools != NULL) {
+ struct element *upper;
+
+ upper = mapGet(parent, "pools");
+ if (upper == NULL)
+ mapSet(parent, pools->value, "pools");
+ else
+ concat(upper, pools->value);
+ }
+}
+
+static void
+option_data_derive(struct parse *cfile, struct handle *src,
+ struct handle *dst, isc_boolean_t is_pools)
+{
+ struct element *list;
+ struct element *item;
+ struct element *opt_list;
+ size_t i;
+
+ if (dst == NULL)
+ return;
+ list = dst->value;
+ assert(list != NULL);
+ assert(list->type == ELEMENT_LIST);
+ for (i = 0; i < listSize(list); i++) {
+ item = listGet(list, i);
+ assert(item != NULL);
+ assert(item->type == ELEMENT_MAP);
+ opt_list = mapGet(item, src->key);
+ if (opt_list != NULL) {
+ merge_option_data(src->value, opt_list);
+ continue;
+ }
+ opt_list = copy(src->value);
+ if (is_pools && (local_family == AF_INET)) {
+ struct comment *comment;
+
+ comment = createComment("/// Kea doesn't support "
+ "option-data in DHCPv4 pools");
+ TAILQ_INSERT_TAIL(&opt_list->comments, comment);
+ if (!opt_list->skip) {
+ opt_list->skip = ISC_TRUE;
+ cfile->issue_counter++;
+ }
+ mapSet(item, opt_list, src->key);
+ }
}
- /* to finish */
- TAILQ_REMOVE(&parent->value.map_value, group);
}
/* fixed-addr-parameter :== ip-addrs-or-hostnames SEMI
diff --git a/keama/data.c b/keama/data.c
index 78865f44..f9ccd7cc 100644
--- a/keama/data.c
+++ b/keama/data.c
@@ -940,3 +940,25 @@ mapPop(struct element *m)
return h;
}
+
+void
+derive(struct handle *src, struct handle *dst)
+{
+ struct element *list;
+ struct element *item;
+ size_t i;
+
+ if (dst == NULL)
+ return;
+ list = dst->value;
+ assert(list != NULL);
+ assert(list->type == ELEMENT_LIST);
+ for (i = 0; i < listSize(list); i++) {
+ item = listGet(list, i);
+ assert(item != NULL);
+ assert(item->type == ELEMENT_MAP);
+ if (mapContains(item, src->key))
+ continue;
+ mapSet(item, copy(src->value), src->key);
+ }
+}
diff --git a/keama/data.h b/keama/data.h
index 7c810890..a7f1225e 100644
--- a/keama/data.h
+++ b/keama/data.h
@@ -287,5 +287,6 @@ struct handle {
};
struct handle* mapPop(struct element *);
+void derive(struct handle *, struct handle *);
#endif /* DATA_H */
diff --git a/keama/doc.txt b/keama/doc.txt
index f7ae353e..6a43e536 100644
--- a/keama/doc.txt
+++ b/keama/doc.txt
@@ -363,6 +363,8 @@ TODO implement and check move
ISC DHCP supports interval ranges for prefix6. Kea has a different
and IMHO more powerful model.
Pool6 permissions are not supported.
+ option-data is not allowed in DHCPv4 pools
+TODO create a ticket about this silly constraint
Authoritative declaration:
--------------------------
@@ -389,7 +391,9 @@ Hostnames:
ISC DHCP does dynamic resolution in parse_ip_addr_or_hostname.
Static (at conversion time) resolution to one address is done by
the MA for fixed-address. Resolution is considered as painful
- in option values, i.e. there are better (and safer) ways to do this.
+ there are better (and safer) ways to do this. The -r (resolve)
+ command line parameter controls the at-conversion-time resolution.
+ Note only the first address is returned.
TODO: check the multiple address comment is correctly taken
Server DUID:
@@ -442,5 +446,3 @@ Dynamic DNS:
------------
Details are very different so the MA maps only basic parameters
at the global scope.
-
-
diff --git a/keama/keama.8 b/keama/keama.8
index 34ecca58..146dfc46 100644
--- a/keama/keama.8
+++ b/keama/keama.8
@@ -36,6 +36,10 @@ keama - Kea Migration Assistant
|
.B -6]
[
+.B -r
+.I {perform|fatal|pass}
+]
+[
.B -i
.I input-file
]
@@ -58,6 +62,10 @@ option.
The input configuration is for DHCPv6. Incompatible with the \fB-4\fR
option.
.TP
+-r
+Specify what to do with hostnames: resolve them into their first address,
+raise a fatal error or pass them silently.
+.TP
-i input-file
Specify the ISC DHCP configuration file to read. When it is not
given the standard input is used.
diff --git a/keama/keama.c b/keama/keama.c
index dbb00985..e0d304eb 100644
--- a/keama/keama.c
+++ b/keama/keama.c
@@ -32,7 +32,8 @@
#include "keama.h"
-#define KEAMA_USAGE "Usage: keama [-4|-6] [-i input-file] [-o output-file]"
+#define KEAMA_USAGE "Usage: keama [-4|-6] [-r {perform|fatal|pass}\\n" \
+ " [-i input-file] [-o output-file]\n"
static void
usage(const char *sfmt, const char *sarg) {
@@ -40,7 +41,7 @@ usage(const char *sfmt, const char *sarg) {
fprintf(stderr, sfmt, sarg);
fprintf(stderr, "\n");
}
- fprintf(stderr, "%s\n", KEAMA_USAGE);
+ fputs(KEAMA_USAGE, stderr);
exit(1);
}
@@ -52,6 +53,7 @@ FILE *output = NULL;
isc_boolean_t json = ISC_FALSE;
static const char use_noarg[] = "No argument for command: %s";
+static const char bad_resolve[] = "Bad -r argument: %s";
int
main(int argc, char **argv) {
@@ -70,7 +72,18 @@ main(int argc, char **argv) {
local_family = AF_INET6;
else if (strcmp(argv[i], "-T") == 0)
json = ISC_TRUE;
- else if (strcmp(argv[i], "-i") == 0) {
+ else if (strcmp(argv[i], "-r") == 0) {
+ if (++i == argc)
+ usage(use_noarg, argv[i - 1]);
+ if (strcmp(argv[i], "perform") == 0)
+ resolve = perform;
+ else if (strcmp(argv[i], "fatal") == 0)
+ resolve = fatal;
+ else if (strcmp(argv[i], "pass") == 0)
+ resolve = pass;
+ else
+ usage(bad_resolve, argv[i]);
+ } else if (strcmp(argv[i], "-i") == 0) {
if (++i == argc)
usage(use_noarg, argv[i - 1]);
input_file = argv[i];
@@ -83,7 +96,7 @@ main(int argc, char **argv) {
}
if (!json && (local_family == 0))
- usage("address family must be set using ", "-4 or -6");
+ usage("address family must be set using %s", "-4 or -6");
if (input_file == NULL) {
input_file = "--stdin--";
@@ -194,5 +207,5 @@ parse_error(struct parse *cfile, const char *fmt, ...)
fprintf(stderr, "%s\n%s\n", mbuf, cfile->token_line);
if (cfile->lexchar < 81)
fprintf(stderr, "%s^\n", lexbuf);
- exit(1);
+ exit(-1);
}
diff --git a/keama/keama.h b/keama/keama.h
index 6f158bf8..fb737efe 100644
--- a/keama/keama.h
+++ b/keama/keama.h
@@ -30,11 +30,22 @@
#include <time.h>
+/* Resolution of FQDNs into IPv4 addresses */
+enum resolve {
+ perform = 0, /* resolve */
+ fatal, /* raise a fatal error */
+ pass /* pass the string wth a warning */
+} resolve;
+
/* From includes/dhcp.h */
#define DHO_DHCP_SERVER_IDENTIFIER 54
#define DHO_VENDOR_CLASS_IDENTIFIER 60
#define DHO_USER_CLASS 77
+#define DHO_VIVSO_SUBOPTIONS 125
+
+/* From includes/dhcp6.h */
+#define D6O_VENDOR_OPTS 17
/* From includes/dhcpd.h */
@@ -345,7 +356,7 @@ void parse_option_code_definition(struct parse *, struct option *);
void parse_vendor_code_definition(struct parse *, struct option *);
struct string *parse_base64(struct parse *);
struct string *parse_cshl(struct parse *);
-struct string *parse_hexa(struct parse *, struct string *);
+struct string *parse_hexa(struct parse *);
isc_boolean_t parse_executable_statements(struct element *,
struct parse *, isc_boolean_t *,
enum expression_context);
@@ -394,9 +405,12 @@ void spaces_init(void);
void options_init(void);
struct space *space_lookup(const char *);
struct option *option_lookup_name(const char *, const char *);
+struct option *kea_lookup_name(const char *, const char *);
struct option *option_lookup_code(const char *, unsigned);
void push_space(struct space *);
void push_option(struct option *);
+void add_option_data(struct element *, struct element *);
+void merge_option_data(struct element *, struct element *);
struct comments *get_config_comments(unsigned);
/* json.c */
diff --git a/keama/options.c b/keama/options.c
index 798780eb..89935299 100644
--- a/keama/options.c
+++ b/keama/options.c
@@ -572,6 +572,31 @@ option_lookup_name(const char *space, const char *name)
}
struct option *
+kea_lookup_name(const char *space, const char *name)
+{
+ struct space *universe;
+ struct option *option;
+
+ TAILQ_FOREACH(universe, &spaces) {
+ if (universe->status == kea_unknown)
+ continue;
+ if (strcmp(name, universe->name) == 0)
+ break;
+ }
+ if (universe == NULL)
+ return NULL;
+ TAILQ_FOREACH(option, &options) {
+ if (option->status == kea_unknown)
+ continue;
+ if (universe != option->space)
+ continue;
+ if (strcmp(name, option->name) == 0)
+ return option;
+ }
+ return NULL;
+}
+
+struct option *
option_lookup_code(const char *space, unsigned code)
{
struct space *universe;
@@ -605,6 +630,68 @@ push_option(struct option *option)
TAILQ_INSERT_TAIL(&options, option);
}
+void
+add_option_data(struct element *src, struct element *dst)
+{
+ struct string *sspace;
+ struct element *scode;
+ struct element *name;
+ struct option *option;
+ size_t i;
+
+ sspace = stringValue(mapGet(src, "space"));
+ scode = mapGet(src, "code");
+ name = mapGet(src, "name");
+ assert((scode != NULL) || (name != NULL));
+
+ /* We'll use the code so fill it even it should always be available */
+ if (scode == NULL) {
+ option = kea_lookup_name(sspace->content,
+ stringValue(name)->content);
+ assert(option != NULL);
+ scode = createInt(option->code);
+ mapSet(src, scode, "code");
+ }
+ assert(intValue(scode) != 0);
+
+ for (i = 0; i < listSize(dst); i++) {
+ struct element *od;
+ struct element *space;
+ struct element *code;
+
+ od = listGet(dst, i);
+ space = mapGet(od, "space");
+ if (!eqString(sspace, stringValue(space)))
+ continue;
+ code = mapGet(od, "code");
+ if (code == NULL) {
+ name = mapGet(od, "name");
+ assert(name != NULL);
+ option = kea_lookup_name(sspace->content,
+ stringValue(name)->content);
+ assert(option != NULL);
+ code = createInt(option->code);
+ mapSet(od, code, "code");
+ }
+ /* check if the option is already present */
+ if (intValue(scode) == intValue(code))
+ return;
+ }
+ listPush(dst, copy(src));
+}
+
+void
+merge_option_data(struct element *src, struct element *dst)
+{
+ struct element *od;
+ size_t i;
+
+ for (i = 0; i < listSize(src); i++) {
+ od = listGet(src, i);
+ add_option_data(od, dst);
+ }
+}
+
struct comments *
get_config_comments(unsigned code)
{
diff --git a/keama/parse.c b/keama/parse.c
index bcfcd81b..6d8c1f50 100644
--- a/keama/parse.c
+++ b/keama/parse.c
@@ -244,6 +244,12 @@ parse_ip_addr_or_hostname(struct parse *cfile, isc_boolean_t check_multi)
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))
@@ -1268,6 +1274,7 @@ parse_vendor_code_definition(struct parse *cfile, struct option *option)
struct string *space;
struct space *universe;
struct string *name;
+ unsigned code;
struct element *vendor;
space = makeString(-1, "vendor-");
@@ -1296,11 +1303,16 @@ parse_vendor_code_definition(struct parse *cfile, struct option *option)
universe->name = space->content;
/* Create the vendor option */
vendor = createMap();
- if (local_family == AF_INET)
+ if (local_family == AF_INET) {
name = makeString(-1, "vivso-suboptions");
- else
+ code = DHO_VIVSO_SUBOPTIONS;
+ } else {
name = makeString(-1, "vendor-opts");
+ code = D6O_VENDOR_OPTS;
+ }
+ mapSet(vendor, createString(makeString(-1, universe->name)), "space");
mapSet(vendor, createString(name), "name");
+ mapSet(vendor, createInt(code), "code");
mapSet(vendor, createString(id), "data");
universe->vendor = vendor;
parse_semi(cfile);
@@ -1401,7 +1413,7 @@ parse_cshl(struct parse *cfile)
/* Same but without colons in output */
struct string *
-parse_hexa(struct parse *cfile, struct string *saved)
+parse_hexa(struct parse *cfile)
{
uint8_t ibuf;
char tbuf[4];
@@ -1415,8 +1427,6 @@ parse_hexa(struct parse *cfile, struct string *saved)
token = next_token(&val, NULL, cfile);
if (token != NUMBER && token != NUMBER_OR_NAME)
parse_error(cfile, "expecting hexadecimal number.");
- if (saved != NULL)
- appendString(saved, val);
convert_num(cfile, &ibuf, val, 16, 8);
snprintf(tbuf, sizeof(tbuf), "%02hhx", ibuf);
appendString(data, tbuf);
@@ -1424,8 +1434,6 @@ parse_hexa(struct parse *cfile, struct string *saved)
token = peek_token(&val, NULL, cfile);
if (token != COLON)
break;
- if (saved != NULL)
- appendString(saved, val);
skip_token(&val, NULL, cfile);
}
@@ -3151,7 +3159,7 @@ parse_non_binary(struct element *expr,
/* Return a const-data to make a difference with
a string literal. */
data = makeString(-1, "0x");
- concatString(data, parse_hexa(cfile, NULL));
+ concatString(data, parse_hexa(cfile));
mapSet(expr, createString(data), "const-data");
break;
@@ -3804,20 +3812,44 @@ parse_option_data(struct element *expr,
struct comment *comment;
isc_boolean_t canon_bool = ISC_FALSE;
isc_boolean_t has_ignore = ISC_FALSE;
+ isc_boolean_t has_ambiguous_binary = ISC_FALSE;
+ isc_boolean_t modified = ISC_FALSE;
+ isc_boolean_t consumed;
data = makeString(0, NULL);
+
+ /* Save the initial content */
saved = makeString(0, NULL);
+ save_parse_state(cfile);
+ for (;;) {
+ token = peek_token(&val, NULL, cfile);
+ if ((token == SEMI) || (token == END_OF_FILE))
+ break;
+ skip_token(&val, &len, cfile);
+ item = makeString(len, val);
+ if (token == STRING) {
+ appendString(saved, "\"");
+ concatString(saved, item);
+ appendString(saved, "\"");
+ } else
+ concatString(saved, item);
+ if (token == COMMA)
+ appendString(saved, " ");
+ }
+ restore_parse_state(cfile);
+
format = makeString(0, NULL);
appendString(format, option->format);
/* To be sure we should never go outside it... */
appendString(format, "Ba");
- fmt = format->content;
+ fmt = format->content;
/* Handle ISC DHCP binary data */
if ((*fmt == 'E') || (*fmt == 'X')) {
token = peek_token(&val, NULL, cfile);
if (token == NUMBER_OR_NAME || token == NUMBER) {
- data = parse_hexa(cfile, saved);
+ data = parse_hexa(cfile);
+ modified = ISC_TRUE;
mapSet(expr, createBool(ISC_FALSE), "csv-format");
} else if (token == STRING) {
skip_token(&val, &len, cfile);
@@ -3825,8 +3857,7 @@ parse_option_data(struct element *expr,
} else
parse_error(cfile, "expecting string "
"or hexadecimal data.");
- mapSet(expr, createString(data), "data");
- return ISC_TRUE;
+ goto done;
}
/* Just collect data expecting ISC DHCP and Kea are compatible */
@@ -3835,7 +3866,8 @@ parse_option_data(struct element *expr,
fmt = format->content;
if ((*fmt == 'a') && (fmt != format->content))
fmt -= 1;
- token = peek_token(&val, NULL, cfile);
+ consumed = ISC_FALSE;
+ token = peek_token(&val, &len, cfile);
if (token == END_OF_FILE)
parse_error(cfile, "unexpected end of file");
if (token == SEMI)
@@ -3843,60 +3875,74 @@ parse_option_data(struct element *expr,
if (token == COMMA) {
skip_token(&val, NULL, cfile);
appendString(data, ", ");
- appendString(saved, ",");
fmt++;
continue;
}
- skip_token(&val, &len, cfile);
-
- item = makeString(len, val);
+
+ /* Addresses */
+ if (*fmt == 'I') {
+ item = parse_ip_addr_or_hostname(cfile, ISC_FALSE);
+ modified = ISC_TRUE;
+ consumed = ISC_TRUE;
+ } else if (*fmt == '6') {
+ item = parse_ip6_addr_txt(cfile);
+ modified = ISC_TRUE;
+ consumed = ISC_TRUE;
+ } else
/* Translate booleans */
if ((*fmt == 'f') && is_identifier(token)) {
if ((len == 3) && (memcmp(val, "off", 3) == 0)) {
val = "false";
len = 5;
canon_bool = ISC_TRUE;
+ modified = ISC_TRUE;
} else if (token == ON) {
val = "true";
len = 4;
canon_bool = ISC_TRUE;
+ modified = ISC_TRUE;
} else if (token == IGNORE)
has_ignore = ISC_TRUE;
- }
+ item = makeString(len, val);
+ } else
/* Handle terminating binary */
if ((*fmt == 'X') &&
- (token == NUMBER_OR_NAME || token == NUMBER))
- item = parse_hexa(cfile, saved);
+ (token == NUMBER_OR_NAME || token == NUMBER)) {
+ has_ambiguous_binary = ISC_TRUE;
+ item = parse_hexa(cfile);
+ consumed = ISC_TRUE;
+ } else
/* STRING can return embedded unexpected characters */
- else if (token == STRING) {
- concatString(saved, item);
+ if (token == STRING)
item = escape_option_string(len, val);
- } else {
- concatString(saved, item);
+ else
item = makeString(len, val);
- }
concatString(data, item);
+ if (!consumed)
+ skip_token(&val, NULL, cfile);
fmt++;
}
+done:
+ if (modified && !eqString(saved, data)) {
+ elem = createString(saved);
+ elem->skip = ISC_TRUE;
+ mapSet(expr, elem, "original-data");
+ }
+
+ elem = createString(data);
if (canon_bool) {
comment = createComment("/// canonized booleans to "
" lowercase true or false");
- TAILQ_INSERT_TAIL(&expr->comments, comment);
+ TAILQ_INSERT_TAIL(&elem->comments, comment);
}
if (has_ignore) {
comment = createComment("/// 'ignore' pseudo-boolean is used");
- TAILQ_INSERT_TAIL(&expr->comments, comment);
+ TAILQ_INSERT_TAIL(&elem->comments, comment);
expr->skip = ISC_TRUE;
cfile->issue_counter++;
}
-
- if (canon_bool || has_ignore) {
- elem = createString(saved);
- elem->skip = ISC_TRUE;
- mapSet(expr, elem, "original-data");
- }
- mapSet(expr, createString(data), "data");
+ mapSet(expr, elem, "data");
return ISC_TRUE;
}
@@ -4017,54 +4063,8 @@ parse_option_statement(struct element *result,
opt_data_list = createList();
mapSet(cfile->stack[where], opt_data_list, "option-data");
}
- if (!opt_data->skip && (option->space->vendor != NULL)) {
- size_t i;
- isc_boolean_t already = ISC_FALSE;
- struct string *vo_name;
- struct string *vo_data;
- struct element *opt;
-
- vo_name = stringValue(mapGet(option->space->vendor, "name"));
- vo_data = stringValue(mapGet(option->space->vendor, "data"));
- for (i = 0; i < listSize(opt_data_list); i++) {
- struct element *name;
- struct element *data;
-
- opt = listGet(opt_data_list, 0);
- if (opt == NULL)
- parse_error(cfile, "null option-data at %u",
- (unsigned) i);
- name = mapGet(opt, "name");
- if (name == NULL)
- continue;
- if (name->type != ELEMENT_STRING)
- parse_error(cfile, "bad name in option-data "
- "at %u: unexpected type %s",
- (unsigned) i,
- type2name(name->type));
- if (!eqString(stringValue(name), vo_name))
- continue;
- data = mapGet(opt, "data");
- /* Can be expression too */
- if (data == NULL)
- continue;
- if (data->type != ELEMENT_STRING)
- parse_error(cfile, "bad data in option-data "
- "at %u: unexpected type %s",
- (unsigned) i,
- type2name(data->type));
- if (eqString(stringValue(data), vo_data)) {
- already = ISC_TRUE;
- break;
- }
- }
- if (!already) {
- opt = createMap();
- mapSet(opt, createString(vo_name), "name");
- mapSet(opt, createString(vo_data), "data");
- listPush(opt_data_list, opt);
- }
- }
+ 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;
diff --git a/keama/tests/empty.err b/keama/tests/empty.err
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/keama/tests/empty.err
diff --git a/keama/tests/empty.msg b/keama/tests/empty.msg
new file mode 100644
index 00000000..098ffb61
--- /dev/null
+++ b/keama/tests/empty.msg
@@ -0,0 +1 @@
+tests/empty.err line 0: missing top level authoritative statement
diff --git a/keama/tests/minimal4.in4 b/keama/tests/minimal4.in4
new file mode 100644
index 00000000..c8c76f04
--- /dev/null
+++ b/keama/tests/minimal4.in4
@@ -0,0 +1,7 @@
+# minimal config
+
+# authoritative is mandatory
+authoritative;
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
diff --git a/keama/tests/minimal4.out b/keama/tests/minimal4.out
new file mode 100644
index 00000000..cbc68ddc
--- /dev/null
+++ b/keama/tests/minimal4.out
@@ -0,0 +1,5 @@
+{
+ "Dhcp4": {
+ "valid-lifetime": 1800
+ }
+}
diff --git a/keama/tests/minimal6.in6 b/keama/tests/minimal6.in6
new file mode 100644
index 00000000..c8c76f04
--- /dev/null
+++ b/keama/tests/minimal6.in6
@@ -0,0 +1,7 @@
+# minimal config
+
+# authoritative is mandatory
+authoritative;
+
+# empty configs are not accepted by Kea
+default-lease-time 1800;
diff --git a/keama/tests/minimal6.out b/keama/tests/minimal6.out
new file mode 100644
index 00000000..b8082ca4
--- /dev/null
+++ b/keama/tests/minimal6.out
@@ -0,0 +1,5 @@
+{
+ "Dhcp6": {
+ "valid-lifetime": 1800
+ }
+}
diff --git a/keama/tests/runall.sh b/keama/tests/runall.sh
new file mode 100644
index 00000000..c877102b
--- /dev/null
+++ b/keama/tests/runall.sh
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+#set -x
+
+cd "$(dirname "$0")"
+
+for t in *.err* *.in*
+do
+ echo `basename $t`
+ /bin/sh runone.sh $t
+done
diff --git a/keama/tests/runone.sh b/keama/tests/runone.sh
new file mode 100644
index 00000000..c6b0bcd9
--- /dev/null
+++ b/keama/tests/runone.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+
+#set -x
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 test-name" >&2
+ exit 1
+fi
+
+file=$1
+
+cd "$(dirname "$0")"
+
+isin=$(expr $file : ".*\.in*")
+iserr=$(expr $file : ".*\.err*")
+if [ \( $isin -eq 0 \) -a \( $iserr -eq 0 \) ]; then
+ full=$file.in*
+ if [ ! -f $full ]; then
+ full=$file.err*
+ fi
+else
+ full=$file
+fi
+
+if [ ! -f $full ]; then
+ echo "can't find $file" >&2
+ exit 1
+fi
+
+errcase=$(expr $full : ".*\.err*")
+
+trail=
+if [ $errcase -eq 0 ]; then
+ trail=$(expr $full : ".*\.in\(.\)")
+else
+ trail=$(expr $full : ".*\.err\(.\)")
+fi
+
+options=""
+dual=0
+
+case $trail in
+ '') dual=1;;
+ 4) options="-4";;
+ 6) options="-6";;
+ F) options="-4 -r fatal";;
+ P) options="-4 -r pass";;
+ *) echo "unrecognized trail '$trail' in '$full'" >&2; exit 1;;
+esac
+
+if [ $errcase -ne 0 ]; then
+ base=$(basename $full .err$trail)
+else
+ if [ $dual -ne 0 ]; then
+ echo "required trail ([45FP]) in '$full'" >&2
+ exit 1
+ fi
+ base=$(basename $full .in$trail)
+fi
+
+out=/tmp/$base.out$$
+expected=""
+if [ $errcase ]; then
+ expected=$base.msg
+else
+ expected=$base.out
+fi
+
+if [ $errcase -ne 0 ]; then
+ if [ $dual -eq 1 ]; then
+ ../keama -4 -i $full 2> $out > /dev/null
+ if [ $? -ne 255 ]; then
+ echo "$full -4 doesn't fail as expected" >&2
+ exit 1
+ fi
+ ../keama -6 -i $full 2> $out > /dev/null
+ if [ $? -ne 255 ]; then
+ echo "$full -6 doesn't fail as expected" >&2
+ exit 1
+ fi
+ else
+ ../keama $options -i $full 2> $out > /dev/null
+ if [ $? -ne 255 ]; then
+ echo "$full doesn't fail as expected" >&2
+ exit 1
+ fi
+ fi
+else
+ ../keama $options -i $full -o $out >&2
+ if [ $? -eq 255 ]; then
+ echo "$full raised an error" >&2
+ exit 1
+ fi
+fi
+
+if `cmp -s $out $expected`; then
+ echo "$full not expected output" >&2
+ exit 1
+fi