diff options
-rw-r--r-- | keama/confparse.c | 247 | ||||
-rw-r--r-- | keama/data.c | 22 | ||||
-rw-r--r-- | keama/data.h | 1 | ||||
-rw-r--r-- | keama/doc.txt | 8 | ||||
-rw-r--r-- | keama/keama.8 | 8 | ||||
-rw-r--r-- | keama/keama.c | 23 | ||||
-rw-r--r-- | keama/keama.h | 16 | ||||
-rw-r--r-- | keama/options.c | 87 | ||||
-rw-r--r-- | keama/parse.c | 164 | ||||
-rw-r--r-- | keama/tests/empty.err | 0 | ||||
-rw-r--r-- | keama/tests/empty.msg | 1 | ||||
-rw-r--r-- | keama/tests/minimal4.in4 | 7 | ||||
-rw-r--r-- | keama/tests/minimal4.out | 5 | ||||
-rw-r--r-- | keama/tests/minimal6.in6 | 7 | ||||
-rw-r--r-- | keama/tests/minimal6.out | 5 | ||||
-rw-r--r-- | keama/tests/runall.sh | 11 | ||||
-rw-r--r-- | keama/tests/runone.sh | 99 |
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(¶m->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(¶m->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 |