diff --git a/bin/named/config.c b/bin/named/config.c new file mode 100644 index 0000000000..dce527075e --- /dev/null +++ b/bin/named/config.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2001, 2002 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* $Id: config.c,v 1.11.2.4 2002/03/20 20:32:41 marka Exp $ */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +static char defaultconf[] = "\ +options {\n\ +# blackhole {none;};\n\ + coresize default;\n\ + datasize default;\n\ + deallocate-on-exit true;\n\ +# directory \n\ + dump-file \"named_dump.db\";\n\ + fake-iquery no;\n\ + files default;\n\ + has-old-clients false;\n\ + heartbeat-interval 60;\n\ + host-statistics no;\n\ + interface-interval 60;\n\ + listen-on {any;};\n\ + listen-on-v6 {none;};\n\ + match-mapped-addresses no;\n\ + memstatistics-file \"named.memstats\";\n\ + multiple-cnames no;\n\ +# named-xfer ;\n\ +# pid-file \"" NS_LOCALSTATEDIR "/named.pid\"; /* or /lwresd.pid */\n\ + port 53;\n\ +" +#ifdef PATH_RANDOMDEV +"\ + random-device \"" PATH_RANDOMDEV "\";\n\ +" +#endif +"\ + recursive-clients 1000;\n\ + rrset-order {order cyclic;};\n\ + serial-queries 20;\n\ + serial-query-rate 20;\n\ + stacksize default;\n\ + statistics-file \"named.stats\";\n\ + statistics-interval 60;\n\ + tcp-clients 100;\n\ +# tkey-dhkey \n\ +# tkey-gssapi-credential \n\ +# tkey-domain \n\ + transfers-per-ns 2;\n\ + transfers-in 10;\n\ + transfers-out 10;\n\ + treat-cr-as-space true;\n\ + use-id-pool true;\n\ + use-ixfr true;\n\ + version \""VERSION"\";\n\ +\n\ + /* view */\n\ + allow-notify {none;};\n\ + allow-update-forwarding {none;};\n\ + allow-recursion {any;};\n\ + allow-v6-synthesis {none;};\n\ +# sortlist \n\ +# topology \n\ + auth-nxdomain false;\n\ + minimal-responses false;\n\ + recursion true;\n\ + provide-ixfr true;\n\ + request-ixfr true;\n\ + fetch-glue no;\n\ + rfc2308-type1 no;\n\ + additional-from-auth true;\n\ + additional-from-cache true;\n\ + query-source address *;\n\ + query-source-v6 address *;\n\ + notify-source *;\n\ + notify-source-v6 *;\n\ + cleaning-interval 60;\n\ + min-roots 2;\n\ + lame-ttl 600;\n\ + max-ncache-ttl 10800; /* 3 hours */\n\ + max-cache-ttl 604800; /* 1 week */\n\ + transfer-format many-answers;\n\ + max-cache-size 0;\n\ + check-names master ignore;\n\ + check-names slave ignore;\n\ + check-names response ignore;\n\ +\n\ + /* zone */\n\ + allow-query {any;};\n\ + allow-transfer {any;};\n\ + notify yes;\n\ +# also-notify \n\ + dialup no;\n\ +# forward \n\ +# forwarders \n\ + maintain-ixfr-base no;\n\ +# max-ixfr-log-size \n\ + transfer-source *;\n\ + transfer-source-v6 *;\n\ + max-transfer-time-in 120;\n\ + max-transfer-time-out 120;\n\ + max-transfer-idle-in 60;\n\ + max-transfer-idle-out 60;\n\ + max-retry-time 1209600; /* 2 weeks */\n\ + min-retry-time 500;\n\ + max-refresh-time 2419200; /* 4 weeks */\n\ + min-refresh-time 300;\n\ + sig-validity-interval 30; /* days */\n\ + zone-statistics false;\n\ +};"; + +isc_result_t +ns_config_parsedefaults(cfg_parser_t *parser, cfg_obj_t **conf) { + isc_buffer_t b; + + isc_buffer_init(&b, defaultconf, sizeof(defaultconf) - 1); + isc_buffer_add(&b, sizeof(defaultconf) - 1); + return (cfg_parse_buffer(parser, &b, &cfg_type_namedconf, conf)); +} + +isc_result_t +ns_config_get(cfg_obj_t **maps, const char* name, cfg_obj_t **obj) { + int i; + + for (i = 0; ; i++) { + if (maps[i] == NULL) + return (ISC_R_NOTFOUND); + if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) + return (ISC_R_SUCCESS); + } +} + +int +ns_config_listcount(cfg_obj_t *list) { + cfg_listelt_t *e; + int i = 0; + + for (e = cfg_list_first(list); e != NULL; e = cfg_list_next(e)) + i++; + + return (i); +} + +isc_result_t +ns_config_getclass(cfg_obj_t *classobj, dns_rdataclass_t defclass, + dns_rdataclass_t *classp) { + char *str; + isc_textregion_t r; + isc_result_t result; + + if (!cfg_obj_isstring(classobj)) { + *classp = defclass; + return (ISC_R_SUCCESS); + } + str = cfg_obj_asstring(classobj); + r.base = str; + r.length = strlen(str); + result = dns_rdataclass_fromtext(classp, &r); + if (result != ISC_R_SUCCESS) + cfg_obj_log(classobj, ns_g_lctx, ISC_LOG_ERROR, + "unknown class '%s'", str); + return (result); +} + +dns_zonetype_t +ns_config_getzonetype(cfg_obj_t *zonetypeobj) { + dns_zonetype_t ztype = dns_zone_none; + char *str; + + str = cfg_obj_asstring(zonetypeobj); + if (strcasecmp(str, "master") == 0) + ztype = dns_zone_master; + else if (strcasecmp(str, "slave") == 0) + ztype = dns_zone_slave; + else if (strcasecmp(str, "stub") == 0) + ztype = dns_zone_stub; + else + INSIST(0); + return (ztype); +} + +isc_result_t +ns_config_getiplist(cfg_obj_t *config, cfg_obj_t *list, + in_port_t defport, isc_mem_t *mctx, + isc_sockaddr_t **addrsp, isc_uint32_t *countp) +{ + int count, i = 0; + cfg_obj_t *addrlist; + cfg_obj_t *portobj; + cfg_listelt_t *element; + isc_sockaddr_t *addrs; + in_port_t port; + isc_result_t result; + + INSIST(addrsp != NULL && *addrsp == NULL); + + addrlist = cfg_tuple_get(list, "addresses"); + count = ns_config_listcount(addrlist); + + portobj = cfg_tuple_get(list, "port"); + if (cfg_obj_isuint32(portobj)) { + isc_uint32_t val = cfg_obj_asuint32(portobj); + if (val > ISC_UINT16_MAX) { + cfg_obj_log(portobj, ns_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + return (ISC_R_RANGE); + } + port = (in_port_t) val; + } else if (defport != 0) + port = defport; + else { + result = ns_config_getport(config, &port); + if (result != ISC_R_SUCCESS) + return (result); + } + + addrs = isc_mem_get(mctx, count * sizeof(isc_sockaddr_t)); + if (addrs == NULL) + return (ISC_R_NOMEMORY); + + for (element = cfg_list_first(addrlist); + element != NULL; + element = cfg_list_next(element), i++) + { + INSIST(i < count); + addrs[i] = *cfg_obj_assockaddr(cfg_listelt_value(element)); + if (isc_sockaddr_getport(&addrs[i]) == 0) + isc_sockaddr_setport(&addrs[i], port); + } + INSIST(i == count); + + *addrsp = addrs; + *countp = count; + + return (ISC_R_SUCCESS); +} + +void +ns_config_putiplist(isc_mem_t *mctx, isc_sockaddr_t **addrsp, + isc_uint32_t count) +{ + INSIST(addrsp != NULL && *addrsp != NULL); + + isc_mem_put(mctx, *addrsp, count * sizeof(isc_sockaddr_t)); + *addrsp = NULL; +} + +isc_result_t +ns_config_getipandkeylist(cfg_obj_t *config, cfg_obj_t *list, isc_mem_t *mctx, + isc_sockaddr_t **addrsp, dns_name_t ***keysp, + isc_uint32_t *countp) +{ + isc_uint32_t count, i = 0; + isc_result_t result; + cfg_listelt_t *element; + cfg_obj_t *addrlist; + cfg_obj_t *portobj; + in_port_t port; + dns_fixedname_t fname; + isc_sockaddr_t *addrs = NULL; + dns_name_t **keys = NULL; + + INSIST(addrsp != NULL && *addrsp == NULL); + + addrlist = cfg_tuple_get(list, "addresses"); + count = ns_config_listcount(addrlist); + + portobj = cfg_tuple_get(list, "port"); + if (cfg_obj_isuint32(portobj)) { + isc_uint32_t val = cfg_obj_asuint32(portobj); + if (val > ISC_UINT16_MAX) { + cfg_obj_log(portobj, ns_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", val); + return (ISC_R_RANGE); + } + port = (in_port_t) val; + } else { + result = ns_config_getport(config, &port); + if (result != ISC_R_SUCCESS) + return (result); + } + + result = ISC_R_NOMEMORY; + + addrs = isc_mem_get(mctx, count * sizeof(isc_sockaddr_t)); + if (addrs == NULL) + goto cleanup; + + keys = isc_mem_get(mctx, count * sizeof(dns_name_t *)); + if (keys == NULL) + goto cleanup; + + for (element = cfg_list_first(addrlist); + element != NULL; + element = cfg_list_next(element), i++) + { + cfg_obj_t *addr; + cfg_obj_t *key; + char *keystr; + isc_buffer_t b; + + INSIST(i < count); + + addr = cfg_tuple_get(cfg_listelt_value(element), "sockaddr"); + key = cfg_tuple_get(cfg_listelt_value(element), "key"); + + addrs[i] = *cfg_obj_assockaddr(addr); + if (isc_sockaddr_getport(&addrs[i]) == 0) + isc_sockaddr_setport(&addrs[i], port); + + keys[i] = NULL; + if (!cfg_obj_isstring(key)) + continue; + keys[i] = isc_mem_get(mctx, sizeof(dns_name_t)); + if (keys[i] == NULL) + goto cleanup; + dns_name_init(keys[i], NULL); + + keystr = cfg_obj_asstring(key); + isc_buffer_init(&b, keystr, strlen(keystr)); + isc_buffer_add(&b, strlen(keystr)); + dns_fixedname_init(&fname); + result = dns_name_fromtext(dns_fixedname_name(&fname), &b, + dns_rootname, ISC_FALSE, NULL); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = dns_name_dup(dns_fixedname_name(&fname), mctx, + keys[i]); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + INSIST(i == count); + + *addrsp = addrs; + *keysp = keys; + *countp = count; + + return (ISC_R_SUCCESS); + + cleanup: + if (addrs != NULL) + isc_mem_put(mctx, addrs, count * sizeof(isc_sockaddr_t)); + if (keys != NULL) { + unsigned int j; + for (j = 0 ; j <= i; j++) { + if (keys[j] == NULL) + continue; + if (dns_name_dynamic(keys[j])) + dns_name_free(keys[j], mctx); + isc_mem_put(mctx, keys[j], sizeof(dns_name_t)); + } + isc_mem_put(mctx, keys, count * sizeof(dns_name_t *)); + } + return (result); +} + +void +ns_config_putipandkeylist(isc_mem_t *mctx, isc_sockaddr_t **addrsp, + dns_name_t ***keysp, isc_uint32_t count) +{ + unsigned int i; + dns_name_t **keys = *keysp; + + INSIST(addrsp != NULL && *addrsp != NULL); + + isc_mem_put(mctx, *addrsp, count * sizeof(isc_sockaddr_t)); + for (i = 0; i < count; i++) { + if (keys[i] == NULL) + continue; + if (dns_name_dynamic(keys[i])) + dns_name_free(keys[i], mctx); + isc_mem_put(mctx, keys[i], sizeof(dns_name_t)); + } + isc_mem_put(mctx, *keysp, count * sizeof(dns_name_t *)); + *addrsp = NULL; + *keysp = NULL; +} + +isc_result_t +ns_config_getport(cfg_obj_t *config, in_port_t *portp) { + cfg_obj_t *maps[3]; + cfg_obj_t *options = NULL; + cfg_obj_t *portobj = NULL; + isc_result_t result; + int i; + + cfg_map_get(config, "options", &options); + i = 0; + if (options != NULL) + maps[i++] = options; + maps[i++] = ns_g_defaults; + maps[i] = NULL; + + result = ns_config_get(maps, "port", &portobj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_asuint32(portobj) >= ISC_UINT16_MAX) { + cfg_obj_log(portobj, ns_g_lctx, ISC_LOG_ERROR, + "port '%u' out of range", + cfg_obj_asuint32(portobj)); + return (ISC_R_RANGE); + } + *portp = (in_port_t)cfg_obj_asuint32(portobj); + return (ISC_R_SUCCESS); +} + +isc_result_t +ns_config_getkeyalgorithm(const char *str, dns_name_t **name) +{ + if (strcasecmp(str, "hmac-md5") == 0 || + strcasecmp(str, "hmac-md5.sig-alg.reg.int") == 0 || + strcasecmp(str, "hmac-md5.sig-alg.reg.int.") == 0) + { + if (name != NULL) + *name = dns_tsig_hmacmd5_name; + return (ISC_R_SUCCESS); + } + return (ISC_R_NOTFOUND); +} diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c new file mode 100644 index 0000000000..9ea1aea104 --- /dev/null +++ b/bin/named/zoneconf.c @@ -0,0 +1,608 @@ +/* + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* $Id: zoneconf.c,v 1.87.2.4 2001/11/13 01:15:33 gson Exp $ */ + +#include + +#include +#include +#include /* Required for HP/UX (and others?) */ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * These are BIND9 server defaults, not necessarily identical to the + * library defaults defined in zone.c. + */ +#define RETERR(x) do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return (_r); \ + } while (0) + +/* + * Convenience function for configuring a single zone ACL. + */ +static isc_result_t +configure_zone_acl(cfg_obj_t *zconfig, cfg_obj_t *vconfig, cfg_obj_t *config, + const char *aclname, ns_aclconfctx_t *actx, + dns_zone_t *zone, + void (*setzacl)(dns_zone_t *, dns_acl_t *), + void (*clearzacl)(dns_zone_t *)) +{ + isc_result_t result; + cfg_obj_t *maps[4]; + cfg_obj_t *aclobj = NULL; + int i = 0; + dns_acl_t *dacl = NULL; + + if (zconfig != NULL) + maps[i++] = cfg_tuple_get(zconfig, "options"); + if (vconfig != NULL) + maps[i++] = cfg_tuple_get(vconfig, "options"); + if (config != NULL) { + cfg_obj_t *options = NULL; + (void)cfg_map_get(config, "options", &options); + if (options != NULL) + maps[i++] = options; + } + maps[i] = NULL; + + result = ns_config_get(maps, aclname, &aclobj); + if (aclobj == NULL) { + (*clearzacl)(zone); + return (ISC_R_SUCCESS); + } + + result = ns_acl_fromconfig(aclobj, config, actx, + dns_zone_getmctx(zone), &dacl); + if (result != ISC_R_SUCCESS) + return (result); + (*setzacl)(zone, dacl); + dns_acl_detach(&dacl); + return (ISC_R_SUCCESS); +} + +/* + * Parse the zone update-policy statement. + */ +static isc_result_t +configure_zone_ssutable(cfg_obj_t *zconfig, dns_zone_t *zone) { + cfg_obj_t *updatepolicy = NULL; + cfg_listelt_t *element, *element2; + dns_ssutable_t *table = NULL; + isc_mem_t *mctx = dns_zone_getmctx(zone); + isc_result_t result; + + (void)cfg_map_get(zconfig, "update-policy", &updatepolicy); + if (updatepolicy == NULL) + return (ISC_R_SUCCESS); + + result = dns_ssutable_create(mctx, &table); + if (result != ISC_R_SUCCESS) + return (result); + + for (element = cfg_list_first(updatepolicy); + element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *stmt = cfg_listelt_value(element); + cfg_obj_t *mode = cfg_tuple_get(stmt, "mode"); + cfg_obj_t *identity = cfg_tuple_get(stmt, "identity"); + cfg_obj_t *matchtype = cfg_tuple_get(stmt, "matchtype"); + cfg_obj_t *dname = cfg_tuple_get(stmt, "name"); + cfg_obj_t *typelist = cfg_tuple_get(stmt, "types"); + char *str; + isc_boolean_t grant = ISC_FALSE; + unsigned int mtype = DNS_SSUMATCHTYPE_NAME; + dns_fixedname_t fname, fident; + isc_buffer_t b; + dns_rdatatype_t *types; + unsigned int i, n; + + str = cfg_obj_asstring(mode); + if (strcasecmp(str, "grant") == 0) + grant = ISC_TRUE; + else if (strcasecmp(str, "deny") == 0) + grant = ISC_FALSE; + else + INSIST(0); + + str = cfg_obj_asstring(matchtype); + if (strcasecmp(str, "name") == 0) + mtype = DNS_SSUMATCHTYPE_NAME; + else if (strcasecmp(str, "subdomain") == 0) + mtype = DNS_SSUMATCHTYPE_SUBDOMAIN; + else if (strcasecmp(str, "wildcard") == 0) + mtype = DNS_SSUMATCHTYPE_WILDCARD; + else if (strcasecmp(str, "self") == 0) + mtype = DNS_SSUMATCHTYPE_SELF; + else + INSIST(0); + + dns_fixedname_init(&fident); + str = cfg_obj_asstring(identity); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(dns_fixedname_name(&fident), &b, + dns_rootname, ISC_FALSE, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(identity, ns_g_lctx, ISC_LOG_ERROR, + "'%s' is not a valid name", str); + goto cleanup; + } + + dns_fixedname_init(&fname); + str = cfg_obj_asstring(dname); + isc_buffer_init(&b, str, strlen(str)); + isc_buffer_add(&b, strlen(str)); + result = dns_name_fromtext(dns_fixedname_name(&fname), &b, + dns_rootname, ISC_FALSE, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(identity, ns_g_lctx, ISC_LOG_ERROR, + "'%s' is not a valid name", str); + goto cleanup; + } + + n = ns_config_listcount(typelist); + if (n == 0) + types = NULL; + else { + types = isc_mem_get(mctx, n * sizeof(dns_rdatatype_t)); + if (types == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } + + i = 0; + for (element2 = cfg_list_first(typelist); + element2 != NULL; + element2 = cfg_list_next(element2)) + { + cfg_obj_t *typeobj; + isc_textregion_t r; + + INSIST(i < n); + + typeobj = cfg_listelt_value(element2); + str = cfg_obj_asstring(typeobj); + r.base = str; + r.length = strlen(str); + + result = dns_rdatatype_fromtext(&types[i++], &r); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(identity, ns_g_lctx, ISC_LOG_ERROR, + "'%s' is not a valid type", str); + isc_mem_put(mctx, types, + n * sizeof(dns_rdatatype_t)); + goto cleanup; + } + } + INSIST(i == n); + + result = dns_ssutable_addrule(table, grant, + dns_fixedname_name(&fident), + mtype, + dns_fixedname_name(&fname), + n, types); + if (types != NULL) + isc_mem_put(mctx, types, n * sizeof(dns_rdatatype_t)); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + } + + result = ISC_R_SUCCESS; + dns_zone_setssutable(zone, table); + + cleanup: + dns_ssutable_detach(&table); + return (result); +} + +/* + * Convert a config file zone type into a server zone type. + */ +static inline dns_zonetype_t +zonetype_fromconfig(cfg_obj_t *map) { + cfg_obj_t *obj = NULL; + isc_result_t result; + + result = cfg_map_get(map, "type", &obj); + INSIST(result == ISC_R_SUCCESS); + return (ns_config_getzonetype(obj)); +} + +/* + * Helper function for strtoargv(). Pardon the gratuitous recursion. + */ +static isc_result_t +strtoargvsub(isc_mem_t *mctx, char *s, unsigned int *argcp, + char ***argvp, unsigned int n) +{ + isc_result_t result; + + /* Discard leading whitespace. */ + while (*s == ' ' || *s == '\t') + s++; + + if (*s == '\0') { + /* We have reached the end of the string. */ + *argcp = n; + *argvp = isc_mem_get(mctx, n * sizeof(char *)); + if (*argvp == NULL) + return (ISC_R_NOMEMORY); + } else { + char *p = s; + while (*p != ' ' && *p != '\t' && *p != '\0') + p++; + if (*p != '\0') + *p++ = '\0'; + + result = strtoargvsub(mctx, p, argcp, argvp, n + 1); + if (result != ISC_R_SUCCESS) + return (result); + (*argvp)[n] = s; + } + return (ISC_R_SUCCESS); +} + +/* + * Tokenize the string "s" into whitespace-separated words, + * return the number of words in '*argcp' and an array + * of pointers to the words in '*argvp'. The caller + * must free the array using isc_mem_put(). The string + * is modified in-place. + */ +static isc_result_t +strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, char ***argvp) { + return (strtoargvsub(mctx, s, argcp, argvp, 0)); +} + +isc_result_t +ns_zone_configure(cfg_obj_t *config, cfg_obj_t *vconfig, cfg_obj_t *zconfig, + ns_aclconfctx_t *ac, dns_zone_t *zone) +{ + isc_result_t result; + char *zname; + dns_rdataclass_t zclass; + dns_rdataclass_t vclass; + cfg_obj_t *maps[5]; + cfg_obj_t *zoptions = NULL; + cfg_obj_t *options = NULL; + cfg_obj_t *obj; + const char *filename = NULL; + dns_notifytype_t notifytype = dns_notifytype_yes; + isc_sockaddr_t *addrs; + dns_name_t **keynames; + isc_uint32_t count; + char *cpval; + unsigned int dbargc; + char **dbargv; + static char default_dbtype[] = "rbt"; + isc_mem_t *mctx = dns_zone_getmctx(zone); + dns_dialuptype_t dialup = dns_dialuptype_no; + dns_zonetype_t ztype; + int i; + + i = 0; + if (zconfig != NULL) { + zoptions = cfg_tuple_get(zconfig, "options"); + maps[i++] = zoptions; + } + if (vconfig != NULL) + maps[i++] = cfg_tuple_get(vconfig, "options"); + if (config != NULL) { + (void)cfg_map_get(config, "options", &options); + if (options != NULL) + maps[i++] = options; + } + maps[i++] = ns_g_defaults; + maps[i++] = NULL; + + if (vconfig != NULL) + RETERR(ns_config_getclass(cfg_tuple_get(vconfig, "class"), + dns_rdataclass_in, &vclass)); + else + vclass = dns_rdataclass_in; + + /* + * Configure values common to all zone types. + */ + + zname = cfg_obj_asstring(cfg_tuple_get(zconfig, "name")); + + RETERR(ns_config_getclass(cfg_tuple_get(zconfig, "class"), + vclass, &zclass)); + dns_zone_setclass(zone, zclass); + + ztype = zonetype_fromconfig(zoptions); + dns_zone_settype(zone, ztype); + + obj = NULL; + result = cfg_map_get(zoptions, "database", &obj); + if (result == ISC_R_SUCCESS) + cpval = cfg_obj_asstring(obj); + else + cpval = default_dbtype; + RETERR(strtoargv(mctx, cpval, &dbargc, &dbargv)); + /* + * ANSI C is strange here. There is no logical reason why (char **) + * cannot be promoted automatically to (const char * const *) by the + * compiler w/o generating a warning. + */ + RETERR(dns_zone_setdbtype(zone, dbargc, (const char * const *)dbargv)); + isc_mem_put(mctx, dbargv, dbargc * sizeof(*dbargv)); + + obj = NULL; + result = cfg_map_get(zoptions, "file", &obj); + if (result == ISC_R_SUCCESS) + filename = cfg_obj_asstring(obj); + RETERR(dns_zone_setfile(zone, filename)); + + if (ztype == dns_zone_slave) + RETERR(configure_zone_acl(zconfig, vconfig, config, + "allow-notify", ac, zone, + dns_zone_setnotifyacl, + dns_zone_clearnotifyacl)); + /* + * XXXAG This probably does not make sense for stubs. + */ + RETERR(configure_zone_acl(zconfig, vconfig, config, + "allow-query", ac, zone, + dns_zone_setqueryacl, + dns_zone_clearqueryacl)); + + obj = NULL; + result = ns_config_get(maps, "dialup", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) + dialup = dns_dialuptype_yes; + else + dialup = dns_dialuptype_no; + } else { + char *dialupstr = cfg_obj_asstring(obj); + if (strcasecmp(dialupstr, "notify") == 0) + dialup = dns_dialuptype_notify; + else if (strcasecmp(dialupstr, "notify-passive") == 0) + dialup = dns_dialuptype_notifypassive; + else if (strcasecmp(dialupstr, "refresh") == 0) + dialup = dns_dialuptype_refresh; + else if (strcasecmp(dialupstr, "passive") == 0) + dialup = dns_dialuptype_passive; + else + INSIST(0); + } + dns_zone_setdialup(zone, dialup); + + obj = NULL; + result = ns_config_get(maps, "zone-statistics", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setstatistics(zone, cfg_obj_asboolean(obj)); + + /* + * Configure master functionality. This applies + * to primary masters (type "master") and slaves + * acting as masters (type "slave"), but not to stubs. + */ + if (ztype != dns_zone_stub) { + obj = NULL; + result = ns_config_get(maps, "notify", &obj); + INSIST(result == ISC_R_SUCCESS); + if (cfg_obj_isboolean(obj)) { + if (cfg_obj_asboolean(obj)) + notifytype = dns_notifytype_yes; + else + notifytype = dns_notifytype_no; + } else { + char *notifystr = cfg_obj_asstring(obj); + if (strcasecmp(notifystr, "explicit") == 0) + notifytype = dns_notifytype_explicit; + else + INSIST(0); + } + dns_zone_setnotifytype(zone, notifytype); + + obj = NULL; + result = ns_config_get(maps, "also-notify", &obj); + if (result == ISC_R_SUCCESS) { + isc_sockaddr_t *addrs = NULL; + isc_uint32_t addrcount; + result = ns_config_getiplist(config, obj, 0, mctx, + &addrs, &addrcount); + if (result != ISC_R_SUCCESS) + return (result); + result = dns_zone_setalsonotify(zone, addrs, + addrcount); + ns_config_putiplist(mctx, &addrs, addrcount); + if (result != ISC_R_SUCCESS) + return (result); + } else + RETERR(dns_zone_setalsonotify(zone, NULL, 0)); + + obj = NULL; + result = ns_config_get(maps, "notify-source", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setnotifysrc4(zone, cfg_obj_assockaddr(obj)); + + obj = NULL; + result = ns_config_get(maps, "notify-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setnotifysrc6(zone, cfg_obj_assockaddr(obj)); + + RETERR(configure_zone_acl(zconfig, vconfig, config, + "allow-transfer", ac, zone, + dns_zone_setxfracl, + dns_zone_clearxfracl)); + + obj = NULL; + result = ns_config_get(maps, "max-transfer-time-out", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setmaxxfrout(zone, cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = ns_config_get(maps, "max-transfer-idle-out", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setidleout(zone, cfg_obj_asuint32(obj) * 60); + } + + /* + * Configure update-related options. These apply to + * primary masters only. + */ + if (ztype == dns_zone_master) { + dns_acl_t *updateacl; + RETERR(configure_zone_acl(zconfig, vconfig, config, + "allow-update", ac, zone, + dns_zone_setupdateacl, + dns_zone_clearupdateacl)); + + updateacl = dns_zone_getupdateacl(zone); + if (updateacl != NULL && dns_acl_isinsecure(updateacl)) + isc_log_write(ns_g_lctx, DNS_LOGCATEGORY_SECURITY, + NS_LOGMODULE_SERVER, ISC_LOG_WARNING, + "zone '%s' allows updates by IP " + "address, which is insecure", + zname); + + RETERR(configure_zone_ssutable(zoptions, zone)); + + obj = NULL; + result = ns_config_get(maps, "sig-validity-interval", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setsigvalidityinterval(zone, + cfg_obj_asuint32(obj) * 86400); + } else if (ztype == dns_zone_slave) { + RETERR(configure_zone_acl(zconfig, vconfig, config, + "allow-update-forwarding", ac, zone, + dns_zone_setforwardacl, + dns_zone_clearforwardacl)); + } + + /* + * Configure slave functionality. + */ + switch (ztype) { + case dns_zone_slave: + case dns_zone_stub: + obj = NULL; + result = cfg_map_get(zoptions, "masters", &obj); + if (obj != NULL) { + addrs = NULL; + keynames = NULL; + RETERR(ns_config_getipandkeylist(config, obj, mctx, + &addrs, &keynames, + &count)); + result = dns_zone_setmasterswithkeys(zone, addrs, + keynames, count); + ns_config_putipandkeylist(mctx, &addrs, &keynames, + count); + } else + result = dns_zone_setmasters(zone, NULL, 0); + RETERR(result); + + obj = NULL; + result = ns_config_get(maps, "max-transfer-time-in", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setmaxxfrin(zone, cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = ns_config_get(maps, "max-transfer-idle-in", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setidlein(zone, cfg_obj_asuint32(obj) * 60); + + obj = NULL; + result = ns_config_get(maps, "max-refresh-time", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setmaxrefreshtime(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "min-refresh-time", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setminrefreshtime(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "max-retry-time", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setmaxretrytime(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "min-retry-time", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setminretrytime(zone, cfg_obj_asuint32(obj)); + + obj = NULL; + result = ns_config_get(maps, "transfer-source", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setxfrsource4(zone, cfg_obj_assockaddr(obj)); + + obj = NULL; + result = ns_config_get(maps, "transfer-source-v6", &obj); + INSIST(result == ISC_R_SUCCESS); + dns_zone_setxfrsource6(zone, cfg_obj_assockaddr(obj)); + + break; + + default: + break; + } + + return (ISC_R_SUCCESS); +} + +isc_boolean_t +ns_zone_reusable(dns_zone_t *zone, cfg_obj_t *zconfig) { + cfg_obj_t *zoptions = NULL; + cfg_obj_t *obj = NULL; + const char *cfilename; + const char *zfilename; + + zoptions = cfg_tuple_get(zconfig, "options"); + + if (zonetype_fromconfig(zoptions) != dns_zone_gettype(zone)) + return (ISC_FALSE); + + obj = NULL; + (void)cfg_map_get(zoptions, "file", &obj); + if (obj != NULL) + cfilename = cfg_obj_asstring(obj); + else + cfilename = NULL; + zfilename = dns_zone_getfile(zone); + if (!((cfilename == NULL && zfilename == NULL) || + (cfilename != NULL && zfilename != NULL && + strcmp(cfilename, zfilename) == 0))) + return (ISC_FALSE); + + return (ISC_TRUE); +} diff --git a/lib/dns/include/dns/journal.h b/lib/dns/include/dns/journal.h new file mode 100644 index 0000000000..e28dc83ef4 --- /dev/null +++ b/lib/dns/include/dns/journal.h @@ -0,0 +1,263 @@ +/* + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* $Id: journal.h,v 1.23 2001/05/21 23:56:33 gson Exp $ */ + +#ifndef DNS_JOURNAL_H +#define DNS_JOURNAL_H 1 + +/***** + ***** Module Info + *****/ + +/* + * Database journalling. + */ + +/*** + *** Imports + ***/ + +#include +#include + +#include +#include +#include +#include + +/*** + *** Types + ***/ + +/* + * A dns_journal_t represents an open journal file. This is an opaque type. + * + * A particular dns_journal_t object may be opened for writing, in which case + * it can be used for writing transactions to a journal file, or it can be + * opened for reading, in which case it can be used for reading transactions + * from (iterating over) a journal file. A single dns_journal_t object may + * not be used for both purposes. + */ +typedef struct dns_journal dns_journal_t; + + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +/**************************************************************************/ + +isc_result_t +dns_db_createsoatuple(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx, + dns_diffop_t op, dns_difftuple_t **tp); +/* + * Create a diff tuple for the current database SOA. + * XXX this probably belongs somewhere else. + */ + + +#define DNS_SERIAL_GT(a, b) ((int)(((a) - (b)) & 0xFFFFFFFF) > 0) +#define DNS_SERIAL_GE(a, b) ((int)(((a) - (b)) & 0xFFFFFFFF) >= 0) +/* + * Compare SOA serial numbers. DNS_SERIAL_GT(a, b) returns true iff + * a is "greater than" b where "greater than" is as defined in RFC1982. + * DNS_SERIAL_GE(a, b) returns true iff a is "greater than or equal to" b. + */ + +/**************************************************************************/ +/* + * Journal object creation and destruction. + */ + +isc_result_t +dns_journal_open(isc_mem_t *mctx, const char *filename, isc_boolean_t write, + dns_journal_t **journalp); +/* + * Open the journal file 'filename' and create a dns_journal_t object for it. + * + * If 'write' is ISC_TRUE, the journal is open for writing. If it does + * not exist, it is created. + * + * If 'write' is ISC_FALSE, the journal is open for reading. If it does + * not exist, ISC_R_NOTFOUND is returned. + */ + +void +dns_journal_destroy(dns_journal_t **journalp); +/* + * Destroy a dns_journal_t, closing any open files and freeing its memory. + */ + +/**************************************************************************/ +/* + * Writing transactions to journals. + */ + +isc_result_t +dns_journal_begin_transaction(dns_journal_t *j); +/* + * Prepare to write a new transaction to the open journal file 'j'. + * + * Requires: + * 'j' is open for writing. + */ + +isc_result_t +dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff); +/* + * Write 'diff' to the current transaction of journal file 'j'. + * + * Requires: + * 'j' is open for writing and dns_journal_begin_transaction() + * has been called. + * + * 'diff' is a full or partial, correctly ordered IXFR + * difference sequence. + */ + +isc_result_t +dns_journal_commit(dns_journal_t *j); +/* + * Commit the current transaction of journal file 'j'. + * + * Requires: + * 'j' is open for writing and dns_journal_begin_transaction() + * has been called. + * + * dns_journal_writediff() has been called one or more times + * to form a complete, correctly ordered IXFR difference + * sequence. + */ + +isc_result_t +dns_journal_write_transaction(dns_journal_t *j, dns_diff_t *diff); +/* + * Write a complete transaction at once to a journal file, + * sorting it if necessary, and commit it. Equivalent to calling + * dns_diff_sort(), dns_journal_begin_transaction(), + * dns_journal_writediff(), and dns_journal_commit(). + * + * Requires: + * 'j' is open for writing. + * + * 'diff' contains exactly one SOA deletion, one SOA addition + * with a greater serial number, and possibly other changes, + * in arbitrary order. + */ + +/**************************************************************************/ +/* + * Reading transactions from journals. + */ + +isc_uint32_t +dns_journal_first_serial(dns_journal_t *j); +isc_uint32_t +dns_journal_last_serial(dns_journal_t *j); +/* + * Get the first and last addressable serial number in the journal. + */ + +isc_result_t +dns_journal_iter_init(dns_journal_t *j, + isc_uint32_t begin_serial, isc_uint32_t end_serial); +/* + * Prepare to iterate over the transactions that will bring the database + * from SOA serial number 'begin_serial' to 'end_serial'. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_RANGE begin_serial is outside the addressable range. + * ISC_R_NOTFOUND begin_serial is within the range of adressable + * serial numbers covered by the journal, but + * this particular serial number does not exist. + */ + +isc_result_t +dns_journal_first_rr(dns_journal_t *j); +isc_result_t +dns_journal_next_rr(dns_journal_t *j); +/* + * Position the iterator at the first/next RR in a journal + * transaction sequence established using dns_journal_iter_init(). + * + * Requires: + * dns_journal_iter_init() has been called. + * + */ + +void +dns_journal_current_rr(dns_journal_t *j, dns_name_t **name, isc_uint32_t *ttl, + dns_rdata_t **rdata); +/* + * Get the name, ttl, and rdata of the current journal RR. + * + * Requires: + * The last call to dns_journal_first_rr() or dns_journal_next_rr() + * returned ISC_R_SUCCESS. + */ + +/**************************************************************************/ +/* + * Database roll-forward. + */ + +isc_result_t +dns_journal_rollforward(isc_mem_t *mctx, dns_db_t *db, const char *filename); +/* + * Roll forward (play back) the journal file "filename" into the + * database "db". This should be called when the server starts + * after a shutdown or crash. + * + * Requires: + * 'mctx' is a valid memory context. + * 'db' is a valid database which does not have a version + * open for writing. + * 'filename' is the name of the journal file belonging to 'db'. + * + * Returns: + * DNS_R_NOJOURNAL when journal does not exist. + * ISC_R_NOTFOUND when current serial in not in journal. + * ISC_R_RANGE when current serial in not in journals range. + * ISC_R_SUCCESS journal has been applied successfully to database. + * others + */ + +isc_result_t +dns_journal_print(isc_mem_t *mctx, const char *filename, FILE *file); +/* For debugging not general use */ + +isc_result_t +dns_db_diff(isc_mem_t *mctx, + dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + const char *journal_filename); +/* + * Compare the databases 'dba' and 'dbb' and generate a journal + * entry containing the changes to make 'dba' from 'dbb' (note + * the order). This journal entry will consist of a single, + * possibly very large transaction. Append the journal + * entry to the journal file specified by 'journal_filename'. + */ + + +ISC_LANG_ENDDECLS + +#endif /* DNS_JOURNAL_H */ diff --git a/lib/dns/journal.c b/lib/dns/journal.c new file mode 100644 index 0000000000..e431f3581e --- /dev/null +++ b/lib/dns/journal.c @@ -0,0 +1,1908 @@ +/* + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* $Id: journal.c,v 1.77.2.1 2001/10/15 04:57:51 marka Exp $ */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * When true, accept IXFR difference sequences where the + * SOA serial number does not change (BIND 8 sends such + * sequences). + */ +static isc_boolean_t bind8_compat = ISC_TRUE; /* XXX config */ + +/**************************************************************************/ +/* + * Miscellaneous utilities. + */ + +#define JOURNAL_COMMON_LOGARGS \ + dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_JOURNAL + +#define JOURNAL_DEBUG_LOGARGS(n) \ + JOURNAL_COMMON_LOGARGS, ISC_LOG_DEBUG(n) + +/* + * It would be non-sensical (or at least obtuse) to use FAIL() with an + * ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler + * from complaining about "end-of-loop code not reached". + */ +#define FAIL(code) \ + do { result = (code); \ + if (result != ISC_R_SUCCESS) goto failure; \ + } while (0) + +#define CHECK(op) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) goto failure; \ + } while (0) + +static inline isc_uint32_t +decode_uint32(unsigned char *p) { + return ((p[0] << 24) + + (p[1] << 16) + + (p[2] << 8) + + (p[3] << 0)); +} + +static inline void +encode_uint32(isc_uint32_t val, unsigned char *p) { + p[0] = (isc_uint8_t)(val >> 24); + p[1] = (isc_uint8_t)(val >> 16); + p[2] = (isc_uint8_t)(val >> 8); + p[3] = (isc_uint8_t)(val >> 0); +} + +isc_result_t +dns_db_createsoatuple(dns_db_t *db, dns_dbversion_t *ver, isc_mem_t *mctx, + dns_diffop_t op, dns_difftuple_t **tp) +{ + isc_result_t result; + dns_dbnode_t *node; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_name_t *zonename; + + zonename = dns_db_origin(db); + + node = NULL; + result = dns_db_findnode(db, zonename, ISC_FALSE, &node); + if (result != ISC_R_SUCCESS) + goto nonode; + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_soa, 0, + (isc_stdtime_t)0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) + goto freenode; + + result = dns_rdataset_first(&rdataset); + if (result != ISC_R_SUCCESS) + goto freenode; + + dns_rdataset_current(&rdataset, &rdata); + + result = dns_difftuple_create(mctx, op, zonename, rdataset.ttl, + &rdata, tp); + + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + return (ISC_R_SUCCESS); + + freenode: + dns_db_detachnode(db, &node); + nonode: + UNEXPECTED_ERROR(__FILE__, __LINE__, "missing SOA"); + return (result); +} + +/**************************************************************************/ +/* + * Journalling. + */ + +/* + * A journal file consists of + * + * - A fixed-size header of type journal_rawheader_t. + * + * - The index. This is an unordered array of index entries + * of type journal_rawpos_t giving the locations + * of some arbitrary subset of the journal's addressable + * transactions. The index entries are used as hints to + * speed up the process of locating a transaction with a given + * serial number. Unused index entries have an "offset" + * field of zero. The size of the index can vary between + * journal files, but does not change during the lifetime + * of a file. The size can be zero. + * + * - The journal data. This consists of one or more transactions. + * Each transaction begins with a transaction header of type + * journal_rawxhdr_t. The transaction header is followed by a + * sequence of RRs, similar in structure to an IXFR difference + * sequence (RFC1995). That is, the pre-transaction SOA, + * zero or more other deleted RRs, the post-transaction SOA, + * and zero or more other added RRs. Unlike in IXFR, each RR + * is prefixed with a 32-bit length. + * + * The journal data part grows as new transactions are + * appended to the file. Only those transactions + * whose serial number is current-(2^31-1) to current + * are considered "addressable" and may be pointed + * to from the header or index. They may be preceded + * by old transactions that are no longer addressable, + * and they may be followed by transactions that were + * appended to the journal but never committed by updating + * the "end" position in the header. The latter will + * be overwritten when new transactions are added. + */ + +/* + * On-disk representation of a "pointer" to a journal entry. + * These are used in the journal header to locate the beginning + * and end of the journal, and in the journal index to locate + * other transactions. + */ +typedef struct { + unsigned char serial[4]; /* SOA serial before update. */ + /* + * XXXRTH Should offset be 8 bytes? + * XXXDCL ... probably, since isc_offset_t is 8 bytes on many OSs. + * XXXAG ... but we will not be able to seek >2G anyway on many + * platforms as long as we are using fseek() rather + * than lseek(). + */ + unsigned char offset[4]; /* Offset from beginning of file. */ +} journal_rawpos_t; + +/* + * The on-disk representation of the journal header. + * All numbers are stored in big-endian order. + */ + +/* + * The header is of a fixed size, with some spare room for future + * extensions. + */ +#define JOURNAL_HEADER_SIZE 64 /* Bytes. */ + +typedef union { + struct { + /* File format version ID. */ + unsigned char format[16]; + /* Position of the first addressable transaction */ + journal_rawpos_t begin; + /* Position of the next (yet nonexistent) transaction. */ + journal_rawpos_t end; + /* Number of index entries following the header. */ + unsigned char index_size[4]; + } h; + /* Pad the header to a fixed size. */ + unsigned char pad[JOURNAL_HEADER_SIZE]; +} journal_rawheader_t; + +/* + * The on-disk representation of the transaction header. + * There is one of these at the beginning of each transaction. + */ +typedef struct { + unsigned char size[4]; /* In bytes, excluding header. */ + unsigned char serial0[4]; /* SOA serial before update. */ + unsigned char serial1[4]; /* SOA serial after update. */ +} journal_rawxhdr_t; + +/* + * The on-disk representation of the RR header. + * There is one of these at the beginning of each RR. + */ +typedef struct { + unsigned char size[4]; /* In bytes, excluding header. */ +} journal_rawrrhdr_t; + +/* + * The in-core representation of the journal header. + */ +typedef struct { + isc_uint32_t serial; + isc_offset_t offset; +} journal_pos_t; + +#define POS_VALID(pos) ((pos).offset != 0) +#define POS_INVALIDATE(pos) ((pos).offset = 0, (pos).serial = 0) + +typedef struct { + unsigned char format[16]; + journal_pos_t begin; + journal_pos_t end; + isc_uint32_t index_size; +} journal_header_t; + +/* + * The in-core representation of the transaction header. + */ + +typedef struct { + isc_uint32_t size; + isc_uint32_t serial0; + isc_uint32_t serial1; +} journal_xhdr_t; + +/* + * The in-core representation of the RR header. + */ +typedef struct { + isc_uint32_t size; +} journal_rrhdr_t; + + +/* + * Initial contents to store in the header of a newly created + * journal file. + * + * The header starts with the magic string ";BIND LOG V9\n" + * to identify the file as a BIND 9 journal file. An ASCII + * identification string is used rather than a binary magic + * number to be consistent with BIND 8 (BIND 8 journal files + * are ASCII text files). + */ + +static journal_header_t +initial_journal_header = { ";BIND LOG V9\n", { 0, 0 }, { 0, 0 }, 0 }; + +#define JOURNAL_EMPTY(h) ((h)->begin.offset == (h)->end.offset) + +typedef enum { + JOURNAL_STATE_INVALID, + JOURNAL_STATE_READ, + JOURNAL_STATE_WRITE, + JOURNAL_STATE_TRANSACTION +} journal_state_t; + +struct dns_journal { + unsigned int magic; /* JOUR */ + isc_mem_t *mctx; /* Memory context */ + journal_state_t state; + const char *filename; /* Journal file name */ + FILE * fp; /* File handle */ + isc_offset_t offset; /* Current file offset */ + journal_header_t header; /* In-core journal header */ + unsigned char *rawindex; /* In-core buffer for journal + index in on-disk format */ + journal_pos_t *index; /* In-core journal index */ + + /* Current transaction state (when writing). */ + struct { + unsigned int n_soa; /* Number of SOAs seen */ + journal_pos_t pos[2]; /* Begin/end position */ + } x; + + /* Iteration state (when reading). */ + struct { + /* These define the part of the journal we iterate over. */ + journal_pos_t bpos; /* Position before first, */ + journal_pos_t epos; /* and after last + transaction */ + /* The rest is iterator state. */ + isc_uint32_t current_serial; /* Current SOA serial */ + isc_buffer_t source; /* Data from disk */ + isc_buffer_t target; /* Data from _fromwire check */ + dns_decompress_t dctx; /* Dummy decompression ctx */ + dns_name_t name; /* Current domain name */ + dns_rdata_t rdata; /* Current rdata */ + isc_uint32_t ttl; /* Current TTL */ + unsigned int xsize; /* Size of transaction data */ + unsigned int xpos; /* Current position in it */ + isc_result_t result; /* Result of last call */ + } it; +}; + +#define DNS_JOURNAL_MAGIC ISC_MAGIC('J', 'O', 'U', 'R') +#define DNS_JOURNAL_VALID(t) ISC_MAGIC_VALID(t, DNS_JOURNAL_MAGIC) + +static void +journal_pos_decode(journal_rawpos_t *raw, journal_pos_t *cooked) { + cooked->serial = decode_uint32(raw->serial); + cooked->offset = decode_uint32(raw->offset); +} + +static void +journal_pos_encode(journal_rawpos_t *raw, journal_pos_t *cooked) { + encode_uint32(cooked->serial, raw->serial); + encode_uint32(cooked->offset, raw->offset); +} + +static void +journal_header_decode(journal_rawheader_t *raw, journal_header_t *cooked) { + INSIST(sizeof(cooked->format) == sizeof(raw->h.format)); + memcpy(cooked->format, raw->h.format, sizeof(cooked->format)); + journal_pos_decode(&raw->h.begin, &cooked->begin); + journal_pos_decode(&raw->h.end, &cooked->end); + cooked->index_size = decode_uint32(raw->h.index_size); +} + +static void +journal_header_encode(journal_header_t *cooked, journal_rawheader_t *raw) { + INSIST(sizeof(cooked->format) == sizeof(raw->h.format)); + memset(raw->pad, 0, sizeof(raw->pad)); + memcpy(raw->h.format, cooked->format, sizeof(raw->h.format)); + journal_pos_encode(&raw->h.begin, &cooked->begin); + journal_pos_encode(&raw->h.end, &cooked->end); + encode_uint32(cooked->index_size, raw->h.index_size); +} + +/* + * Journal file I/O subroutines, with error checking and reporting. + */ +static isc_result_t +journal_seek(dns_journal_t *j, isc_uint32_t offset) { + isc_result_t result; + result = isc_stdio_seek(j->fp, (long)offset, SEEK_SET); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: seek: %s", j->filename, + isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + j->offset = offset; + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_read(dns_journal_t *j, void *mem, size_t nbytes) { + isc_result_t result; + + result = isc_stdio_read(mem, 1, nbytes, j->fp, NULL); + if (result != ISC_R_SUCCESS) { + if (result == ISC_R_EOF) + return (ISC_R_NOMORE); + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: read: %s", + j->filename, isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + j->offset += nbytes; + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_write(dns_journal_t *j, void *mem, size_t nbytes) { + isc_result_t result; + + result = isc_stdio_write(mem, 1, nbytes, j->fp, NULL); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: write: %s", + j->filename, isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + j->offset += nbytes; + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_fsync(dns_journal_t *j) { + isc_result_t result; + result = isc_stdio_flush(j->fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: flush: %s", + j->filename, isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + result = isc_stdio_sync(j->fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: fsync: %s", + j->filename, isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + return (ISC_R_SUCCESS); +} + +/* + * Read/write a transaction header at the current file position. + */ + +static isc_result_t +journal_read_xhdr(dns_journal_t *j, journal_xhdr_t *xhdr) { + journal_rawxhdr_t raw; + isc_result_t result; + result = journal_read(j, &raw, sizeof(raw)); + if (result != ISC_R_SUCCESS) + return (result); + xhdr->size = decode_uint32(raw.size); + xhdr->serial0 = decode_uint32(raw.serial0); + xhdr->serial1 = decode_uint32(raw.serial1); + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_write_xhdr(dns_journal_t *j, isc_uint32_t size, + isc_uint32_t serial0, isc_uint32_t serial1) +{ + journal_rawxhdr_t raw; + encode_uint32(size, raw.size); + encode_uint32(serial0, raw.serial0); + encode_uint32(serial1, raw.serial1); + return (journal_write(j, &raw, sizeof(raw))); +} + + +/* + * Read an RR header at the current file position. + */ + +static isc_result_t +journal_read_rrhdr(dns_journal_t *j, journal_rrhdr_t *rrhdr) { + journal_rawrrhdr_t raw; + isc_result_t result; + result = journal_read(j, &raw, sizeof(raw)); + if (result != ISC_R_SUCCESS) + return (result); + rrhdr->size = decode_uint32(raw.size); + return (ISC_R_SUCCESS); +} + +static isc_result_t +journal_file_create(isc_mem_t *mctx, const char *filename) { + FILE *fp = NULL; + isc_result_t result; + journal_header_t header; + journal_rawheader_t rawheader; + int index_size = 56; /* XXX configurable */ + int size; + void *mem; /* Memory for temporary index image. */ + + INSIST(sizeof(journal_rawheader_t) == JOURNAL_HEADER_SIZE); + + result = isc_stdio_open(filename, "wb", &fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: create: %s", + filename, isc_result_totext(result)); + return (ISC_R_UNEXPECTED); + } + + header = initial_journal_header; + header.index_size = index_size; + journal_header_encode(&header, &rawheader); + + size = sizeof(journal_rawheader_t) + + index_size * sizeof(journal_rawpos_t); + + mem = isc_mem_get(mctx, size); + if (mem == NULL) { + (void)isc_stdio_close(fp); + (void)isc_file_remove(filename); + return (ISC_R_NOMEMORY); + } + memset(mem, 0, size); + memcpy(mem, &rawheader, sizeof(rawheader)); + + result = isc_stdio_write(mem, 1, (size_t) size, fp, NULL); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: write: %s", + filename, isc_result_totext(result)); + (void)isc_stdio_close(fp); + (void)isc_file_remove(filename); + isc_mem_put(mctx, mem, size); + return (ISC_R_UNEXPECTED); + } + isc_mem_put(mctx, mem, size); + + result = isc_stdio_close(fp); + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: close: %s", + filename, isc_result_totext(result)); + (void)isc_file_remove(filename); + return (ISC_R_UNEXPECTED); + } + + return (ISC_R_SUCCESS); +} + + +isc_result_t +dns_journal_open(isc_mem_t *mctx, const char *filename, isc_boolean_t write, + dns_journal_t **journalp) { + FILE *fp = NULL; + isc_result_t result; + journal_rawheader_t rawheader; + dns_journal_t *j; + + INSIST(journalp != NULL && *journalp == NULL); + j = isc_mem_get(mctx, sizeof(*j)); + if (j == NULL) + return (ISC_R_NOMEMORY); + + j->mctx = mctx; + j->state = JOURNAL_STATE_INVALID; + j->fp = NULL; + j->filename = filename; + j->index = NULL; + j->rawindex = NULL; + + result = isc_stdio_open(j->filename, write ? "rb+" : "rb", &fp); + + if (result == ISC_R_FILENOTFOUND) { + if (write) { + isc_log_write(JOURNAL_COMMON_LOGARGS, + ISC_LOG_INFO, + "journal file %s does not exist, " + "creating it", + j->filename); + CHECK(journal_file_create(mctx, filename)); + /* + * Retry. + */ + result = isc_stdio_open(j->filename, "rb+", &fp); + } else { + FAIL(ISC_R_NOTFOUND); + } + } + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: open: %s", + j->filename, isc_result_totext(result)); + FAIL(ISC_R_UNEXPECTED); + } + + j->fp = fp; + + /* + * Set magic early so that seek/read can succeed. + */ + j->magic = DNS_JOURNAL_MAGIC; + + CHECK(journal_seek(j, 0)); + CHECK(journal_read(j, &rawheader, sizeof(rawheader))); + + if (memcmp(rawheader.h.format, initial_journal_header.format, + sizeof(initial_journal_header.format)) != 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal format not recognized", + j->filename); + FAIL(ISC_R_UNEXPECTED); + } + journal_header_decode(&rawheader, &j->header); + + /* + * If there is an index, read the raw index into a dynamically + * allocated buffer and then convert it into a cooked index. + */ + if (j->header.index_size != 0) { + unsigned int i; + unsigned int rawbytes; + unsigned char *p; + + rawbytes = j->header.index_size * sizeof(journal_rawpos_t); + j->rawindex = isc_mem_get(mctx, rawbytes); + if (j->rawindex == NULL) + FAIL(ISC_R_NOMEMORY); + + CHECK(journal_read(j, j->rawindex, rawbytes)); + + j->index = isc_mem_get(mctx, j->header.index_size * + sizeof(journal_pos_t)); + if (j->index == NULL) + FAIL(ISC_R_NOMEMORY); + + p = j->rawindex; + for (i = 0; i < j->header.index_size; i++) { + j->index[i].serial = decode_uint32(p); + p += 4; + j->index[i].offset = decode_uint32(p); + p += 4; + } + INSIST(p == j->rawindex + rawbytes); + } + j->offset = -1; /* Invalid, must seek explicitly. */ + + /* + * Initialize the iterator. + */ + dns_name_init(&j->it.name, NULL); + dns_rdata_init(&j->it.rdata); + + /* + * Set up empty initial buffers for uncheched and checked + * wire format RR data. They will be reallocated + * later. + */ + isc_buffer_init(&j->it.source, NULL, 0); + isc_buffer_init(&j->it.target, NULL, 0); + dns_decompress_init(&j->it.dctx, -1, DNS_DECOMPRESS_NONE); + + j->state = + write ? JOURNAL_STATE_WRITE : JOURNAL_STATE_READ; + + *journalp = j; + return (ISC_R_SUCCESS); + + failure: + j->magic = 0; + if (j->index != NULL) { + isc_mem_put(j->mctx, j->index, j->header.index_size * + sizeof(journal_rawpos_t)); + j->index = NULL; + } + if (j->fp != NULL) + (void)isc_stdio_close(j->fp); + isc_mem_put(j->mctx, j, sizeof(*j)); + return (result); +} + +/* + * A comparison function defining the sorting order for + * entries in the IXFR-style journal file. + * + * The IXFR format requires that deletions are sorted before + * additions, and within either one, SOA records are sorted + * before others. + * + * Also sort the non-SOA records by type as a courtesy to the + * server receiving the IXFR - it may help reduce the amount of + * rdataset merging it has to do. + */ +static int +ixfr_order(const void *av, const void *bv) { + dns_difftuple_t const * const *ap = av; + dns_difftuple_t const * const *bp = bv; + dns_difftuple_t const *a = *ap; + dns_difftuple_t const *b = *bp; + int r; + + r = (b->op == DNS_DIFFOP_DEL) - (a->op == DNS_DIFFOP_DEL); + if (r != 0) + return (r); + + r = (b->rdata.type == dns_rdatatype_soa) - + (a->rdata.type == dns_rdatatype_soa); + if (r != 0) + return (r); + + r = (a->rdata.type - b->rdata.type); + return (r); +} + +/* + * Advance '*pos' to the next journal transaction. + * + * Requires: + * *pos refers to a valid journal transaction. + * + * Ensures: + * When ISC_R_SUCCESS is returned, + * *pos refers to the next journal transaction. + * + * Returns one of: + * + * ISC_R_SUCCESS + * ISC_R_NOMORE *pos pointed at the last transaction + * Other results due to file errors are possible. + */ +static isc_result_t +journal_next(dns_journal_t *j, journal_pos_t *pos) { + isc_result_t result; + journal_xhdr_t xhdr; + REQUIRE(DNS_JOURNAL_VALID(j)); + + result = journal_seek(j, pos->offset); + if (result != ISC_R_SUCCESS) + return (result); + + /* + * Read the header of the current transaction. + * This will return ISC_R_NOMORE if we are at EOF. + */ + result = journal_read_xhdr(j, &xhdr); + if (result != ISC_R_SUCCESS) + return (result); + + /* + * Check serial number consistency. + */ + if (xhdr.serial0 != pos->serial) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: " + "expected serial %u, got %u", + j->filename, pos->serial, xhdr.serial0); + return (ISC_R_UNEXPECTED); + } + + /* + * Check for offset wraparound. + */ + if ((isc_offset_t)(pos->offset + sizeof(journal_rawxhdr_t) + xhdr.size) + < pos->offset) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: offset too large", j->filename); + return (ISC_R_UNEXPECTED); + } + + pos->offset += sizeof(journal_rawxhdr_t) + xhdr.size; + pos->serial = xhdr.serial1; + return (ISC_R_SUCCESS); +} + +/* + * If the index of the journal 'j' contains an entry "better" + * than '*best_guess', replace '*best_guess' with it. + * + * "Better" means having a serial number closer to 'serial' + * but not greater than 'serial'. + */ +static void +index_find(dns_journal_t *j, isc_uint32_t serial, journal_pos_t *best_guess) { + unsigned int i; + if (j->index == NULL) + return; + for (i = 0; i < j->header.index_size; i++) { + if (POS_VALID(j->index[i]) && + DNS_SERIAL_GE(serial, j->index[i].serial) && + DNS_SERIAL_GT(j->index[i].serial, best_guess->serial)) + *best_guess = j->index[i]; + } +} + +/* + * Add a new index entry. If there is no room, make room by removing + * the odd-numbered entries and compacting the others into the first + * half of the index. This decimates old index entries exponentially + * over time, so that the index always contains a much larger fraction + * of recent serial numbers than of old ones. This is deliberate - + * most index searches are for outgoing IXFR, and IXFR tends to request + * recent versions more often than old ones. + */ +static void +index_add(dns_journal_t *j, journal_pos_t *pos) { + unsigned int i; + if (j->index == NULL) + return; + /* + * Search for a vacant position. + */ + for (i = 0; i < j->header.index_size; i++) { + if (! POS_VALID(j->index[i])) + break; + } + if (i == j->header.index_size) { + unsigned int k = 0; + /* + * Found no vacant position. Make some room. + */ + for (i = 0; i < j->header.index_size; i += 2) { + j->index[k++] = j->index[i]; + } + i = k; /* 'i' identifies the first vacant position. */ + while (k < j->header.index_size) { + POS_INVALIDATE(j->index[k]); + k++; + } + } + INSIST(i < j->header.index_size); + INSIST(! POS_VALID(j->index[i])); + + /* + * Store the new index entry. + */ + j->index[i] = *pos; +} + +/* + * Invalidate any existing index entries that could become + * ambiguous when a new transaction with number 'serial' is added. + */ +static void +index_invalidate(dns_journal_t *j, isc_uint32_t serial) { + unsigned int i; + if (j->index == NULL) + return; + for (i = 0; i < j->header.index_size; i++) { + if (! DNS_SERIAL_GT(serial, j->index[i].serial)) + POS_INVALIDATE(j->index[i]); + } +} + +/* + * Try to find a transaction with initial serial number 'serial' + * in the journal 'j'. + * + * If found, store its position at '*pos' and return ISC_R_SUCCESS. + * + * If 'serial' is current (= the ending serial number of the + * last transaction in the journal), set '*pos' to + * the position immediately following the last transaction and + * return ISC_R_SUCCESS. + * + * If 'serial' is within the range of addressable serial numbers + * covered by the journal but that particular serial number is missing + * (from the journal, not just from the index), return ISC_R_NOTFOUND. + * + * If 'serial' is outside the range of addressable serial numbers + * covered by the journal, return ISC_R_RANGE. + * + */ +static isc_result_t +journal_find(dns_journal_t *j, isc_uint32_t serial, journal_pos_t *pos) { + isc_result_t result; + journal_pos_t current_pos; + REQUIRE(DNS_JOURNAL_VALID(j)); + + if (DNS_SERIAL_GT(j->header.begin.serial, serial)) + return (ISC_R_RANGE); + if (DNS_SERIAL_GT(serial, j->header.end.serial)) + return (ISC_R_RANGE); + if (serial == j->header.end.serial) { + *pos = j->header.end; + return (ISC_R_SUCCESS); + } + + current_pos = j->header.begin; + index_find(j, serial, ¤t_pos); + + while (current_pos.serial != serial) { + if (DNS_SERIAL_GT(current_pos.serial, serial)) + return (ISC_R_NOTFOUND); + result = journal_next(j, ¤t_pos); + if (result != ISC_R_SUCCESS) + return (result); + } + *pos = current_pos; + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_journal_begin_transaction(dns_journal_t *j) { + isc_uint32_t offset; + isc_result_t result; + journal_rawxhdr_t hdr; + + REQUIRE(DNS_JOURNAL_VALID(j)); + REQUIRE(j->state == JOURNAL_STATE_WRITE); + + /* + * Find the file offset where the new transaction should + * be written, and seek there. + */ + if (JOURNAL_EMPTY(&j->header)) { + offset = sizeof(journal_rawheader_t) + + j->header.index_size * sizeof(journal_rawpos_t); + } else { + offset = j->header.end.offset; + } + j->x.pos[0].offset = offset; + j->x.pos[1].offset = offset; /* Initial value, will be incremented. */ + j->x.n_soa = 0; + + CHECK(journal_seek(j, offset)); + + /* + * Write a dummy transaction header of all zeroes to reserve + * space. It will be filled in when the transaction is + * finished. + */ + memset(&hdr, 0, sizeof(hdr)); + CHECK(journal_write(j, &hdr, sizeof(hdr))); + j->x.pos[1].offset = j->offset; + + j->state = JOURNAL_STATE_TRANSACTION; + result = ISC_R_SUCCESS; + failure: + return (result); +} + +isc_result_t +dns_journal_writediff(dns_journal_t *j, dns_diff_t *diff) { + dns_difftuple_t *t; + isc_buffer_t buffer; + void *mem = NULL; + unsigned int size; + isc_result_t result; + isc_region_t used; + + REQUIRE(DNS_DIFF_VALID(diff)); + REQUIRE(j->state == JOURNAL_STATE_TRANSACTION); + + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "writing to journal"); + dns_diff_print(diff, NULL); + + /* + * Pass 1: determine the buffer size needed, and + * keep track of SOA serial numbers. + */ + size = 0; + for (t = ISC_LIST_HEAD(diff->tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + if (t->rdata.type == dns_rdatatype_soa) { + if (j->x.n_soa < 2) + j->x.pos[j->x.n_soa].serial = + dns_soa_getserial(&t->rdata); + j->x.n_soa++; + } + size += sizeof(journal_rawrrhdr_t); + size += t->name.length; /* XXX should have access macro? */ + size += 10; + size += t->rdata.length; + } + + mem = isc_mem_get(j->mctx, size); + if (mem == NULL) + return (ISC_R_NOMEMORY); + + isc_buffer_init(&buffer, mem, size); + + /* + * Pass 2. Write RRs to buffer. + */ + for (t = ISC_LIST_HEAD(diff->tuples); t != NULL; + t = ISC_LIST_NEXT(t, link)) + { + /* + * Write the RR header. + */ + isc_buffer_putuint32(&buffer, t->name.length + 10 + + t->rdata.length); + /* + * Write the owner name, RR header, and RR data. + */ + isc_buffer_putmem(&buffer, t->name.ndata, t->name.length); + isc_buffer_putuint16(&buffer, t->rdata.type); + isc_buffer_putuint16(&buffer, t->rdata.rdclass); + isc_buffer_putuint32(&buffer, t->ttl); + INSIST(t->rdata.length < 65536); + isc_buffer_putuint16(&buffer, (isc_uint16_t)t->rdata.length); + INSIST(isc_buffer_availablelength(&buffer) >= t->rdata.length); + isc_buffer_putmem(&buffer, t->rdata.data, t->rdata.length); + } + + isc_buffer_usedregion(&buffer, &used); + INSIST(used.length == size); + + j->x.pos[1].offset += used.length; + + /* + * Write the buffer contents to the journal file. + */ + CHECK(journal_write(j, used.base, used.length)); + + result = ISC_R_SUCCESS; + + failure: + if (mem != NULL) + isc_mem_put(j->mctx, mem, size); + return (result); + +} + +isc_result_t +dns_journal_commit(dns_journal_t *j) { + isc_result_t result; + journal_rawheader_t rawheader; + + REQUIRE(DNS_JOURNAL_VALID(j)); + REQUIRE(j->state == JOURNAL_STATE_TRANSACTION); + + /* + * Perform some basic consistency checks. + */ + if (j->x.n_soa != 2) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "malformed transaction: %d SOAs", + j->x.n_soa); + return (ISC_R_UNEXPECTED); + } + if (! (DNS_SERIAL_GT(j->x.pos[1].serial, j->x.pos[0].serial) || + (bind8_compat && + j->x.pos[1].serial == j->x.pos[0].serial))) + { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "malformed transaction: serial number " + "would decrease"); + return (ISC_R_UNEXPECTED); + } + if (! JOURNAL_EMPTY(&j->header)) { + if (j->x.pos[0].serial != j->header.end.serial) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "malformed transaction: " + "%s last serial %u != " + "transaction first serial %u", + j->filename, + j->header.end.serial, + j->x.pos[0].serial); + return (ISC_R_UNEXPECTED); + } + } + + /* + * Some old journal entries may become non-addressable + * when we increment the current serial number. Purge them + * by stepping header.begin forward to the first addressable + * transaction. Also purge them from the index. + */ + if (! JOURNAL_EMPTY(&j->header)) { + while (! DNS_SERIAL_GT(j->x.pos[1].serial, + j->header.begin.serial)) { + CHECK(journal_next(j, &j->header.begin)); + } + index_invalidate(j, j->x.pos[1].serial); + } +#ifdef notyet + if (DNS_SERIAL_GT(last_dumped_serial, j->x.pos[1].serial)) { + force_dump(...); + } +#endif + + /* + * Commit the transaction data to stable storage. + */ + CHECK(journal_fsync(j)); + + /* + * Update the transaction header. + */ + CHECK(journal_seek(j, j->x.pos[0].offset)); + CHECK(journal_write_xhdr(j, (j->x.pos[1].offset - j->x.pos[0].offset) - + sizeof(journal_rawxhdr_t), + j->x.pos[0].serial, j->x.pos[1].serial)); + + /* + * Update the journal header. + */ + if (JOURNAL_EMPTY(&j->header)) { + j->header.begin = j->x.pos[0]; + } + j->header.end = j->x.pos[1]; + journal_header_encode(&j->header, &rawheader); + CHECK(journal_seek(j, 0)); + CHECK(journal_write(j, &rawheader, sizeof(rawheader))); + + /* + * Update the index. + */ + index_add(j, &j->x.pos[0]); + + /* + * Convert the index into on-disk format and write + * it to disk. + */ + if (j->header.index_size != 0) { + unsigned int i; + unsigned char *p; + unsigned int rawbytes; + + rawbytes = j->header.index_size * sizeof(journal_rawpos_t); + + p = j->rawindex; + for (i = 0; i < j->header.index_size; i++) { + encode_uint32(j->index[i].serial, p); + p += 4; + encode_uint32(j->index[i].offset, p); + p += 4; + } + INSIST(p == j->rawindex + rawbytes); + + CHECK(journal_write(j, j->rawindex, rawbytes)); + } + + /* + * Commit the header to stable storage. + */ + CHECK(journal_fsync(j)); + + /* + * We no longer have a transaction open. + */ + j->state = JOURNAL_STATE_WRITE; + + result = ISC_R_SUCCESS; + + failure: + return (result); +} + +isc_result_t +dns_journal_write_transaction(dns_journal_t *j, dns_diff_t *diff) { + isc_result_t result; + CHECK(dns_diff_sort(diff, ixfr_order)); + CHECK(dns_journal_begin_transaction(j)); + CHECK(dns_journal_writediff(j, diff)); + CHECK(dns_journal_commit(j)); + result = ISC_R_SUCCESS; + failure: + return (result); +} + +void +dns_journal_destroy(dns_journal_t **journalp) { + dns_journal_t *j = *journalp; + REQUIRE(DNS_JOURNAL_VALID(j)); + + j->it.result = ISC_R_FAILURE; + dns_name_invalidate(&j->it.name); + dns_decompress_invalidate(&j->it.dctx); + if (j->rawindex != NULL) + isc_mem_put(j->mctx, j->rawindex, j->header.index_size * + sizeof(journal_rawpos_t)); + if (j->index != NULL) + isc_mem_put(j->mctx, j->index, j->header.index_size * + sizeof(journal_pos_t)); + if (j->it.target.base != NULL) + isc_mem_put(j->mctx, j->it.target.base, j->it.target.length); + if (j->it.source.base != NULL) + isc_mem_put(j->mctx, j->it.source.base, j->it.source.length); + + if (j->fp != NULL) + (void)isc_stdio_close(j->fp); + j->magic = 0; + isc_mem_put(j->mctx, j, sizeof(*j)); + *journalp = NULL; +} + +/* + * Roll the open journal 'j' into the database 'db'. + * A new database version will be created. + */ + +/* XXX Share code with incoming IXFR? */ + +static isc_result_t +roll_forward(dns_journal_t *j, dns_db_t *db) { + isc_buffer_t source; /* Transaction data from disk */ + isc_buffer_t target; /* Ditto after _fromwire check */ + isc_uint32_t db_serial; /* Database SOA serial */ + isc_uint32_t end_serial; /* Last journal SOA serial */ + isc_result_t result; + dns_dbversion_t *ver = NULL; + journal_pos_t pos; + dns_diff_t diff; + unsigned int n_soa = 0; + unsigned int n_put = 0; + + REQUIRE(DNS_JOURNAL_VALID(j)); + REQUIRE(DNS_DB_VALID(db)); + + dns_diff_init(j->mctx, &diff); + + /* + * Set up empty initial buffers for uncheched and checked + * wire format transaction data. They will be reallocated + * later. + */ + isc_buffer_init(&source, NULL, 0); + isc_buffer_init(&target, NULL, 0); + + /* + * Create the new database version. + */ + CHECK(dns_db_newversion(db, &ver)); + + /* + * Get the current database SOA serial number. + */ + CHECK(dns_db_getsoaserial(db, ver, &db_serial)); + + /* + * Locate a journal entry for the current database serial. + */ + CHECK(journal_find(j, db_serial, &pos)); + /* + * XXX do more drastic things, like marking zone stale, + * if this fails? + */ + /* + * XXXRTH The zone code should probably mark the zone as bad and + * scream loudly into the log if this is a dynamic update + * log reply that failed. + */ + + end_serial = dns_journal_last_serial(j); + if (db_serial == end_serial) + CHECK(DNS_R_UPTODATE); + + CHECK(dns_journal_iter_init(j, db_serial, end_serial)); + + for (result = dns_journal_first_rr(j); + result == ISC_R_SUCCESS; + result = dns_journal_next_rr(j)) + { + dns_name_t *name; + isc_uint32_t ttl; + dns_rdata_t *rdata; + dns_difftuple_t *tuple = NULL; + + name = NULL; + rdata = NULL; + dns_journal_current_rr(j, &name, &ttl, &rdata); + + if (rdata->type == dns_rdatatype_soa) + n_soa++; + + if (n_soa == 3) + n_soa = 1; + if (n_soa == 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: missing " + "initial SOA", j->filename); + FAIL(ISC_R_UNEXPECTED); + } + CHECK(dns_difftuple_create(diff.mctx, n_soa == 1 ? + DNS_DIFFOP_DEL : DNS_DIFFOP_ADD, + name, ttl, rdata, &tuple)); + dns_diff_append(&diff, &tuple); + + if (++n_put > 100) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), + "applying diff to database"); + dns_diff_print(&diff, NULL); + CHECK(dns_diff_apply(&diff, db, ver)); + dns_diff_clear(&diff); + n_put = 0; + } + } + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + CHECK(result); + + if (n_put != 0) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), + "applying final diff to database"); + dns_diff_print(&diff, NULL); + CHECK(dns_diff_apply(&diff, db, ver)); + dns_diff_clear(&diff); + } + + failure: + if (ver != NULL) + dns_db_closeversion(db, &ver, result == ISC_R_SUCCESS ? + ISC_TRUE : ISC_FALSE); + + if (source.base != NULL) + isc_mem_put(j->mctx, source.base, source.length); + if (target.base != NULL) + isc_mem_put(j->mctx, target.base, target.length); + + dns_diff_clear(&diff); + + return (result); +} + +isc_result_t +dns_journal_rollforward(isc_mem_t *mctx, dns_db_t *db, const char *filename) { + dns_journal_t *j; + isc_result_t result; + + REQUIRE(DNS_DB_VALID(db)); + REQUIRE(filename != NULL); + + j = NULL; + result = dns_journal_open(mctx, filename, ISC_FALSE, &j); + if (result == ISC_R_NOTFOUND) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), + "no journal file, but that's OK"); + return (DNS_R_NOJOURNAL); + } + if (result != ISC_R_SUCCESS) + return (result); + if (JOURNAL_EMPTY(&j->header)) + result = DNS_R_UPTODATE; + else + result = roll_forward(j, db); + + dns_journal_destroy(&j); + + return (result); +} + +isc_result_t +dns_journal_print(isc_mem_t *mctx, const char *filename, FILE *file) { + dns_journal_t *j; + isc_buffer_t source; /* Transaction data from disk */ + isc_buffer_t target; /* Ditto after _fromwire check */ + isc_uint32_t start_serial; /* Database SOA serial */ + isc_uint32_t end_serial; /* Last journal SOA serial */ + isc_result_t result; + dns_diff_t diff; + unsigned int n_soa = 0; + unsigned int n_put = 0; + + REQUIRE(filename != NULL); + + j = NULL; + result = dns_journal_open(mctx, filename, ISC_FALSE, &j); + if (result == ISC_R_NOTFOUND) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "no journal file"); + return (DNS_R_NOJOURNAL); + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "journal open failure"); + return (result); + } + + dns_diff_init(j->mctx, &diff); + + /* + * Set up empty initial buffers for uncheched and checked + * wire format transaction data. They will be reallocated + * later. + */ + isc_buffer_init(&source, NULL, 0); + isc_buffer_init(&target, NULL, 0); + + start_serial = dns_journal_first_serial(j); + end_serial = dns_journal_last_serial(j); + + CHECK(dns_journal_iter_init(j, start_serial, end_serial)); + + for (result = dns_journal_first_rr(j); + result == ISC_R_SUCCESS; + result = dns_journal_next_rr(j)) + { + dns_name_t *name; + isc_uint32_t ttl; + dns_rdata_t *rdata; + dns_difftuple_t *tuple = NULL; + + name = NULL; + rdata = NULL; + dns_journal_current_rr(j, &name, &ttl, &rdata); + + if (rdata->type == dns_rdatatype_soa) + n_soa++; + + if (n_soa == 3) + n_soa = 1; + if (n_soa == 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: missing " + "initial SOA", j->filename); + FAIL(ISC_R_UNEXPECTED); + } + CHECK(dns_difftuple_create(diff.mctx, n_soa == 1 ? + DNS_DIFFOP_DEL : DNS_DIFFOP_ADD, + name, ttl, rdata, &tuple)); + dns_diff_append(&diff, &tuple); + + if (++n_put > 100) { + result = dns_diff_print(&diff, file); + dns_diff_clear(&diff); + n_put = 0; + if (result != ISC_R_SUCCESS) + break; + } + } + if (result == ISC_R_NOMORE) + result = ISC_R_SUCCESS; + CHECK(result); + + if (n_put != 0) { + result = dns_diff_print(&diff, file); + dns_diff_clear(&diff); + } + goto cleanup; + + failure: + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: cannot print: journal file corrupt", j->filename); + + cleanup: + if (source.base != NULL) + isc_mem_put(j->mctx, source.base, source.length); + if (target.base != NULL) + isc_mem_put(j->mctx, target.base, target.length); + + dns_diff_clear(&diff); + dns_journal_destroy(&j); + + return (result); +} + +/**************************************************************************/ +/* + * Miscellaneous accessors. + */ +isc_uint32_t dns_journal_first_serial(dns_journal_t *j) { + return (j->header.begin.serial); +} + +isc_uint32_t dns_journal_last_serial(dns_journal_t *j) { + return (j->header.end.serial); +} + +/**************************************************************************/ +/* + * Iteration support. + * + * When serving an outgoing IXFR, we transmit a part the journal starting + * at the serial number in the IXFR request and ending at the serial + * number that is current when the IXFR request arrives. The ending + * serial number is not necessarily at the end of the journal: + * the journal may grow while the IXFR is in progress, but we stop + * when we reach the serial number that was current when the IXFR started. + */ + +static isc_result_t read_one_rr(dns_journal_t *j); + +/* + * Make sure the buffer 'b' is has at least 'size' bytes + * allocated, and clear it. + * + * Requires: + * Either b->base is NULL, or it points to b->length bytes of memory + * previously allocated by isc_mem_get(). + */ + +static isc_result_t +size_buffer(isc_mem_t *mctx, isc_buffer_t *b, unsigned size) { + if (b->length < size) { + void *mem = isc_mem_get(mctx, size); + if (mem == NULL) + return (ISC_R_NOMEMORY); + if (b->base != NULL) + isc_mem_put(mctx, b->base, b->length); + b->base = mem; + b->length = size; + } + isc_buffer_clear(b); + return (ISC_R_SUCCESS); +} + +isc_result_t +dns_journal_iter_init(dns_journal_t *j, + isc_uint32_t begin_serial, isc_uint32_t end_serial) +{ + isc_result_t result; + + CHECK(journal_find(j, begin_serial, &j->it.bpos)); + INSIST(j->it.bpos.serial == begin_serial); + + CHECK(journal_find(j, end_serial, &j->it.epos)); + INSIST(j->it.epos.serial == end_serial); + + result = ISC_R_SUCCESS; + failure: + j->it.result = result; + return (j->it.result); +} + + +isc_result_t +dns_journal_first_rr(dns_journal_t *j) { + isc_result_t result; + + /* + * Seek to the beginning of the first transaction we are + * interested in. + */ + CHECK(journal_seek(j, j->it.bpos.offset)); + j->it.current_serial = j->it.bpos.serial; + + j->it.xsize = 0; /* We have no transaction data yet... */ + j->it.xpos = 0; /* ...and haven't used any of it. */ + + return (read_one_rr(j)); + + failure: + return (result); +} + +static isc_result_t +read_one_rr(dns_journal_t *j) { + isc_result_t result; + + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + unsigned int rdlen; + isc_uint32_t ttl; + journal_xhdr_t xhdr; + journal_rrhdr_t rrhdr; + + INSIST(j->offset <= j->it.epos.offset); + if (j->offset == j->it.epos.offset) + return (ISC_R_NOMORE); + if (j->it.xpos == j->it.xsize) { + /* + * We are at a transaction boundary. + * Read another transaction header. + */ + CHECK(journal_read_xhdr(j, &xhdr)); + if (xhdr.size == 0) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "journal corrupt: empty transaction"); + FAIL(ISC_R_UNEXPECTED); + } + if (xhdr.serial0 != j->it.current_serial) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal file corrupt: " + "expected serial %u, got %u", + j->filename, + j->it.current_serial, xhdr.serial0); + FAIL(ISC_R_UNEXPECTED); + } + j->it.xsize = xhdr.size; + j->it.xpos = 0; + } + /* + * Read an RR. + */ + result = journal_read_rrhdr(j, &rrhdr); + /* + * Perform a sanity check on the journal RR size. + * The smallest possible RR has a 1-byte owner name + * and a 10-byte header. The largest possible + * RR has 65535 bytes of data, a header, and a maximum- + * size owner name, well below 70 k total. + */ + if (rrhdr.size < 1+10 || rrhdr.size > 70000) { + isc_log_write(JOURNAL_COMMON_LOGARGS, ISC_LOG_ERROR, + "%s: journal corrupt: impossible RR size " + "(%d bytes)", j->filename, rrhdr.size); + FAIL(ISC_R_UNEXPECTED); + } + + CHECK(size_buffer(j->mctx, &j->it.source, rrhdr.size)); + CHECK(journal_read(j, j->it.source.base, rrhdr.size)); + isc_buffer_add(&j->it.source, rrhdr.size); + + /* + * The target buffer is made the same size + * as the source buffer, with the assumption that when + * no compression in present, the output of dns_*_fromwire() + * is no larger than the input. + */ + CHECK(size_buffer(j->mctx, &j->it.target, rrhdr.size)); + + /* + * Parse the owner name. We don't know where it + * ends yet, so we make the entire "remaining" + * part of the buffer "active". + */ + isc_buffer_setactive(&j->it.source, + j->it.source.used - j->it.source.current); + CHECK(dns_name_fromwire(&j->it.name, &j->it.source, + &j->it.dctx, ISC_FALSE, &j->it.target)); + + /* + * Check that the RR header is there, and parse it. + */ + if (isc_buffer_remaininglength(&j->it.source) < 10) + FAIL(DNS_R_FORMERR); + + rdtype = isc_buffer_getuint16(&j->it.source); + rdclass = isc_buffer_getuint16(&j->it.source); + ttl = isc_buffer_getuint32(&j->it.source); + rdlen = isc_buffer_getuint16(&j->it.source); + + /* + * Parse the rdata. + */ + isc_buffer_setactive(&j->it.source, rdlen); + dns_rdata_reset(&j->it.rdata); + CHECK(dns_rdata_fromwire(&j->it.rdata, rdclass, + rdtype, &j->it.source, &j->it.dctx, + ISC_FALSE, &j->it.target)); + j->it.ttl = ttl; + + j->it.xpos += sizeof(journal_rawrrhdr_t) + rrhdr.size; + if (rdtype == dns_rdatatype_soa) { + /* XXX could do additional consistency checks here */ + j->it.current_serial = dns_soa_getserial(&j->it.rdata); + } + + result = ISC_R_SUCCESS; + + failure: + j->it.result = result; + return (result); +} + +isc_result_t +dns_journal_next_rr(dns_journal_t *j) { + j->it.result = read_one_rr(j); + return (j->it.result); +} + +void +dns_journal_current_rr(dns_journal_t *j, dns_name_t **name, isc_uint32_t *ttl, + dns_rdata_t **rdata) +{ + REQUIRE(j->it.result == ISC_R_SUCCESS); + *name = &j->it.name; + *ttl = j->it.ttl; + *rdata = &j->it.rdata; +} + +/**************************************************************************/ +/* + * Generating diffs from databases + */ + +/* + * Construct a diff containing all the RRs at the current name of the + * database iterator 'dbit' in database 'db', version 'ver'. + * Set '*name' to the current name, and append the diff to 'diff'. + * All new tuples will have the operation 'op'. + * + * Requires: 'name' must have buffer large enough to hold the name. + * Typically, a dns_fixedname_t would be used. + */ +static isc_result_t +get_name_diff(dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, + dns_dbiterator_t *dbit, dns_name_t *name, dns_diffop_t op, + dns_diff_t *diff) +{ + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_difftuple_t *tuple = NULL; + + result = dns_dbiterator_current(dbit, &node, name); + if (result != ISC_R_SUCCESS) + return (result); + + result = dns_db_allrdatasets(db, node, ver, now, &rdsiter); + if (result != ISC_R_SUCCESS) + goto cleanup_node; + + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + dns_rdatasetiter_current(rdsiter, &rdataset); + + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); + result = dns_difftuple_create(diff->mctx, op, name, + rdataset.ttl, &rdata, + &tuple); + if (result != ISC_R_SUCCESS) { + dns_rdataset_disassociate(&rdataset); + goto cleanup_iterator; + } + dns_diff_append(diff, &tuple); + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_NOMORE) + goto cleanup_iterator; + } + if (result != ISC_R_NOMORE) + goto cleanup_iterator; + + result = ISC_R_SUCCESS; + + cleanup_iterator: + dns_rdatasetiter_destroy(&rdsiter); + + cleanup_node: + dns_db_detachnode(db, &node); + + return (result); +} + +/* + * Comparison function for use by dns_diff_subtract when sorting + * the diffs to be subtracted. The sort keys are the rdata type + * and the rdata itself. The owner name is ignored, because + * it is known to be the same for all tuples. + */ +static int +rdata_order(const void *av, const void *bv) { + dns_difftuple_t const * const *ap = av; + dns_difftuple_t const * const *bp = bv; + dns_difftuple_t const *a = *ap; + dns_difftuple_t const *b = *bp; + int r; + r = (b->rdata.type - a->rdata.type); + if (r != 0) + return (r); + r = dns_rdata_compare(&a->rdata, &b->rdata); + return (r); +} + +static isc_result_t +dns_diff_subtract(dns_diff_t diff[2], dns_diff_t *r) { + isc_result_t result; + dns_difftuple_t *p[2]; + int i, t; + CHECK(dns_diff_sort(&diff[0], rdata_order)); + CHECK(dns_diff_sort(&diff[1], rdata_order)); + + for (;;) { + p[0] = ISC_LIST_HEAD(diff[0].tuples); + p[1] = ISC_LIST_HEAD(diff[1].tuples); + if (p[0] == NULL && p[1] == NULL) + break; + + for (i = 0; i < 2; i++) + if (p[!i] == NULL) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + ISC_LIST_APPEND(r->tuples, p[i], link); + goto next; + } + t = rdata_order(&p[0], &p[1]); + if (t < 0) { + ISC_LIST_UNLINK(diff[0].tuples, p[0], link); + ISC_LIST_APPEND(r->tuples, p[0], link); + goto next; + } + if (t > 0) { + ISC_LIST_UNLINK(diff[1].tuples, p[1], link); + ISC_LIST_APPEND(r->tuples, p[1], link); + goto next; + } + INSIST(t == 0); + /* + * Identical RRs in both databases; skip them both. + */ + for (i = 0; i < 2; i++) { + ISC_LIST_UNLINK(diff[i].tuples, p[i], link); + dns_difftuple_free(&p[i]); + } + next: ; + } + result = ISC_R_SUCCESS; + failure: + return (result); +} + +/* + * Compare the databases 'dba' and 'dbb' and generate a journal + * entry containing the changes to make 'dba' from 'dbb' (note + * the order). This journal entry will consist of a single, + * possibly very large transaction. + */ + +isc_result_t +dns_db_diff(isc_mem_t *mctx, + dns_db_t *dba, dns_dbversion_t *dbvera, + dns_db_t *dbb, dns_dbversion_t *dbverb, + const char *journal_filename) +{ + dns_db_t *db[2]; + dns_dbversion_t *ver[2]; + dns_dbiterator_t *dbit[2] = { NULL, NULL }; + isc_boolean_t have[2] = { ISC_FALSE, ISC_FALSE }; + dns_fixedname_t fixname[2]; + isc_result_t result, itresult[2]; + dns_diff_t diff[2], resultdiff; + int i, t; + dns_journal_t *journal = NULL; + + db[0] = dba, db[1] = dbb; + ver[0] = dbvera, ver[1] = dbverb; + + dns_diff_init(mctx, &diff[0]); + dns_diff_init(mctx, &diff[1]); + dns_diff_init(mctx, &resultdiff); + + dns_fixedname_init(&fixname[0]); + dns_fixedname_init(&fixname[1]); + + CHECK(dns_journal_open(mctx, journal_filename, ISC_TRUE, &journal)); + + CHECK(dns_db_createiterator(db[0], ISC_FALSE, &dbit[0])); + CHECK(dns_db_createiterator(db[1], ISC_FALSE, &dbit[1])); + + itresult[0] = dns_dbiterator_first(dbit[0]); + itresult[1] = dns_dbiterator_first(dbit[1]); + + for (;;) { + for (i = 0; i < 2; i++) { + if (! have[i] && itresult[i] == ISC_R_SUCCESS) { + CHECK(get_name_diff(db[i], ver[i], 0, dbit[i], + dns_fixedname_name(&fixname[i]), + i == 0 ? + DNS_DIFFOP_ADD : + DNS_DIFFOP_DEL, + &diff[i])); + itresult[i] = dns_dbiterator_next(dbit[i]); + have[i] = ISC_TRUE; + } + } + + if (! have[0] && ! have[1]) { + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + break; + } + + for (i = 0; i < 2; i++) { + if (! have[!i]) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[i].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[i].tuples)); + have[i] = ISC_FALSE; + goto next; + } + } + + t = dns_name_compare(dns_fixedname_name(&fixname[0]), + dns_fixedname_name(&fixname[1])); + if (t < 0) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[0].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + have[0] = ISC_FALSE; + continue; + } + if (t > 0) { + ISC_LIST_APPENDLIST(resultdiff.tuples, + diff[1].tuples, link); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[1] = ISC_FALSE; + continue; + } + INSIST(t == 0); + CHECK(dns_diff_subtract(diff, &resultdiff)); + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + have[0] = have[1] = ISC_FALSE; + next: ; + } + if (itresult[0] != ISC_R_NOMORE) + FAIL(itresult[0]); + if (itresult[1] != ISC_R_NOMORE) + FAIL(itresult[1]); + + if (ISC_LIST_EMPTY(resultdiff.tuples)) { + isc_log_write(JOURNAL_DEBUG_LOGARGS(3), "no changes"); + } else { + CHECK(dns_journal_write_transaction(journal, &resultdiff)); + } + INSIST(ISC_LIST_EMPTY(diff[0].tuples)); + INSIST(ISC_LIST_EMPTY(diff[1].tuples)); + dns_diff_clear(&resultdiff); + + failure: + dns_dbiterator_destroy(&dbit[0]); + dns_dbiterator_destroy(&dbit[1]); + dns_journal_destroy(&journal); + return (result); +} diff --git a/lib/isc/include/isc/file.h b/lib/isc/include/isc/file.h new file mode 100644 index 0000000000..649fef4574 --- /dev/null +++ b/lib/isc/include/isc/file.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2000, 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* $Id: file.h,v 1.24 2001/07/16 18:33:00 gson Exp $ */ + +#ifndef ISC_FILE_H +#define ISC_FILE_H 1 + +#include + +#include +#include + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_file_settime(const char *file, isc_time_t *time); + +isc_result_t +isc_file_getmodtime(const char *file, isc_time_t *time); +/* + * Get the time of last modication of a file. + * + * Notes: + * The time that is set is relative to the (OS-specific) epoch, as are + * all isc_time_t structures. + * + * Requires: + * file != NULL. + * time != NULL. + * + * Ensures: + * If the file could not be accessed, 'time' is unchanged. + * + * Returns: + * ISC_R_SUCCESS + * Success. + * ISC_R_NOTFOUND + * No such file exists. + * ISC_R_INVALIDFILE + * The path specified was not usable by the operating system. + * ISC_R_NOPERM + * The file's metainformation could not be retrieved because + * permission was denied to some part of the file's path. + * ISC_R_EIO + * Hardware error interacting with the filesystem. + * ISC_R_UNEXPECTED + * Something totally unexpected happened. + * + */ + +isc_result_t +isc_file_mktemplate(const char *path, char *buf, size_t buflen); +/* + * Generate a template string suitable for use with isc_file_openunique. + * + * Notes: + * This function is intended to make creating temporary files + * portable between different operating systems. + * + * The path is prepended to an implementation-defined string and + * placed into buf. The string has no path characters in it, + * and its maximum length is 14 characters plus a NUL. Thus + * buflen should be at least strlen(path) + 15 characters or + * an error will be returned. + * + * Requires: + * buf != NULL. + * + * Ensures: + * If result == ISC_R_SUCCESS: + * buf contains a string suitable for use as the template argument + * to isc_file_openunique. + * + * If result != ISC_R_SUCCESS: + * buf is unchanged. + * + * Returns: + * ISC_R_SUCCESS Success. + * ISC_R_NOSPACE buflen indicates buf is too small for the catenation + * of the path with the internal template string. + */ + + +isc_result_t +isc_file_openunique(char *templet, FILE **fp); +/* + * Create and open a file with a unique name based on 'templet'. + * + * Notes: + * 'template' is a reserved work in C++. If you want to complain + * about the spelling of 'templet', first look it up in the + * Merriam-Webster English dictionary. (http://www.m-w.com/) + * + * This function works by using the template to generate file names. + * The template must be a writable string, as it is modified in place. + * Trailing X characters in the file name (full file name on Unix, + * basename on Win32 -- eg, tmp-XXXXXX vs XXXXXX.tmp, respectively) + * are replaced with ASCII characters until a non-existent filename + * is found. If the template does not include pathname information, + * the files in the working directory of the program are searched. + * + * isc_file_mktemplate is a good, portable way to get a template. + * + * Requires: + * 'fp' is non-NULL and '*fp' is NULL. + * + * 'template' is non-NULL, and of a form suitable for use by + * the system as described above. + * + * Ensures: + * If result is ISC_R_SUCCESS: + * *fp points to an stream opening in stdio's "w+" mode. + * + * If result is not ISC_R_SUCCESS: + * *fp is NULL. + * + * No file is open. Even if one was created (but unable + * to be reopened as a stdio FILE pointer) then it has been + * removed. + * + * This function does *not* ensure that the template string has not been + * modified, even if the operation was unsuccessful. + * + * Returns: + * ISC_R_SUCCESS + * Success. + * ISC_R_EXISTS + * No file with a unique name could be created based on the + * template. + * ISC_R_INVALIDFILE + * The path specified was not usable by the operating system. + * ISC_R_NOPERM + * The file could not be created because permission was denied + * to some part of the file's path. + * ISC_R_EIO + * Hardware error interacting with the filesystem. + * ISC_R_UNEXPECTED + * Something totally unexpected happened. + */ + +isc_result_t +isc_file_remove(const char *filename); +/* + * Remove the file named by 'filename'. + */ + +isc_result_t +isc_file_rename(const char *oldname, const char *newname); +/* + * Rename the file 'oldname' to 'newname'. + */ + +isc_boolean_t +isc_file_exists(const char *pathname); +/* + * Return ISC_TRUE iff the calling process can tell that the given file exists. + * Will not return true if the calling process has insufficient privileges + * to search the entire path. + */ + +isc_boolean_t +isc_file_isabsolute(const char *filename); +/* + * Return ISC_TRUE iff the given file name is absolute. + */ + +isc_boolean_t +isc_file_iscurrentdir(const char *filename); +/* + * Return ISC_TRUE iff the given file name is the current directory ("."). + */ + +isc_boolean_t +isc_file_ischdiridempotent(const char *filename); +/* + * Return ISC_TRUE if calling chdir(filename) multiple times will give + * the same result as calling it once. + */ + +const char * +isc_file_basename(const char *filename); +/* + * Return the final component of the path in the file name. + */ + +isc_result_t +isc_file_progname(const char *filename, char *buf, size_t buflen); +/* + * Given an operating system specific file name "filename" + * referring to a program, return the canonical program name. + * Any directory prefix or executable file name extension (if + * used on the OS in case) is stripped. On systems where program + * names are case insensitive, the name is canonicalized to all + * lower case. The name is written to 'buf', an array of 'buflen' + * chars, and null terminated. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOSPACE The name did not fit in 'buf'. + */ + +isc_result_t +isc_file_template(const char *path, const char *templet, char *buf, + size_t buflen); +/* + * Create an OS specific template using 'path' to define the directory + * 'templet' to describe the filename and store the result in 'buf' + * such that path can be renamed to buf atomically. + */ + +isc_result_t +isc_file_renameunique(const char *file, char *templet); +/* + * Rename 'file' using 'templet' as a template for the new file name. + */ + +isc_result_t +isc_file_absolutepath(const char *filename, char *path, size_t pathlen); +/* + * Given a file name, return the fully qualified path to the file. + */ + +/* + * XXX We should also have a isc_file_writeeopen() function + * for safely open a file in a publicly writable directory + * (see write_open() in BIND 8's ns_config.c). + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_FILE_H */ diff --git a/lib/isc/unix/file.c b/lib/isc/unix/file.c new file mode 100644 index 0000000000..da00282812 --- /dev/null +++ b/lib/isc/unix/file.c @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2000, 2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* $Id: file.c,v 1.38 2001/07/16 18:33:01 gson Exp $ */ + +#include + +#include +#include +#include +#include /* Required for utimes on some platforms. */ +#include /* Required for mkstemp on NetBSD. */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "errno2result.h" + +/* + * XXXDCL As the API for accessing file statistics undoubtedly gets expanded, + * it might be good to provide a mechanism that allows for the results + * of a previous stat() to be used again without having to do another stat, + * such as perl's mechanism of using "_" in place of a file name to indicate + * that the results of the last stat should be used. But then you get into + * annoying MP issues. BTW, Win32 has stat(). + */ +static isc_result_t +file_stats(const char *file, struct stat *stats) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(file != NULL); + REQUIRE(stats != NULL); + + if (stat(file, stats) != 0) + result = isc__errno2result(errno); + + return (result); +} + +isc_result_t +isc_file_getmodtime(const char *file, isc_time_t *time) { + isc_result_t result; + struct stat stats; + + REQUIRE(file != NULL); + REQUIRE(time != NULL); + + result = file_stats(file, &stats); + + if (result == ISC_R_SUCCESS) + /* + * XXXDCL some operating systems provide nanoseconds, too, + * such as BSD/OS via st_mtimespec. + */ + isc_time_set(time, stats.st_mtime, 0); + + return (result); +} + +isc_result_t +isc_file_settime(const char *file, isc_time_t *time) { + struct timeval times[2]; + + REQUIRE(file != NULL && time != NULL); + + /* + * tv_sec is at least a 32 bit quantity on all platforms we're + * dealing with, but it is signed on most (all?) of them, + * so we need to make sure the high bit isn't set. This unfortunately + * loses when either: + * * tv_sec becomes a signed 64 bit integer but long is 32 bits + * and isc_time_seconds > LONG_MAX, or + * * isc_time_seconds is changed to be > 32 bits but long is 32 bits + * and isc_time_seconds has at least 33 significant bits. + */ + times[0].tv_sec = times[1].tv_sec = (long)isc_time_seconds(time); + + /* + * Here is the real check for the high bit being set. + */ + if ((times[0].tv_sec & + (1ULL << (sizeof(times[0].tv_sec) * CHAR_BIT - 1))) != 0) + return (ISC_R_RANGE); + + /* + * isc_time_nanoseconds guarantees a value that divided by 1000 will + * fit into the minimum possible size tv_usec field. Unfortunately, + * we don't know what that type is so can't cast directly ... but + * we can at least cast to signed so the IRIX compiler shuts up. + */ + times[0].tv_usec = times[1].tv_usec = + (isc_int32_t)(isc_time_nanoseconds(time) / 1000); + + if (utimes(file, times) < 0) + return (isc__errno2result(errno)); + + return (ISC_R_SUCCESS); + +} + +#undef TEMPLATE +#define TEMPLATE "tmp-XXXXXXXXXX" /* 14 characters. */ + +isc_result_t +isc_file_mktemplate(const char *path, char *buf, size_t buflen) { + return (isc_file_template(path, TEMPLATE, buf, buflen)); +} + +isc_result_t +isc_file_template(const char *path, const char *templet, char *buf, + size_t buflen) { + char *s; + + REQUIRE(path != NULL); + REQUIRE(templet != NULL); + REQUIRE(buf != NULL); + + s = strrchr(templet, '/'); + if (s != NULL) + templet = s + 1; + + s = strrchr(path, '/'); + + if (s != NULL) { + if ((s - path + 1 + strlen(templet) + 1) > buflen) + return (ISC_R_NOSPACE); + + strncpy(buf, path, s - path + 1); + buf[s - path + 1] = '\0'; + strcat(buf, templet); + } else { + if ((strlen(templet) + 1) > buflen) + return (ISC_R_NOSPACE); + + strcpy(buf, templet); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_renameunique(const char *file, char *templet) { + int fd = -1; + int res = 0; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(file != NULL); + REQUIRE(templet != NULL); + + fd = mkstemp(templet); + if (fd == -1) { + result = isc__errno2result(errno); + } + if (result == ISC_R_SUCCESS) { + res = rename(file, templet); + if (res != 0) { + result = isc__errno2result(errno); + (void)unlink(templet); + } + } + if (fd != -1) + close(fd); + return (result); +} + +isc_result_t +isc_file_openunique(char *templet, FILE **fp) { + int fd; + FILE *f; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(templet != NULL); + REQUIRE(fp != NULL && *fp == NULL); + + /* + * Win32 does not have mkstemp. + */ + fd = mkstemp(templet); + + if (fd == -1) + result = isc__errno2result(errno); + if (result == ISC_R_SUCCESS) { + f = fdopen(fd, "w+"); + if (f == NULL) { + result = isc__errno2result(errno); + (void)remove(templet); + (void)close(fd); + + } else + *fp = f; + } + + return (result); +} + +isc_result_t +isc_file_remove(const char *filename) { + int r; + + REQUIRE(filename != NULL); + + r = unlink(filename); + if (r == 0) + return (ISC_R_SUCCESS); + else + return (isc__errno2result(errno)); +} + +isc_result_t +isc_file_rename(const char *oldname, const char *newname) { + int r; + + REQUIRE(oldname != NULL); + REQUIRE(newname != NULL); + + r = rename(oldname, newname); + if (r == 0) + return (ISC_R_SUCCESS); + else + return (isc__errno2result(errno)); +} + +isc_boolean_t +isc_file_exists(const char *pathname) { + struct stat stats; + + REQUIRE(pathname != NULL); + + return (ISC_TF(file_stats(pathname, &stats) == ISC_R_SUCCESS)); +} + +isc_boolean_t +isc_file_isabsolute(const char *filename) { + REQUIRE(filename != NULL); + return (ISC_TF(filename[0] == '/')); +} + +isc_boolean_t +isc_file_iscurrentdir(const char *filename) { + REQUIRE(filename != NULL); + return (ISC_TF(filename[0] == '.' && filename[1] == '\0')); +} + +isc_boolean_t +isc_file_ischdiridempotent(const char *filename) { + REQUIRE(filename != NULL); + if (isc_file_isabsolute(filename)) + return (ISC_TRUE); + if (isc_file_iscurrentdir(filename)) + return (ISC_TRUE); + return (ISC_FALSE); +} + +const char * +isc_file_basename(const char *filename) { + char *s; + + REQUIRE(filename != NULL); + + s = strrchr(filename, '/'); + if (s == NULL) + return (filename); + + return (s + 1); +} + +isc_result_t +isc_file_progname(const char *filename, char *buf, size_t buflen) { + const char *base; + size_t len; + + REQUIRE(filename != NULL); + REQUIRE(buf != NULL); + + base = isc_file_basename(filename); + len = strlen(base) + 1; + + if (len > buflen) + return (ISC_R_NOSPACE); + memcpy(buf, base, len); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_absolutepath(const char *filename, char *path, size_t pathlen) { + isc_result_t result; + result = isc_dir_current(path, pathlen, ISC_TRUE); + if (result != ISC_R_SUCCESS) + return (result); + if (strlen(path) + strlen(filename) + 1 > pathlen) + return (ISC_R_NOSPACE); + strcat(path, filename); + return (ISC_R_SUCCESS); +} diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c new file mode 100644 index 0000000000..c9dd28b97d --- /dev/null +++ b/lib/isccfg/parser.c @@ -0,0 +1,3955 @@ +/* + * Copyright (C) 2000-2003 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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. + */ + +/* $Id: parser.c,v 1.70.2.20 2003/07/23 06:57:55 marka Exp $ */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Shorthand */ +#define CAT CFG_LOGCATEGORY_CONFIG +#define MOD CFG_LOGMODULE_PARSER + +#define QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE) + +/* + * Pass one of these flags to parser_error() to include the + * token text in log message. + */ +#define LOG_NEAR 0x00000001 /* Say "near " */ +#define LOG_BEFORE 0x00000002 /* Say "before " */ +#define LOG_NOPREP 0x00000004 /* Say just "" */ + +#define MAP_SYM 1 /* Unique type for isc_symtab */ + +/* Clause may occur multiple times (e.g., "zone") */ +#define CFG_CLAUSEFLAG_MULTI 0x00000001 +/* Clause is obsolete */ +#define CFG_CLAUSEFLAG_OBSOLETE 0x00000002 +/* Clause is not implemented, and may never be */ +#define CFG_CLAUSEFLAG_NOTIMP 0x00000004 +/* Clause is not implemented yet */ +#define CFG_CLAUSEFLAG_NYI 0x00000008 +/* Default value has changed since earlier release */ +#define CFG_CLAUSEFLAG_NEWDEFAULT 0x00000010 +/* + * Clause needs to be interpreted during parsing + * by calling a callback function, like the + * "directory" option. + */ +#define CFG_CLAUSEFLAG_CALLBACK 0x00000020 + +/* + * Flags defining whether to accept certain types of network addresses. + */ +#define V4OK 0x00000001 +#define V4PREFIXOK 0x00000002 +#define V6OK 0x00000004 +#define WILDOK 0x00000008 + +/* Check a return value. */ +#define CHECK(op) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) goto cleanup; \ + } while (0) + +/* Clean up a configuration object if non-NULL. */ +#define CLEANUP_OBJ(obj) \ + do { if ((obj) != NULL) cfg_obj_destroy(pctx, &(obj)); } while (0) + + +typedef struct cfg_clausedef cfg_clausedef_t; +typedef struct cfg_tuplefielddef cfg_tuplefielddef_t; +typedef struct cfg_printer cfg_printer_t; +typedef ISC_LIST(cfg_listelt_t) cfg_list_t; +typedef struct cfg_map cfg_map_t; +typedef struct cfg_rep cfg_rep_t; + +/* + * Function types for configuration object methods + */ + +typedef isc_result_t (*cfg_parsefunc_t)(cfg_parser_t *, const cfg_type_t *type, + cfg_obj_t **); +typedef void (*cfg_printfunc_t)(cfg_printer_t *, cfg_obj_t *); +typedef void (*cfg_freefunc_t)(cfg_parser_t *, cfg_obj_t *); + + +/* + * Structure definitions + */ + +/* The parser object. */ +struct cfg_parser { + isc_mem_t * mctx; + isc_log_t * lctx; + isc_lex_t * lexer; + unsigned int errors; + unsigned int warnings; + isc_token_t token; + + /* We are at the end of all input. */ + isc_boolean_t seen_eof; + + /* The current token has been pushed back. */ + isc_boolean_t ungotten; + + /* + * The stack of currently active files, represented + * as a configuration list of configuration strings. + * The head is the top-level file, subsequent elements + * (if any) are the nested include files, and the + * last element is the file currently being parsed. + */ + cfg_obj_t * open_files; + + /* + * Names of files that we have parsed and closed + * and were previously on the open_file list. + * We keep these objects around after closing + * the files because the file names may still be + * referenced from other configuration objects + * for use in reporting semantic errors after + * parsing is complete. + */ + cfg_obj_t * closed_files; + + /* + * Current line number. We maintain our own + * copy of this so that it is available even + * when a file has just been closed. + */ + unsigned int line; + + cfg_parsecallback_t callback; + void *callbackarg; +}; + +/* + * A configuration printer object. This is an abstract + * interface to a destination to which text can be printed + * by calling the function 'f'. + */ +struct cfg_printer { + void (*f)(void *closure, const char *text, int textlen); + void *closure; + int indent; +}; + +/* A clause definition. */ + +struct cfg_clausedef { + const char *name; + cfg_type_t *type; + unsigned int flags; +}; + +/* A tuple field definition. */ + +struct cfg_tuplefielddef { + const char *name; + cfg_type_t *type; + unsigned int flags; +}; + +/* A configuration object type definition. */ +struct cfg_type { + const char *name; /* For debugging purposes only */ + cfg_parsefunc_t parse; + cfg_printfunc_t print; + cfg_rep_t * rep; /* Data representation */ + const void * of; /* For meta-types */ +}; + +/* A keyword-type definition, for things like "port ". */ + +typedef struct { + const char *name; + const cfg_type_t *type; +} keyword_type_t; + +struct cfg_map { + cfg_obj_t *id; /* Used for 'named maps' like keys, zones, &c */ + const cfg_clausedef_t * const *clausesets; /* The clauses that + can occur in this map; + used for printing */ + isc_symtab_t *symtab; +}; + +typedef struct cfg_netprefix cfg_netprefix_t; + +struct cfg_netprefix { + isc_netaddr_t address; /* IP4/IP6 */ + unsigned int prefixlen; +}; + +/* + * A configuration data representation. + */ +struct cfg_rep { + const char * name; /* For debugging only */ + cfg_freefunc_t free; /* How to free this kind of data. */ +}; + +/* + * A configuration object. This is the main building block + * of the configuration parse tree. + */ + +struct cfg_obj { + const cfg_type_t *type; + union { + isc_uint32_t uint32; + isc_uint64_t uint64; + isc_textregion_t string; /* null terminated, too */ + isc_boolean_t boolean; + cfg_map_t map; + cfg_list_t list; + cfg_obj_t ** tuple; + isc_sockaddr_t sockaddr; + cfg_netprefix_t netprefix; + } value; + char * file; + unsigned int line; +}; + + +/* A list element. */ + +struct cfg_listelt { + cfg_obj_t *obj; + ISC_LINK(cfg_listelt_t) link; +}; + +/* + * Forward declarations of static functions. + */ + +static isc_result_t +create_cfgobj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +static isc_result_t +create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +static isc_result_t +create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp); + +static void +free_list(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type, + cfg_obj_t **ret); + +static void +free_string(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +static isc_result_t +create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +static void +free_map(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +get_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na); + +static void +print(cfg_printer_t *pctx, const char *text, int len); + +static void +print_void(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_enum_or_other(cfg_parser_t *pctx, const cfg_type_t *enumtype, + const cfg_type_t *othertype, cfg_obj_t **ret); + +static isc_result_t +parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +print_mapbody(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +print_map(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +parse_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +print_list(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +print_tuple(cfg_printer_t *pctx, cfg_obj_t *obj); + +static void +free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +print_spacelist(cfg_printer_t *pctx, cfg_obj_t *obj); + +static void +print_sockaddr(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_addrmatchelt(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +print_bracketed_list(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static isc_result_t +parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +static void +print_keyvalue(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_symtab_elt(cfg_parser_t *pctx, const char *name, + cfg_type_t *elttype, isc_symtab_t *symtab, + isc_boolean_t callback); + +static void +free_noop(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +cfg_gettoken(cfg_parser_t *pctx, int options); + +static void +cfg_ungettoken(cfg_parser_t *pctx); + +static isc_result_t +cfg_peektoken(cfg_parser_t *pctx, int options); + +static isc_result_t +cfg_getstringtoken(cfg_parser_t *pctx); + +static void +parser_error(cfg_parser_t *pctx, unsigned int flags, + const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); + +static void +parser_warning(cfg_parser_t *pctx, unsigned int flags, + const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); + +static void +parser_complain(cfg_parser_t *pctx, isc_boolean_t is_warning, + unsigned int flags, const char *format, va_list args); + +static void +print_uint32(cfg_printer_t *pctx, cfg_obj_t *obj); + +static void +print_ustring(cfg_printer_t *pctx, cfg_obj_t *obj); + +static isc_result_t +parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +/* + * Data representations. These correspond to members of the + * "value" union in struct cfg_obj (except "void", which does + * not need a union member). + */ + +cfg_rep_t cfg_rep_uint32 = { "uint32", free_noop }; +cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop }; +cfg_rep_t cfg_rep_string = { "string", free_string }; +cfg_rep_t cfg_rep_boolean = { "boolean", free_noop }; +cfg_rep_t cfg_rep_map = { "map", free_map }; +cfg_rep_t cfg_rep_list = { "list", free_list }; +cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple }; +cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_noop }; +cfg_rep_t cfg_rep_netprefix = { "netprefix", free_noop }; +cfg_rep_t cfg_rep_void = { "void", free_noop }; + +/* + * Forward declarations of configuration type definitions. + * Additional types are declared publicly in cfg.h. + */ + +static cfg_type_t cfg_type_boolean; +static cfg_type_t cfg_type_uint32; +static cfg_type_t cfg_type_qstring; +static cfg_type_t cfg_type_astring; +static cfg_type_t cfg_type_ustring; +static cfg_type_t cfg_type_optional_port; +static cfg_type_t cfg_type_bracketed_aml; +static cfg_type_t cfg_type_acl; +static cfg_type_t cfg_type_portiplist; +static cfg_type_t cfg_type_bracketed_sockaddrlist; +static cfg_type_t cfg_type_sockaddr; +static cfg_type_t cfg_type_netaddr; +static cfg_type_t cfg_type_optional_keyref; +static cfg_type_t cfg_type_options; +static cfg_type_t cfg_type_view; +static cfg_type_t cfg_type_viewopts; +static cfg_type_t cfg_type_key; +static cfg_type_t cfg_type_server; +static cfg_type_t cfg_type_controls; +static cfg_type_t cfg_type_bracketed_sockaddrkeylist; +static cfg_type_t cfg_type_querysource4; +static cfg_type_t cfg_type_querysource6; +static cfg_type_t cfg_type_querysource; +static cfg_type_t cfg_type_sockaddr4wild; +static cfg_type_t cfg_type_sockaddr6wild; +static cfg_type_t cfg_type_sockaddr; +static cfg_type_t cfg_type_netprefix; +static cfg_type_t cfg_type_zone; +static cfg_type_t cfg_type_zoneopts; +static cfg_type_t cfg_type_logging; +static cfg_type_t cfg_type_optional_facility; +static cfg_type_t cfg_type_void; +static cfg_type_t cfg_type_optional_class; +static cfg_type_t cfg_type_destinationlist; +static cfg_type_t cfg_type_size; +static cfg_type_t cfg_type_sizenodefault; +static cfg_type_t cfg_type_negated; +static cfg_type_t cfg_type_addrmatchelt; +static cfg_type_t cfg_type_unsupported; +static cfg_type_t cfg_type_token; +static cfg_type_t cfg_type_server_key_kludge; +static cfg_type_t cfg_type_optional_facility; +static cfg_type_t cfg_type_logseverity; +static cfg_type_t cfg_type_logfile; +static cfg_type_t cfg_type_lwres; +static cfg_type_t cfg_type_controls_sockaddr; +static cfg_type_t cfg_type_notifytype; +static cfg_type_t cfg_type_dialuptype; + +/* + * Configuration type definitions. + */ + +/* tkey-dhkey */ + +static cfg_tuplefielddef_t tkey_dhkey_fields[] = { + { "name", &cfg_type_qstring, 0 }, + { "keyid", &cfg_type_uint32, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_tkey_dhkey = { + "tkey-dhkey", parse_tuple, print_tuple, &cfg_rep_tuple, + tkey_dhkey_fields +}; + +/* listen-on */ + +static cfg_tuplefielddef_t listenon_fields[] = { + { "port", &cfg_type_optional_port, 0 }, + { "acl", &cfg_type_bracketed_aml, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_listenon = { + "listenon", parse_tuple, print_tuple, &cfg_rep_tuple, listenon_fields }; + +/* acl */ + +static cfg_tuplefielddef_t acl_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "value", &cfg_type_bracketed_aml, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_acl = { + "acl", parse_tuple, print_tuple, &cfg_rep_tuple, acl_fields }; + + +/* + * "sockaddrkeylist", a list of socket addresses with optional keys + * and an optional default port, as used in the masters option. + * E.g., + * "port 1234 { 10.0.0.1 key foo; 1::2 port 69; }" + */ + +static cfg_tuplefielddef_t sockaddrkey_fields[] = { + { "sockaddr", &cfg_type_sockaddr, 0 }, + { "key", &cfg_type_optional_keyref, 0 }, + { NULL, NULL, 0 }, +}; + +static cfg_type_t cfg_type_sockaddrkey = { + "sockaddrkey", parse_tuple, print_tuple, &cfg_rep_tuple, + sockaddrkey_fields +}; + +static cfg_type_t cfg_type_bracketed_sockaddrkeylist = { + "bracketed_sockaddrkeylist", parse_bracketed_list, + print_bracketed_list, &cfg_rep_list, &cfg_type_sockaddrkey +}; + +static cfg_tuplefielddef_t sockaddrkeylist_fields[] = { + { "port", &cfg_type_optional_port, 0 }, + { "addresses", &cfg_type_bracketed_sockaddrkeylist, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_sockaddrkeylist = { + "sockaddrkeylist", parse_tuple, print_tuple, &cfg_rep_tuple, + sockaddrkeylist_fields +}; + +/* + * A list of socket addresses with an optional default port, + * as used in the also-notify option. E.g., + * "port 1234 { 10.0.0.1; 1::2 port 69; }" + */ +static cfg_tuplefielddef_t portiplist_fields[] = { + { "port", &cfg_type_optional_port, 0 }, + { "addresses", &cfg_type_bracketed_sockaddrlist, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_portiplist = { + "portiplist", parse_tuple, print_tuple, &cfg_rep_tuple, + portiplist_fields +}; + +/* + * A public key, as in the "pubkey" statement. + */ +static cfg_tuplefielddef_t pubkey_fields[] = { + { "flags", &cfg_type_uint32, 0 }, + { "protocol", &cfg_type_uint32, 0 }, + { "algorithm", &cfg_type_uint32, 0 }, + { "key", &cfg_type_qstring, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_pubkey = { + "pubkey", parse_tuple, print_tuple, &cfg_rep_tuple, pubkey_fields }; + + +/* + * A list of RR types, used in grant statements. + * Note that the old parser allows quotes around the RR type names. + */ +static cfg_type_t cfg_type_rrtypelist = { + "rrtypelist", parse_spacelist, print_spacelist, &cfg_rep_list, + &cfg_type_astring +}; + +static const char *mode_enums[] = { "grant", "deny", NULL }; +static cfg_type_t cfg_type_mode = { + "mode", parse_enum, print_ustring, &cfg_rep_string, + &mode_enums +}; + +static const char *matchtype_enums[] = { + "name", "subdomain", "wildcard", "self", NULL }; +static cfg_type_t cfg_type_matchtype = { + "matchtype", parse_enum, print_ustring, &cfg_rep_string, + &matchtype_enums +}; + +/* + * A grant statement, used in the update policy. + */ +static cfg_tuplefielddef_t grant_fields[] = { + { "mode", &cfg_type_mode, 0 }, + { "identity", &cfg_type_astring, 0 }, /* domain name */ + { "matchtype", &cfg_type_matchtype, 0 }, + { "name", &cfg_type_astring, 0 }, /* domain name */ + { "types", &cfg_type_rrtypelist, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_grant = { + "grant", parse_tuple, print_tuple, &cfg_rep_tuple, grant_fields }; + +static cfg_type_t cfg_type_updatepolicy = { + "update_policy", parse_bracketed_list, print_bracketed_list, + &cfg_rep_list, &cfg_type_grant +}; + +/* + * A view statement. + */ +static cfg_tuplefielddef_t view_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "class", &cfg_type_optional_class, 0 }, + { "options", &cfg_type_viewopts, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_view = { + "view", parse_tuple, print_tuple, &cfg_rep_tuple, view_fields }; + +/* + * A zone statement. + */ +static cfg_tuplefielddef_t zone_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "class", &cfg_type_optional_class, 0 }, + { "options", &cfg_type_zoneopts, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_zone = { + "zone", parse_tuple, print_tuple, &cfg_rep_tuple, zone_fields }; + +/* + * A "category" clause in the "logging" statement. + */ +static cfg_tuplefielddef_t category_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "destinations", &cfg_type_destinationlist,0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_category = { + "category", parse_tuple, print_tuple, &cfg_rep_tuple, category_fields }; + + +/* + * A trusted key, as used in the "trusted-keys" statement. + */ +static cfg_tuplefielddef_t trustedkey_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "flags", &cfg_type_uint32, 0 }, + { "protocol", &cfg_type_uint32, 0 }, + { "algorithm", &cfg_type_uint32, 0 }, + { "key", &cfg_type_qstring, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_trustedkey = { + "trustedkey", parse_tuple, print_tuple, &cfg_rep_tuple, + trustedkey_fields +}; + + +static keyword_type_t wild_class_kw = { "class", &cfg_type_ustring }; + +static cfg_type_t cfg_type_optional_wild_class = { + "optional_wild_class", parse_optional_keyvalue, + print_keyvalue, &cfg_rep_string, &wild_class_kw +}; + +static keyword_type_t wild_type_kw = { "type", &cfg_type_ustring }; + +static cfg_type_t cfg_type_optional_wild_type = { + "optional_wild_type", parse_optional_keyvalue, + print_keyvalue, &cfg_rep_string, &wild_type_kw +}; + +static keyword_type_t wild_name_kw = { "name", &cfg_type_qstring }; + +static cfg_type_t cfg_type_optional_wild_name = { + "optional_wild_name", parse_optional_keyvalue, + print_keyvalue, &cfg_rep_string, &wild_name_kw +}; + +/* + * An rrset ordering element. + */ +static cfg_tuplefielddef_t rrsetorderingelement_fields[] = { + { "class", &cfg_type_optional_wild_class, 0 }, + { "type", &cfg_type_optional_wild_type, 0 }, + { "name", &cfg_type_optional_wild_name, 0 }, + { "order", &cfg_type_ustring, 0 }, /* must be literal "order" */ + { "ordering", &cfg_type_ustring, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_rrsetorderingelement = { + "rrsetorderingelement", parse_tuple, print_tuple, &cfg_rep_tuple, + rrsetorderingelement_fields +}; + +/* + * A global or view "check-names" option. Note that the zone + * "check-names" option has a different syntax. + */ +static cfg_tuplefielddef_t checknames_fields[] = { + { "type", &cfg_type_ustring, 0 }, + { "mode", &cfg_type_ustring, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_checknames = { + "checknames", parse_tuple, print_tuple, &cfg_rep_tuple, + checknames_fields +}; + +static cfg_type_t cfg_type_bracketed_sockaddrlist = { + "bracketed_sockaddrlist", parse_bracketed_list, print_bracketed_list, + &cfg_rep_list, &cfg_type_sockaddr +}; + +static cfg_type_t cfg_type_rrsetorder = { + "rrsetorder", parse_bracketed_list, print_bracketed_list, + &cfg_rep_list, &cfg_type_rrsetorderingelement +}; + +static keyword_type_t port_kw = { "port", &cfg_type_uint32 }; + +static cfg_type_t cfg_type_optional_port = { + "optional_port", parse_optional_keyvalue, print_keyvalue, + &cfg_rep_uint32, &port_kw +}; + +/* A list of keys, as in the "key" clause of the controls statement. */ +static cfg_type_t cfg_type_keylist = { + "keylist", parse_bracketed_list, print_bracketed_list, &cfg_rep_list, + &cfg_type_astring +}; + +static cfg_type_t cfg_type_trustedkeys = { + "trusted-keys", parse_bracketed_list, print_bracketed_list, &cfg_rep_list, + &cfg_type_trustedkey +}; + +/* + * An implicit list. These are formed by clauses that occur multiple times. + */ +static cfg_type_t cfg_type_implicitlist = { + "implicitlist", NULL, print_list, &cfg_rep_list, NULL }; + +static const char *forwardtype_enums[] = { "first", "only", NULL }; +static cfg_type_t cfg_type_forwardtype = { + "forwardtype", parse_enum, print_ustring, &cfg_rep_string, + &forwardtype_enums +}; + +static const char *zonetype_enums[] = { + "master", "slave", "stub", "hint", "forward", NULL }; +static cfg_type_t cfg_type_zonetype = { + "zonetype", parse_enum, print_ustring, &cfg_rep_string, + &zonetype_enums +}; + +static const char *loglevel_enums[] = { + "critical", "error", "warning", "notice", "info", "dynamic", NULL }; +static cfg_type_t cfg_type_loglevel = { + "loglevel", parse_enum, print_ustring, &cfg_rep_string, + &loglevel_enums +}; + +static const char *transferformat_enums[] = { + "many-answers", "one-answer", NULL }; +static cfg_type_t cfg_type_transferformat = { + "transferformat", parse_enum, print_ustring, &cfg_rep_string, + &transferformat_enums +}; + +/* + * Clauses that can be found within the top level of the named.conf + * file only. + */ +static cfg_clausedef_t +namedconf_clauses[] = { + { "options", &cfg_type_options, 0 }, + { "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI }, + { "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI }, + { "logging", &cfg_type_logging, 0 }, + { "view", &cfg_type_view, CFG_CLAUSEFLAG_MULTI }, + { "lwres", &cfg_type_lwres, CFG_CLAUSEFLAG_MULTI }, + { NULL, NULL, 0 } +}; + +/* + * Clauses that can occur at the top level or in the view + * statement, but not in the options block. + */ +static cfg_clausedef_t +namedconf_or_view_clauses[] = { + { "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI }, + { "zone", &cfg_type_zone, CFG_CLAUSEFLAG_MULTI }, + { "server", &cfg_type_server, CFG_CLAUSEFLAG_MULTI }, +#ifdef ISC_RFC2535 + { "trusted-keys", &cfg_type_trustedkeys, CFG_CLAUSEFLAG_MULTI }, +#else + { "trusted-keys", &cfg_type_trustedkeys, + CFG_CLAUSEFLAG_MULTI|CFG_CLAUSEFLAG_OBSOLETE }, +#endif + { NULL, NULL, 0 } +}; + +/* + * Clauses that can be found within the 'options' statement. + */ +static cfg_clausedef_t +options_clauses[] = { + { "blackhole", &cfg_type_bracketed_aml, 0 }, + { "coresize", &cfg_type_size, 0 }, + { "datasize", &cfg_type_size, 0 }, + { "deallocate-on-exit", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "directory", &cfg_type_qstring, CFG_CLAUSEFLAG_CALLBACK }, + { "dump-file", &cfg_type_qstring, 0 }, + { "fake-iquery", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "files", &cfg_type_size, 0 }, + { "has-old-clients", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "heartbeat-interval", &cfg_type_uint32, 0 }, + { "host-statistics", &cfg_type_boolean, CFG_CLAUSEFLAG_NOTIMP }, + { "interface-interval", &cfg_type_uint32, 0 }, + { "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, + { "listen-on-v6", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI }, + { "match-mapped-addresses", &cfg_type_boolean, 0 }, + { "memstatistics-file", &cfg_type_qstring, CFG_CLAUSEFLAG_NOTIMP }, + { "multiple-cnames", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "named-xfer", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE }, + { "pid-file", &cfg_type_qstring, 0 }, + { "port", &cfg_type_uint32, 0 }, + { "random-device", &cfg_type_qstring, 0 }, + { "recursive-clients", &cfg_type_uint32, 0 }, + { "rrset-order", &cfg_type_rrsetorder, CFG_CLAUSEFLAG_NOTIMP }, + { "serial-queries", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE }, + { "serial-query-rate", &cfg_type_uint32, 0 }, + { "stacksize", &cfg_type_size, 0 }, + { "statistics-file", &cfg_type_qstring, 0 }, + { "statistics-interval", &cfg_type_uint32, CFG_CLAUSEFLAG_NYI }, + { "tcp-clients", &cfg_type_uint32, 0 }, + { "tkey-dhkey", &cfg_type_tkey_dhkey, 0 }, + { "tkey-gssapi-credential", &cfg_type_qstring, 0 }, + { "tkey-domain", &cfg_type_qstring, 0 }, + { "transfers-per-ns", &cfg_type_uint32, 0 }, + { "transfers-in", &cfg_type_uint32, 0 }, + { "transfers-out", &cfg_type_uint32, 0 }, + { "treat-cr-as-space", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "use-id-pool", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "use-ixfr", &cfg_type_boolean, 0 }, + { "version", &cfg_type_qstring, 0 }, + { NULL, NULL, 0 } +}; + +/* + * Clauses that can be found within the 'view' statement, + * with defaults in the 'options' statement. + */ + +static cfg_clausedef_t +view_clauses[] = { + { "allow-recursion", &cfg_type_bracketed_aml, 0 }, + { "allow-v6-synthesis", &cfg_type_bracketed_aml, 0 }, + { "sortlist", &cfg_type_bracketed_aml, 0 }, + { "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_NOTIMP }, + { "auth-nxdomain", &cfg_type_boolean, CFG_CLAUSEFLAG_NEWDEFAULT }, + { "minimal-responses", &cfg_type_boolean, 0 }, + { "recursion", &cfg_type_boolean, 0 }, + { "provide-ixfr", &cfg_type_boolean, 0 }, + { "request-ixfr", &cfg_type_boolean, 0 }, + { "fetch-glue", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "rfc2308-type1", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, + { "additional-from-auth", &cfg_type_boolean, 0 }, + { "additional-from-cache", &cfg_type_boolean, 0 }, + /* + * Note that the query-source option syntax is different + * from the other -source options. + */ + { "query-source", &cfg_type_querysource4, 0 }, + { "query-source-v6", &cfg_type_querysource6, 0 }, + { "cleaning-interval", &cfg_type_uint32, 0 }, + { "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP }, + { "lame-ttl", &cfg_type_uint32, 0 }, + { "max-ncache-ttl", &cfg_type_uint32, 0 }, + { "max-cache-ttl", &cfg_type_uint32, 0 }, + { "transfer-format", &cfg_type_transferformat, 0 }, + { "max-cache-size", &cfg_type_sizenodefault, 0 }, + { "check-names", &cfg_type_checknames, + CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NOTIMP }, + { "cache-file", &cfg_type_qstring, 0 }, + { NULL, NULL, 0 } +}; + +/* + * Clauses that can be found within the 'view' statement only. + */ +static cfg_clausedef_t +view_only_clauses[] = { + { "match-clients", &cfg_type_bracketed_aml, 0 }, + { "match-destinations", &cfg_type_bracketed_aml, 0 }, + { "match-recursive-only", &cfg_type_boolean, 0 }, + { NULL, NULL, 0 } +}; + +/* + * Clauses that can be found in a 'zone' statement, + * with defaults in the 'view' or 'options' statement. + */ +static cfg_clausedef_t +zone_clauses[] = { + { "allow-query", &cfg_type_bracketed_aml, 0 }, + { "allow-transfer", &cfg_type_bracketed_aml, 0 }, + { "allow-update-forwarding", &cfg_type_bracketed_aml, 0 }, + { "allow-notify", &cfg_type_bracketed_aml, 0 }, + { "notify", &cfg_type_notifytype, 0 }, + { "notify-source", &cfg_type_sockaddr4wild, 0 }, + { "notify-source-v6", &cfg_type_sockaddr6wild, 0 }, + { "also-notify", &cfg_type_portiplist, 0 }, + { "dialup", &cfg_type_dialuptype, 0 }, + { "forward", &cfg_type_forwardtype, 0 }, + { "forwarders", &cfg_type_portiplist, 0 }, + { "maintain-ixfr-base", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "max-ixfr-log-size", &cfg_type_size, CFG_CLAUSEFLAG_OBSOLETE }, + { "transfer-source", &cfg_type_sockaddr4wild, 0 }, + { "transfer-source-v6", &cfg_type_sockaddr6wild, 0 }, + { "max-transfer-time-in", &cfg_type_uint32, 0 }, + { "max-transfer-time-out", &cfg_type_uint32, 0 }, + { "max-transfer-idle-in", &cfg_type_uint32, 0 }, + { "max-transfer-idle-out", &cfg_type_uint32, 0 }, + { "max-retry-time", &cfg_type_uint32, 0 }, + { "min-retry-time", &cfg_type_uint32, 0 }, + { "max-refresh-time", &cfg_type_uint32, 0 }, + { "min-refresh-time", &cfg_type_uint32, 0 }, + { "sig-validity-interval", &cfg_type_uint32, 0 }, + { "zone-statistics", &cfg_type_boolean, 0 }, + { NULL, NULL, 0 } +}; + +/* + * Clauses that can be found in a 'zone' statement + * only. + */ +static cfg_clausedef_t +zone_only_clauses[] = { + { "type", &cfg_type_zonetype, 0 }, + { "allow-update", &cfg_type_bracketed_aml, 0 }, + { "file", &cfg_type_qstring, 0 }, + { "ixfr-base", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE }, + { "ixfr-tmp-file", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE }, + { "masters", &cfg_type_sockaddrkeylist, 0 }, + { "pubkey", &cfg_type_pubkey, + CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_OBSOLETE }, + { "update-policy", &cfg_type_updatepolicy, 0 }, + { "database", &cfg_type_astring, 0 }, + /* + * Note that the format of the check-names option is different between + * the zone options and the global/view options. Ugh. + */ + { "check-names", &cfg_type_ustring, CFG_CLAUSEFLAG_NOTIMP }, + { NULL, NULL, 0 } +}; + + +/* The top-level named.conf syntax. */ + +static cfg_clausedef_t * +namedconf_clausesets[] = { + namedconf_clauses, + namedconf_or_view_clauses, + NULL +}; + +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_namedconf = { + "namedconf", parse_mapbody, print_mapbody, &cfg_rep_map, + namedconf_clausesets +}; + +/* The "options" statement syntax. */ + +static cfg_clausedef_t * +options_clausesets[] = { + options_clauses, + view_clauses, + zone_clauses, + NULL +}; +static cfg_type_t cfg_type_options = { + "options", parse_map, print_map, &cfg_rep_map, options_clausesets }; + +/* The "view" statement syntax. */ + +static cfg_clausedef_t * +view_clausesets[] = { + view_only_clauses, + namedconf_or_view_clauses, + view_clauses, + zone_clauses, + NULL +}; +static cfg_type_t cfg_type_viewopts = { + "view", parse_map, print_map, &cfg_rep_map, view_clausesets }; + +/* The "zone" statement syntax. */ + +static cfg_clausedef_t * +zone_clausesets[] = { + zone_only_clauses, + zone_clauses, + NULL +}; +static cfg_type_t cfg_type_zoneopts = { + "zoneopts", parse_map, print_map, &cfg_rep_map, zone_clausesets }; + +/* + * Clauses that can be found within the 'key' statement. + */ +static cfg_clausedef_t +key_clauses[] = { + { "algorithm", &cfg_type_astring, 0 }, + { "secret", &cfg_type_astring, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t * +key_clausesets[] = { + key_clauses, + NULL +}; +static cfg_type_t cfg_type_key = { + "key", parse_named_map, print_map, &cfg_rep_map, key_clausesets }; + + +/* + * Clauses that can be found in a 'server' statement. + */ +static cfg_clausedef_t +server_clauses[] = { + { "bogus", &cfg_type_boolean, 0 }, + { "provide-ixfr", &cfg_type_boolean, 0 }, + { "request-ixfr", &cfg_type_boolean, 0 }, + { "support-ixfr", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "transfers", &cfg_type_uint32, 0 }, + { "transfer-format", &cfg_type_transferformat, 0 }, + { "keys", &cfg_type_server_key_kludge, 0 }, + { "edns", &cfg_type_boolean, 0 }, + { NULL, NULL, 0 } +}; +static cfg_clausedef_t * +server_clausesets[] = { + server_clauses, + NULL +}; +static cfg_type_t cfg_type_server = { + "server", parse_addressed_map, print_map, &cfg_rep_map, + server_clausesets +}; + + +/* + * Clauses that can be found in a 'channel' clause in the + * 'logging' statement. + * + * These have some additional constraints that need to be + * checked after parsing: + * - There must exactly one of file/syslog/null/stderr + * + */ +static cfg_clausedef_t +channel_clauses[] = { + /* Destinations. We no longer require these to be first. */ + { "file", &cfg_type_logfile, 0 }, + { "syslog", &cfg_type_optional_facility, 0 }, + { "null", &cfg_type_void, 0 }, + { "stderr", &cfg_type_void, 0 }, + /* Options. We now accept these for the null channel, too. */ + { "severity", &cfg_type_logseverity, 0 }, + { "print-time", &cfg_type_boolean, 0 }, + { "print-severity", &cfg_type_boolean, 0 }, + { "print-category", &cfg_type_boolean, 0 }, + { NULL, NULL, 0 } +}; +static cfg_clausedef_t * +channel_clausesets[] = { + channel_clauses, + NULL +}; +static cfg_type_t cfg_type_channel = { + "channel", parse_named_map, print_map, + &cfg_rep_map, channel_clausesets +}; + +/* A list of log destination, used in the "category" clause. */ +static cfg_type_t cfg_type_destinationlist = { + "destinationlist", parse_bracketed_list, print_bracketed_list, + &cfg_rep_list, &cfg_type_astring }; + +/* + * Clauses that can be found in a 'logging' statement. + */ +static cfg_clausedef_t +logging_clauses[] = { + { "channel", &cfg_type_channel, CFG_CLAUSEFLAG_MULTI }, + { "category", &cfg_type_category, CFG_CLAUSEFLAG_MULTI }, + { NULL, NULL, 0 } +}; +static cfg_clausedef_t * +logging_clausesets[] = { + logging_clauses, + NULL +}; +static cfg_type_t cfg_type_logging = { + "logging", parse_map, print_map, &cfg_rep_map, logging_clausesets }; + + +/* Functions. */ + +static void +print_obj(cfg_printer_t *pctx, cfg_obj_t *obj) { + obj->type->print(pctx, obj); +} + +static void +print(cfg_printer_t *pctx, const char *text, int len) { + pctx->f(pctx->closure, text, len); +} + +static void +print_open(cfg_printer_t *pctx) { + print(pctx, "{\n", 2); + pctx->indent++; +} + +static void +print_indent(cfg_printer_t *pctx) { + int indent = pctx->indent; + while (indent > 0) { + print(pctx, "\t", 1); + indent--; + } +} + +static void +print_close(cfg_printer_t *pctx) { + pctx->indent--; + print_indent(pctx); + print(pctx, "}", 1); +} + +static isc_result_t +parse(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + INSIST(ret != NULL && *ret == NULL); + result = type->parse(pctx, type, ret); + if (result != ISC_R_SUCCESS) + return (result); + INSIST(*ret != NULL); + return (ISC_R_SUCCESS); +} + +void +cfg_print(cfg_obj_t *obj, + void (*f)(void *closure, const char *text, int textlen), + void *closure) +{ + cfg_printer_t pctx; + pctx.f = f; + pctx.closure = closure; + pctx.indent = 0; + obj->type->print(&pctx, obj); +} + + +/* Tuples. */ + +static isc_result_t +create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + const cfg_tuplefielddef_t *fields = type->of; + const cfg_tuplefielddef_t *f; + cfg_obj_t *obj = NULL; + unsigned int nfields = 0; + int i; + + for (f = fields; f->name != NULL; f++) + nfields++; + + CHECK(create_cfgobj(pctx, type, &obj)); + obj->value.tuple = isc_mem_get(pctx->mctx, + nfields * sizeof(cfg_obj_t *)); + if (obj->value.tuple == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + for (f = fields, i = 0; f->name != NULL; f++, i++) + obj->value.tuple[i] = NULL; + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + if (obj != NULL) + isc_mem_put(pctx->mctx, obj, sizeof(*obj)); + return (result); +} + +static isc_result_t +parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + const cfg_tuplefielddef_t *fields = type->of; + const cfg_tuplefielddef_t *f; + cfg_obj_t *obj = NULL; + unsigned int i; + + CHECK(create_tuple(pctx, type, &obj)); + for (f = fields, i = 0; f->name != NULL; f++, i++) + CHECK(parse(pctx, f->type, &obj->value.tuple[i])); + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static void +print_tuple(cfg_printer_t *pctx, cfg_obj_t *obj) { + unsigned int i; + const cfg_tuplefielddef_t *fields = obj->type->of; + const cfg_tuplefielddef_t *f; + isc_boolean_t need_space = ISC_FALSE; + + for (f = fields, i = 0; f->name != NULL; f++, i++) { + cfg_obj_t *fieldobj = obj->value.tuple[i]; + if (need_space) + print(pctx, " ", 1); + print_obj(pctx, fieldobj); + need_space = ISC_TF(fieldobj->type->print != print_void); + } +} + +static void +free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj) { + unsigned int i; + const cfg_tuplefielddef_t *fields = obj->type->of; + const cfg_tuplefielddef_t *f; + unsigned int nfields = 0; + + if (obj->value.tuple == NULL) + return; + + for (f = fields, i = 0; f->name != NULL; f++, i++) { + CLEANUP_OBJ(obj->value.tuple[i]); + nfields++; + } + isc_mem_put(pctx->mctx, obj->value.tuple, + nfields * sizeof(cfg_obj_t *)); +} + +isc_boolean_t +cfg_obj_istuple(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_tuple)); +} + +cfg_obj_t * +cfg_tuple_get(cfg_obj_t *tupleobj, const char* name) { + unsigned int i; + const cfg_tuplefielddef_t *fields; + const cfg_tuplefielddef_t *f; + + REQUIRE(tupleobj != NULL && tupleobj->type->rep == &cfg_rep_tuple); + + fields = tupleobj->type->of; + for (f = fields, i = 0; f->name != NULL; f++, i++) { + if (strcmp(f->name, name) == 0) + return (tupleobj->value.tuple[i]); + } + INSIST(0); + return (NULL); +} + +/* + * Parse a required special character. + */ +static isc_result_t +parse_special(cfg_parser_t *pctx, int special) { + isc_result_t result; + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == special) + return (ISC_R_SUCCESS); + + parser_error(pctx, LOG_NEAR, "'%c' expected", special); + return (ISC_R_UNEXPECTEDTOKEN); + cleanup: + return (result); +} + +/* + * Parse a required semicolon. If it is not there, log + * an error and increment the error count but continue + * parsing. Since the next token is pushed back, + * care must be taken to make sure it is eventually + * consumed or an infinite loop may result. + */ +static isc_result_t +parse_semicolon(cfg_parser_t *pctx) { + isc_result_t result; + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == ';') + return (ISC_R_SUCCESS); + + parser_error(pctx, LOG_BEFORE, "missing ';'"); + cfg_ungettoken(pctx); + cleanup: + return (result); +} + +/* + * Parse EOF, logging and returning an error if not there. + */ +static isc_result_t +parse_eof(cfg_parser_t *pctx) { + isc_result_t result; + CHECK(cfg_gettoken(pctx, 0)); + + if (pctx->token.type == isc_tokentype_eof) + return (ISC_R_SUCCESS); + + parser_error(pctx, LOG_NEAR, "syntax error"); + return (ISC_R_UNEXPECTEDTOKEN); + cleanup: + return(result); +} + +/* A list of files, used internally for pctx->files. */ + +static cfg_type_t cfg_type_filelist = { + "filelist", NULL, print_list, &cfg_rep_list, + &cfg_type_qstring +}; + +isc_result_t +cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret) +{ + isc_result_t result; + cfg_parser_t *pctx; + isc_lexspecials_t specials; + + REQUIRE(mctx != NULL); + REQUIRE(ret != NULL && *ret == NULL); + + pctx = isc_mem_get(mctx, sizeof(*pctx)); + if (pctx == NULL) + return (ISC_R_NOMEMORY); + + pctx->mctx = mctx; + pctx->lctx = lctx; + pctx->lexer = NULL; + pctx->seen_eof = ISC_FALSE; + pctx->ungotten = ISC_FALSE; + pctx->errors = 0; + pctx->warnings = 0; + pctx->open_files = NULL; + pctx->closed_files = NULL; + pctx->line = 0; + pctx->callback = NULL; + pctx->callbackarg = NULL; + pctx->token.type = isc_tokentype_unknown; + + memset(specials, 0, sizeof(specials)); + specials['{'] = 1; + specials['}'] = 1; + specials[';'] = 1; + specials['/'] = 1; + specials['"'] = 1; + specials['!'] = 1; + + CHECK(isc_lex_create(pctx->mctx, 1024, &pctx->lexer)); + + isc_lex_setspecials(pctx->lexer, specials); + isc_lex_setcomments(pctx->lexer, (ISC_LEXCOMMENT_C | + ISC_LEXCOMMENT_CPLUSPLUS | + ISC_LEXCOMMENT_SHELL)); + + CHECK(create_list(pctx, &cfg_type_filelist, &pctx->open_files)); + CHECK(create_list(pctx, &cfg_type_filelist, &pctx->closed_files)); + + *ret = pctx; + return (ISC_R_SUCCESS); + + cleanup: + if (pctx->lexer != NULL) + isc_lex_destroy(&pctx->lexer); + CLEANUP_OBJ(pctx->open_files); + CLEANUP_OBJ(pctx->closed_files); + isc_mem_put(mctx, pctx, sizeof(*pctx)); + return (result); +} + +static isc_result_t +parser_openfile(cfg_parser_t *pctx, const char *filename) { + isc_result_t result; + cfg_listelt_t *elt = NULL; + cfg_obj_t *stringobj = NULL; + + result = isc_lex_openfile(pctx->lexer, filename); + if (result != ISC_R_SUCCESS) { + parser_error(pctx, 0, "open: %s: %s", + filename, isc_result_totext(result)); + goto cleanup; + } + + CHECK(create_string(pctx, filename, &cfg_type_qstring, &stringobj)); + CHECK(create_listelt(pctx, &elt)); + elt->obj = stringobj; + ISC_LIST_APPEND(pctx->open_files->value.list, elt, link); + + return (ISC_R_SUCCESS); + cleanup: + CLEANUP_OBJ(stringobj); + return (result); +} + +void +cfg_parser_setcallback(cfg_parser_t *pctx, + cfg_parsecallback_t callback, + void *arg) +{ + pctx->callback = callback; + pctx->callbackarg = arg; +} + +/* + * Parse a configuration using a pctx where a lexer has already + * been set up with a source. + */ +static isc_result_t +parse2(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + + result = parse(pctx, type, &obj); + + if (pctx->errors != 0) { + /* Errors have been logged. */ + if (result == ISC_R_SUCCESS) + result = ISC_R_FAILURE; + goto cleanup; + } + + if (result != ISC_R_SUCCESS) { + /* Parsing failed but no errors have been logged. */ + parser_error(pctx, 0, "parsing failed"); + goto cleanup; + } + + CHECK(parse_eof(pctx)); + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +isc_result_t +cfg_parse_file(cfg_parser_t *pctx, const char *filename, + const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + + REQUIRE(filename != NULL); + + CHECK(parser_openfile(pctx, filename)); + CHECK(parse2(pctx, type, ret)); + cleanup: + return (result); +} + + +isc_result_t +cfg_parse_buffer(cfg_parser_t *pctx, isc_buffer_t *buffer, + const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + REQUIRE(buffer != NULL); + CHECK(isc_lex_openbuffer(pctx->lexer, buffer)); + CHECK(parse2(pctx, type, ret)); + cleanup: + return (result); +} + +void +cfg_parser_destroy(cfg_parser_t **pctxp) { + cfg_parser_t *pctx = *pctxp; + isc_lex_destroy(&pctx->lexer); + /* + * Cleaning up open_files does not + * close the files; that was already done + * by closing the lexer. + */ + CLEANUP_OBJ(pctx->open_files); + CLEANUP_OBJ(pctx->closed_files); + isc_mem_put(pctx->mctx, pctx, sizeof(*pctx)); + *pctxp = NULL; +} + +/* + * void + */ +static isc_result_t +parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + UNUSED(type); + return (create_cfgobj(pctx, &cfg_type_void, ret)); +} + +static void +print_void(cfg_printer_t *pctx, cfg_obj_t *obj) { + UNUSED(pctx); + UNUSED(obj); +} + +isc_boolean_t +cfg_obj_isvoid(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_void)); +} + +static cfg_type_t cfg_type_void = { + "void", parse_void, print_void, &cfg_rep_void, NULL }; + + +/* + * uint32 + */ +static isc_result_t +parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + UNUSED(type); + + CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER)); + if (pctx->token.type != isc_tokentype_number) { + parser_error(pctx, LOG_NEAR, "expected number"); + return (ISC_R_UNEXPECTEDTOKEN); + } + + CHECK(create_cfgobj(pctx, &cfg_type_uint32, &obj)); + + obj->value.uint32 = pctx->token.value.as_ulong; + *ret = obj; + cleanup: + return (result); +} + +static void +print_cstr(cfg_printer_t *pctx, const char *s) { + print(pctx, s, strlen(s)); +} + +static void +print_uint(cfg_printer_t *pctx, unsigned int u) { + char buf[32]; + snprintf(buf, sizeof(buf), "%u", u); + print_cstr(pctx, buf); +} + +static void +print_uint32(cfg_printer_t *pctx, cfg_obj_t *obj) { + print_uint(pctx, obj->value.uint32); +} + +isc_boolean_t +cfg_obj_isuint32(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_uint32)); +} + +isc_uint32_t +cfg_obj_asuint32(cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint32); + return (obj->value.uint32); +} + +static cfg_type_t cfg_type_uint32 = { + "integer", parse_uint32, print_uint32, &cfg_rep_uint32, NULL }; + + +/* + * uint64 + */ +isc_boolean_t +cfg_obj_isuint64(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_uint64)); +} + +isc_uint64_t +cfg_obj_asuint64(cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint64); + return (obj->value.uint64); +} + +static isc_result_t +parse_unitstring(char *str, isc_resourcevalue_t *valuep) { + char *endp; + unsigned int len; + isc_uint64_t value; + isc_uint64_t unit; + + value = isc_string_touint64(str, &endp, 10); + if (*endp == 0) { + *valuep = value; + return (ISC_R_SUCCESS); + } + + len = strlen(str); + if (len < 2 || endp[1] != '\0') + return (ISC_R_FAILURE); + + switch (str[len - 1]) { + case 'k': + case 'K': + unit = 1024; + break; + case 'm': + case 'M': + unit = 1024 * 1024; + break; + case 'g': + case 'G': + unit = 1024 * 1024 * 1024; + break; + default: + return (ISC_R_FAILURE); + } + if (value > ISC_UINT64_MAX / unit) + return (ISC_R_FAILURE); + *valuep = value * unit; + return (ISC_R_SUCCESS); +} + +static void +print_uint64(cfg_printer_t *pctx, cfg_obj_t *obj) { + char buf[32]; + sprintf(buf, "%" ISC_PRINT_QUADFORMAT "u", obj->value.uint64); + print_cstr(pctx, buf); +} + +static cfg_type_t cfg_type_uint64 = { + "64_bit_integer", NULL, print_uint64, &cfg_rep_uint64, NULL }; + +static isc_result_t +parse_sizeval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + isc_uint64_t val; + + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + CHECK(parse_unitstring(pctx->token.value.as_pointer, &val)); + + CHECK(create_cfgobj(pctx, &cfg_type_uint64, &obj)); + obj->value.uint64 = val; + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + parser_error(pctx, LOG_NEAR, "expected integer and optional unit"); + return (result); +} + +/* + * A size value (number + optional unit). + */ +static cfg_type_t cfg_type_sizeval = { + "sizeval", parse_sizeval, print_uint64, &cfg_rep_uint64, NULL }; + +/* + * A size, "unlimited", or "default". + */ + +static isc_result_t +parse_size(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_enum_or_other(pctx, type, &cfg_type_sizeval, ret)); +} + +static const char *size_enums[] = { "unlimited", "default", NULL }; +static cfg_type_t cfg_type_size = { + "size", parse_size, print_ustring, &cfg_rep_string, size_enums +}; + +/* + * A size or "unlimited", but not "default". + */ +static const char *sizenodefault_enums[] = { "unlimited", NULL }; +static cfg_type_t cfg_type_sizenodefault = { + "size_no_default", parse_size, print_ustring, &cfg_rep_string, + sizenodefault_enums +}; + +/* + * optional_keyvalue + */ +static isc_result_t +parse_maybe_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, + isc_boolean_t optional, cfg_obj_t **ret) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + const keyword_type_t *kw = type->of; + + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(pctx->token.value.as_pointer, kw->name) == 0) { + CHECK(cfg_gettoken(pctx, 0)); + CHECK(kw->type->parse(pctx, kw->type, &obj)); + obj->type = type; /* XXX kludge */ + } else { + if (optional) { + CHECK(parse_void(pctx, NULL, &obj)); + } else { + parser_error(pctx, LOG_NEAR, "expected '%s'", + kw->name); + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + } + *ret = obj; + cleanup: + return (result); +} + +static isc_result_t +parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_maybe_optional_keyvalue(pctx, type, ISC_FALSE, ret)); +} + +static isc_result_t +parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_maybe_optional_keyvalue(pctx, type, ISC_TRUE, ret)); +} + +static void +print_keyvalue(cfg_printer_t *pctx, cfg_obj_t *obj) { + const keyword_type_t *kw = obj->type->of; + print_cstr(pctx, kw->name); + print(pctx, " ", 1); + kw->type->print(pctx, obj); +} + +/* + * qstring, ustring, astring + */ + +/* Create a string object from a null-terminated C string. */ +static isc_result_t +create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + int len; + + CHECK(create_cfgobj(pctx, type, &obj)); + len = strlen(contents); + obj->value.string.length = len; + obj->value.string.base = isc_mem_get(pctx->mctx, len + 1); + if (obj->value.string.base == 0) { + isc_mem_put(pctx->mctx, obj, sizeof(*obj)); + return (ISC_R_NOMEMORY); + } + memcpy(obj->value.string.base, contents, len); + obj->value.string.base[len] = '\0'; + + *ret = obj; + cleanup: + return (result); +} + +static isc_result_t +parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + UNUSED(type); + + CHECK(cfg_gettoken(pctx, QSTRING)); + if (pctx->token.type != isc_tokentype_qstring) { + parser_error(pctx, LOG_NEAR, "expected quoted string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (create_string(pctx, + pctx->token.value.as_pointer, + &cfg_type_qstring, + ret)); + cleanup: + return (result); +} + +static isc_result_t +parse_ustring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + UNUSED(type); + + CHECK(cfg_gettoken(pctx, 0)); + if (pctx->token.type != isc_tokentype_string) { + parser_error(pctx, LOG_NEAR, "expected unquoted string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (create_string(pctx, + pctx->token.value.as_pointer, + &cfg_type_ustring, + ret)); + cleanup: + return (result); +} + +static isc_result_t +parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + UNUSED(type); + + CHECK(cfg_getstringtoken(pctx)); + return (create_string(pctx, + pctx->token.value.as_pointer, + &cfg_type_qstring, + ret)); + cleanup: + return (result); +} + +static isc_boolean_t +is_enum(const char *s, const char *const *enums) { + const char * const *p; + for (p = enums; *p != NULL; p++) { + if (strcasecmp(*p, s) == 0) + return (ISC_TRUE); + } + return (ISC_FALSE); +} + +static isc_result_t +check_enum(cfg_parser_t *pctx, cfg_obj_t *obj, const char *const *enums) { + const char *s = obj->value.string.base; + if (is_enum(s, enums)) + return (ISC_R_SUCCESS); + parser_error(pctx, 0, "'%s' unexpected", s); + return (ISC_R_UNEXPECTEDTOKEN); +} + +static isc_result_t +parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + CHECK(parse_ustring(pctx, NULL, &obj)); + CHECK(check_enum(pctx, obj, type->of)); + *ret = obj; + return (ISC_R_SUCCESS); + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static isc_result_t +parse_enum_or_other(cfg_parser_t *pctx, const cfg_type_t *enumtype, + const cfg_type_t *othertype, cfg_obj_t **ret) +{ + isc_result_t result; + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string && + is_enum(pctx->token.value.as_pointer, enumtype->of)) { + CHECK(parse_enum(pctx, enumtype, ret)); + } else { + CHECK(parse(pctx, othertype, ret)); + } + cleanup: + return (result); +} + + +/* + * Print a string object. + */ +static void +print_ustring(cfg_printer_t *pctx, cfg_obj_t *obj) { + print(pctx, obj->value.string.base, obj->value.string.length); +} + +static void +print_qstring(cfg_printer_t *pctx, cfg_obj_t *obj) { + print(pctx, "\"", 1); + print_ustring(pctx, obj); + print(pctx, "\"", 1); +} + +static void +free_string(cfg_parser_t *pctx, cfg_obj_t *obj) { + isc_mem_put(pctx->mctx, obj->value.string.base, + obj->value.string.length + 1); +} + +isc_boolean_t +cfg_obj_isstring(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_string)); +} + +char * +cfg_obj_asstring(cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string); + return (obj->value.string.base); +} + +isc_boolean_t +cfg_obj_isboolean(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_boolean)); +} + +isc_boolean_t +cfg_obj_asboolean(cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_boolean); + return (obj->value.boolean); +} + +/* Quoted string only */ +static cfg_type_t cfg_type_qstring = { + "quoted_string", parse_qstring, print_qstring, &cfg_rep_string, NULL }; + +/* Unquoted string only */ +static cfg_type_t cfg_type_ustring = { + "string", parse_ustring, print_ustring, &cfg_rep_string, NULL }; + +/* Any string (quoted or unquoted); printed with quotes */ +static cfg_type_t cfg_type_astring = { + "string", parse_astring, print_qstring, &cfg_rep_string, NULL }; + + +/* + * boolean + */ +static isc_result_t +parse_boolean(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + isc_boolean_t value; + cfg_obj_t *obj = NULL; + UNUSED(type); + + result = cfg_gettoken(pctx, 0); + if (result != ISC_R_SUCCESS) + return (result); + + if (pctx->token.type != isc_tokentype_string) + goto bad_boolean; + + if ((strcasecmp(pctx->token.value.as_pointer, "true") == 0) || + (strcasecmp(pctx->token.value.as_pointer, "yes") == 0) || + (strcmp(pctx->token.value.as_pointer, "1") == 0)) { + value = ISC_TRUE; + } else if ((strcasecmp(pctx->token.value.as_pointer, "false") == 0) || + (strcasecmp(pctx->token.value.as_pointer, "no") == 0) || + (strcmp(pctx->token.value.as_pointer, "0") == 0)) { + value = ISC_FALSE; + } else { + goto bad_boolean; + } + + CHECK(create_cfgobj(pctx, &cfg_type_boolean, &obj)); + obj->value.boolean = value; + *ret = obj; + return (result); + + bad_boolean: + parser_error(pctx, LOG_NEAR, "boolean expected"); + return (ISC_R_UNEXPECTEDTOKEN); + + cleanup: + return (result); +} + +static void +print_boolean(cfg_printer_t *pctx, cfg_obj_t *obj) { + if (obj->value.boolean) + print(pctx, "yes", 3); + else + print(pctx, "no", 2); +} + +static cfg_type_t cfg_type_boolean = { + "boolean", parse_boolean, print_boolean, &cfg_rep_boolean, NULL }; + +static const char *dialup_enums[] = { + "notify", "notify-passive", "refresh", "passive", NULL }; +static isc_result_t +parse_dialup_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_enum_or_other(pctx, type, &cfg_type_boolean, ret)); +} +static cfg_type_t cfg_type_dialuptype = { + "dialuptype", parse_dialup_type, print_ustring, + &cfg_rep_string, dialup_enums +}; + +static const char *notify_enums[] = { "explicit", NULL }; +static isc_result_t +parse_notify_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_enum_or_other(pctx, type, &cfg_type_boolean, ret)); +} +static cfg_type_t cfg_type_notifytype = { + "notifytype", parse_notify_type, print_ustring, + &cfg_rep_string, notify_enums, +}; + +static keyword_type_t key_kw = { "key", &cfg_type_astring }; + +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_keyref = { + "keyref", parse_keyvalue, print_keyvalue, + &cfg_rep_string, &key_kw +}; + +static cfg_type_t cfg_type_optional_keyref = { + "optional_keyref", parse_optional_keyvalue, print_keyvalue, + &cfg_rep_string, &key_kw +}; + + +/* + * Lists. + */ + +static isc_result_t +create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) { + isc_result_t result; + CHECK(create_cfgobj(pctx, type, obj)); + ISC_LIST_INIT((*obj)->value.list); + cleanup: + return (result); +} + +static isc_result_t +create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp) { + cfg_listelt_t *elt; + elt = isc_mem_get(pctx->mctx, sizeof(*elt)); + if (elt == NULL) + return (ISC_R_NOMEMORY); + elt->obj = NULL; + ISC_LINK_INIT(elt, link); + *eltp = elt; + return (ISC_R_SUCCESS); +} + +static void +free_list_elt(cfg_parser_t *pctx, cfg_listelt_t *elt) { + cfg_obj_destroy(pctx, &elt->obj); + isc_mem_put(pctx->mctx, elt, sizeof(*elt)); +} + +static void +free_list(cfg_parser_t *pctx, cfg_obj_t *obj) { + cfg_listelt_t *elt, *next; + for (elt = ISC_LIST_HEAD(obj->value.list); + elt != NULL; + elt = next) + { + next = ISC_LIST_NEXT(elt, link); + free_list_elt(pctx, elt); + } +} + +static isc_result_t +parse_list_elt(cfg_parser_t *pctx, const cfg_type_t *elttype, + cfg_listelt_t **ret) +{ + isc_result_t result; + cfg_listelt_t *elt = NULL; + cfg_obj_t *value = NULL; + + CHECK(create_listelt(pctx, &elt)); + + result = parse(pctx, elttype, &value); + if (result != ISC_R_SUCCESS) + goto cleanup; + + elt->obj = value; + + *ret = elt; + return (ISC_R_SUCCESS); + + cleanup: + isc_mem_put(pctx->mctx, elt, sizeof(*elt)); + return (result); +} + +/* + * Parse a homogeneous list whose elements are of type 'elttype' + * and where each element is terminated by a semicolon. + */ +static isc_result_t +parse_list(cfg_parser_t *pctx, const cfg_type_t *listtype, cfg_obj_t **ret) +{ + cfg_obj_t *listobj = NULL; + const cfg_type_t *listof = listtype->of; + isc_result_t result; + cfg_listelt_t *elt = NULL; + + CHECK(create_list(pctx, listtype, &listobj)); + + for (;;) { + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == /*{*/ '}') + break; + CHECK(parse_list_elt(pctx, listof, &elt)); + CHECK(parse_semicolon(pctx)); + ISC_LIST_APPEND(listobj->value.list, elt, link); + elt = NULL; + } + *ret = listobj; + return (ISC_R_SUCCESS); + + cleanup: + if (elt != NULL) + free_list_elt(pctx, elt); + CLEANUP_OBJ(listobj); + return (result); +} + +static void +print_list(cfg_printer_t *pctx, cfg_obj_t *obj) { + cfg_list_t *list = &obj->value.list; + cfg_listelt_t *elt; + + for (elt = ISC_LIST_HEAD(*list); + elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) { + print_indent(pctx); + print_obj(pctx, elt->obj); + print(pctx, ";\n", 2); + } +} + +static isc_result_t +parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + CHECK(parse_special(pctx, '{')); + CHECK(parse_list(pctx, type, ret)); + CHECK(parse_special(pctx, '}')); + cleanup: + return (result); +} + +static void +print_bracketed_list(cfg_printer_t *pctx, cfg_obj_t *obj) { + print_open(pctx); + print_list(pctx, obj); + print_close(pctx); +} + +/* + * Parse a homogeneous list whose elements are of type 'elttype' + * and where elements are separated by space. The list ends + * before the first semicolon. + */ +static isc_result_t +parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *listtype, cfg_obj_t **ret) +{ + cfg_obj_t *listobj = NULL; + const cfg_type_t *listof = listtype->of; + isc_result_t result; + + CHECK(create_list(pctx, listtype, &listobj)); + + for (;;) { + cfg_listelt_t *elt = NULL; + + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == ';') + break; + CHECK(parse_list_elt(pctx, listof, &elt)); + ISC_LIST_APPEND(listobj->value.list, elt, link); + } + *ret = listobj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(listobj); + return (result); +} + +static void +print_spacelist(cfg_printer_t *pctx, cfg_obj_t *obj) { + cfg_list_t *list = &obj->value.list; + cfg_listelt_t *elt; + + for (elt = ISC_LIST_HEAD(*list); + elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) { + print_obj(pctx, elt->obj); + if (ISC_LIST_NEXT(elt, link) != NULL) + print(pctx, " ", 1); + } +} + +isc_boolean_t +cfg_obj_islist(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_list)); +} + +cfg_listelt_t * +cfg_list_first(cfg_obj_t *obj) { + REQUIRE(obj == NULL || obj->type->rep == &cfg_rep_list); + if (obj == NULL) + return (NULL); + return (ISC_LIST_HEAD(obj->value.list)); +} + +cfg_listelt_t * +cfg_list_next(cfg_listelt_t *elt) { + REQUIRE(elt != NULL); + return (ISC_LIST_NEXT(elt, link)); +} + +cfg_obj_t * +cfg_listelt_value(cfg_listelt_t *elt) { + REQUIRE(elt != NULL); + return (elt->obj); +} + +/* + * Maps. + */ + +/* + * Parse a map body. That's something like + * + * "foo 1; bar { glub; }; zap true; zap false;" + * + * i.e., a sequence of option names followed by values and + * terminated by semicolons. Used for the top level of + * the named.conf syntax, as well as for the body of the + * options, view, zone, and other statements. + */ +static isc_result_t +parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + const cfg_clausedef_t * const *clausesets = type->of; + isc_result_t result; + const cfg_clausedef_t * const *clauseset; + const cfg_clausedef_t *clause; + cfg_obj_t *value = NULL; + cfg_obj_t *obj = NULL; + cfg_obj_t *eltobj = NULL; + cfg_obj_t *includename = NULL; + isc_symvalue_t symval; + cfg_list_t *list = NULL; + + CHECK(create_map(pctx, type, &obj)); + + obj->value.map.clausesets = clausesets; + + for (;;) { + cfg_listelt_t *elt; + + redo: + /* + * Parse the option name and see if it is known. + */ + CHECK(cfg_gettoken(pctx, 0)); + + if (pctx->token.type != isc_tokentype_string) { + cfg_ungettoken(pctx); + break; + } + + /* + * We accept "include" statements wherever a map body + * clause can occur. + */ + if (strcasecmp(pctx->token.value.as_pointer, "include") == 0) { + /* + * Turn the file name into a temporary configuration + * object just so that it is not overwritten by the + * semicolon token. + */ + CHECK(parse(pctx, &cfg_type_qstring, &includename)); + CHECK(parse_semicolon(pctx)); + CHECK(parser_openfile(pctx, includename-> + value.string.base)); + cfg_obj_destroy(pctx, &includename); + goto redo; + } + + clause = NULL; + for (clauseset = clausesets; *clauseset != NULL; clauseset++) { + for (clause = *clauseset; + clause->name != NULL; + clause++) { + if (strcasecmp(pctx->token.value.as_pointer, + clause->name) == 0) + goto done; + } + } + done: + if (clause == NULL || clause->name == NULL) { + parser_error(pctx, LOG_NOPREP, "unknown option"); + /* + * Try to recover by parsing this option as an unknown + * option and discarding it. + */ + CHECK(parse(pctx, &cfg_type_unsupported, &eltobj)); + cfg_obj_destroy(pctx, &eltobj); + CHECK(parse_semicolon(pctx)); + continue; + } + + /* Clause is known. */ + + /* Issue warnings if appropriate */ + if ((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) + parser_warning(pctx, 0, "option '%s' is obsolete", + clause->name); + if ((clause->flags & CFG_CLAUSEFLAG_NOTIMP) != 0) + parser_warning(pctx, 0, "option '%s' is " + "not implemented", clause->name); + if ((clause->flags & CFG_CLAUSEFLAG_NYI) != 0) + parser_warning(pctx, 0, "option '%s' is " + "not implemented", clause->name); + /* + * Don't log options with CFG_CLAUSEFLAG_NEWDEFAULT + * set here - we need to log the *lack* of such an option, + * not its presence. + */ + + /* See if the clause already has a value; if not create one. */ + result = isc_symtab_lookup(obj->value.map.symtab, + clause->name, 0, &symval); + + if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) { + /* Multivalued clause */ + cfg_obj_t *listobj = NULL; + if (result == ISC_R_NOTFOUND) { + CHECK(create_list(pctx, + &cfg_type_implicitlist, + &listobj)); + symval.as_pointer = listobj; + result = isc_symtab_define(obj->value. + map.symtab, + clause->name, + 1, symval, + isc_symexists_reject); + if (result != ISC_R_SUCCESS) { + parser_error(pctx, LOG_NEAR, + "isc_symtab_define(%s) " + "failed", clause->name); + isc_mem_put(pctx->mctx, list, + sizeof(cfg_list_t)); + goto cleanup; + } + } else { + INSIST(result == ISC_R_SUCCESS); + listobj = symval.as_pointer; + } + + elt = NULL; + CHECK(parse_list_elt(pctx, clause->type, &elt)); + CHECK(parse_semicolon(pctx)); + + ISC_LIST_APPEND(listobj->value.list, elt, link); + } else { + /* Single-valued clause */ + if (result == ISC_R_NOTFOUND) { + isc_boolean_t callback = + ISC_TF((clause->flags & + CFG_CLAUSEFLAG_CALLBACK) != 0); + CHECK(parse_symtab_elt(pctx, clause->name, + clause->type, + obj->value.map.symtab, + callback)); + CHECK(parse_semicolon(pctx)); + } else if (result == ISC_R_SUCCESS) { + parser_error(pctx, LOG_NEAR, "'%s' redefined", + clause->name); + result = ISC_R_EXISTS; + goto cleanup; + } else { + parser_error(pctx, LOG_NEAR, + "isc_symtab_define() failed"); + goto cleanup; + } + } + } + + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(value); + CLEANUP_OBJ(obj); + CLEANUP_OBJ(eltobj); + CLEANUP_OBJ(includename); + return (result); +} + +static isc_result_t +parse_symtab_elt(cfg_parser_t *pctx, const char *name, + cfg_type_t *elttype, isc_symtab_t *symtab, + isc_boolean_t callback) +{ + isc_result_t result; + cfg_obj_t *obj = NULL; + isc_symvalue_t symval; + + CHECK(parse(pctx, elttype, &obj)); + + if (callback && pctx->callback != NULL) + CHECK(pctx->callback(name, obj, pctx->callbackarg)); + + symval.as_pointer = obj; + CHECK(isc_symtab_define(symtab, name, + 1, symval, + isc_symexists_reject)); + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +/* + * Parse a map; e.g., "{ foo 1; bar { glub; }; zap true; zap false; }" + */ +static isc_result_t +parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + CHECK(parse_special(pctx, '{')); + CHECK(parse_mapbody(pctx, type, ret)); + CHECK(parse_special(pctx, '}')); + cleanup: + return (result); +} + +/* + * Subroutine for parse_named_map() and parse_addressed_map(). + */ +static isc_result_t +parse_any_named_map(cfg_parser_t *pctx, cfg_type_t *nametype, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + cfg_obj_t *idobj = NULL; + cfg_obj_t *mapobj = NULL; + + CHECK(parse(pctx, nametype, &idobj)); + CHECK(parse_map(pctx, type, &mapobj)); + mapobj->value.map.id = idobj; + idobj = NULL; + *ret = mapobj; + cleanup: + CLEANUP_OBJ(idobj); + return (result); +} + +/* + * Parse a map identified by a string name. E.g., "name { foo 1; }". + * Used for the "key" and "channel" statements. + */ +static isc_result_t +parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_any_named_map(pctx, &cfg_type_astring, type, ret)); +} + +/* + * Parse a map identified by a network address. + * Used for the "server" statement. + */ +static isc_result_t +parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_any_named_map(pctx, &cfg_type_netaddr, type, ret)); +} + +static void +print_mapbody(cfg_printer_t *pctx, cfg_obj_t *obj) { + isc_result_t result = ISC_R_SUCCESS; + + const cfg_clausedef_t * const *clauseset; + + for (clauseset = obj->value.map.clausesets; + *clauseset != NULL; + clauseset++) + { + isc_symvalue_t symval; + const cfg_clausedef_t *clause; + + for (clause = *clauseset; + clause->name != NULL; + clause++) { + result = isc_symtab_lookup(obj->value.map.symtab, + clause->name, 0, &symval); + if (result == ISC_R_SUCCESS) { + cfg_obj_t *obj = symval.as_pointer; + if (obj->type == &cfg_type_implicitlist) { + /* Multivalued. */ + cfg_list_t *list = &obj->value.list; + cfg_listelt_t *elt; + for (elt = ISC_LIST_HEAD(*list); + elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) { + print_indent(pctx); + print_cstr(pctx, clause->name); + print(pctx, " ", 1); + print_obj(pctx, elt->obj); + print(pctx, ";\n", 2); + } + } else { + /* Single-valued. */ + print_indent(pctx); + print_cstr(pctx, clause->name); + print(pctx, " ", 1); + print_obj(pctx, obj); + print(pctx, ";\n", 2); + } + } else if (result == ISC_R_NOTFOUND) { + ; /* do nothing */ + } else { + INSIST(0); + } + } + } +} + +static void +print_map(cfg_printer_t *pctx, cfg_obj_t *obj) { + if (obj->value.map.id != NULL) { + print_obj(pctx, obj->value.map.id); + print(pctx, " ", 1); + } + print_open(pctx); + print_mapbody(pctx, obj); + print_close(pctx); +} + +isc_boolean_t +cfg_obj_ismap(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_map)); +} + +isc_result_t +cfg_map_get(cfg_obj_t *mapobj, const char* name, cfg_obj_t **obj) { + isc_result_t result; + isc_symvalue_t val; + cfg_map_t *map; + + REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map); + REQUIRE(name != NULL); + REQUIRE(obj != NULL && *obj == NULL); + + map = &mapobj->value.map; + + result = isc_symtab_lookup(map->symtab, name, MAP_SYM, &val); + if (result != ISC_R_SUCCESS) + return (result); + *obj = val.as_pointer; + return (ISC_R_SUCCESS); +} + +cfg_obj_t * +cfg_map_getname(cfg_obj_t *mapobj) { + REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map); + return (mapobj->value.map.id); +} + + +/* Parse an arbitrary token, storing its raw text representation. */ +static isc_result_t +parse_token(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + cfg_obj_t *obj = NULL; + isc_result_t result; + isc_region_t r; + + UNUSED(type); + + CHECK(create_cfgobj(pctx, &cfg_type_token, &obj)); + CHECK(cfg_gettoken(pctx, QSTRING)); + if (pctx->token.type == isc_tokentype_eof) { + cfg_ungettoken(pctx); + result = ISC_R_EOF; + goto cleanup; + } + + isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r); + + obj->value.string.base = isc_mem_get(pctx->mctx, r.length + 1); + obj->value.string.length = r.length; + memcpy(obj->value.string.base, r.base, r.length); + obj->value.string.base[r.length] = '\0'; + *ret = obj; + + cleanup: + return (result); +} + +static cfg_type_t cfg_type_token = { + "token", parse_token, print_ustring, &cfg_rep_string, NULL }; + +/* + * An unsupported option. This is just a list of tokens with balanced braces + * ending in a semicolon. + */ + +static isc_result_t +parse_unsupported(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + cfg_obj_t *listobj = NULL; + isc_result_t result; + int braces = 0; + + CHECK(create_list(pctx, type, &listobj)); + + for (;;) { + cfg_listelt_t *elt = NULL; + + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special) { + if (pctx->token.value.as_char == '{') + braces++; + else if (pctx->token.value.as_char == '}') + braces--; + else if (pctx->token.value.as_char == ';') + if (braces == 0) + break; + } + if (pctx->token.type == isc_tokentype_eof || braces < 0) { + parser_error(pctx, LOG_NEAR, "unexpected token"); + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + CHECK(parse_list_elt(pctx, &cfg_type_token, &elt)); + ISC_LIST_APPEND(listobj->value.list, elt, link); + } + INSIST(braces == 0); + *ret = listobj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(listobj); + return (result); +} + +static cfg_type_t cfg_type_unsupported = { + "unsupported", parse_unsupported, print_spacelist, + &cfg_rep_list, NULL +}; + +/* + * A "controls" statement is represented as a map with the multivalued + * "inet" and "unix" clauses. Inet controls are tuples; unix controls + * are cfg_unsupported_t objects. + */ + +static keyword_type_t controls_allow_kw = { + "allow", &cfg_type_bracketed_aml }; +static cfg_type_t cfg_type_controls_allow = { + "controls_allow", parse_keyvalue, + print_keyvalue, &cfg_rep_list, &controls_allow_kw +}; + +static keyword_type_t controls_keys_kw = { + "keys", &cfg_type_keylist }; +static cfg_type_t cfg_type_controls_keys = { + "controls_keys", parse_optional_keyvalue, + print_keyvalue, &cfg_rep_list, &controls_keys_kw +}; + +static cfg_tuplefielddef_t inetcontrol_fields[] = { + { "address", &cfg_type_controls_sockaddr, 0 }, + { "allow", &cfg_type_controls_allow, 0 }, + { "keys", &cfg_type_controls_keys, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_inetcontrol = { + "inetcontrol", parse_tuple, print_tuple, &cfg_rep_tuple, + inetcontrol_fields +}; + +static cfg_clausedef_t +controls_clauses[] = { + { "inet", &cfg_type_inetcontrol, CFG_CLAUSEFLAG_MULTI }, + { "unix", &cfg_type_unsupported, + CFG_CLAUSEFLAG_MULTI|CFG_CLAUSEFLAG_NOTIMP }, + { NULL, NULL, 0 } +}; +static cfg_clausedef_t * +controls_clausesets[] = { + controls_clauses, + NULL +}; +static cfg_type_t cfg_type_controls = { + "controls", parse_map, print_map, &cfg_rep_map, &controls_clausesets +}; + +/* + * An optional class, as used in view and zone statements. + */ +static isc_result_t +parse_optional_class(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + UNUSED(type); + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string) + CHECK(parse(pctx, &cfg_type_ustring, ret)); + else + CHECK(parse(pctx, &cfg_type_void, ret)); + cleanup: + return (result); +} + +static cfg_type_t cfg_type_optional_class = { + "optional_class", parse_optional_class, NULL, NULL, NULL }; + + +/* + * Try interpreting the current token as a network address. + * + * If WILDOK is set in flags, "*" can be used as a wildcard + * and at least one of V4OK and V6OK must also be set. The + * "*" is interpreted as the IPv4 wildcard address if V4OK is + * set (including the case where V4OK and V6OK are both set), + * and the IPv6 wildcard address otherwise. + */ +static isc_result_t +token_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) { + char *s; + struct in_addr in4a; + struct in6_addr in6a; + + if (pctx->token.type != isc_tokentype_string) + return (ISC_R_UNEXPECTEDTOKEN); + + s = pctx->token.value.as_pointer; + if ((flags & WILDOK) != 0 && strcmp(s, "*") == 0) { + if ((flags & V4OK) != 0) { + isc_netaddr_any(na); + return (ISC_R_SUCCESS); + } else if ((flags & V6OK) != 0) { + isc_netaddr_any6(na); + return (ISC_R_SUCCESS); + } else { + INSIST(0); + } + } else { + if ((flags & (V4OK | V4PREFIXOK)) != 0) { + if (inet_pton(AF_INET, s, &in4a) == 1) { + isc_netaddr_fromin(na, &in4a); + return (ISC_R_SUCCESS); + } + } + if ((flags & V4PREFIXOK) != 0 && + strlen(s) <= 15U) { + char buf[64]; + int i; + + strcpy(buf, s); + for (i = 0; i < 3; i++) { + strcat(buf, ".0"); + if (inet_pton(AF_INET, buf, &in4a) == 1) { + isc_netaddr_fromin(na, &in4a); + return (ISC_R_SUCCESS); + } + } + } + if (flags & V6OK) { + if (inet_pton(AF_INET6, s, &in6a) == 1) { + isc_netaddr_fromin6(na, &in6a); + return (ISC_R_SUCCESS); + } + } + } + return (ISC_R_UNEXPECTEDTOKEN); +} + +static isc_result_t +get_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) { + isc_result_t result; + CHECK(cfg_gettoken(pctx, 0)); + result = token_addr(pctx, flags, na); + if (result == ISC_R_UNEXPECTEDTOKEN) + parser_error(pctx, LOG_NEAR, "expected IP address"); + cleanup: + return (result); +} + +static isc_boolean_t +looking_at_netaddr(cfg_parser_t *pctx, unsigned int flags) { + isc_result_t result; + isc_netaddr_t na_dummy; + result = token_addr(pctx, flags, &na_dummy); + return (ISC_TF(result == ISC_R_SUCCESS)); +} + +static isc_result_t +get_port(cfg_parser_t *pctx, unsigned int flags, in_port_t *port) { + isc_result_t result; + + CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER)); + + if ((flags & WILDOK) != 0 && + pctx->token.type == isc_tokentype_string && + strcmp(pctx->token.value.as_pointer, "*") == 0) { + *port = 0; + return (ISC_R_SUCCESS); + } + if (pctx->token.type != isc_tokentype_number) { + parser_error(pctx, LOG_NEAR, + "expected port number or '*'"); + return (ISC_R_UNEXPECTEDTOKEN); + } + if (pctx->token.value.as_ulong >= 65536U) { + parser_error(pctx, LOG_NEAR, + "port number out of range"); + return (ISC_R_UNEXPECTEDTOKEN); + } + *port = (in_port_t)(pctx->token.value.as_ulong); + return (ISC_R_SUCCESS); + cleanup: + return (result); +} + +static isc_result_t +parse_querysource(cfg_parser_t *pctx, int flags, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + isc_netaddr_t netaddr; + in_port_t port; + unsigned int have_address = 0; + unsigned int have_port = 0; + + if ((flags & V4OK) != 0) + isc_netaddr_any(&netaddr); + else if ((flags & V6OK) != 0) + isc_netaddr_any6(&netaddr); + else + INSIST(0); + + port = 0; + + CHECK(create_cfgobj(pctx, &cfg_type_querysource, &obj)); + for (;;) { + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string) { + if (strcasecmp(pctx->token.value.as_pointer, + "address") == 0) + { + /* read "address" */ + CHECK(cfg_gettoken(pctx, 0)); + CHECK(get_addr(pctx, flags|WILDOK, &netaddr)); + have_address++; + } else if (strcasecmp(pctx->token.value.as_pointer, + "port") == 0) + { + /* read "port" */ + CHECK(cfg_gettoken(pctx, 0)); + CHECK(get_port(pctx, WILDOK, &port)); + have_port++; + } else { + parser_error(pctx, LOG_NEAR, + "expected 'address' or 'port'"); + return (ISC_R_UNEXPECTEDTOKEN); + } + } else + break; + } + if (have_address > 1 || have_port > 1 || + have_address + have_port == 0) { + parser_error(pctx, 0, "expected one address and/or port"); + return (ISC_R_UNEXPECTEDTOKEN); + } + + isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port); + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + parser_error(pctx, LOG_NEAR, "invalid query source"); + CLEANUP_OBJ(obj); + return (result); +} + +static isc_result_t +parse_querysource4(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + UNUSED(type); + return (parse_querysource(pctx, V4OK, ret)); +} + +static isc_result_t +parse_querysource6(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + UNUSED(type); + return (parse_querysource(pctx, V6OK, ret)); +} + +static void +print_isc_netaddr(cfg_printer_t *pctx, isc_netaddr_t *na) { + isc_result_t result; + char text[128]; + isc_buffer_t buf; + + isc_buffer_init(&buf, text, sizeof(text)); + result = isc_netaddr_totext(na, &buf); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + print(pctx, isc_buffer_base(&buf), isc_buffer_usedlength(&buf)); +} + +static void +print_querysource(cfg_printer_t *pctx, cfg_obj_t *obj) { + isc_netaddr_t na; + isc_netaddr_fromsockaddr(&na, &obj->value.sockaddr); + print(pctx, "address ", 8); + print_isc_netaddr(pctx, &na); + print(pctx, " port ", 6); + print_uint(pctx, isc_sockaddr_getport(&obj->value.sockaddr)); +} + +static cfg_type_t cfg_type_querysource4 = { + "querysource4", parse_querysource4, NULL, NULL, NULL }; +static cfg_type_t cfg_type_querysource6 = { + "querysource6", parse_querysource6, NULL, NULL, NULL }; +static cfg_type_t cfg_type_querysource = { + "querysource", NULL, print_querysource, &cfg_rep_sockaddr, NULL }; + +/* netaddr */ + +static isc_result_t +parse_netaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + isc_netaddr_t netaddr; + UNUSED(type); + CHECK(create_cfgobj(pctx, type, &obj)); + CHECK(get_addr(pctx, V4OK|V6OK, &netaddr)); + isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, 0); + *ret = obj; + return (ISC_R_SUCCESS); + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static cfg_type_t cfg_type_netaddr = { + "netaddr", parse_netaddr, print_sockaddr, &cfg_rep_sockaddr, NULL }; + +/* netprefix */ + +static isc_result_t +parse_netprefix(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + cfg_obj_t *obj = NULL; + isc_result_t result; + isc_netaddr_t netaddr; + unsigned int addrlen, prefixlen; + UNUSED(type); + + CHECK(get_addr(pctx, V4OK|V4PREFIXOK|V6OK, &netaddr)); + switch (netaddr.family) { + case AF_INET: + addrlen = 32; + break; + case AF_INET6: + addrlen = 128; + break; + default: + addrlen = 0; + INSIST(0); + break; + } + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == '/') { + CHECK(cfg_gettoken(pctx, 0)); /* read "/" */ + CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER)); + if (pctx->token.type != isc_tokentype_number) { + parser_error(pctx, LOG_NEAR, + "expected prefix length"); + return (ISC_R_UNEXPECTEDTOKEN); + } + prefixlen = pctx->token.value.as_ulong; + if (prefixlen > addrlen) { + parser_error(pctx, LOG_NOPREP, + "invalid prefix length"); + return (ISC_R_RANGE); + } + } else { + prefixlen = addrlen; + } + CHECK(create_cfgobj(pctx, &cfg_type_netprefix, &obj)); + obj->value.netprefix.address = netaddr; + obj->value.netprefix.prefixlen = prefixlen; + *ret = obj; + return (ISC_R_SUCCESS); + cleanup: + parser_error(pctx, LOG_NEAR, "expected network prefix"); + return (result); +} + +static void +print_netprefix(cfg_printer_t *pctx, cfg_obj_t *obj) { + cfg_netprefix_t *p = &obj->value.netprefix; + print_isc_netaddr(pctx, &p->address); + print(pctx, "/", 1); + print_uint(pctx, p->prefixlen); +} + +isc_boolean_t +cfg_obj_isnetprefix(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_netprefix)); +} + +void +cfg_obj_asnetprefix(cfg_obj_t *obj, isc_netaddr_t *netaddr, + unsigned int *prefixlen) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_netprefix); + *netaddr = obj->value.netprefix.address; + *prefixlen = obj->value.netprefix.prefixlen; +} + +static cfg_type_t cfg_type_netprefix = { + "netprefix", parse_netprefix, print_netprefix, &cfg_rep_netprefix, NULL }; + +/* addrmatchelt */ + +static isc_result_t +parse_addrmatchelt(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + UNUSED(type); + + CHECK(cfg_peektoken(pctx, QSTRING)); + + if (pctx->token.type == isc_tokentype_string || + pctx->token.type == isc_tokentype_qstring) { + if (pctx->token.type == isc_tokentype_string && + (strcasecmp(pctx->token.value.as_pointer, "key") == 0)) { + CHECK(parse(pctx, &cfg_type_keyref, ret)); + } else { + if (looking_at_netaddr(pctx, V4OK|V4PREFIXOK|V6OK)) { + CHECK(parse_netprefix(pctx, NULL, ret)); + } else { + CHECK(parse_astring(pctx, NULL, ret)); + } + } + } else if (pctx->token.type == isc_tokentype_special) { + if (pctx->token.value.as_char == '{') { + /* Nested match list. */ + CHECK(parse(pctx, &cfg_type_bracketed_aml, ret)); + } else if (pctx->token.value.as_char == '!') { + CHECK(cfg_gettoken(pctx, 0)); /* read "!" */ + CHECK(parse(pctx, &cfg_type_negated, ret)); + } else { + goto bad; + } + } else { + bad: + parser_error(pctx, LOG_NEAR, + "expected IP match list element"); + return (ISC_R_UNEXPECTEDTOKEN); + } + cleanup: + return (result); +} + +/* + * A negated address match list element (like "! 10.0.0.1"). + * Somewhat sneakily, the caller is expected to parse the + * "!", but not to print it. + */ + +static cfg_tuplefielddef_t negated_fields[] = { + { "value", &cfg_type_addrmatchelt, 0 }, + { NULL, NULL, 0 } +}; + +static void +print_negated(cfg_printer_t *pctx, cfg_obj_t *obj) { + print(pctx, "!", 1); + print_tuple(pctx, obj); +} + +static cfg_type_t cfg_type_negated = { + "negated", parse_tuple, print_negated, &cfg_rep_tuple, + &negated_fields +}; + +/* an address match list element */ + +static cfg_type_t cfg_type_addrmatchelt = { + "address_match_element", parse_addrmatchelt, NULL, NULL, NULL }; +static cfg_type_t cfg_type_bracketed_aml = { + "bracketed_aml", parse_bracketed_list, print_bracketed_list, + &cfg_rep_list, &cfg_type_addrmatchelt +}; + +static isc_result_t +parse_sockaddrsub(cfg_parser_t *pctx, const cfg_type_t *type, + int flags, cfg_obj_t **ret) +{ + isc_result_t result; + isc_netaddr_t netaddr; + in_port_t port = 0; + cfg_obj_t *obj = NULL; + + CHECK(create_cfgobj(pctx, type, &obj)); + CHECK(get_addr(pctx, flags, &netaddr)); + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(pctx->token.value.as_pointer, "port") == 0) { + CHECK(cfg_gettoken(pctx, 0)); /* read "port" */ + CHECK(get_port(pctx, flags, &port)); + } + isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port); + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static isc_result_t +parse_sockaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + const unsigned int *flagp = type->of; + return (parse_sockaddrsub(pctx, &cfg_type_sockaddr4wild, *flagp, ret)); +} + +static void +print_sockaddr(cfg_printer_t *pctx, cfg_obj_t *obj) { + isc_netaddr_t netaddr; + in_port_t port; + char buf[ISC_NETADDR_FORMATSIZE]; + + isc_netaddr_fromsockaddr(&netaddr, &obj->value.sockaddr); + isc_netaddr_format(&netaddr, buf, sizeof(buf)); + print_cstr(pctx, buf); + port = isc_sockaddr_getport(&obj->value.sockaddr); + if (port != 0) { + print(pctx, " port ", 6); + print_uint(pctx, port); + } +} + +isc_boolean_t +cfg_obj_issockaddr(cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_sockaddr)); +} + +isc_sockaddr_t * +cfg_obj_assockaddr(cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_sockaddr); + return (&obj->value.sockaddr); +} + +/* An IPv4/IPv6 address with optional port, "*" accepted as wildcard. */ +static unsigned int sockaddr4wild_flags = WILDOK|V4OK; +static cfg_type_t cfg_type_sockaddr4wild = { + "sockaddr4wild", parse_sockaddr, print_sockaddr, + &cfg_rep_sockaddr, &sockaddr4wild_flags +}; + +static unsigned int sockaddr6wild_flags = WILDOK|V6OK; +static cfg_type_t cfg_type_sockaddr6wild = { + "v6addrportwild", parse_sockaddr, print_sockaddr, + &cfg_rep_sockaddr, &sockaddr6wild_flags +}; + +static unsigned int sockaddr_flags = V4OK|V6OK; +static cfg_type_t cfg_type_sockaddr = { + "sockaddr", parse_sockaddr, print_sockaddr, + &cfg_rep_sockaddr, &sockaddr_flags +}; + +/* + * The socket address syntax in the "controls" statement is silly. + * It allows both socket address families, but also allows "*", + * whis is gratuitously interpreted as the IPv4 wildcard address. + */ +static unsigned int controls_sockaddr_flags = V4OK|V6OK|WILDOK; +static cfg_type_t cfg_type_controls_sockaddr = { + "controls_sockaddr", parse_sockaddr, print_sockaddr, + &cfg_rep_sockaddr, &controls_sockaddr_flags }; + + +/* + * Handle the special kludge syntax of the "keys" clause in the "server" + * statement, which takes a single key with our without braces and semicolon. + */ +static isc_result_t +parse_server_key_kludge(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + isc_boolean_t braces = ISC_FALSE; + UNUSED(type); + + /* Allow opening brace. */ + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == '{') { + result = cfg_gettoken(pctx, 0); + braces = ISC_TRUE; + } + + CHECK(parse(pctx, &cfg_type_astring, ret)); + + if (braces) { + /* Skip semicolon if present. */ + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_special && + pctx->token.value.as_char == ';') + CHECK(cfg_gettoken(pctx, 0)); + + CHECK(parse_special(pctx, '}')); + } + cleanup: + return (result); +} +static cfg_type_t cfg_type_server_key_kludge = { + "server_key", parse_server_key_kludge, NULL, NULL, NULL }; + + +/* + * An optional logging facility. + */ + +static isc_result_t +parse_optional_facility(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) +{ + isc_result_t result; + UNUSED(type); + + CHECK(cfg_peektoken(pctx, QSTRING)); + if (pctx->token.type == isc_tokentype_string || + pctx->token.type == isc_tokentype_qstring) { + CHECK(parse(pctx, &cfg_type_astring, ret)); + } else { + CHECK(parse(pctx, &cfg_type_void, ret)); + } + cleanup: + return (result); +} + +static cfg_type_t cfg_type_optional_facility = { + "optional_facility", parse_optional_facility, NULL, NULL, NULL }; + + +/* + * A log severity. Return as a string, except "debug N", + * which is returned as a keyword object. + */ + +static keyword_type_t debug_kw = { "debug", &cfg_type_uint32 }; +static cfg_type_t cfg_type_debuglevel = { + "debuglevel", parse_keyvalue, + print_keyvalue, &cfg_rep_uint32, &debug_kw +}; + +static isc_result_t +parse_logseverity(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + UNUSED(type); + + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(pctx->token.value.as_pointer, "debug") == 0) { + CHECK(cfg_gettoken(pctx, 0)); /* read "debug" */ + CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER)); + if (pctx->token.type == isc_tokentype_number) { + CHECK(parse_uint32(pctx, NULL, ret)); + } else { + /* + * The debug level is optional and defaults to 1. + * This makes little sense, but we support it for + * compatibility with BIND 8. + */ + CHECK(create_cfgobj(pctx, &cfg_type_uint32, ret)); + (*ret)->value.uint32 = 1; + } + (*ret)->type = &cfg_type_debuglevel; /* XXX kludge */ + } else { + CHECK(parse(pctx, &cfg_type_loglevel, ret)); + } + cleanup: + return (result); +} + +static cfg_type_t cfg_type_logseverity = { + "logseverity", parse_logseverity, NULL, NULL, NULL }; + +/* + * The "file" clause of the "channel" statement. + * This is yet another special case. + */ + +static const char *logversions_enums[] = { "unlimited", NULL }; +static isc_result_t +parse_logversions(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_enum_or_other(pctx, type, &cfg_type_uint32, ret)); +} +static cfg_type_t cfg_type_logversions = { + "logversions", parse_logversions, print_ustring, + &cfg_rep_string, logversions_enums +}; + +static cfg_tuplefielddef_t logfile_fields[] = { + { "file", &cfg_type_qstring, 0 }, + { "versions", &cfg_type_logversions, 0 }, + { "size", &cfg_type_size, 0 }, + { NULL, NULL, 0 } +}; + +static isc_result_t +parse_logfile(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + const cfg_tuplefielddef_t *fields = type->of; + + CHECK(create_tuple(pctx, type, &obj)); + + /* Parse the mandatory "file" field */ + CHECK(parse(pctx, fields[0].type, &obj->value.tuple[0])); + + /* Parse "versions" and "size" fields in any order. */ + for (;;) { + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string) { + CHECK(cfg_gettoken(pctx, 0)); + if (strcasecmp(pctx->token.value.as_pointer, + "versions") == 0 && + obj->value.tuple[1] == NULL) { + CHECK(parse(pctx, fields[1].type, + &obj->value.tuple[1])); + } else if (strcasecmp(pctx->token.value.as_pointer, + "size") == 0 && + obj->value.tuple[2] == NULL) { + CHECK(parse(pctx, fields[2].type, + &obj->value.tuple[2])); + } else { + break; + } + } else { + break; + } + } + + /* Create void objects for missing optional values. */ + if (obj->value.tuple[1] == NULL) + CHECK(parse_void(pctx, NULL, &obj->value.tuple[1])); + if (obj->value.tuple[2] == NULL) + CHECK(parse_void(pctx, NULL, &obj->value.tuple[2])); + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static void +print_logfile(cfg_printer_t *pctx, cfg_obj_t *obj) { + print_obj(pctx, obj->value.tuple[0]); /* file */ + if (obj->value.tuple[1]->type->print != print_void) { + print(pctx, " versions ", 10); + print_obj(pctx, obj->value.tuple[1]); + } + if (obj->value.tuple[2]->type->print != print_void) { + print(pctx, " size ", 6); + print_obj(pctx, obj->value.tuple[2]); + } +} + +static cfg_type_t cfg_type_logfile = { + "logfile", parse_logfile, print_logfile, &cfg_rep_tuple, + logfile_fields +}; + + +/* + * lwres + */ + +static cfg_tuplefielddef_t lwres_view_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "class", &cfg_type_optional_class, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_lwres_view = { + "lwres_view", parse_tuple, print_tuple, &cfg_rep_tuple, + lwres_view_fields +}; + +static cfg_type_t cfg_type_lwres_searchlist = { + "lwres_searchlist", parse_bracketed_list, print_bracketed_list, + &cfg_rep_list, &cfg_type_astring }; + +static cfg_clausedef_t +lwres_clauses[] = { + { "listen-on", &cfg_type_portiplist, 0 }, + { "view", &cfg_type_lwres_view, 0 }, + { "search", &cfg_type_lwres_searchlist, 0 }, + { "ndots", &cfg_type_uint32, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t * +lwres_clausesets[] = { + lwres_clauses, + NULL +}; +static cfg_type_t cfg_type_lwres = { + "lwres", parse_map, print_map, &cfg_rep_map, lwres_clausesets }; + +/* + * rndc + */ + +static cfg_clausedef_t +rndcconf_options_clauses[] = { + { "default-server", &cfg_type_astring, 0 }, + { "default-key", &cfg_type_astring, 0 }, + { "default-port", &cfg_type_uint32, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t * +rndcconf_options_clausesets[] = { + rndcconf_options_clauses, + NULL +}; + +static cfg_type_t cfg_type_rndcconf_options = { + "rndcconf_options", parse_map, print_map, &cfg_rep_map, + rndcconf_options_clausesets +}; + +static cfg_clausedef_t +rndcconf_server_clauses[] = { + { "key", &cfg_type_astring, 0 }, + { "port", &cfg_type_uint32, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t * +rndcconf_server_clausesets[] = { + rndcconf_server_clauses, + NULL +}; + +static cfg_type_t cfg_type_rndcconf_server = { + "rndcconf_server", parse_named_map, print_map, &cfg_rep_map, + rndcconf_server_clausesets +}; + +static cfg_clausedef_t +rndcconf_clauses[] = { + { "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI }, + { "server", &cfg_type_rndcconf_server, CFG_CLAUSEFLAG_MULTI }, + { "options", &cfg_type_rndcconf_options, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t * +rndcconf_clausesets[] = { + rndcconf_clauses, + NULL +}; + +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_rndcconf = { + "rndcconf", parse_mapbody, print_mapbody, &cfg_rep_map, + rndcconf_clausesets +}; + +static cfg_clausedef_t +rndckey_clauses[] = { + { "key", &cfg_type_key, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t * +rndckey_clausesets[] = { + rndckey_clauses, + NULL +}; + +LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_rndckey = { + "rndckey", parse_mapbody, print_mapbody, &cfg_rep_map, + rndckey_clausesets +}; + + +static isc_result_t +cfg_gettoken(cfg_parser_t *pctx, int options) { + isc_result_t result; + + if (pctx->seen_eof) + return (ISC_R_SUCCESS); + + options |= (ISC_LEXOPT_EOF | ISC_LEXOPT_NOMORE); + + redo: + pctx->token.type = isc_tokentype_unknown; + result = isc_lex_gettoken(pctx->lexer, options, &pctx->token); + pctx->ungotten = ISC_FALSE; + pctx->line = isc_lex_getsourceline(pctx->lexer); + + switch (result) { + case ISC_R_SUCCESS: + if (pctx->token.type == isc_tokentype_eof) { + result = isc_lex_close(pctx->lexer); + INSIST(result == ISC_R_NOMORE || + result == ISC_R_SUCCESS); + + if (isc_lex_getsourcename(pctx->lexer) != NULL) { + /* + * Closed an included file, not the main file. + */ + cfg_listelt_t *elt; + elt = ISC_LIST_TAIL(pctx->open_files-> + value.list); + INSIST(elt != NULL); + ISC_LIST_UNLINK(pctx->open_files-> + value.list, elt, link); + ISC_LIST_APPEND(pctx->closed_files-> + value.list, elt, link); + goto redo; + } + pctx->seen_eof = ISC_TRUE; + } + break; + + case ISC_R_NOSPACE: + /* More understandable than "ran out of space". */ + parser_error(pctx, LOG_NEAR, "token too big"); + break; + + case ISC_R_IOERROR: + parser_error(pctx, 0, "%s", + isc_result_totext(result)); + break; + + default: + parser_error(pctx, LOG_NEAR, "%s", + isc_result_totext(result)); + break; + } + return (result); +} + +static void +cfg_ungettoken(cfg_parser_t *pctx) { + if (pctx->seen_eof) + return; + isc_lex_ungettoken(pctx->lexer, &pctx->token); + pctx->ungotten = ISC_TRUE; +} + +static isc_result_t +cfg_peektoken(cfg_parser_t *pctx, int options) { + isc_result_t result; + CHECK(cfg_gettoken(pctx, options)); + cfg_ungettoken(pctx); + cleanup: + return (result); +} + +/* + * Get a string token, accepting both the quoted and the unquoted form. + * Log an error if the next token is not a string. + */ +static isc_result_t +cfg_getstringtoken(cfg_parser_t *pctx) { + isc_result_t result; + + result = cfg_gettoken(pctx, QSTRING); + if (result != ISC_R_SUCCESS) + return (result); + + if (pctx->token.type != isc_tokentype_string && + pctx->token.type != isc_tokentype_qstring) { + parser_error(pctx, LOG_NEAR, "expected string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (ISC_R_SUCCESS); +} + +static void +parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + parser_complain(pctx, ISC_FALSE, flags, fmt, args); + va_end(args); + pctx->errors++; +} + +static void +parser_warning(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + parser_complain(pctx, ISC_TRUE, flags, fmt, args); + va_end(args); + pctx->warnings++; +} + +#define MAX_LOG_TOKEN 30 /* How much of a token to quote in log messages. */ + +static char * +current_file(cfg_parser_t *pctx) { + static char none[] = "none"; + cfg_listelt_t *elt; + cfg_obj_t *fileobj; + + if (pctx->open_files == NULL) + return (none); + elt = ISC_LIST_TAIL(pctx->open_files->value.list); + if (elt == NULL) + return (none); + + fileobj = elt->obj; + INSIST(fileobj->type == &cfg_type_qstring); + return (fileobj->value.string.base); +} + +static void +parser_complain(cfg_parser_t *pctx, isc_boolean_t is_warning, + unsigned int flags, const char *format, + va_list args) +{ + char tokenbuf[MAX_LOG_TOKEN + 10]; + static char where[ISC_DIR_PATHMAX + 100]; + static char message[2048]; + int level = ISC_LOG_ERROR; + const char *prep = ""; + + if (is_warning) + level = ISC_LOG_WARNING; + + sprintf(where, "%s:%u: ", current_file(pctx), pctx->line); + + if ((unsigned int)vsprintf(message, format, args) >= sizeof message) + FATAL_ERROR(__FILE__, __LINE__, + "error message would overflow"); + + if ((flags & (LOG_NEAR|LOG_BEFORE|LOG_NOPREP)) != 0) { + isc_region_t r; + + if (pctx->ungotten) + (void)cfg_gettoken(pctx, 0); + + if (pctx->token.type == isc_tokentype_eof) { + snprintf(tokenbuf, sizeof(tokenbuf), "end of file"); + } else if (pctx->token.type == isc_tokentype_unknown) { + flags = 0; + tokenbuf[0] = '\0'; + } else { + isc_lex_getlasttokentext(pctx->lexer, + &pctx->token, &r); + if (r.length > MAX_LOG_TOKEN) + snprintf(tokenbuf, sizeof(tokenbuf), + "'%.*s...'", MAX_LOG_TOKEN, r.base); + else + snprintf(tokenbuf, sizeof(tokenbuf), + "'%.*s'", (int)r.length, r.base); + } + + /* Choose a preposition. */ + if (flags & LOG_NEAR) + prep = " near "; + else if (flags & LOG_BEFORE) + prep = " before "; + else + prep = " "; + } else { + tokenbuf[0] = '\0'; + } + isc_log_write(pctx->lctx, CAT, MOD, level, + "%s%s%s%s", where, message, prep, tokenbuf); +} + +void +cfg_obj_log(cfg_obj_t *obj, isc_log_t *lctx, int level, const char *fmt, ...) { + va_list ap; + char msgbuf[2048]; + + if (! isc_log_wouldlog(lctx, level)) + return; + + va_start(ap, fmt); + + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + isc_log_write(lctx, CAT, MOD, level, + "%s:%u: %s", + obj->file == NULL ? "" : obj->file, + obj->line, msgbuf); + va_end(ap); +} + +static isc_result_t +create_cfgobj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + cfg_obj_t *obj; + + obj = isc_mem_get(pctx->mctx, sizeof(cfg_obj_t)); + if (obj == NULL) + return (ISC_R_NOMEMORY); + obj->type = type; + obj->file = current_file(pctx); + obj->line = pctx->line; + *ret = obj; + return (ISC_R_SUCCESS); +} + +static void +map_symtabitem_destroy(char *key, unsigned int type, + isc_symvalue_t symval, void *userarg) +{ + cfg_obj_t *obj = symval.as_pointer; + cfg_parser_t *pctx = (cfg_parser_t *)userarg; + + UNUSED(key); + UNUSED(type); + + cfg_obj_destroy(pctx, &obj); +} + + +static isc_result_t +create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + isc_symtab_t *symtab = NULL; + cfg_obj_t *obj = NULL; + + CHECK(create_cfgobj(pctx, type, &obj)); + CHECK(isc_symtab_create(pctx->mctx, 5, /* XXX */ + map_symtabitem_destroy, + pctx, ISC_FALSE, &symtab)); + + obj->value.map.symtab = symtab; + obj->value.map.id = NULL; + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + if (obj != NULL) + isc_mem_put(pctx->mctx, obj, sizeof(*obj)); + return (result); +} + +static void +free_map(cfg_parser_t *pctx, cfg_obj_t *obj) { + CLEANUP_OBJ(obj->value.map.id); + isc_symtab_destroy(&obj->value.map.symtab); +} + +isc_boolean_t +cfg_obj_istype(cfg_obj_t *obj, const cfg_type_t *type) { + return (ISC_TF(obj->type == type)); +} + +/* + * Destroy 'obj', a configuration object created in 'pctx'. + */ +void +cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **objp) { + cfg_obj_t *obj = *objp; + obj->type->rep->free(pctx, obj); + isc_mem_put(pctx->mctx, obj, sizeof(cfg_obj_t)); + *objp = NULL; +} + +static void +free_noop(cfg_parser_t *pctx, cfg_obj_t *obj) { + UNUSED(pctx); + UNUSED(obj); +} + +/* + * Data and functions for printing grammar summaries. + */ +static struct flagtext { + unsigned int flag; + const char *text; +} flagtexts[] = { + { CFG_CLAUSEFLAG_NOTIMP, "not implemented" }, + { CFG_CLAUSEFLAG_NYI, "not yet implemented" }, + { CFG_CLAUSEFLAG_OBSOLETE, "obsolete" }, + { CFG_CLAUSEFLAG_NEWDEFAULT, "default changed" }, + { 0, NULL } +}; + +static void +print_clause_flags(cfg_printer_t *pctx, unsigned int flags) { + struct flagtext *p; + isc_boolean_t first = ISC_TRUE; + for (p = flagtexts; p->flag != 0; p++) { + if ((flags & p->flag) != 0) { + if (first) + print(pctx, " // ", 4); + else + print(pctx, ", ", 2); + print_cstr(pctx, p->text); + first = ISC_FALSE; + } + } +} + +static void +print_grammar(cfg_printer_t *pctx, const cfg_type_t *type) { + if (type->print == print_mapbody) { + const cfg_clausedef_t * const *clauseset; + const cfg_clausedef_t *clause; + + for (clauseset = type->of; *clauseset != NULL; clauseset++) { + for (clause = *clauseset; + clause->name != NULL; + clause++) { + print_cstr(pctx, clause->name); + print(pctx, " ", 1); + print_grammar(pctx, clause->type); + print(pctx, ";", 1); + /* XXX print flags here? */ + print(pctx, "\n\n", 2); + } + } + } else if (type->print == print_map) { + const cfg_clausedef_t * const *clauseset; + const cfg_clausedef_t *clause; + + if (type->parse == parse_named_map) { + print_grammar(pctx, &cfg_type_astring); + print(pctx, " ", 1); + } + + print_open(pctx); + + for (clauseset = type->of; *clauseset != NULL; clauseset++) { + for (clause = *clauseset; + clause->name != NULL; + clause++) { + print_indent(pctx); + print_cstr(pctx, clause->name); + if (clause->type->print != print_void) + print(pctx, " ", 1); + print_grammar(pctx, clause->type); + print(pctx, ";", 1); + print_clause_flags(pctx, clause->flags); + print(pctx, "\n", 1); + } + } + print_close(pctx); + } else if (type->print == print_tuple) { + const cfg_tuplefielddef_t *fields = type->of; + const cfg_tuplefielddef_t *f; + isc_boolean_t need_space = ISC_FALSE; + + for (f = fields; f->name != NULL; f++) { + if (need_space) + print(pctx, " ", 1); + print_grammar(pctx, f->type); + need_space = ISC_TF(f->type->print != print_void); + } + } else if (type->parse == parse_enum) { + const char * const *p; + print(pctx, "( ", 2); + for (p = type->of; *p != NULL; p++) { + print_cstr(pctx, *p); + if (p[1] != NULL) + print(pctx, " | ", 3); + } + print(pctx, " )", 2); + } else if (type->print == print_bracketed_list) { + print(pctx, "{ ", 2); + print_grammar(pctx, type->of); + print(pctx, "; ... }", 7); + } else if (type->parse == parse_keyvalue) { + const keyword_type_t *kw = type->of; + print_cstr(pctx, kw->name); + print(pctx, " ", 1); + print_grammar(pctx, kw->type); + } else if (type->parse == parse_optional_keyvalue) { + const keyword_type_t *kw = type->of; + print(pctx, "[ ", 2); + print_cstr(pctx, kw->name); + print(pctx, " ", 1); + print_grammar(pctx, kw->type); + print(pctx, " ]", 2); + } else if (type->parse == parse_sockaddr) { + const unsigned int *flagp = type->of; + int n = 0; + print(pctx, "( ", 2); + if (*flagp & V4OK) { + if (n != 0) + print(pctx, " | ", 3); + print_cstr(pctx, ""); + n++; + } + if (*flagp & V6OK) { + if (n != 0) + print(pctx, " | ", 3); + print_cstr(pctx, ""); + n++; + } + if (*flagp & WILDOK) { + if (n != 0) + print(pctx, " | ", 3); + print(pctx, "*", 1); + n++; + } + print(pctx, " ) ", 3); + if (*flagp & WILDOK) { + print_cstr(pctx, "[ port ( | * ) ]"); + } else { + print_cstr(pctx, "[ port ]"); + } + } else if (type->print == print_void) { + /* Print nothing. */ + } else { + print(pctx, "<", 1); + print_cstr(pctx, type->name); + print(pctx, ">", 1); + } +} + +void +cfg_print_grammar(const cfg_type_t *type, + void (*f)(void *closure, const char *text, int textlen), + void *closure) +{ + cfg_printer_t pctx; + pctx.f = f; + pctx.closure = closure; + pctx.indent = 0; + print_grammar(&pctx, type); +}