diff options
Diffstat (limited to 'lib/isccfg')
-rw-r--r-- | lib/isccfg/Makefile.in | 83 | ||||
-rw-r--r-- | lib/isccfg/aclconf.c | 256 | ||||
-rw-r--r-- | lib/isccfg/api | 3 | ||||
-rw-r--r-- | lib/isccfg/include/Makefile.in | 25 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/Makefile.in | 42 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/aclconf.h | 73 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/cfg.h | 417 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/grammar.h | 452 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/log.h | 55 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/namedconf.h | 45 | ||||
-rw-r--r-- | lib/isccfg/include/isccfg/version.h | 28 | ||||
-rw-r--r-- | lib/isccfg/log.c | 52 | ||||
-rw-r--r-- | lib/isccfg/namedconf.c | 2049 | ||||
-rw-r--r-- | lib/isccfg/parser.c | 2382 | ||||
-rw-r--r-- | lib/isccfg/version.c | 29 |
15 files changed, 5991 insertions, 0 deletions
diff --git a/lib/isccfg/Makefile.in b/lib/isccfg/Makefile.in new file mode 100644 index 0000000..7d19123 --- /dev/null +++ b/lib/isccfg/Makefile.in @@ -0,0 +1,83 @@ +# Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2001-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 ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: Makefile.in,v 1.12.18.4 2005/09/05 00:18:30 marka Exp $ + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +@BIND9_VERSION@ + +@LIBISCCFG_API@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = -I. ${DNS_INCLUDES} ${ISC_INCLUDES} ${ISCCFG_INCLUDES} + +CDEFINES = @USE_DLZ@ +CWARNINGS = + +ISCLIBS = ../../lib/isc/libisc.@A@ +ISCCCLIBS = ../../lib/isccc/libisccc.@A@ +DNSLIBS = ../../lib/dns/libdns.@A@ +ISCCFGLIBS = ../../lib/cfg/libisccfg.@A@ + +ISCDEPLIBS = ../../lib/isc/libisc.@A@ +ISCCFGDEPLIBS = libisccfg.@A@ + +LIBS = @LIBS@ + +SUBDIRS = include + +# Alphabetically +OBJS = aclconf.@O@ log.@O@ namedconf.@O@ parser.@O@ version.@O@ + +# Alphabetically +SRCS = aclconf.c log.c namedconf.c parser.c version.c + +TARGETS = timestamp + +@BIND9_MAKE_RULES@ + +version.@O@: version.c + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \ + -DVERSION=\"${VERSION}\" \ + -DLIBINTERFACE=${LIBINTERFACE} \ + -DLIBREVISION=${LIBREVISION} \ + -DLIBAGE=${LIBAGE} \ + -c ${srcdir}/version.c + +libisccfg.@SA@: ${OBJS} + ${AR} ${ARFLAGS} $@ ${OBJS} + ${RANLIB} $@ + +libisccfg.la: ${OBJS} + ${LIBTOOL_MODE_LINK} \ + ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libisccfg.la -rpath ${libdir} \ + -version-info ${LIBINTERFACE}:${LIBREVISION}:${LIBAGE} \ + ${OBJS} ${LIBS} ${DNSLIBS} ${ISCCCLIBS} ${ISCLIBS} + +timestamp: libisccfg.@A@ + touch timestamp + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir} + +install:: timestamp installdirs + ${LIBTOOL_MODE_INSTALL} ${INSTALL_DATA} libisccfg.@A@ ${DESTDIR}${libdir} + +clean distclean:: + rm -f libisccfg.@A@ timestamp diff --git a/lib/isccfg/aclconf.c b/lib/isccfg/aclconf.c new file mode 100644 index 0000000..d7b41ce --- /dev/null +++ b/lib/isccfg/aclconf.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2004-2006 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1999-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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: aclconf.c,v 1.2.2.6 2006/03/02 00:37:22 marka Exp $ */ + +#include <config.h> + +#include <isc/mem.h> +#include <isc/string.h> /* Required for HP/UX (and others?) */ +#include <isc/util.h> + +#include <isccfg/namedconf.h> +#include <isccfg/aclconf.h> + +#include <dns/acl.h> +#include <dns/fixedname.h> +#include <dns/log.h> + +#define LOOP_MAGIC ISC_MAGIC('L','O','O','P') + +void +cfg_aclconfctx_init(cfg_aclconfctx_t *ctx) { + ISC_LIST_INIT(ctx->named_acl_cache); +} + +void +cfg_aclconfctx_destroy(cfg_aclconfctx_t *ctx) { + dns_acl_t *dacl, *next; + for (dacl = ISC_LIST_HEAD(ctx->named_acl_cache); + dacl != NULL; + dacl = next) + { + next = ISC_LIST_NEXT(dacl, nextincache); + dns_acl_detach(&dacl); + } +} + +/* + * Find the definition of the named acl whose name is "name". + */ +static isc_result_t +get_acl_def(const cfg_obj_t *cctx, const char *name, const cfg_obj_t **ret) { + isc_result_t result; + const cfg_obj_t *acls = NULL; + const cfg_listelt_t *elt; + + result = cfg_map_get(cctx, "acl", &acls); + if (result != ISC_R_SUCCESS) + return (result); + for (elt = cfg_list_first(acls); + elt != NULL; + elt = cfg_list_next(elt)) { + const cfg_obj_t *acl = cfg_listelt_value(elt); + const char *aclname = cfg_obj_asstring(cfg_tuple_get(acl, "name")); + if (strcasecmp(aclname, name) == 0) { + *ret = cfg_tuple_get(acl, "value"); + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} + +static isc_result_t +convert_named_acl(const cfg_obj_t *nameobj, const cfg_obj_t *cctx, + isc_log_t *lctx, cfg_aclconfctx_t *ctx, + isc_mem_t *mctx, dns_acl_t **target) +{ + isc_result_t result; + const cfg_obj_t *cacl = NULL; + dns_acl_t *dacl; + dns_acl_t loop; + const char *aclname = cfg_obj_asstring(nameobj); + + /* Look for an already-converted version. */ + for (dacl = ISC_LIST_HEAD(ctx->named_acl_cache); + dacl != NULL; + dacl = ISC_LIST_NEXT(dacl, nextincache)) + { + if (strcasecmp(aclname, dacl->name) == 0) { + if (ISC_MAGIC_VALID(dacl, LOOP_MAGIC)) { + cfg_obj_log(nameobj, lctx, ISC_LOG_ERROR, + "acl loop detected: %s", aclname); + return (ISC_R_FAILURE); + } + dns_acl_attach(dacl, target); + return (ISC_R_SUCCESS); + } + } + /* Not yet converted. Convert now. */ + result = get_acl_def(cctx, aclname, &cacl); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(nameobj, lctx, ISC_LOG_WARNING, + "undefined ACL '%s'", aclname); + return (result); + } + /* + * Add a loop detection element. + */ + memset(&loop, 0, sizeof(loop)); + ISC_LINK_INIT(&loop, nextincache); + DE_CONST(aclname, loop.name); + loop.magic = LOOP_MAGIC; + ISC_LIST_APPEND(ctx->named_acl_cache, &loop, nextincache); + result = cfg_acl_fromconfig(cacl, cctx, lctx, ctx, mctx, &dacl); + ISC_LIST_UNLINK(ctx->named_acl_cache, &loop, nextincache); + loop.magic = 0; + loop.name = NULL; + if (result != ISC_R_SUCCESS) + return (result); + dacl->name = isc_mem_strdup(dacl->mctx, aclname); + if (dacl->name == NULL) + return (ISC_R_NOMEMORY); + ISC_LIST_APPEND(ctx->named_acl_cache, dacl, nextincache); + dns_acl_attach(dacl, target); + return (ISC_R_SUCCESS); +} + +static isc_result_t +convert_keyname(const cfg_obj_t *keyobj, isc_log_t *lctx, isc_mem_t *mctx, + dns_name_t *dnsname) +{ + isc_result_t result; + isc_buffer_t buf; + dns_fixedname_t fixname; + unsigned int keylen; + const char *txtname = cfg_obj_asstring(keyobj); + + keylen = strlen(txtname); + isc_buffer_init(&buf, txtname, keylen); + isc_buffer_add(&buf, keylen); + dns_fixedname_init(&fixname); + result = dns_name_fromtext(dns_fixedname_name(&fixname), &buf, + dns_rootname, ISC_FALSE, NULL); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(keyobj, lctx, ISC_LOG_WARNING, + "key name '%s' is not a valid domain name", + txtname); + return (result); + } + return (dns_name_dup(dns_fixedname_name(&fixname), mctx, dnsname)); +} + +isc_result_t +cfg_acl_fromconfig(const cfg_obj_t *caml, + const cfg_obj_t *cctx, + isc_log_t *lctx, + cfg_aclconfctx_t *ctx, + isc_mem_t *mctx, + dns_acl_t **target) +{ + isc_result_t result; + unsigned int count; + dns_acl_t *dacl = NULL; + dns_aclelement_t *de; + const cfg_listelt_t *elt; + + REQUIRE(target != NULL && *target == NULL); + + count = 0; + for (elt = cfg_list_first(caml); + elt != NULL; + elt = cfg_list_next(elt)) + count++; + + result = dns_acl_create(mctx, count, &dacl); + if (result != ISC_R_SUCCESS) + return (result); + + de = dacl->elements; + for (elt = cfg_list_first(caml); + elt != NULL; + elt = cfg_list_next(elt)) + { + const cfg_obj_t *ce = cfg_listelt_value(elt); + if (cfg_obj_istuple(ce)) { + /* This must be a negated element. */ + ce = cfg_tuple_get(ce, "value"); + de->negative = ISC_TRUE; + } else { + de->negative = ISC_FALSE; + } + + if (cfg_obj_isnetprefix(ce)) { + /* Network prefix */ + de->type = dns_aclelementtype_ipprefix; + + cfg_obj_asnetprefix(ce, + &de->u.ip_prefix.address, + &de->u.ip_prefix.prefixlen); + } else if (cfg_obj_istype(ce, &cfg_type_keyref)) { + /* Key name */ + de->type = dns_aclelementtype_keyname; + dns_name_init(&de->u.keyname, NULL); + result = convert_keyname(ce, lctx, mctx, + &de->u.keyname); + if (result != ISC_R_SUCCESS) + goto cleanup; + } else if (cfg_obj_islist(ce)) { + /* Nested ACL */ + de->type = dns_aclelementtype_nestedacl; + result = cfg_acl_fromconfig(ce, cctx, lctx, ctx, + mctx, &de->u.nestedacl); + if (result != ISC_R_SUCCESS) + goto cleanup; + } else if (cfg_obj_isstring(ce)) { + /* ACL name */ + const char *name = cfg_obj_asstring(ce); + if (strcasecmp(name, "localhost") == 0) { + de->type = dns_aclelementtype_localhost; + } else if (strcasecmp(name, "localnets") == 0) { + de->type = dns_aclelementtype_localnets; + } else if (strcasecmp(name, "any") == 0) { + de->type = dns_aclelementtype_any; + } else if (strcasecmp(name, "none") == 0) { + de->type = dns_aclelementtype_any; + de->negative = ISC_TF(! de->negative); + } else { + de->type = dns_aclelementtype_nestedacl; + result = convert_named_acl(ce, cctx, lctx, + ctx, mctx, + &de->u.nestedacl); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + } else { + cfg_obj_log(ce, lctx, ISC_LOG_WARNING, + "address match list contains " + "unsupported element type"); + result = ISC_R_FAILURE; + goto cleanup; + } + de++; + dacl->length++; + } + + *target = dacl; + return (ISC_R_SUCCESS); + + cleanup: + dns_acl_detach(&dacl); + return (result); +} diff --git a/lib/isccfg/api b/lib/isccfg/api new file mode 100644 index 0000000..aba393a --- /dev/null +++ b/lib/isccfg/api @@ -0,0 +1,3 @@ +LIBINTERFACE = 30 +LIBREVISION = 3 +LIBAGE = 0 diff --git a/lib/isccfg/include/Makefile.in b/lib/isccfg/include/Makefile.in new file mode 100644 index 0000000..4eddd92 --- /dev/null +++ b/lib/isccfg/include/Makefile.in @@ -0,0 +1,25 @@ +# Copyright (C) 2004 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 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 ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: Makefile.in,v 1.5 2004/03/05 05:12:24 marka Exp $ + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +SUBDIRS = isccfg +TARGETS = + +@BIND9_MAKE_RULES@ diff --git a/lib/isccfg/include/isccfg/Makefile.in b/lib/isccfg/include/isccfg/Makefile.in new file mode 100644 index 0000000..d71d2c2 --- /dev/null +++ b/lib/isccfg/include/isccfg/Makefile.in @@ -0,0 +1,42 @@ +# Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") +# 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 ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# $Id: Makefile.in,v 1.8.18.2 2005/01/12 01:54:57 marka Exp $ + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +@BIND9_VERSION@ + +# +# Only list headers that are to be installed and are not +# machine generated. The latter are handled specially in the +# install target below. +# +HEADERS = aclconf.h cfg.h grammar.h log.h namedconf.h version.h + +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isccfg + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/isccfg ; \ + done diff --git a/lib/isccfg/include/isccfg/aclconf.h b/lib/isccfg/include/isccfg/aclconf.h new file mode 100644 index 0000000..a13740c --- /dev/null +++ b/lib/isccfg/include/isccfg/aclconf.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2004-2006 Internet Systems Consortium, Inc. ("ISC") + * 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: aclconf.h,v 1.2.2.5 2006/03/02 00:37:22 marka Exp $ */ + +#ifndef ISCCFG_ACLCONF_H +#define ISCCFG_ACLCONF_H 1 + +#include <isc/lang.h> + +#include <isccfg/cfg.h> + +#include <dns/types.h> + +typedef struct cfg_aclconfctx { + ISC_LIST(dns_acl_t) named_acl_cache; +} cfg_aclconfctx_t; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +void +cfg_aclconfctx_init(cfg_aclconfctx_t *ctx); +/* + * Initialize an ACL configuration context. + */ + +void +cfg_aclconfctx_destroy(cfg_aclconfctx_t *ctx); +/* + * Destroy an ACL configuration context. + */ + +isc_result_t +cfg_acl_fromconfig(const cfg_obj_t *caml, + const cfg_obj_t *cctx, + isc_log_t *lctx, + cfg_aclconfctx_t *ctx, + isc_mem_t *mctx, + dns_acl_t **target); +/* + * Construct a new dns_acl_t from configuration data in 'caml' and + * 'cctx'. Memory is allocated through 'mctx'. + * + * Any named ACLs referred to within 'caml' will be be converted + * into nested dns_acl_t objects. Multiple references to the same + * named ACLs will be converted into shared references to a single + * nested dns_acl_t object when the referring objects were created + * passing the same ACL configuration context 'ctx'. + * + * On success, attach '*target' to the new dns_acl_t object. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISCCFG_ACLCONF_H */ diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h new file mode 100644 index 0000000..6a30a1c --- /dev/null +++ b/lib/isccfg/include/isccfg/cfg.h @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2004-2006 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2000-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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: cfg.h,v 1.34.18.5 2006/03/02 00:37:22 marka Exp $ */ + +#ifndef ISCCFG_CFG_H +#define ISCCFG_CFG_H 1 + +/***** + ***** Module Info + *****/ + +/*! \file + * \brief + * This is the new, table-driven, YACC-free configuration file parser. + */ + +/*** + *** Imports + ***/ + +#include <isc/formatcheck.h> +#include <isc/lang.h> +#include <isc/types.h> +#include <isc/list.h> + + +/*** + *** Types + ***/ + +/*% + * A configuration parser. + */ +typedef struct cfg_parser cfg_parser_t; + +/*% + * A configuration type definition object. There is a single + * static cfg_type_t object for each data type supported by + * the configuration parser. + */ +typedef struct cfg_type cfg_type_t; + +/*% + * A configuration object. This is the basic building block of the + * configuration parse tree. It contains a value (which may be + * of one of several types) and information identifying the file + * and line number the value came from, for printing error + * messages. + */ +typedef struct cfg_obj cfg_obj_t; + +/*% + * A configuration object list element. + */ +typedef struct cfg_listelt cfg_listelt_t; + +/*% + * A callback function to be called when parsing an option + * that needs to be interpreted at parsing time, like + * "directory". + */ +typedef isc_result_t +(*cfg_parsecallback_t)(const char *clausename, const cfg_obj_t *obj, void *arg); + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret); +/*%< + * Create a configuration file parser. Any warning and error + * messages will be logged to 'lctx'. + * + * The parser object returned can be used for a single call + * to cfg_parse_file() or cfg_parse_buffer(). It must not + * be reused for parsing multiple files or buffers. + */ + +void +cfg_parser_setcallback(cfg_parser_t *pctx, + cfg_parsecallback_t callback, + void *arg); +/*%< + * Make the parser call 'callback' whenever it encounters + * a configuration clause with the callback attribute, + * passing it the clause name, the clause value, + * and 'arg' as arguments. + * + * To restore the default of not invoking callbacks, pass + * callback==NULL and arg==NULL. + */ + +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 +cfg_parse_buffer(cfg_parser_t *pctx, isc_buffer_t *buffer, + const cfg_type_t *type, cfg_obj_t **ret); +/*%< + * Read a configuration containing data of type 'type' + * and make '*ret' point to its parse tree. + * + * The configuration is read from the file 'filename' + * (isc_parse_file()) or the buffer 'buffer' + * (isc_parse_buffer()). + * + * Returns an error if the file does not parse correctly. + * + * Requires: + *\li "filename" is valid. + *\li "mem" is valid. + *\li "type" is valid. + *\li "cfg" is non-NULL and "*cfg" is NULL. + * + * Returns: + * \li #ISC_R_SUCCESS - success + *\li #ISC_R_NOMEMORY - no memory available + *\li #ISC_R_INVALIDFILE - file doesn't exist or is unreadable + *\li others - file contains errors + */ + +void +cfg_parser_destroy(cfg_parser_t **pctxp); +/*%< + * Destroy a configuration parser. + */ + +isc_boolean_t +cfg_obj_isvoid(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of void type (e.g., an optional + * value not specified). + */ + +isc_boolean_t +cfg_obj_ismap(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of a map type. + */ + +isc_result_t +cfg_map_get(const cfg_obj_t *mapobj, const char* name, const cfg_obj_t **obj); +/*%< + * Extract an element from a configuration object, which + * must be of a map type. + * + * Requires: + * \li 'mapobj' points to a valid configuration object of a map type. + * \li 'name' points to a null-terminated string. + * \li 'obj' is non-NULL and '*obj' is NULL. + * + * Returns: + * \li #ISC_R_SUCCESS - success + * \li #ISC_R_NOTFOUND - name not found in map + */ + +const cfg_obj_t * +cfg_map_getname(const cfg_obj_t *mapobj); +/*%< + * Get the name of a named map object, like a server "key" clause. + * + * Requires: + * \li 'mapobj' points to a valid configuration object of a map type. + * + * Returns: + * \li A pointer to a configuration object naming the map object, + * or NULL if the map object does not have a name. + */ + +isc_boolean_t +cfg_obj_istuple(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of a map type. + */ + +const cfg_obj_t * +cfg_tuple_get(const cfg_obj_t *tupleobj, const char *name); +/*%< + * Extract an element from a configuration object, which + * must be of a tuple type. + * + * Requires: + * \li 'tupleobj' points to a valid configuration object of a tuple type. + * \li 'name' points to a null-terminated string naming one of the + *\li fields of said tuple type. + */ + +isc_boolean_t +cfg_obj_isuint32(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of integer type. + */ + +isc_uint32_t +cfg_obj_asuint32(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object of 32-bit integer type. + * + * Requires: + * \li 'obj' points to a valid configuration object of 32-bit integer type. + * + * Returns: + * \li A 32-bit unsigned integer. + */ + +isc_boolean_t +cfg_obj_isuint64(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of integer type. + */ + +isc_uint64_t +cfg_obj_asuint64(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object of 64-bit integer type. + * + * Requires: + * \li 'obj' points to a valid configuration object of 64-bit integer type. + * + * Returns: + * \li A 64-bit unsigned integer. + */ + +isc_boolean_t +cfg_obj_isstring(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of string type. + */ + +const char * +cfg_obj_asstring(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object of a string type + * as a null-terminated string. + * + * Requires: + * \li 'obj' points to a valid configuration object of a string type. + * + * Returns: + * \li A pointer to a null terminated string. + */ + +isc_boolean_t +cfg_obj_isboolean(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of a boolean type. + */ + +isc_boolean_t +cfg_obj_asboolean(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object of a boolean type. + * + * Requires: + * \li 'obj' points to a valid configuration object of a boolean type. + * + * Returns: + * \li A boolean value. + */ + +isc_boolean_t +cfg_obj_issockaddr(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is a socket address. + */ + +const isc_sockaddr_t * +cfg_obj_assockaddr(const cfg_obj_t *obj); +/*%< + * Returns the value of a configuration object representing a socket address. + * + * Requires: + * \li 'obj' points to a valid configuration object of a socket address type. + * + * Returns: + * \li A pointer to a sockaddr. The sockaddr must be copied by the caller + * if necessary. + */ + +isc_boolean_t +cfg_obj_isnetprefix(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is a network prefix. + */ + +void +cfg_obj_asnetprefix(const cfg_obj_t *obj, isc_netaddr_t *netaddr, + unsigned int *prefixlen); +/*%< + * Gets the value of a configuration object representing a network + * prefix. The network address is returned through 'netaddr' and the + * prefix length in bits through 'prefixlen'. + * + * Requires: + * \li 'obj' points to a valid configuration object of network prefix type. + *\li 'netaddr' and 'prefixlen' are non-NULL. + */ + +isc_boolean_t +cfg_obj_islist(const cfg_obj_t *obj); +/*%< + * Return true iff 'obj' is of list type. + */ + +const cfg_listelt_t * +cfg_list_first(const cfg_obj_t *obj); +/*%< + * Returns the first list element in a configuration object of a list type. + * + * Requires: + * \li 'obj' points to a valid configuration object of a list type or NULL. + * + * Returns: + * \li A pointer to a cfg_listelt_t representing the first list element, + * or NULL if the list is empty or nonexistent. + */ + +const cfg_listelt_t * +cfg_list_next(const cfg_listelt_t *elt); +/*%< + * Returns the next element of a list of configuration objects. + * + * Requires: + * \li 'elt' points to cfg_listelt_t obtained from cfg_list_first() or + * a previous call to cfg_list_next(). + * + * Returns: + * \li A pointer to a cfg_listelt_t representing the next element, + * or NULL if there are no more elements. + */ + +const cfg_obj_t * +cfg_listelt_value(const cfg_listelt_t *elt); +/*%< + * Returns the configuration object associated with cfg_listelt_t. + * + * Requires: + * \li 'elt' points to cfg_listelt_t obtained from cfg_list_first() or + * cfg_list_next(). + * + * Returns: + * \li A non-NULL pointer to a configuration object. + */ + +void +cfg_print(const cfg_obj_t *obj, + void (*f)(void *closure, const char *text, int textlen), + void *closure); +/*%< + * Print the configuration object 'obj' by repeatedly calling the + * function 'f', passing 'closure' and a region of text starting + * at 'text' and comprising 'textlen' characters. + */ + +void +cfg_print_grammar(const cfg_type_t *type, + void (*f)(void *closure, const char *text, int textlen), + void *closure); +/*%< + * Print a summary of the grammar of the configuration type 'type'. + */ + +isc_boolean_t +cfg_obj_istype(const cfg_obj_t *obj, const cfg_type_t *type); +/*%< + * Return true iff 'obj' is of type 'type'. + */ + +void cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **obj); +/*%< + * Destroy a configuration object. + */ + +void +cfg_obj_log(const cfg_obj_t *obj, isc_log_t *lctx, int level, + const char *fmt, ...) + ISC_FORMAT_PRINTF(4, 5); +/*%< + * Log a message concerning configuration object 'obj' to the logging + * channel of 'pctx', at log level 'level'. The message will be prefixed + * with the file name(s) and line number where 'obj' was defined. + */ + +const char * +cfg_obj_file(const cfg_obj_t *obj); +/*%< + * Return the file that defined this object. + */ + +unsigned int +cfg_obj_line(const cfg_obj_t *obj); +/*%< + * Return the line in file where this object was defined. + */ + + +ISC_LANG_ENDDECLS + +#endif /* ISCCFG_CFG_H */ diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h new file mode 100644 index 0000000..fa66146 --- /dev/null +++ b/lib/isccfg/include/isccfg/grammar.h @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2004-2006 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2002, 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: grammar.h,v 1.4.18.8 2006/02/28 03:10:49 marka Exp $ */ + +#ifndef ISCCFG_GRAMMAR_H +#define ISCCFG_GRAMMAR_H 1 + +/*! \file */ + +#include <isc/lex.h> +#include <isc/netaddr.h> +#include <isc/sockaddr.h> +#include <isc/region.h> +#include <isc/types.h> + +#include <isccfg/cfg.h> + +/* + * Definitions shared between the configuration parser + * and the grammars; not visible to users of the parser. + */ + +/*% 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 + +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 *, const cfg_obj_t *); +typedef void (*cfg_docfunc_t)(cfg_printer_t *, const cfg_type_t *); +typedef void (*cfg_freefunc_t)(cfg_parser_t *, cfg_obj_t *); + +/* + * Structure definitions + */ + +/*% + * 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_docfunc_t doc; /*%< Print grammar description */ + cfg_rep_t * rep; /*%< Data representation */ + const void * of; /*%< Additional data for meta-types */ +}; + +/*% A keyword-type definition, for things like "port <integer>". */ +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; + const char * file; + unsigned int line; +}; + + +/*% A list element. */ +struct cfg_listelt { + cfg_obj_t *obj; + ISC_LINK(cfg_listelt_t) link; +}; + +/*% 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; +}; + + +/*@{*/ +/*% + * Flags defining whether to accept certain types of network addresses. + */ +#define CFG_ADDR_V4OK 0x00000001 +#define CFG_ADDR_V4PREFIXOK 0x00000002 +#define CFG_ADDR_V6OK 0x00000004 +#define CFG_ADDR_WILDOK 0x00000008 +#define CFG_ADDR_MASK (CFG_ADDR_V6OK|CFG_ADDR_V4OK) +/*@}*/ + +/*@{*/ +/*% + * Predefined data representation types. + */ +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_uint32; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_uint64; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_string; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_boolean; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_map; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_list; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_tuple; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_sockaddr; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_netprefix; +LIBISCCFG_EXTERNAL_DATA extern cfg_rep_t cfg_rep_void; +/*@}*/ + +/*@{*/ +/*% + * Predefined configuration object types. + */ +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_boolean; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_uint32; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_uint64; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_qstring; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_astring; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_ustring; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_sockaddr; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr4; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr4wild; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr6; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netaddr6wild; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_netprefix; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_void; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_token; +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_unsupported; +/*@}*/ + +isc_result_t +cfg_gettoken(cfg_parser_t *pctx, int options); + +isc_result_t +cfg_peektoken(cfg_parser_t *pctx, int options); + +void +cfg_ungettoken(cfg_parser_t *pctx); + +#define CFG_LEXOPT_QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE) + +isc_result_t +cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +void +cfg_print_rawuint(cfg_printer_t *pctx, unsigned int u); + +isc_result_t +cfg_parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_uint32(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_print_uint64(cfg_printer_t *pctx, const cfg_obj_t *obj); + +isc_result_t +cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_ustring(cfg_printer_t *pctx, const cfg_obj_t *obj); + +isc_result_t +cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +isc_result_t +cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na); + +void +cfg_print_rawaddr(cfg_printer_t *pctx, const isc_netaddr_t *na); + +isc_boolean_t +cfg_lookingat_netaddr(cfg_parser_t *pctx, unsigned int flags); + +isc_result_t +cfg_parse_rawport(cfg_parser_t *pctx, unsigned int flags, in_port_t *port); + +isc_result_t +cfg_parse_sockaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_sockaddr(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_doc_sockaddr(cfg_printer_t *pctx, const cfg_type_t *type); + +isc_result_t +cfg_parse_netprefix(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +isc_result_t +cfg_parse_special(cfg_parser_t *pctx, int special); +/*%< Parse a required special character 'special'. */ + +isc_result_t +cfg_create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +isc_result_t +cfg_parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_doc_tuple(cfg_printer_t *pctx, const cfg_type_t *type); + +isc_result_t +cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); + +isc_result_t +cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype, + cfg_listelt_t **ret); + +isc_result_t +cfg_parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_bracketed_list(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type); + +isc_result_t +cfg_parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_spacelist(cfg_printer_t *pctx, const cfg_obj_t *obj); + +isc_result_t +cfg_parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_doc_enum(cfg_printer_t *pctx, const cfg_type_t *type); + +void +cfg_print_chars(cfg_printer_t *pctx, const char *text, int len); +/*%< Print 'len' characters at 'text' */ + +void +cfg_print_cstr(cfg_printer_t *pctx, const char *s); +/*%< Print the null-terminated string 's' */ + +isc_result_t +cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +isc_result_t +cfg_parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +isc_result_t +cfg_parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +isc_result_t +cfg_parse_netprefix_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t ** +ret); + +void +cfg_print_map(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_doc_map(cfg_printer_t *pctx, const cfg_type_t *type); + +isc_result_t +cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_mapbody(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_doc_mapbody(cfg_printer_t *pctx, const cfg_type_t *type); + +isc_result_t +cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_void(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_doc_void(cfg_printer_t *pctx, const cfg_type_t *type); + +isc_result_t +cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); + +void +cfg_print_obj(cfg_printer_t *pctx, const cfg_obj_t *obj); + +void +cfg_doc_obj(cfg_printer_t *pctx, const cfg_type_t *type); +/*%< + * Print a description of the grammar of an arbitrary configuration + * type 'type' + */ + +void +cfg_doc_terminal(cfg_printer_t *pctx, const cfg_type_t *type); +/*%< + * Document the type 'type' as a terminal by printing its + * name in angle brackets, e.g., <uint32>. + */ + +void +cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, + const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); +/*! + * Pass one of these flags to cfg_parser_error() to include the + * token text in log message. + */ +#define CFG_LOG_NEAR 0x00000001 /*%< Say "near <token>" */ +#define CFG_LOG_BEFORE 0x00000002 /*%< Say "before <token>" */ +#define CFG_LOG_NOPREP 0x00000004 /*%< Say just "<token>" */ + +void +cfg_parser_warning(cfg_parser_t *pctx, unsigned int flags, + const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4); + +isc_boolean_t +cfg_is_enum(const char *s, const char *const *enums); +/*%< Return true iff the string 's' is one of the strings in 'enums' */ + +#endif /* ISCCFG_GRAMMAR_H */ diff --git a/lib/isccfg/include/isccfg/log.h b/lib/isccfg/include/isccfg/log.h new file mode 100644 index 0000000..f66c37f --- /dev/null +++ b/lib/isccfg/include/isccfg/log.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: log.h,v 1.6.18.2 2005/04/29 00:17:16 marka Exp $ */ + +#ifndef ISCCFG_LOG_H +#define ISCCFG_LOG_H 1 + +/*! \file */ + +#include <isc/lang.h> +#include <isc/log.h> + +LIBISCCFG_EXTERNAL_DATA extern isc_logcategory_t cfg_categories[]; +LIBISCCFG_EXTERNAL_DATA extern isc_logmodule_t cfg_modules[]; + +#define CFG_LOGCATEGORY_CONFIG (&cfg_categories[0]) + +#define CFG_LOGMODULE_PARSER (&cfg_modules[0]) + +ISC_LANG_BEGINDECLS + +void +cfg_log_init(isc_log_t *lctx); +/*%< + * Make the libisccfg categories and modules available for use with the + * ISC logging library. + * + * Requires: + *\li lctx is a valid logging context. + * + *\li cfg_log_init() is called only once. + * + * Ensures: + * \li The catgories and modules defined above are available for + * use by isc_log_usechannnel() and isc_log_write(). + */ + +ISC_LANG_ENDDECLS + +#endif /* ISCCFG_LOG_H */ diff --git a/lib/isccfg/include/isccfg/namedconf.h b/lib/isccfg/include/isccfg/namedconf.h new file mode 100644 index 0000000..6125b26 --- /dev/null +++ b/lib/isccfg/include/isccfg/namedconf.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: namedconf.h,v 1.3.18.2 2005/04/29 00:17:16 marka Exp $ */ + +#ifndef ISCCFG_NAMEDCONF_H +#define ISCCFG_NAMEDCONF_H 1 + +/*! \file + * \brief + * This module defines the named.conf, rndc.conf, and rndc.key grammars. + */ + +#include <isccfg/cfg.h> + +/* + * Configuration object types. + */ +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_namedconf; +/*%< A complete named.conf file. */ + +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_rndcconf; +/*%< A complete rndc.conf file. */ + +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_rndckey; +/*%< A complete rndc.key file. */ + +LIBISCCFG_EXTERNAL_DATA extern cfg_type_t cfg_type_keyref; +/*%< A key reference, used as an ACL element */ + +#endif /* ISCCFG_CFG_H */ diff --git a/lib/isccfg/include/isccfg/version.h b/lib/isccfg/include/isccfg/version.h new file mode 100644 index 0000000..38bb14b --- /dev/null +++ b/lib/isccfg/include/isccfg/version.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: version.h,v 1.3.18.2 2005/04/29 00:17:16 marka Exp $ */ + +/*! \file */ + +#include <isc/platform.h> + +LIBISCCFG_EXTERNAL_DATA extern const char cfg_version[]; + +LIBISCCFG_EXTERNAL_DATA extern const unsigned int cfg_libinterface; +LIBISCCFG_EXTERNAL_DATA extern const unsigned int cfg_librevision; +LIBISCCFG_EXTERNAL_DATA extern const unsigned int cfg_libage; diff --git a/lib/isccfg/log.c b/lib/isccfg/log.c new file mode 100644 index 0000000..5d5ccb5 --- /dev/null +++ b/lib/isccfg/log.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: log.c,v 1.5.18.2 2005/04/29 00:17:15 marka Exp $ */ + +/*! \file */ + +#include <config.h> + +#include <isc/util.h> + +#include <isccfg/log.h> + +/*% + * When adding a new category, be sure to add the appropriate + * #define to <isccfg/log.h>. + */ +LIBISCCFG_EXTERNAL_DATA isc_logcategory_t cfg_categories[] = { + { "config", 0 }, + { NULL, 0 } +}; + +/*% + * When adding a new module, be sure to add the appropriate + * #define to <isccfg/log.h>. + */ +LIBISCCFG_EXTERNAL_DATA isc_logmodule_t cfg_modules[] = { + { "isccfg/parser", 0 }, + { NULL, 0 } +}; + +void +cfg_log_init(isc_log_t *lctx) { + REQUIRE(lctx != NULL); + + isc_log_registercategories(lctx, cfg_categories); + isc_log_registermodules(lctx, cfg_modules); +} diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c new file mode 100644 index 0000000..65e30a2 --- /dev/null +++ b/lib/isccfg/namedconf.c @@ -0,0 +1,2049 @@ +/* + * Copyright (C) 2004-2006 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 2002, 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: namedconf.c,v 1.30.18.38 2006/05/03 01:46:40 marka Exp $ */ + +/*! \file */ + +#include <config.h> + +#include <string.h> + +#include <isc/lex.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <isccfg/cfg.h> +#include <isccfg/grammar.h> +#include <isccfg/log.h> + +#define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base) + +/*% 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) + + +/*% + * Forward declarations of static functions. + */ + +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_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, const cfg_obj_t *obj); + +static void +doc_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type); + +static void +doc_optional_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type); + +static cfg_type_t cfg_type_acl; +static cfg_type_t cfg_type_addrmatchelt; +static cfg_type_t cfg_type_bracketed_aml; +static cfg_type_t cfg_type_bracketed_namesockaddrkeylist; +static cfg_type_t cfg_type_bracketed_sockaddrlist; +static cfg_type_t cfg_type_bracketed_sockaddrnameportlist; +static cfg_type_t cfg_type_controls; +static cfg_type_t cfg_type_controls_sockaddr; +static cfg_type_t cfg_type_destinationlist; +static cfg_type_t cfg_type_dialuptype; +static cfg_type_t cfg_type_ixfrdifftype; +static cfg_type_t cfg_type_key; +static cfg_type_t cfg_type_logfile; +static cfg_type_t cfg_type_logging; +static cfg_type_t cfg_type_logseverity; +static cfg_type_t cfg_type_lwres; +static cfg_type_t cfg_type_masterselement; +static cfg_type_t cfg_type_nameportiplist; +static cfg_type_t cfg_type_negated; +static cfg_type_t cfg_type_notifytype; +static cfg_type_t cfg_type_optional_class; +static cfg_type_t cfg_type_optional_facility; +static cfg_type_t cfg_type_optional_facility; +static cfg_type_t cfg_type_optional_keyref; +static cfg_type_t cfg_type_optional_port; +static cfg_type_t cfg_type_options; +static cfg_type_t cfg_type_portiplist; +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_server; +static cfg_type_t cfg_type_server_key_kludge; +static cfg_type_t cfg_type_size; +static cfg_type_t cfg_type_sizenodefault; +static cfg_type_t cfg_type_sockaddr4wild; +static cfg_type_t cfg_type_sockaddr6wild; +static cfg_type_t cfg_type_view; +static cfg_type_t cfg_type_viewopts; +static cfg_type_t cfg_type_zone; +static cfg_type_t cfg_type_zoneopts; +static cfg_type_t cfg_type_dynamically_loadable_zones; +static cfg_type_t cfg_type_dynamically_loadable_zones_opts; + +/* + * Clauses that can be found in a 'dynamically loadable zones' statement + */ +static cfg_clausedef_t +dynamically_loadable_zones_clauses[] = { + { "database", &cfg_type_astring, 0 }, + { NULL, NULL, 0 } +}; + +/* + * A dynamically loadable zones statement. + */ +static cfg_tuplefielddef_t dynamically_loadable_zones_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "options", &cfg_type_dynamically_loadable_zones_opts, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_dynamically_loadable_zones = { + "dlz", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, + dynamically_loadable_zones_fields + }; + + +/*% 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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, acl_fields }; + +/*% masters */ +static cfg_tuplefielddef_t masters_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "port", &cfg_type_optional_port, 0 }, + { "addresses", &cfg_type_bracketed_namesockaddrkeylist, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_masters = { + "masters", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, masters_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 { mymasters; 10.0.0.1 key foo; 1::2 port 69; }" + */ + +static cfg_tuplefielddef_t namesockaddrkey_fields[] = { + { "masterselement", &cfg_type_masterselement, 0 }, + { "key", &cfg_type_optional_keyref, 0 }, + { NULL, NULL, 0 }, +}; + +static cfg_type_t cfg_type_namesockaddrkey = { + "namesockaddrkey", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, + namesockaddrkey_fields +}; + +static cfg_type_t cfg_type_bracketed_namesockaddrkeylist = { + "bracketed_namesockaddrkeylist", cfg_parse_bracketed_list, + cfg_print_bracketed_list, cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_namesockaddrkey +}; + +static cfg_tuplefielddef_t namesockaddrkeylist_fields[] = { + { "port", &cfg_type_optional_port, 0 }, + { "addresses", &cfg_type_bracketed_namesockaddrkeylist, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_namesockaddrkeylist = { + "sockaddrkeylist", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, + namesockaddrkeylist_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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", cfg_parse_spacelist, cfg_print_spacelist, cfg_doc_terminal, + &cfg_rep_list, &cfg_type_astring +}; + +static const char *mode_enums[] = { "grant", "deny", NULL }; +static cfg_type_t cfg_type_mode = { + "mode", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, &cfg_rep_string, + &mode_enums +}; + +static const char *matchtype_enums[] = { + "name", "subdomain", "wildcard", "self", "selfsub", "selfwild", NULL }; +static cfg_type_t cfg_type_matchtype = { + "matchtype", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, &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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, grant_fields }; + +static cfg_type_t cfg_type_updatepolicy = { + "update_policy", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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, + doc_optional_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, doc_optional_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, doc_optional_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_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 const char *checktype_enums[] = { "master", "slave", "response", NULL }; +static cfg_type_t cfg_type_checktype = { + "checktype", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &checktype_enums +}; + +static const char *checkmode_enums[] = { "fail", "warn", "ignore", NULL }; +static cfg_type_t cfg_type_checkmode = { + "checkmode", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &checkmode_enums +}; + +static cfg_tuplefielddef_t checknames_fields[] = { + { "type", &cfg_type_checktype, 0 }, + { "mode", &cfg_type_checkmode, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_checknames = { + "checknames", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, + checknames_fields +}; + +static cfg_type_t cfg_type_bracketed_sockaddrlist = { + "bracketed_sockaddrlist", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_bracketed_list, + &cfg_rep_list, &cfg_type_sockaddr +}; + +static cfg_type_t cfg_type_rrsetorder = { + "rrsetorder", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_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, + doc_optional_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", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_bracketed_list, &cfg_rep_list, + &cfg_type_astring +}; + +static cfg_type_t cfg_type_trustedkeys = { + "trusted-keys", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_bracketed_list, &cfg_rep_list, + &cfg_type_trustedkey +}; + +static const char *forwardtype_enums[] = { "first", "only", NULL }; +static cfg_type_t cfg_type_forwardtype = { + "forwardtype", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, &cfg_rep_string, + &forwardtype_enums +}; + +static const char *zonetype_enums[] = { + "master", "slave", "stub", "hint", "forward", "delegation-only", NULL }; +static cfg_type_t cfg_type_zonetype = { + "zonetype", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &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", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, &cfg_rep_string, + &loglevel_enums +}; + +static const char *transferformat_enums[] = { + "many-answers", "one-answer", NULL }; +static cfg_type_t cfg_type_transferformat = { + "transferformat", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, &cfg_rep_string, + &transferformat_enums +}; + +/*% + * The special keyword "none", as used in the pid-file option. + */ + +static void +print_none(cfg_printer_t *pctx, const cfg_obj_t *obj) { + UNUSED(obj); + cfg_print_chars(pctx, "none", 4); +} + +static cfg_type_t cfg_type_none = { + "none", NULL, print_none, NULL, &cfg_rep_void, NULL +}; + +/*% + * A quoted string or the special keyword "none". Used in the pid-file option. + */ +static isc_result_t +parse_qstringornone(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(TOKEN_STRING(pctx), "none") == 0) + return (cfg_create_obj(pctx, &cfg_type_none, ret)); + cfg_ungettoken(pctx); + return (cfg_parse_qstring(pctx, type, ret)); + cleanup: + return (result); +} + +static void +doc_qstringornone(cfg_printer_t *pctx, const cfg_type_t *type) { + UNUSED(type); + cfg_print_chars(pctx, "( <quoted_string> | none )", 26); +} + +static cfg_type_t cfg_type_qstringornone = { + "qstringornone", parse_qstringornone, NULL, doc_qstringornone, NULL, NULL }; + +/*% + * keyword hostname + */ + +static void +print_hostname(cfg_printer_t *pctx, const cfg_obj_t *obj) { + UNUSED(obj); + cfg_print_chars(pctx, "hostname", 4); +} + +static cfg_type_t cfg_type_hostname = { + "hostname", NULL, print_hostname, NULL, &cfg_rep_boolean, NULL +}; + +/*% + * "server-id" argument. + */ + +static isc_result_t +parse_serverid(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(TOKEN_STRING(pctx), "none") == 0) + return (cfg_create_obj(pctx, &cfg_type_none, ret)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(TOKEN_STRING(pctx), "hostname") == 0) { + return (cfg_create_obj(pctx, &cfg_type_hostname, ret)); + } + cfg_ungettoken(pctx); + return (cfg_parse_qstring(pctx, type, ret)); + cleanup: + return (result); +} + +static void +doc_serverid(cfg_printer_t *pctx, const cfg_type_t *type) { + UNUSED(type); + cfg_print_chars(pctx, "( <quoted_string> | none | hostname )", 26); +} + +static cfg_type_t cfg_type_serverid = { + "serverid", parse_serverid, NULL, doc_serverid, NULL, NULL }; + +/*% + * Port list. + */ +static isc_result_t +parse_port(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + + UNUSED(type); + + CHECK(cfg_parse_uint32(pctx, NULL, ret)); + if ((*ret)->value.uint32 > 0xffff) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "invalid port"); + cfg_obj_destroy(pctx, ret); + result = ISC_R_RANGE; + } + cleanup: + return (result); +} + +static cfg_type_t cfg_type_port = { + "port", parse_port, NULL, cfg_doc_terminal, + NULL, NULL +}; + +static cfg_type_t cfg_type_bracketed_portlist = { + "bracketed_sockaddrlist", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_bracketed_list, + &cfg_rep_list, &cfg_type_port +}; + +/*% + * 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 }, + { "masters", &cfg_type_masters, 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 }, + /* only 1 DLZ per view allowed */ + { "dlz", &cfg_type_dynamically_loadable_zones, 0 }, + { "server", &cfg_type_server, CFG_CLAUSEFLAG_MULTI }, + { "trusted-keys", &cfg_type_trustedkeys, CFG_CLAUSEFLAG_MULTI }, + { NULL, NULL, 0 } +}; + +/*% + * Clauses that can be found within the 'options' statement. + */ +static cfg_clausedef_t +options_clauses[] = { + { "avoid-v4-udp-ports", &cfg_type_bracketed_portlist, 0 }, + { "avoid-v6-udp-ports", &cfg_type_bracketed_portlist, 0 }, + { "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 }, + { "host-statistics-max", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP }, + { "hostname", &cfg_type_qstringornone, 0 }, + { "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, 0 }, + { "multiple-cnames", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE }, + { "named-xfer", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE }, + { "pid-file", &cfg_type_qstringornone, 0 }, + { "port", &cfg_type_uint32, 0 }, + { "querylog", &cfg_type_boolean, 0 }, + { "recursing-file", &cfg_type_qstring, 0 }, + { "random-device", &cfg_type_qstring, 0 }, + { "recursive-clients", &cfg_type_uint32, 0 }, + { "serial-queries", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE }, + { "serial-query-rate", &cfg_type_uint32, 0 }, + { "server-id", &cfg_type_serverid, 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 }, + { "tcp-listen-queue", &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_qstringornone, 0 }, + { "flush-zones-on-shutdown", &cfg_type_boolean, 0 }, + { NULL, NULL, 0 } +}; + + +static cfg_type_t cfg_type_namelist = { + "namelist", cfg_parse_bracketed_list, cfg_print_bracketed_list, + cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_qstring }; + +static keyword_type_t exclude_kw = { "exclude", &cfg_type_namelist }; + +static cfg_type_t cfg_type_optional_exclude = { + "optional_exclude", parse_optional_keyvalue, print_keyvalue, + doc_optional_keyvalue, &cfg_rep_list, &exclude_kw }; + +static cfg_type_t cfg_type_algorithmlist = { + "algorithmlist", cfg_parse_bracketed_list, cfg_print_bracketed_list, + cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_astring }; + +static cfg_tuplefielddef_t disablealgorithm_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "algorithms", &cfg_type_algorithmlist, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_disablealgorithm = { + "disablealgorithm", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, disablealgorithm_fields +}; + +static cfg_tuplefielddef_t mustbesecure_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "value", &cfg_type_boolean, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_mustbesecure = { + "mustbesecure", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, mustbesecure_fields +}; + +static const char *masterformat_enums[] = { "text", "raw", NULL }; +static cfg_type_t cfg_type_masterformat = { + "masterformat", cfg_parse_enum, cfg_print_ustring, cfg_doc_enum, + &cfg_rep_string, &masterformat_enums +}; + +/*% + * dnssec-lookaside + */ + +static keyword_type_t trustanchor_kw = { "trust-anchor", &cfg_type_astring }; + +static cfg_type_t cfg_type_trustanchor = { + "trust-anchor", parse_keyvalue, print_keyvalue, doc_keyvalue, + &cfg_rep_string, &trustanchor_kw +}; + +static cfg_tuplefielddef_t lookaside_fields[] = { + { "domain", &cfg_type_astring, 0 }, + { "trust-anchor", &cfg_type_trustanchor, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_lookaside = { + "lookaside", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, lookaside_fields +}; + +/*% + * Clauses that can be found within the 'view' statement, + * with defaults in the 'options' statement. + */ + +static cfg_clausedef_t +view_clauses[] = { + { "allow-query-cache", &cfg_type_bracketed_aml, 0 }, + { "allow-recursion", &cfg_type_bracketed_aml, 0 }, + { "allow-v6-synthesis", &cfg_type_bracketed_aml, + CFG_CLAUSEFLAG_OBSOLETE }, + { "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 }, + { "rrset-order", &cfg_type_rrsetorder, 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 }, + { "cache-file", &cfg_type_qstring, 0 }, + { "suppress-initial-notify", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI }, + { "preferred-glue", &cfg_type_astring, 0 }, + { "dual-stack-servers", &cfg_type_nameportiplist, 0 }, + { "edns-udp-size", &cfg_type_uint32, 0 }, + { "max-udp-size", &cfg_type_uint32, 0 }, + { "root-delegation-only", &cfg_type_optional_exclude, 0 }, + { "disable-algorithms", &cfg_type_disablealgorithm, + CFG_CLAUSEFLAG_MULTI }, + { "dnssec-enable", &cfg_type_boolean, 0 }, + { "dnssec-validation", &cfg_type_boolean, 0 }, + { "dnssec-lookaside", &cfg_type_lookaside, CFG_CLAUSEFLAG_MULTI }, + { "dnssec-must-be-secure", &cfg_type_mustbesecure, + CFG_CLAUSEFLAG_MULTI }, + { "dnssec-accept-expired", &cfg_type_boolean, 0 }, + { "ixfr-from-differences", &cfg_type_ixfrdifftype, 0 }, + { "acache-enable", &cfg_type_boolean, 0 }, + { "acache-cleaning-interval", &cfg_type_uint32, 0 }, + { "max-acache-size", &cfg_type_sizenodefault, 0 }, + { "clients-per-query", &cfg_type_uint32, 0 }, + { "max-clients-per-query", &cfg_type_uint32, 0 }, + { "empty-server", &cfg_type_astring, 0 }, + { "empty-contact", &cfg_type_astring, 0 }, + { "empty-zones-enable", &cfg_type_boolean, 0 }, + { "disable-empty-zone", &cfg_type_astring, CFG_CLAUSEFLAG_MULTI }, + { "zero-no-soa-ttl-cache", &cfg_type_boolean, 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", &cfg_type_bracketed_aml, 0 }, + { "allow-update-forwarding", &cfg_type_bracketed_aml, 0 }, + { "allow-notify", &cfg_type_bracketed_aml, 0 }, + { "masterfile-format", &cfg_type_masterformat, 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 }, + { "notify-delay", &cfg_type_uint32, 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 }, + { "max-journal-size", &cfg_type_sizenodefault, 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 }, + { "multi-master", &cfg_type_boolean, 0 }, + { "sig-validity-interval", &cfg_type_uint32, 0 }, + { "transfer-source", &cfg_type_sockaddr4wild, 0 }, + { "transfer-source-v6", &cfg_type_sockaddr6wild, 0 }, + { "alt-transfer-source", &cfg_type_sockaddr4wild, 0 }, + { "alt-transfer-source-v6", &cfg_type_sockaddr6wild, 0 }, + { "use-alt-transfer-source", &cfg_type_boolean, 0 }, + { "zone-statistics", &cfg_type_boolean, 0 }, + { "key-directory", &cfg_type_qstring, 0 }, + { "check-wildcard", &cfg_type_boolean, 0 }, + { "check-integrity", &cfg_type_boolean, 0 }, + { "check-mx", &cfg_type_checkmode, 0 }, + { "check-mx-cname", &cfg_type_checkmode, 0 }, + { "check-srv-cname", &cfg_type_checkmode, 0 }, + { "check-sibling", &cfg_type_boolean, 0 }, + { "zero-no-soa-ttl", &cfg_type_boolean, 0 }, + { "update-check-ksk", &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 }, + { "file", &cfg_type_qstring, 0 }, + { "journal", &cfg_type_qstring, 0 }, + { "ixfr-base", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE }, + { "ixfr-tmp-file", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE }, + { "masters", &cfg_type_namesockaddrkeylist, 0 }, + { "pubkey", &cfg_type_pubkey, + CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_OBSOLETE }, + { "update-policy", &cfg_type_updatepolicy, 0 }, + { "database", &cfg_type_astring, 0 }, + { "delegation-only", &cfg_type_boolean, 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_checkmode, 0 }, + { "ixfr-from-differences", &cfg_type_boolean, 0 }, + { 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", cfg_parse_mapbody, cfg_print_mapbody, cfg_doc_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", cfg_parse_map, cfg_print_map, cfg_doc_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, + dynamically_loadable_zones_clauses, + NULL +}; +static cfg_type_t cfg_type_viewopts = { + "view", cfg_parse_map, cfg_print_map, cfg_doc_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", cfg_parse_map, cfg_print_map, + cfg_doc_map, &cfg_rep_map, zone_clausesets }; + +/*% The "dynamically loadable zones" statement syntax. */ + +static cfg_clausedef_t * +dynamically_loadable_zones_clausesets[] = { + dynamically_loadable_zones_clauses, + NULL +}; +static cfg_type_t cfg_type_dynamically_loadable_zones_opts = { + "dynamically_loadable_zones_opts", cfg_parse_map, + cfg_print_map, cfg_doc_map, &cfg_rep_map, + dynamically_loadable_zones_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", cfg_parse_named_map, cfg_print_map, + cfg_doc_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 }, + { "edns-udp-size", &cfg_type_uint32, 0 }, + { "max-udp-size", &cfg_type_uint32, 0 }, + { "notify-source", &cfg_type_sockaddr4wild, 0 }, + { "notify-source-v6", &cfg_type_sockaddr6wild, 0 }, + { "query-source", &cfg_type_querysource4, 0 }, + { "query-source-v6", &cfg_type_querysource6, 0 }, + { "transfer-source", &cfg_type_sockaddr4wild, 0 }, + { "transfer-source-v6", &cfg_type_sockaddr6wild, 0 }, + { NULL, NULL, 0 } +}; +static cfg_clausedef_t * +server_clausesets[] = { + server_clauses, + NULL +}; +static cfg_type_t cfg_type_server = { + "server", cfg_parse_netprefix_map, cfg_print_map, cfg_doc_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", cfg_parse_named_map, cfg_print_map, cfg_doc_map, + &cfg_rep_map, channel_clausesets +}; + +/*% A list of log destination, used in the "category" clause. */ +static cfg_type_t cfg_type_destinationlist = { + "destinationlist", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_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", cfg_parse_map, cfg_print_map, cfg_doc_map, &cfg_rep_map, logging_clausesets }; + + +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 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(TOKEN_STRING(pctx), &val)); + + CHECK(cfg_create_obj(pctx, &cfg_type_uint64, &obj)); + obj->value.uint64 = val; + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + cfg_parser_error(pctx, CFG_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, cfg_print_uint64, cfg_doc_terminal, + &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, cfg_print_ustring, cfg_doc_terminal, + &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, cfg_print_ustring, cfg_doc_terminal, + &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(TOKEN_STRING(pctx), 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(cfg_parse_void(pctx, NULL, &obj)); + } else { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected '%s'", + kw->name); + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + } + *ret = obj; + cleanup: + 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 && + cfg_is_enum(TOKEN_STRING(pctx), enumtype->of)) { + CHECK(cfg_parse_enum(pctx, enumtype, ret)); + } else { + CHECK(cfg_parse_obj(pctx, othertype, ret)); + } + cleanup: + return (result); +} + +static void +doc_enum_or_other(cfg_printer_t *pctx, const cfg_type_t *type) { + cfg_doc_terminal(pctx, type); +#if 0 /* XXX */ + cfg_print_chars(pctx, "( ", 2);... +#endif + +} + +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, const cfg_obj_t *obj) { + const keyword_type_t *kw = obj->type->of; + cfg_print_cstr(pctx, kw->name); + cfg_print_chars(pctx, " ", 1); + kw->type->print(pctx, obj); +} + +static void +doc_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type) { + const keyword_type_t *kw = type->of; + cfg_print_cstr(pctx, kw->name); + cfg_print_chars(pctx, " ", 1); + cfg_doc_obj(pctx, kw->type); +} + +static void +doc_optional_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type) { + const keyword_type_t *kw = type->of; + cfg_print_chars(pctx, "[ ", 2); + cfg_print_cstr(pctx, kw->name); + cfg_print_chars(pctx, " ", 1); + cfg_doc_obj(pctx, kw->type); + cfg_print_chars(pctx, " ]", 2); +} + +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, cfg_print_ustring, doc_enum_or_other, + &cfg_rep_string, dialup_enums +}; + +static const char *notify_enums[] = { "explicit", "master-only", 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, cfg_print_ustring, doc_enum_or_other, + &cfg_rep_string, notify_enums, +}; + +static const char *ixfrdiff_enums[] = { "master", "slave", NULL }; +static isc_result_t +parse_ixfrdiff_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_ixfrdifftype = { + "ixfrdiff", parse_ixfrdiff_type, cfg_print_ustring, doc_enum_or_other, + &cfg_rep_string, ixfrdiff_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, doc_keyvalue, + &cfg_rep_string, &key_kw +}; + +static cfg_type_t cfg_type_optional_keyref = { + "optional_keyref", parse_optional_keyvalue, print_keyvalue, + doc_optional_keyvalue, &cfg_rep_string, &key_kw +}; + +/*% + * A "controls" statement is represented as a map with the multivalued + * "inet" and "unix" clauses. + */ + +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, doc_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, doc_optional_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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, + inetcontrol_fields +}; + +static keyword_type_t controls_perm_kw = { + "perm", &cfg_type_uint32 }; + +static cfg_type_t cfg_type_controls_perm = { + "controls_perm", parse_keyvalue, + print_keyvalue, doc_keyvalue, + &cfg_rep_uint32, &controls_perm_kw +}; + +static keyword_type_t controls_owner_kw = { + "owner", &cfg_type_uint32 }; + +static cfg_type_t cfg_type_controls_owner = { + "controls_owner", parse_keyvalue, + print_keyvalue, doc_keyvalue, + &cfg_rep_uint32, &controls_owner_kw +}; + +static keyword_type_t controls_group_kw = { + "group", &cfg_type_uint32 }; + +static cfg_type_t cfg_type_controls_group = { + "controls_allow", parse_keyvalue, + print_keyvalue, doc_keyvalue, + &cfg_rep_uint32, &controls_group_kw +}; + +static cfg_tuplefielddef_t unixcontrol_fields[] = { + { "path", &cfg_type_qstring, 0 }, + { "perm", &cfg_type_controls_perm, 0 }, + { "owner", &cfg_type_controls_owner, 0 }, + { "group", &cfg_type_controls_group, 0 }, + { "keys", &cfg_type_controls_keys, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_unixcontrol = { + "unixcontrol", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, + unixcontrol_fields +}; + +static cfg_clausedef_t +controls_clauses[] = { + { "inet", &cfg_type_inetcontrol, CFG_CLAUSEFLAG_MULTI }, + { "unix", &cfg_type_unixcontrol, CFG_CLAUSEFLAG_MULTI }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t * +controls_clausesets[] = { + controls_clauses, + NULL +}; +static cfg_type_t cfg_type_controls = { + "controls", cfg_parse_map, cfg_print_map, cfg_doc_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(cfg_parse_obj(pctx, &cfg_type_ustring, ret)); + else + CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret)); + cleanup: + return (result); +} + +static cfg_type_t cfg_type_optional_class = { + "optional_class", parse_optional_class, NULL, cfg_doc_terminal, + NULL, NULL +}; + +static isc_result_t +parse_querysource(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; + in_port_t port; + unsigned int have_address = 0; + unsigned int have_port = 0; + const unsigned int *flagp = type->of; + + if ((*flagp & CFG_ADDR_V4OK) != 0) + isc_netaddr_any(&netaddr); + else if ((*flagp & CFG_ADDR_V6OK) != 0) + isc_netaddr_any6(&netaddr); + else + INSIST(0); + + port = 0; + + for (;;) { + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string) { + if (strcasecmp(TOKEN_STRING(pctx), + "address") == 0) + { + /* read "address" */ + CHECK(cfg_gettoken(pctx, 0)); + CHECK(cfg_parse_rawaddr(pctx, *flagp, + &netaddr)); + have_address++; + } else if (strcasecmp(TOKEN_STRING(pctx), "port") == 0) + { + /* read "port" */ + CHECK(cfg_gettoken(pctx, 0)); + CHECK(cfg_parse_rawport(pctx, + CFG_ADDR_WILDOK, + &port)); + have_port++; + } else if (have_port == 0 && have_address == 0) { + return (cfg_parse_sockaddr(pctx, type, ret)); + } else { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected 'address' or 'port'"); + return (ISC_R_UNEXPECTEDTOKEN); + } + } else + break; + } + if (have_address > 1 || have_port > 1 || + have_address + have_port == 0) { + cfg_parser_error(pctx, 0, "expected one address and/or port"); + return (ISC_R_UNEXPECTEDTOKEN); + } + + CHECK(cfg_create_obj(pctx, &cfg_type_querysource, &obj)); + isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port); + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, "invalid query source"); + CLEANUP_OBJ(obj); + return (result); +} + +static void +print_querysource(cfg_printer_t *pctx, const cfg_obj_t *obj) { + isc_netaddr_t na; + isc_netaddr_fromsockaddr(&na, &obj->value.sockaddr); + cfg_print_chars(pctx, "address ", 8); + cfg_print_rawaddr(pctx, &na); + cfg_print_chars(pctx, " port ", 6); + cfg_print_rawuint(pctx, isc_sockaddr_getport(&obj->value.sockaddr)); +} + +static unsigned int sockaddr4wild_flags = CFG_ADDR_WILDOK | CFG_ADDR_V4OK; +static unsigned int sockaddr6wild_flags = CFG_ADDR_WILDOK | CFG_ADDR_V6OK; +static cfg_type_t cfg_type_querysource4 = { + "querysource4", parse_querysource, NULL, cfg_doc_terminal, + NULL, &sockaddr4wild_flags +}; + +static cfg_type_t cfg_type_querysource6 = { + "querysource6", parse_querysource, NULL, cfg_doc_terminal, + NULL, &sockaddr6wild_flags +}; + +static cfg_type_t cfg_type_querysource = { + "querysource", NULL, print_querysource, NULL, &cfg_rep_sockaddr, 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, CFG_LEXOPT_QSTRING)); + + if (pctx->token.type == isc_tokentype_string || + pctx->token.type == isc_tokentype_qstring) { + if (pctx->token.type == isc_tokentype_string && + (strcasecmp(TOKEN_STRING(pctx), "key") == 0)) { + CHECK(cfg_parse_obj(pctx, &cfg_type_keyref, ret)); + } else { + if (cfg_lookingat_netaddr(pctx, CFG_ADDR_V4OK | + CFG_ADDR_V4PREFIXOK | + CFG_ADDR_V6OK)) + { + CHECK(cfg_parse_netprefix(pctx, NULL, ret)); + } else { + CHECK(cfg_parse_astring(pctx, NULL, ret)); + } + } + } else if (pctx->token.type == isc_tokentype_special) { + if (pctx->token.value.as_char == '{') { + /* Nested match list. */ + CHECK(cfg_parse_obj(pctx, &cfg_type_bracketed_aml, ret)); + } else if (pctx->token.value.as_char == '!') { + CHECK(cfg_gettoken(pctx, 0)); /* read "!" */ + CHECK(cfg_parse_obj(pctx, &cfg_type_negated, ret)); + } else { + goto bad; + } + } else { + bad: + cfg_parser_error(pctx, CFG_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, const cfg_obj_t *obj) { + cfg_print_chars(pctx, "!", 1); + cfg_print_tuple(pctx, obj); +} + +static cfg_type_t cfg_type_negated = { + "negated", cfg_parse_tuple, print_negated, NULL, &cfg_rep_tuple, + &negated_fields +}; + +/*% An address match list element */ + +static cfg_type_t cfg_type_addrmatchelt = { + "address_match_element", parse_addrmatchelt, NULL, cfg_doc_terminal, + NULL, NULL +}; + +/*% A bracketed address match list */ + +static cfg_type_t cfg_type_bracketed_aml = { + "bracketed_aml", cfg_parse_bracketed_list, cfg_print_bracketed_list, + cfg_doc_bracketed_list, &cfg_rep_list, &cfg_type_addrmatchelt +}; + +/*% + * 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 = + CFG_ADDR_V4OK | CFG_ADDR_V6OK | CFG_ADDR_WILDOK; +static cfg_type_t cfg_type_controls_sockaddr = { + "controls_sockaddr", cfg_parse_sockaddr, cfg_print_sockaddr, + cfg_doc_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 or 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(cfg_parse_obj(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(cfg_parse_special(pctx, '}')); + } + cleanup: + return (result); +} +static cfg_type_t cfg_type_server_key_kludge = { + "server_key", parse_server_key_kludge, NULL, cfg_doc_terminal, + 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, CFG_LEXOPT_QSTRING)); + if (pctx->token.type == isc_tokentype_string || + pctx->token.type == isc_tokentype_qstring) { + CHECK(cfg_parse_obj(pctx, &cfg_type_astring, ret)); + } else { + CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret)); + } + cleanup: + return (result); +} + +static cfg_type_t cfg_type_optional_facility = { + "optional_facility", parse_optional_facility, NULL, cfg_doc_terminal, + 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, doc_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(TOKEN_STRING(pctx), "debug") == 0) { + CHECK(cfg_gettoken(pctx, 0)); /* read "debug" */ + CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER)); + if (pctx->token.type == isc_tokentype_number) { + CHECK(cfg_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(cfg_create_obj(pctx, &cfg_type_uint32, ret)); + (*ret)->value.uint32 = 1; + } + (*ret)->type = &cfg_type_debuglevel; /* XXX kludge */ + } else { + CHECK(cfg_parse_obj(pctx, &cfg_type_loglevel, ret)); + } + cleanup: + return (result); +} + +static cfg_type_t cfg_type_logseverity = { + "log_severity", parse_logseverity, NULL, cfg_doc_terminal, + 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, cfg_print_ustring, cfg_doc_terminal, + &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(cfg_create_tuple(pctx, type, &obj)); + + /* Parse the mandatory "file" field */ + CHECK(cfg_parse_obj(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(TOKEN_STRING(pctx), + "versions") == 0 && + obj->value.tuple[1] == NULL) { + CHECK(cfg_parse_obj(pctx, fields[1].type, + &obj->value.tuple[1])); + } else if (strcasecmp(TOKEN_STRING(pctx), + "size") == 0 && + obj->value.tuple[2] == NULL) { + CHECK(cfg_parse_obj(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(cfg_parse_void(pctx, NULL, &obj->value.tuple[1])); + if (obj->value.tuple[2] == NULL) + CHECK(cfg_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, const cfg_obj_t *obj) { + cfg_print_obj(pctx, obj->value.tuple[0]); /* file */ + if (obj->value.tuple[1]->type->print != cfg_print_void) { + cfg_print_chars(pctx, " versions ", 10); + cfg_print_obj(pctx, obj->value.tuple[1]); + } + if (obj->value.tuple[2]->type->print != cfg_print_void) { + cfg_print_chars(pctx, " size ", 6); + cfg_print_obj(pctx, obj->value.tuple[2]); + } +} + +static cfg_type_t cfg_type_logfile = { + "log_file", parse_logfile, print_logfile, cfg_doc_terminal, + &cfg_rep_tuple, logfile_fields +}; + +/*% An IPv4 address with optional port, "*" accepted as wildcard. */ +static cfg_type_t cfg_type_sockaddr4wild = { + "sockaddr4wild", cfg_parse_sockaddr, cfg_print_sockaddr, + cfg_doc_sockaddr, &cfg_rep_sockaddr, &sockaddr4wild_flags +}; + +/*% An IPv6 address with optional port, "*" accepted as wildcard. */ +static cfg_type_t cfg_type_sockaddr6wild = { + "v6addrportwild", cfg_parse_sockaddr, cfg_print_sockaddr, + cfg_doc_sockaddr, &cfg_rep_sockaddr, &sockaddr6wild_flags +}; + +/*% + * 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", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, + lwres_view_fields +}; + +static cfg_type_t cfg_type_lwres_searchlist = { + "lwres_searchlist", cfg_parse_bracketed_list, cfg_print_bracketed_list, cfg_doc_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", cfg_parse_map, cfg_print_map, cfg_doc_map, &cfg_rep_map, + lwres_clausesets +}; + +/*% + * rndc + */ + +static cfg_clausedef_t +rndcconf_options_clauses[] = { + { "default-key", &cfg_type_astring, 0 }, + { "default-port", &cfg_type_uint32, 0 }, + { "default-server", &cfg_type_astring, 0 }, + { "default-source-address", &cfg_type_netaddr4wild, 0 }, + { "default-source-address-v6", &cfg_type_netaddr6wild, 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", cfg_parse_map, cfg_print_map, cfg_doc_map, + &cfg_rep_map, rndcconf_options_clausesets +}; + +static cfg_clausedef_t +rndcconf_server_clauses[] = { + { "key", &cfg_type_astring, 0 }, + { "port", &cfg_type_uint32, 0 }, + { "source-address", &cfg_type_netaddr4wild, 0 }, + { "source-address-v6", &cfg_type_netaddr6wild, 0 }, + { "addresses", &cfg_type_bracketed_sockaddrnameportlist, 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", cfg_parse_named_map, cfg_print_map, cfg_doc_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", cfg_parse_mapbody, cfg_print_mapbody, cfg_doc_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", cfg_parse_mapbody, cfg_print_mapbody, cfg_doc_mapbody, + &cfg_rep_map, rndckey_clausesets +}; + +static cfg_tuplefielddef_t nameport_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "port", &cfg_type_optional_port, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_nameport = { + "nameport", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, nameport_fields +}; + +static void +doc_sockaddrnameport(cfg_printer_t *pctx, const cfg_type_t *type) { + UNUSED(type); + cfg_print_chars(pctx, "( ", 2); + cfg_print_cstr(pctx, "<quoted_string>"); + cfg_print_chars(pctx, " ", 1); + cfg_print_cstr(pctx, "[port <integer>]"); + cfg_print_chars(pctx, " | ", 3); + cfg_print_cstr(pctx, "<ipv4_address>"); + cfg_print_chars(pctx, " ", 1); + cfg_print_cstr(pctx, "[port <integer>]"); + cfg_print_chars(pctx, " | ", 3); + cfg_print_cstr(pctx, "<ipv6_address>"); + cfg_print_chars(pctx, " ", 1); + cfg_print_cstr(pctx, "[port <integer>]"); + cfg_print_chars(pctx, " )", 2); +} + +static isc_result_t +parse_sockaddrnameport(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_peektoken(pctx, CFG_LEXOPT_QSTRING)); + if (pctx->token.type == isc_tokentype_string || + pctx->token.type == isc_tokentype_qstring) { + if (cfg_lookingat_netaddr(pctx, CFG_ADDR_V4OK | CFG_ADDR_V6OK)) + CHECK(cfg_parse_sockaddr(pctx, &cfg_type_sockaddr, ret)); + else { + const cfg_tuplefielddef_t *fields = + cfg_type_nameport.of; + CHECK(cfg_create_tuple(pctx, &cfg_type_nameport, + &obj)); + CHECK(cfg_parse_obj(pctx, fields[0].type, + &obj->value.tuple[0])); + CHECK(cfg_parse_obj(pctx, fields[1].type, + &obj->value.tuple[1])); + *ret = obj; + obj = NULL; + } + } else { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected IP address or hostname"); + return (ISC_R_UNEXPECTEDTOKEN); + } + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static cfg_type_t cfg_type_sockaddrnameport = { + "sockaddrnameport_element", parse_sockaddrnameport, NULL, + doc_sockaddrnameport, NULL, NULL +}; + +static cfg_type_t cfg_type_bracketed_sockaddrnameportlist = { + "bracketed_sockaddrnameportlist", cfg_parse_bracketed_list, + cfg_print_bracketed_list, cfg_doc_bracketed_list, + &cfg_rep_list, &cfg_type_sockaddrnameport +}; + +/*% + * A list of socket addresses or name with an optional default port, + * as used in the dual-stack-servers option. E.g., + * "port 1234 { dual-stack-servers.net; 10.0.0.1; 1::2 port 69; }" + */ +static cfg_tuplefielddef_t nameportiplist_fields[] = { + { "port", &cfg_type_optional_port, 0 }, + { "addresses", &cfg_type_bracketed_sockaddrnameportlist, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_type_t cfg_type_nameportiplist = { + "nameportiplist", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, nameportiplist_fields +}; + +/*% + * masters element. + */ + +static void +doc_masterselement(cfg_printer_t *pctx, const cfg_type_t *type) { + UNUSED(type); + cfg_print_chars(pctx, "( ", 2); + cfg_print_cstr(pctx, "<masters>"); + cfg_print_chars(pctx, " | ", 3); + cfg_print_cstr(pctx, "<ipv4_address>"); + cfg_print_chars(pctx, " ", 1); + cfg_print_cstr(pctx, "[port <integer>]"); + cfg_print_chars(pctx, " | ", 3); + cfg_print_cstr(pctx, "<ipv6_address>"); + cfg_print_chars(pctx, " ", 1); + cfg_print_cstr(pctx, "[port <integer>]"); + cfg_print_chars(pctx, " )", 2); +} + +static isc_result_t +parse_masterselement(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_peektoken(pctx, CFG_LEXOPT_QSTRING)); + if (pctx->token.type == isc_tokentype_string || + pctx->token.type == isc_tokentype_qstring) { + if (cfg_lookingat_netaddr(pctx, CFG_ADDR_V4OK | CFG_ADDR_V6OK)) + CHECK(cfg_parse_sockaddr(pctx, &cfg_type_sockaddr, ret)); + else + CHECK(cfg_parse_astring(pctx, &cfg_type_astring, ret)); + } else { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected IP address or masters name"); + return (ISC_R_UNEXPECTEDTOKEN); + } + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static cfg_type_t cfg_type_masterselement = { + "masters_element", parse_masterselement, NULL, + doc_masterselement, NULL, NULL +}; diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c new file mode 100644 index 0000000..19a51a6 --- /dev/null +++ b/lib/isccfg/parser.c @@ -0,0 +1,2382 @@ +/* + * Copyright (C) 2004-2006 Internet Systems Consortium, Inc. ("ISC") + * 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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: parser.c,v 1.112.18.11 2006/02/28 03:10:49 marka Exp $ */ + +/*! \file */ + +#include <config.h> + +#include <isc/buffer.h> +#include <isc/dir.h> +#include <isc/formatcheck.h> +#include <isc/lex.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/net.h> +#include <isc/netaddr.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/sockaddr.h> +#include <isc/netscope.h> +#include <isc/util.h> +#include <isc/symtab.h> + +#include <isccfg/cfg.h> +#include <isccfg/grammar.h> +#include <isccfg/log.h> + +/* Shorthand */ +#define CAT CFG_LOGCATEGORY_CONFIG +#define MOD CFG_LOGMODULE_PARSER + +#define MAP_SYM 1 /* Unique type for isc_symtab */ + +#define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base) + +/* 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) + + +/* + * Forward declarations of static functions. + */ + +static void +free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj); + +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, const cfg_obj_t *obj); + +static void +free_list(cfg_parser_t *pctx, cfg_obj_t *obj); + +static isc_result_t +create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp); + +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 void +free_map(cfg_parser_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_getstringtoken(cfg_parser_t *pctx); + +static void +parser_complain(cfg_parser_t *pctx, isc_boolean_t is_warning, + unsigned int flags, const char *format, va_list args); + +/* + * 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 }; + +/* + * Configuration type definitions. + */ + +/*% + * An implicit list. These are formed by clauses that occur multiple times. + */ +static cfg_type_t cfg_type_implicitlist = { + "implicitlist", NULL, print_list, NULL, &cfg_rep_list, NULL }; + +/* Functions. */ + +void +cfg_print_obj(cfg_printer_t *pctx, const cfg_obj_t *obj) { + obj->type->print(pctx, obj); +} + +void +cfg_print_chars(cfg_printer_t *pctx, const char *text, int len) { + pctx->f(pctx->closure, text, len); +} + +static void +print_open(cfg_printer_t *pctx) { + cfg_print_chars(pctx, "{\n", 2); + pctx->indent++; +} + +static void +print_indent(cfg_printer_t *pctx) { + int indent = pctx->indent; + while (indent > 0) { + cfg_print_chars(pctx, "\t", 1); + indent--; + } +} + +static void +print_close(cfg_printer_t *pctx) { + pctx->indent--; + print_indent(pctx); + cfg_print_chars(pctx, "}", 1); +} + +isc_result_t +cfg_parse_obj(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(const 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. */ + +isc_result_t +cfg_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(cfg_create_obj(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); +} + +isc_result_t +cfg_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(cfg_create_tuple(pctx, type, &obj)); + for (f = fields, i = 0; f->name != NULL; f++, i++) + CHECK(cfg_parse_obj(pctx, f->type, &obj->value.tuple[i])); + + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +void +cfg_print_tuple(cfg_printer_t *pctx, const 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++) { + const cfg_obj_t *fieldobj = obj->value.tuple[i]; + if (need_space) + cfg_print_chars(pctx, " ", 1); + cfg_print_obj(pctx, fieldobj); + need_space = ISC_TF(fieldobj->type->print != cfg_print_void); + } +} + +void +cfg_doc_tuple(cfg_printer_t *pctx, const cfg_type_t *type) { + 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) + cfg_print_chars(pctx, " ", 1); + cfg_doc_obj(pctx, f->type); + need_space = ISC_TF(f->type->print != cfg_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(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_tuple)); +} + +const cfg_obj_t * +cfg_tuple_get(const 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); +} + +isc_result_t +cfg_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); + + cfg_parser_error(pctx, CFG_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); + + cfg_parser_error(pctx, CFG_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); + + cfg_parser_error(pctx, CFG_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, NULL, &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(cfg_create_list(pctx, &cfg_type_filelist, &pctx->open_files)); + CHECK(cfg_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) { + cfg_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 = cfg_parse_obj(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. */ + cfg_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 + */ +isc_result_t +cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + UNUSED(type); + return (cfg_create_obj(pctx, &cfg_type_void, ret)); +} + +void +cfg_print_void(cfg_printer_t *pctx, const cfg_obj_t *obj) { + UNUSED(pctx); + UNUSED(obj); +} + +void +cfg_doc_void(cfg_printer_t *pctx, const cfg_type_t *type) { + UNUSED(pctx); + UNUSED(type); +} + +isc_boolean_t +cfg_obj_isvoid(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_void)); +} + +cfg_type_t cfg_type_void = { + "void", cfg_parse_void, cfg_print_void, cfg_doc_void, &cfg_rep_void, + NULL }; + + +/* + * uint32 + */ +isc_result_t +cfg_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) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected number"); + return (ISC_R_UNEXPECTEDTOKEN); + } + + CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj)); + + obj->value.uint32 = pctx->token.value.as_ulong; + *ret = obj; + cleanup: + return (result); +} + +void +cfg_print_cstr(cfg_printer_t *pctx, const char *s) { + cfg_print_chars(pctx, s, strlen(s)); +} + +void +cfg_print_rawuint(cfg_printer_t *pctx, unsigned int u) { + char buf[32]; + snprintf(buf, sizeof(buf), "%u", u); + cfg_print_cstr(pctx, buf); +} + +void +cfg_print_uint32(cfg_printer_t *pctx, const cfg_obj_t *obj) { + cfg_print_rawuint(pctx, obj->value.uint32); +} + +isc_boolean_t +cfg_obj_isuint32(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_uint32)); +} + +isc_uint32_t +cfg_obj_asuint32(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint32); + return (obj->value.uint32); +} + +cfg_type_t cfg_type_uint32 = { + "integer", cfg_parse_uint32, cfg_print_uint32, cfg_doc_terminal, + &cfg_rep_uint32, NULL +}; + + +/* + * uint64 + */ +isc_boolean_t +cfg_obj_isuint64(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_uint64)); +} + +isc_uint64_t +cfg_obj_asuint64(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint64); + return (obj->value.uint64); +} + +void +cfg_print_uint64(cfg_printer_t *pctx, const cfg_obj_t *obj) { + char buf[32]; + snprintf(buf, sizeof(buf), "%" ISC_PRINT_QUADFORMAT "u", + obj->value.uint64); + cfg_print_cstr(pctx, buf); +} + +cfg_type_t cfg_type_uint64 = { + "64_bit_integer", NULL, cfg_print_uint64, cfg_doc_terminal, + &cfg_rep_uint64, NULL +}; + +/* + * qstring (quoted string), ustring (unquoted string), astring + * (any string) + */ + +/* 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(cfg_create_obj(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); +} + +isc_result_t +cfg_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, CFG_LEXOPT_QSTRING)); + if (pctx->token.type != isc_tokentype_qstring) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected quoted string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (create_string(pctx, + TOKEN_STRING(pctx), + &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) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected unquoted string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (create_string(pctx, + TOKEN_STRING(pctx), + &cfg_type_ustring, + ret)); + cleanup: + return (result); +} + +isc_result_t +cfg_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, + TOKEN_STRING(pctx), + &cfg_type_qstring, + ret)); + cleanup: + return (result); +} + +isc_boolean_t +cfg_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 (cfg_is_enum(s, enums)) + return (ISC_R_SUCCESS); + cfg_parser_error(pctx, 0, "'%s' unexpected", s); + return (ISC_R_UNEXPECTEDTOKEN); +} + +isc_result_t +cfg_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); +} + +void +cfg_doc_enum(cfg_printer_t *pctx, const cfg_type_t *type) { + const char * const *p; + cfg_print_chars(pctx, "( ", 2); + for (p = type->of; *p != NULL; p++) { + cfg_print_cstr(pctx, *p); + if (p[1] != NULL) + cfg_print_chars(pctx, " | ", 3); + } + cfg_print_chars(pctx, " )", 2); +} + +void +cfg_print_ustring(cfg_printer_t *pctx, const cfg_obj_t *obj) { + cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length); +} + +static void +print_qstring(cfg_printer_t *pctx, const cfg_obj_t *obj) { + cfg_print_chars(pctx, "\"", 1); + cfg_print_ustring(pctx, obj); + cfg_print_chars(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(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_string)); +} + +const char * +cfg_obj_asstring(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string); + return (obj->value.string.base); +} + +/* Quoted string only */ +cfg_type_t cfg_type_qstring = { + "quoted_string", cfg_parse_qstring, print_qstring, cfg_doc_terminal, + &cfg_rep_string, NULL +}; + +/* Unquoted string only */ +cfg_type_t cfg_type_ustring = { + "string", parse_ustring, cfg_print_ustring, cfg_doc_terminal, + &cfg_rep_string, NULL +}; + +/* Any string (quoted or unquoted); printed with quotes */ +cfg_type_t cfg_type_astring = { + "string", cfg_parse_astring, print_qstring, cfg_doc_terminal, + &cfg_rep_string, NULL +}; + +/* + * Booleans + */ + +isc_boolean_t +cfg_obj_isboolean(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_boolean)); +} + +isc_boolean_t +cfg_obj_asboolean(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_boolean); + return (obj->value.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(TOKEN_STRING(pctx), "true") == 0) || + (strcasecmp(TOKEN_STRING(pctx), "yes") == 0) || + (strcmp(TOKEN_STRING(pctx), "1") == 0)) { + value = ISC_TRUE; + } else if ((strcasecmp(TOKEN_STRING(pctx), "false") == 0) || + (strcasecmp(TOKEN_STRING(pctx), "no") == 0) || + (strcmp(TOKEN_STRING(pctx), "0") == 0)) { + value = ISC_FALSE; + } else { + goto bad_boolean; + } + + CHECK(cfg_create_obj(pctx, &cfg_type_boolean, &obj)); + obj->value.boolean = value; + *ret = obj; + return (result); + + bad_boolean: + cfg_parser_error(pctx, CFG_LOG_NEAR, "boolean expected"); + return (ISC_R_UNEXPECTEDTOKEN); + + cleanup: + return (result); +} + +static void +print_boolean(cfg_printer_t *pctx, const cfg_obj_t *obj) { + if (obj->value.boolean) + cfg_print_chars(pctx, "yes", 3); + else + cfg_print_chars(pctx, "no", 2); +} + +cfg_type_t cfg_type_boolean = { + "boolean", parse_boolean, print_boolean, cfg_doc_terminal, + &cfg_rep_boolean, NULL +}; + +/* + * Lists. + */ + +isc_result_t +cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) { + isc_result_t result; + CHECK(cfg_create_obj(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); + } +} + +isc_result_t +cfg_parse_listelt(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 = cfg_parse_obj(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(cfg_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(cfg_parse_listelt(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, const cfg_obj_t *obj) { + const cfg_list_t *list = &obj->value.list; + const cfg_listelt_t *elt; + + for (elt = ISC_LIST_HEAD(*list); + elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) { + print_indent(pctx); + cfg_print_obj(pctx, elt->obj); + cfg_print_chars(pctx, ";\n", 2); + } +} + +isc_result_t +cfg_parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type, + cfg_obj_t **ret) +{ + isc_result_t result; + CHECK(cfg_parse_special(pctx, '{')); + CHECK(parse_list(pctx, type, ret)); + CHECK(cfg_parse_special(pctx, '}')); + cleanup: + return (result); +} + +void +cfg_print_bracketed_list(cfg_printer_t *pctx, const cfg_obj_t *obj) { + print_open(pctx); + print_list(pctx, obj); + print_close(pctx); +} + +void +cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type) { + cfg_print_chars(pctx, "{ ", 2); + cfg_doc_obj(pctx, type->of); + cfg_print_chars(pctx, "; ... }", 7); +} + +/* + * Parse a homogeneous list whose elements are of type 'elttype' + * and where elements are separated by space. The list ends + * before the first semicolon. + */ +isc_result_t +cfg_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(cfg_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(cfg_parse_listelt(pctx, listof, &elt)); + ISC_LIST_APPEND(listobj->value.list, elt, link); + } + *ret = listobj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(listobj); + return (result); +} + +void +cfg_print_spacelist(cfg_printer_t *pctx, const cfg_obj_t *obj) { + const cfg_list_t *list = &obj->value.list; + const cfg_listelt_t *elt; + + for (elt = ISC_LIST_HEAD(*list); + elt != NULL; + elt = ISC_LIST_NEXT(elt, link)) { + cfg_print_obj(pctx, elt->obj); + if (ISC_LIST_NEXT(elt, link) != NULL) + cfg_print_chars(pctx, " ", 1); + } +} + +isc_boolean_t +cfg_obj_islist(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_list)); +} + +const cfg_listelt_t * +cfg_list_first(const 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)); +} + +const cfg_listelt_t * +cfg_list_next(const cfg_listelt_t *elt) { + REQUIRE(elt != NULL); + return (ISC_LIST_NEXT(elt, link)); +} + +const cfg_obj_t * +cfg_listelt_value(const 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. + */ +isc_result_t +cfg_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(TOKEN_STRING(pctx), "include") == 0) { + /* + * Turn the file name into a temporary configuration + * object just so that it is not overwritten by the + * semicolon token. + */ + CHECK(cfg_parse_obj(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(TOKEN_STRING(pctx), + clause->name) == 0) + goto done; + } + } + done: + if (clause == NULL || clause->name == NULL) { + cfg_parser_error(pctx, CFG_LOG_NOPREP, "unknown option"); + /* + * Try to recover by parsing this option as an unknown + * option and discarding it. + */ + CHECK(cfg_parse_obj(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) + cfg_parser_warning(pctx, 0, "option '%s' is obsolete", + clause->name); + if ((clause->flags & CFG_CLAUSEFLAG_NOTIMP) != 0) + cfg_parser_warning(pctx, 0, "option '%s' is " + "not implemented", clause->name); + if ((clause->flags & CFG_CLAUSEFLAG_NYI) != 0) + cfg_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(cfg_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) { + cfg_parser_error(pctx, CFG_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(cfg_parse_listelt(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) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "'%s' redefined", + clause->name); + result = ISC_R_EXISTS; + goto cleanup; + } else { + cfg_parser_error(pctx, CFG_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(cfg_parse_obj(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; }" + */ +isc_result_t +cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + CHECK(cfg_parse_special(pctx, '{')); + CHECK(cfg_parse_mapbody(pctx, type, ret)); + CHECK(cfg_parse_special(pctx, '}')); + cleanup: + return (result); +} + +/* + * Subroutine for cfg_parse_named_map() and cfg_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(cfg_parse_obj(pctx, nametype, &idobj)); + CHECK(cfg_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. + */ +isc_result_t +cfg_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 to be used for the "server" statement. + */ +isc_result_t +cfg_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)); +} + +/* + * Parse a map identified by a network prefix. + * Used for the "server" statement. + */ +isc_result_t +cfg_parse_netprefix_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + return (parse_any_named_map(pctx, &cfg_type_netprefix, type, ret)); +} + +void +cfg_print_mapbody(cfg_printer_t *pctx, const 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); + cfg_print_cstr(pctx, clause->name); + cfg_print_chars(pctx, " ", 1); + cfg_print_obj(pctx, elt->obj); + cfg_print_chars(pctx, ";\n", 2); + } + } else { + /* Single-valued. */ + print_indent(pctx); + cfg_print_cstr(pctx, clause->name); + cfg_print_chars(pctx, " ", 1); + cfg_print_obj(pctx, obj); + cfg_print_chars(pctx, ";\n", 2); + } + } else if (result == ISC_R_NOTFOUND) { + ; /* do nothing */ + } else { + INSIST(0); + } + } + } +} + +void +cfg_doc_mapbody(cfg_printer_t *pctx, const cfg_type_t *type) { + 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++) { + cfg_print_cstr(pctx, clause->name); + cfg_print_chars(pctx, " ", 1); + cfg_doc_obj(pctx, clause->type); + cfg_print_chars(pctx, ";", 1); + /* XXX print flags here? */ + cfg_print_chars(pctx, "\n\n", 2); + } + } +} + +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 } +}; + +void +cfg_print_map(cfg_printer_t *pctx, const cfg_obj_t *obj) { + if (obj->value.map.id != NULL) { + cfg_print_obj(pctx, obj->value.map.id); + cfg_print_chars(pctx, " ", 1); + } + print_open(pctx); + cfg_print_mapbody(pctx, obj); + print_close(pctx); +} + +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) + cfg_print_chars(pctx, " // ", 4); + else + cfg_print_chars(pctx, ", ", 2); + cfg_print_cstr(pctx, p->text); + first = ISC_FALSE; + } + } +} + +void +cfg_doc_map(cfg_printer_t *pctx, const cfg_type_t *type) { + const cfg_clausedef_t * const *clauseset; + const cfg_clausedef_t *clause; + + if (type->parse == cfg_parse_named_map) { + cfg_doc_obj(pctx, &cfg_type_astring); + cfg_print_chars(pctx, " ", 1); + } else if (type->parse == cfg_parse_addressed_map) { + cfg_doc_obj(pctx, &cfg_type_netaddr); + cfg_print_chars(pctx, " ", 1); + } else if (type->parse == cfg_parse_netprefix_map) { + cfg_doc_obj(pctx, &cfg_type_netprefix); + cfg_print_chars(pctx, " ", 1); + } + + print_open(pctx); + + for (clauseset = type->of; *clauseset != NULL; clauseset++) { + for (clause = *clauseset; + clause->name != NULL; + clause++) { + print_indent(pctx); + cfg_print_cstr(pctx, clause->name); + if (clause->type->print != cfg_print_void) + cfg_print_chars(pctx, " ", 1); + cfg_doc_obj(pctx, clause->type); + cfg_print_chars(pctx, ";", 1); + print_clause_flags(pctx, clause->flags); + cfg_print_chars(pctx, "\n", 1); + } + } + print_close(pctx); +} + +isc_boolean_t +cfg_obj_ismap(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_map)); +} + +isc_result_t +cfg_map_get(const cfg_obj_t *mapobj, const char* name, const cfg_obj_t **obj) { + isc_result_t result; + isc_symvalue_t val; + const 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); +} + +const cfg_obj_t * +cfg_map_getname(const 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(cfg_create_obj(pctx, &cfg_type_token, &obj)); + CHECK(cfg_gettoken(pctx, CFG_LEXOPT_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); + if (obj->value.string.base == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + obj->value.string.length = r.length; + memcpy(obj->value.string.base, r.base, r.length); + obj->value.string.base[r.length] = '\0'; + *ret = obj; + return (result); + + cleanup: + if (obj != NULL) + isc_mem_put(pctx->mctx, obj, sizeof(*obj)); + return (result); +} + +cfg_type_t cfg_type_token = { + "token", parse_token, cfg_print_ustring, cfg_doc_terminal, + &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(cfg_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) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "unexpected token"); + result = ISC_R_UNEXPECTEDTOKEN; + goto cleanup; + } + + CHECK(cfg_parse_listelt(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); +} + +cfg_type_t cfg_type_unsupported = { + "unsupported", parse_unsupported, cfg_print_spacelist, cfg_doc_terminal, + &cfg_rep_list, NULL +}; + +/* + * Try interpreting the current token as a network address. + * + * If CFG_ADDR_WILDOK is set in flags, "*" can be used as a wildcard + * and at least one of CFG_ADDR_V4OK and CFG_ADDR_V6OK must also be set. The + * "*" is interpreted as the IPv4 wildcard address if CFG_ADDR_V4OK is + * set (including the case where CFG_ADDR_V4OK and CFG_ADDR_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 = TOKEN_STRING(pctx); + if ((flags & CFG_ADDR_WILDOK) != 0 && strcmp(s, "*") == 0) { + if ((flags & CFG_ADDR_V4OK) != 0) { + isc_netaddr_any(na); + return (ISC_R_SUCCESS); + } else if ((flags & CFG_ADDR_V6OK) != 0) { + isc_netaddr_any6(na); + return (ISC_R_SUCCESS); + } else { + INSIST(0); + } + } else { + if ((flags & (CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK)) != 0) { + if (inet_pton(AF_INET, s, &in4a) == 1) { + isc_netaddr_fromin(na, &in4a); + return (ISC_R_SUCCESS); + } + } + if ((flags & CFG_ADDR_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 & CFG_ADDR_V6OK) != 0 && + strlen(s) <= 127U) { + char buf[128]; /* see lib/bind9/getaddresses.c */ + char *d; /* zone delimiter */ + isc_uint32_t zone = 0; /* scope zone ID */ + + strcpy(buf, s); + d = strchr(buf, '%'); + if (d != NULL) + *d = '\0'; + + if (inet_pton(AF_INET6, buf, &in6a) == 1) { + if (d != NULL) { +#ifdef ISC_PLATFORM_HAVESCOPEID + isc_result_t result; + + result = isc_netscope_pton(AF_INET6, + d + 1, + &in6a, + &zone); + if (result != ISC_R_SUCCESS) + return (result); +#else + return (ISC_R_BADADDRESSFORM); +#endif + } + + isc_netaddr_fromin6(na, &in6a); + isc_netaddr_setzone(na, zone); + return (ISC_R_SUCCESS); + } + } + } + return (ISC_R_UNEXPECTEDTOKEN); +} + +isc_result_t +cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) { + isc_result_t result; + const char *wild = ""; + const char *prefix = ""; + + CHECK(cfg_gettoken(pctx, 0)); + result = token_addr(pctx, flags, na); + if (result == ISC_R_UNEXPECTEDTOKEN) { + if ((flags & CFG_ADDR_WILDOK) != 0) + wild = " or '*'"; + if ((flags & CFG_ADDR_V4PREFIXOK) != 0) + wild = " or IPv4 prefix"; + if ((flags & CFG_ADDR_MASK) == CFG_ADDR_V4OK) + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected IPv4 address%s%s", + prefix, wild); + else if ((flags & CFG_ADDR_MASK) == CFG_ADDR_V6OK) + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected IPv6 address%s%s", + prefix, wild); + else + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected IP address%s%s", + prefix, wild); + } + cleanup: + return (result); +} + +isc_boolean_t +cfg_lookingat_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)); +} + +isc_result_t +cfg_parse_rawport(cfg_parser_t *pctx, unsigned int flags, in_port_t *port) { + isc_result_t result; + + CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER)); + + if ((flags & CFG_ADDR_WILDOK) != 0 && + pctx->token.type == isc_tokentype_string && + strcmp(TOKEN_STRING(pctx), "*") == 0) { + *port = 0; + return (ISC_R_SUCCESS); + } + if (pctx->token.type != isc_tokentype_number) { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected port number or '*'"); + return (ISC_R_UNEXPECTEDTOKEN); + } + if (pctx->token.value.as_ulong >= 65536U) { + cfg_parser_error(pctx, CFG_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); +} + +void +cfg_print_rawaddr(cfg_printer_t *pctx, const 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); + cfg_print_chars(pctx, isc_buffer_base(&buf), isc_buffer_usedlength(&buf)); +} + +/* netaddr */ + +static unsigned int netaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK; +static unsigned int netaddr4_flags = CFG_ADDR_V4OK; +static unsigned int netaddr4wild_flags = CFG_ADDR_V4OK | CFG_ADDR_WILDOK; +static unsigned int netaddr6_flags = CFG_ADDR_V6OK; +static unsigned int netaddr6wild_flags = CFG_ADDR_V6OK | CFG_ADDR_WILDOK; + +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; + unsigned int flags = *(const unsigned int *)type->of; + + CHECK(cfg_create_obj(pctx, type, &obj)); + CHECK(cfg_parse_rawaddr(pctx, flags, &netaddr)); + isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, 0); + *ret = obj; + return (ISC_R_SUCCESS); + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static void +cfg_doc_netaddr(cfg_printer_t *pctx, const cfg_type_t *type) { + const unsigned int *flagp = type->of; + int n = 0; + if (*flagp != CFG_ADDR_V4OK && *flagp != CFG_ADDR_V6OK) + cfg_print_chars(pctx, "( ", 2); + if (*flagp & CFG_ADDR_V4OK) { + cfg_print_cstr(pctx, "<ipv4_address>"); + n++; + } + if (*flagp & CFG_ADDR_V6OK) { + if (n != 0) + cfg_print_chars(pctx, " | ", 3); + cfg_print_cstr(pctx, "<ipv6_address>"); + n++; + } + if (*flagp & CFG_ADDR_WILDOK) { + if (n != 0) + cfg_print_chars(pctx, " | ", 3); + cfg_print_chars(pctx, "*", 1); + n++; + } + if (*flagp != CFG_ADDR_V4OK && *flagp != CFG_ADDR_V6OK) + cfg_print_chars(pctx, " )", 2); +} + +cfg_type_t cfg_type_netaddr = { + "netaddr", parse_netaddr, cfg_print_sockaddr, cfg_doc_netaddr, + &cfg_rep_sockaddr, &netaddr_flags +}; + +cfg_type_t cfg_type_netaddr4 = { + "netaddr4", parse_netaddr, cfg_print_sockaddr, cfg_doc_netaddr, + &cfg_rep_sockaddr, &netaddr4_flags +}; + +cfg_type_t cfg_type_netaddr4wild = { + "netaddr4wild", parse_netaddr, cfg_print_sockaddr, cfg_doc_netaddr, + &cfg_rep_sockaddr, &netaddr4wild_flags +}; + +cfg_type_t cfg_type_netaddr6 = { + "netaddr6", parse_netaddr, cfg_print_sockaddr, cfg_doc_netaddr, + &cfg_rep_sockaddr, &netaddr6_flags +}; + +cfg_type_t cfg_type_netaddr6wild = { + "netaddr6wild", parse_netaddr, cfg_print_sockaddr, cfg_doc_netaddr, + &cfg_rep_sockaddr, &netaddr6wild_flags +}; + +/* netprefix */ + +isc_result_t +cfg_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(cfg_parse_rawaddr(pctx, CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK | + CFG_ADDR_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) { + cfg_parser_error(pctx, CFG_LOG_NEAR, + "expected prefix length"); + return (ISC_R_UNEXPECTEDTOKEN); + } + prefixlen = pctx->token.value.as_ulong; + if (prefixlen > addrlen) { + cfg_parser_error(pctx, CFG_LOG_NOPREP, + "invalid prefix length"); + return (ISC_R_RANGE); + } + } else { + prefixlen = addrlen; + } + CHECK(cfg_create_obj(pctx, &cfg_type_netprefix, &obj)); + obj->value.netprefix.address = netaddr; + obj->value.netprefix.prefixlen = prefixlen; + *ret = obj; + return (ISC_R_SUCCESS); + cleanup: + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected network prefix"); + return (result); +} + +static void +print_netprefix(cfg_printer_t *pctx, const cfg_obj_t *obj) { + const cfg_netprefix_t *p = &obj->value.netprefix; + + cfg_print_rawaddr(pctx, &p->address); + cfg_print_chars(pctx, "/", 1); + cfg_print_rawuint(pctx, p->prefixlen); +} + +isc_boolean_t +cfg_obj_isnetprefix(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_netprefix)); +} + +void +cfg_obj_asnetprefix(const 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; +} + +cfg_type_t cfg_type_netprefix = { + "netprefix", cfg_parse_netprefix, print_netprefix, cfg_doc_terminal, + &cfg_rep_netprefix, NULL +}; + +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(cfg_create_obj(pctx, type, &obj)); + CHECK(cfg_parse_rawaddr(pctx, flags, &netaddr)); + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(TOKEN_STRING(pctx), "port") == 0) { + CHECK(cfg_gettoken(pctx, 0)); /* read "port" */ + CHECK(cfg_parse_rawport(pctx, flags, &port)); + } + isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port); + *ret = obj; + return (ISC_R_SUCCESS); + + cleanup: + CLEANUP_OBJ(obj); + return (result); +} + +static unsigned int sockaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK; +cfg_type_t cfg_type_sockaddr = { + "sockaddr", cfg_parse_sockaddr, cfg_print_sockaddr, cfg_doc_sockaddr, + &cfg_rep_sockaddr, &sockaddr_flags +}; + +isc_result_t +cfg_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_sockaddr, *flagp, ret)); +} + +void +cfg_print_sockaddr(cfg_printer_t *pctx, const 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)); + cfg_print_cstr(pctx, buf); + port = isc_sockaddr_getport(&obj->value.sockaddr); + if (port != 0) { + cfg_print_chars(pctx, " port ", 6); + cfg_print_rawuint(pctx, port); + } +} + +void +cfg_doc_sockaddr(cfg_printer_t *pctx, const cfg_type_t *type) { + const unsigned int *flagp = type->of; + int n = 0; + cfg_print_chars(pctx, "( ", 2); + if (*flagp & CFG_ADDR_V4OK) { + cfg_print_cstr(pctx, "<ipv4_address>"); + n++; + } + if (*flagp & CFG_ADDR_V6OK) { + if (n != 0) + cfg_print_chars(pctx, " | ", 3); + cfg_print_cstr(pctx, "<ipv6_address>"); + n++; + } + if (*flagp & CFG_ADDR_WILDOK) { + if (n != 0) + cfg_print_chars(pctx, " | ", 3); + cfg_print_chars(pctx, "*", 1); + n++; + } + cfg_print_chars(pctx, " ) ", 3); + if (*flagp & CFG_ADDR_WILDOK) { + cfg_print_cstr(pctx, "[ port ( <integer> | * ) ]"); + } else { + cfg_print_cstr(pctx, "[ port <integer> ]"); + } +} + +isc_boolean_t +cfg_obj_issockaddr(const cfg_obj_t *obj) { + REQUIRE(obj != NULL); + return (ISC_TF(obj->type->rep == &cfg_rep_sockaddr)); +} + +const isc_sockaddr_t * +cfg_obj_assockaddr(const cfg_obj_t *obj) { + REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_sockaddr); + return (&obj->value.sockaddr); +} + +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". */ + cfg_parser_error(pctx, CFG_LOG_NEAR, "token too big"); + break; + + case ISC_R_IOERROR: + cfg_parser_error(pctx, 0, "%s", + isc_result_totext(result)); + break; + + default: + cfg_parser_error(pctx, CFG_LOG_NEAR, "%s", + isc_result_totext(result)); + break; + } + return (result); +} + +void +cfg_ungettoken(cfg_parser_t *pctx) { + if (pctx->seen_eof) + return; + isc_lex_ungettoken(pctx->lexer, &pctx->token); + pctx->ungotten = ISC_TRUE; +} + +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, CFG_LEXOPT_QSTRING); + if (result != ISC_R_SUCCESS) + return (result); + + if (pctx->token.type != isc_tokentype_string && + pctx->token.type != isc_tokentype_qstring) { + cfg_parser_error(pctx, CFG_LOG_NEAR, "expected string"); + return (ISC_R_UNEXPECTEDTOKEN); + } + return (ISC_R_SUCCESS); +} + +void +cfg_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++; +} + +void +cfg_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 = ""; + size_t len; + + if (is_warning) + level = ISC_LOG_WARNING; + + snprintf(where, sizeof(where), "%s:%u: ", + current_file(pctx), pctx->line); + + len = vsnprintf(message, sizeof(message), format, args); + if (len >= sizeof(message)) + FATAL_ERROR(__FILE__, __LINE__, + "error message would overflow"); + + if ((flags & (CFG_LOG_NEAR|CFG_LOG_BEFORE|CFG_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 & CFG_LOG_NEAR) + prep = " near "; + else if (flags & CFG_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(const 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 ? "<unknown file>" : obj->file, + obj->line, msgbuf); + va_end(ap); +} + +const char * +cfg_obj_file(const cfg_obj_t *obj) { + return (obj->file); +} + +unsigned int +cfg_obj_line(const cfg_obj_t *obj) { + return (obj->line); +} + +isc_result_t +cfg_create_obj(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(cfg_create_obj(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(const 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); +} + +void +cfg_doc_obj(cfg_printer_t *pctx, const cfg_type_t *type) { + type->doc(pctx, type); +} + +void +cfg_doc_terminal(cfg_printer_t *pctx, const cfg_type_t *type) { + cfg_print_chars(pctx, "<", 1); + cfg_print_cstr(pctx, type->name); + cfg_print_chars(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; + cfg_doc_obj(&pctx, type); +} diff --git a/lib/isccfg/version.c b/lib/isccfg/version.c new file mode 100644 index 0000000..0b7287b --- /dev/null +++ b/lib/isccfg/version.c @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2004, 2005 Internet Systems Consortium, Inc. ("ISC") + * Copyright (C) 1998-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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: version.c,v 1.3.18.2 2005/04/29 00:17:15 marka Exp $ */ + +/*! \file */ + +#include <isccfg/version.h> + +const char cfg_version[] = VERSION; + +const unsigned int cfg_libinterface = LIBINTERFACE; +const unsigned int cfg_librevision = LIBREVISION; +const unsigned int cfg_libage = LIBAGE; + |