mirror of
https://github.com/isc-projects/bind9.git
synced 2026-03-14 14:42:22 -04:00
delegation-only check to all TLDs and root.
Note there are some TLDs that are NOT delegation
only (e.g. DE and MUSEUM) these can be excluded
from the checks by using exclude.
root-delegation-only exclude { "DE"; "MUSEUM"; };
3968 lines
101 KiB
C
3968 lines
101 KiB
C
/*
|
|
* Copyright (C) 2000-2003 Internet Software Consortium.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
|
|
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
|
* INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
|
|
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
|
|
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
|
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/* $Id: parser.c,v 1.70.2.22 2003/09/19 13:41:36 marka Exp $ */
|
|
|
|
#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/util.h>
|
|
#include <isc/symtab.h>
|
|
|
|
#include <isccfg/cfg.h>
|
|
#include <isccfg/log.h>
|
|
|
|
/* Shorthand */
|
|
#define CAT CFG_LOGCATEGORY_CONFIG
|
|
#define MOD CFG_LOGMODULE_PARSER
|
|
|
|
#define QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE)
|
|
|
|
/*
|
|
* Pass one of these flags to parser_error() to include the
|
|
* token text in log message.
|
|
*/
|
|
#define LOG_NEAR 0x00000001 /* Say "near <token>" */
|
|
#define LOG_BEFORE 0x00000002 /* Say "before <token>" */
|
|
#define LOG_NOPREP 0x00000004 /* Say just "<token>" */
|
|
|
|
#define MAP_SYM 1 /* Unique type for isc_symtab */
|
|
|
|
/* Clause may occur multiple times (e.g., "zone") */
|
|
#define CFG_CLAUSEFLAG_MULTI 0x00000001
|
|
/* Clause is obsolete */
|
|
#define CFG_CLAUSEFLAG_OBSOLETE 0x00000002
|
|
/* Clause is not implemented, and may never be */
|
|
#define CFG_CLAUSEFLAG_NOTIMP 0x00000004
|
|
/* Clause is not implemented yet */
|
|
#define CFG_CLAUSEFLAG_NYI 0x00000008
|
|
/* Default value has changed since earlier release */
|
|
#define CFG_CLAUSEFLAG_NEWDEFAULT 0x00000010
|
|
/*
|
|
* Clause needs to be interpreted during parsing
|
|
* by calling a callback function, like the
|
|
* "directory" option.
|
|
*/
|
|
#define CFG_CLAUSEFLAG_CALLBACK 0x00000020
|
|
|
|
/*
|
|
* Flags defining whether to accept certain types of network addresses.
|
|
*/
|
|
#define V4OK 0x00000001
|
|
#define V4PREFIXOK 0x00000002
|
|
#define V6OK 0x00000004
|
|
#define WILDOK 0x00000008
|
|
|
|
/* Check a return value. */
|
|
#define CHECK(op) \
|
|
do { result = (op); \
|
|
if (result != ISC_R_SUCCESS) goto cleanup; \
|
|
} while (0)
|
|
|
|
/* Clean up a configuration object if non-NULL. */
|
|
#define CLEANUP_OBJ(obj) \
|
|
do { if ((obj) != NULL) cfg_obj_destroy(pctx, &(obj)); } while (0)
|
|
|
|
|
|
typedef struct cfg_clausedef cfg_clausedef_t;
|
|
typedef struct cfg_tuplefielddef cfg_tuplefielddef_t;
|
|
typedef struct cfg_printer cfg_printer_t;
|
|
typedef ISC_LIST(cfg_listelt_t) cfg_list_t;
|
|
typedef struct cfg_map cfg_map_t;
|
|
typedef struct cfg_rep cfg_rep_t;
|
|
|
|
/*
|
|
* Function types for configuration object methods
|
|
*/
|
|
|
|
typedef isc_result_t (*cfg_parsefunc_t)(cfg_parser_t *, const cfg_type_t *type,
|
|
cfg_obj_t **);
|
|
typedef void (*cfg_printfunc_t)(cfg_printer_t *, cfg_obj_t *);
|
|
typedef void (*cfg_freefunc_t)(cfg_parser_t *, cfg_obj_t *);
|
|
|
|
|
|
/*
|
|
* Structure definitions
|
|
*/
|
|
|
|
/* The parser object. */
|
|
struct cfg_parser {
|
|
isc_mem_t * mctx;
|
|
isc_log_t * lctx;
|
|
isc_lex_t * lexer;
|
|
unsigned int errors;
|
|
unsigned int warnings;
|
|
isc_token_t token;
|
|
|
|
/* We are at the end of all input. */
|
|
isc_boolean_t seen_eof;
|
|
|
|
/* The current token has been pushed back. */
|
|
isc_boolean_t ungotten;
|
|
|
|
/*
|
|
* The stack of currently active files, represented
|
|
* as a configuration list of configuration strings.
|
|
* The head is the top-level file, subsequent elements
|
|
* (if any) are the nested include files, and the
|
|
* last element is the file currently being parsed.
|
|
*/
|
|
cfg_obj_t * open_files;
|
|
|
|
/*
|
|
* Names of files that we have parsed and closed
|
|
* and were previously on the open_file list.
|
|
* We keep these objects around after closing
|
|
* the files because the file names may still be
|
|
* referenced from other configuration objects
|
|
* for use in reporting semantic errors after
|
|
* parsing is complete.
|
|
*/
|
|
cfg_obj_t * closed_files;
|
|
|
|
/*
|
|
* Current line number. We maintain our own
|
|
* copy of this so that it is available even
|
|
* when a file has just been closed.
|
|
*/
|
|
unsigned int line;
|
|
|
|
cfg_parsecallback_t callback;
|
|
void *callbackarg;
|
|
};
|
|
|
|
/*
|
|
* A configuration printer object. This is an abstract
|
|
* interface to a destination to which text can be printed
|
|
* by calling the function 'f'.
|
|
*/
|
|
struct cfg_printer {
|
|
void (*f)(void *closure, const char *text, int textlen);
|
|
void *closure;
|
|
int indent;
|
|
};
|
|
|
|
/* A clause definition. */
|
|
|
|
struct cfg_clausedef {
|
|
const char *name;
|
|
cfg_type_t *type;
|
|
unsigned int flags;
|
|
};
|
|
|
|
/* A tuple field definition. */
|
|
|
|
struct cfg_tuplefielddef {
|
|
const char *name;
|
|
cfg_type_t *type;
|
|
unsigned int flags;
|
|
};
|
|
|
|
/* A configuration object type definition. */
|
|
struct cfg_type {
|
|
const char *name; /* For debugging purposes only */
|
|
cfg_parsefunc_t parse;
|
|
cfg_printfunc_t print;
|
|
cfg_rep_t * rep; /* Data representation */
|
|
const void * of; /* For meta-types */
|
|
};
|
|
|
|
/* A keyword-type definition, for things like "port <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;
|
|
char * file;
|
|
unsigned int line;
|
|
};
|
|
|
|
|
|
/* A list element. */
|
|
|
|
struct cfg_listelt {
|
|
cfg_obj_t *obj;
|
|
ISC_LINK(cfg_listelt_t) link;
|
|
};
|
|
|
|
/*
|
|
* Forward declarations of static functions.
|
|
*/
|
|
|
|
static isc_result_t
|
|
create_cfgobj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
|
|
|
|
static isc_result_t
|
|
create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
|
|
|
|
static isc_result_t
|
|
create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp);
|
|
|
|
static void
|
|
free_list(cfg_parser_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
|
|
cfg_obj_t **ret);
|
|
|
|
static void
|
|
free_string(cfg_parser_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
|
|
|
|
static isc_result_t
|
|
create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
|
|
|
|
static void
|
|
free_map(cfg_parser_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
get_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na);
|
|
|
|
static void
|
|
print(cfg_printer_t *pctx, const char *text, int len);
|
|
|
|
static void
|
|
print_void(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_enum_or_other(cfg_parser_t *pctx, const cfg_type_t *enumtype,
|
|
const cfg_type_t *othertype, cfg_obj_t **ret);
|
|
|
|
static isc_result_t
|
|
parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static void
|
|
print_mapbody(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static void
|
|
print_map(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static isc_result_t
|
|
parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static isc_result_t
|
|
parse_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static void
|
|
print_list(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static void
|
|
print_tuple(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static void
|
|
free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static void
|
|
print_spacelist(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static void
|
|
print_sockaddr(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_addrmatchelt(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static isc_result_t
|
|
parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static void
|
|
print_bracketed_list(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static isc_result_t
|
|
parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
static void
|
|
print_keyvalue(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_symtab_elt(cfg_parser_t *pctx, const char *name,
|
|
cfg_type_t *elttype, isc_symtab_t *symtab,
|
|
isc_boolean_t callback);
|
|
|
|
static void
|
|
free_noop(cfg_parser_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
cfg_gettoken(cfg_parser_t *pctx, int options);
|
|
|
|
static void
|
|
cfg_ungettoken(cfg_parser_t *pctx);
|
|
|
|
static isc_result_t
|
|
cfg_peektoken(cfg_parser_t *pctx, int options);
|
|
|
|
static isc_result_t
|
|
cfg_getstringtoken(cfg_parser_t *pctx);
|
|
|
|
static void
|
|
parser_error(cfg_parser_t *pctx, unsigned int flags,
|
|
const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4);
|
|
|
|
static void
|
|
parser_warning(cfg_parser_t *pctx, unsigned int flags,
|
|
const char *fmt, ...) ISC_FORMAT_PRINTF(3, 4);
|
|
|
|
static void
|
|
parser_complain(cfg_parser_t *pctx, isc_boolean_t is_warning,
|
|
unsigned int flags, const char *format, va_list args);
|
|
|
|
static void
|
|
print_uint32(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static void
|
|
print_ustring(cfg_printer_t *pctx, cfg_obj_t *obj);
|
|
|
|
static isc_result_t
|
|
parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
|
|
|
|
/*
|
|
* Data representations. These correspond to members of the
|
|
* "value" union in struct cfg_obj (except "void", which does
|
|
* not need a union member).
|
|
*/
|
|
|
|
cfg_rep_t cfg_rep_uint32 = { "uint32", free_noop };
|
|
cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop };
|
|
cfg_rep_t cfg_rep_string = { "string", free_string };
|
|
cfg_rep_t cfg_rep_boolean = { "boolean", free_noop };
|
|
cfg_rep_t cfg_rep_map = { "map", free_map };
|
|
cfg_rep_t cfg_rep_list = { "list", free_list };
|
|
cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple };
|
|
cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_noop };
|
|
cfg_rep_t cfg_rep_netprefix = { "netprefix", free_noop };
|
|
cfg_rep_t cfg_rep_void = { "void", free_noop };
|
|
|
|
/*
|
|
* Forward declarations of configuration type definitions.
|
|
* Additional types are declared publicly in cfg.h.
|
|
*/
|
|
|
|
static cfg_type_t cfg_type_boolean;
|
|
static cfg_type_t cfg_type_uint32;
|
|
static cfg_type_t cfg_type_qstring;
|
|
static cfg_type_t cfg_type_astring;
|
|
static cfg_type_t cfg_type_ustring;
|
|
static cfg_type_t cfg_type_optional_port;
|
|
static cfg_type_t cfg_type_bracketed_aml;
|
|
static cfg_type_t cfg_type_acl;
|
|
static cfg_type_t cfg_type_portiplist;
|
|
static cfg_type_t cfg_type_bracketed_sockaddrlist;
|
|
static cfg_type_t cfg_type_sockaddr;
|
|
static cfg_type_t cfg_type_netaddr;
|
|
static cfg_type_t cfg_type_optional_keyref;
|
|
static cfg_type_t cfg_type_options;
|
|
static cfg_type_t cfg_type_view;
|
|
static cfg_type_t cfg_type_viewopts;
|
|
static cfg_type_t cfg_type_key;
|
|
static cfg_type_t cfg_type_server;
|
|
static cfg_type_t cfg_type_controls;
|
|
static cfg_type_t cfg_type_bracketed_sockaddrkeylist;
|
|
static cfg_type_t cfg_type_querysource4;
|
|
static cfg_type_t cfg_type_querysource6;
|
|
static cfg_type_t cfg_type_querysource;
|
|
static cfg_type_t cfg_type_sockaddr4wild;
|
|
static cfg_type_t cfg_type_sockaddr6wild;
|
|
static cfg_type_t cfg_type_sockaddr;
|
|
static cfg_type_t cfg_type_netprefix;
|
|
static cfg_type_t cfg_type_zone;
|
|
static cfg_type_t cfg_type_zoneopts;
|
|
static cfg_type_t cfg_type_logging;
|
|
static cfg_type_t cfg_type_optional_facility;
|
|
static cfg_type_t cfg_type_void;
|
|
static cfg_type_t cfg_type_optional_class;
|
|
static cfg_type_t cfg_type_destinationlist;
|
|
static cfg_type_t cfg_type_size;
|
|
static cfg_type_t cfg_type_sizenodefault;
|
|
static cfg_type_t cfg_type_negated;
|
|
static cfg_type_t cfg_type_addrmatchelt;
|
|
static cfg_type_t cfg_type_unsupported;
|
|
static cfg_type_t cfg_type_token;
|
|
static cfg_type_t cfg_type_server_key_kludge;
|
|
static cfg_type_t cfg_type_optional_facility;
|
|
static cfg_type_t cfg_type_logseverity;
|
|
static cfg_type_t cfg_type_logfile;
|
|
static cfg_type_t cfg_type_lwres;
|
|
static cfg_type_t cfg_type_controls_sockaddr;
|
|
static cfg_type_t cfg_type_notifytype;
|
|
static cfg_type_t cfg_type_dialuptype;
|
|
|
|
/*
|
|
* Configuration type definitions.
|
|
*/
|
|
|
|
/* tkey-dhkey */
|
|
|
|
static cfg_tuplefielddef_t tkey_dhkey_fields[] = {
|
|
{ "name", &cfg_type_qstring, 0 },
|
|
{ "keyid", &cfg_type_uint32, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_type_t cfg_type_tkey_dhkey = {
|
|
"tkey-dhkey", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
tkey_dhkey_fields
|
|
};
|
|
|
|
/* listen-on */
|
|
|
|
static cfg_tuplefielddef_t listenon_fields[] = {
|
|
{ "port", &cfg_type_optional_port, 0 },
|
|
{ "acl", &cfg_type_bracketed_aml, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_listenon = {
|
|
"listenon", parse_tuple, print_tuple, &cfg_rep_tuple, listenon_fields };
|
|
|
|
/* acl */
|
|
|
|
static cfg_tuplefielddef_t acl_fields[] = {
|
|
{ "name", &cfg_type_astring, 0 },
|
|
{ "value", &cfg_type_bracketed_aml, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_type_t cfg_type_acl = {
|
|
"acl", parse_tuple, print_tuple, &cfg_rep_tuple, acl_fields };
|
|
|
|
|
|
/*
|
|
* "sockaddrkeylist", a list of socket addresses with optional keys
|
|
* and an optional default port, as used in the masters option.
|
|
* E.g.,
|
|
* "port 1234 { 10.0.0.1 key foo; 1::2 port 69; }"
|
|
*/
|
|
|
|
static cfg_tuplefielddef_t sockaddrkey_fields[] = {
|
|
{ "sockaddr", &cfg_type_sockaddr, 0 },
|
|
{ "key", &cfg_type_optional_keyref, 0 },
|
|
{ NULL, NULL, 0 },
|
|
};
|
|
|
|
static cfg_type_t cfg_type_sockaddrkey = {
|
|
"sockaddrkey", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
sockaddrkey_fields
|
|
};
|
|
|
|
static cfg_type_t cfg_type_bracketed_sockaddrkeylist = {
|
|
"bracketed_sockaddrkeylist", parse_bracketed_list,
|
|
print_bracketed_list, &cfg_rep_list, &cfg_type_sockaddrkey
|
|
};
|
|
|
|
static cfg_tuplefielddef_t sockaddrkeylist_fields[] = {
|
|
{ "port", &cfg_type_optional_port, 0 },
|
|
{ "addresses", &cfg_type_bracketed_sockaddrkeylist, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_sockaddrkeylist = {
|
|
"sockaddrkeylist", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
sockaddrkeylist_fields
|
|
};
|
|
|
|
/*
|
|
* A list of socket addresses with an optional default port,
|
|
* as used in the also-notify option. E.g.,
|
|
* "port 1234 { 10.0.0.1; 1::2 port 69; }"
|
|
*/
|
|
static cfg_tuplefielddef_t portiplist_fields[] = {
|
|
{ "port", &cfg_type_optional_port, 0 },
|
|
{ "addresses", &cfg_type_bracketed_sockaddrlist, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_portiplist = {
|
|
"portiplist", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
portiplist_fields
|
|
};
|
|
|
|
/*
|
|
* A public key, as in the "pubkey" statement.
|
|
*/
|
|
static cfg_tuplefielddef_t pubkey_fields[] = {
|
|
{ "flags", &cfg_type_uint32, 0 },
|
|
{ "protocol", &cfg_type_uint32, 0 },
|
|
{ "algorithm", &cfg_type_uint32, 0 },
|
|
{ "key", &cfg_type_qstring, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_pubkey = {
|
|
"pubkey", parse_tuple, print_tuple, &cfg_rep_tuple, pubkey_fields };
|
|
|
|
|
|
/*
|
|
* A list of RR types, used in grant statements.
|
|
* Note that the old parser allows quotes around the RR type names.
|
|
*/
|
|
static cfg_type_t cfg_type_rrtypelist = {
|
|
"rrtypelist", parse_spacelist, print_spacelist, &cfg_rep_list,
|
|
&cfg_type_astring
|
|
};
|
|
|
|
static const char *mode_enums[] = { "grant", "deny", NULL };
|
|
static cfg_type_t cfg_type_mode = {
|
|
"mode", parse_enum, print_ustring, &cfg_rep_string,
|
|
&mode_enums
|
|
};
|
|
|
|
static const char *matchtype_enums[] = {
|
|
"name", "subdomain", "wildcard", "self", NULL };
|
|
static cfg_type_t cfg_type_matchtype = {
|
|
"matchtype", parse_enum, print_ustring, &cfg_rep_string,
|
|
&matchtype_enums
|
|
};
|
|
|
|
/*
|
|
* A grant statement, used in the update policy.
|
|
*/
|
|
static cfg_tuplefielddef_t grant_fields[] = {
|
|
{ "mode", &cfg_type_mode, 0 },
|
|
{ "identity", &cfg_type_astring, 0 }, /* domain name */
|
|
{ "matchtype", &cfg_type_matchtype, 0 },
|
|
{ "name", &cfg_type_astring, 0 }, /* domain name */
|
|
{ "types", &cfg_type_rrtypelist, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_grant = {
|
|
"grant", parse_tuple, print_tuple, &cfg_rep_tuple, grant_fields };
|
|
|
|
static cfg_type_t cfg_type_updatepolicy = {
|
|
"update_policy", parse_bracketed_list, print_bracketed_list,
|
|
&cfg_rep_list, &cfg_type_grant
|
|
};
|
|
|
|
/*
|
|
* A view statement.
|
|
*/
|
|
static cfg_tuplefielddef_t view_fields[] = {
|
|
{ "name", &cfg_type_astring, 0 },
|
|
{ "class", &cfg_type_optional_class, 0 },
|
|
{ "options", &cfg_type_viewopts, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_view = {
|
|
"view", parse_tuple, print_tuple, &cfg_rep_tuple, view_fields };
|
|
|
|
/*
|
|
* A zone statement.
|
|
*/
|
|
static cfg_tuplefielddef_t zone_fields[] = {
|
|
{ "name", &cfg_type_astring, 0 },
|
|
{ "class", &cfg_type_optional_class, 0 },
|
|
{ "options", &cfg_type_zoneopts, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_zone = {
|
|
"zone", parse_tuple, print_tuple, &cfg_rep_tuple, zone_fields };
|
|
|
|
/*
|
|
* A "category" clause in the "logging" statement.
|
|
*/
|
|
static cfg_tuplefielddef_t category_fields[] = {
|
|
{ "name", &cfg_type_astring, 0 },
|
|
{ "destinations", &cfg_type_destinationlist,0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_category = {
|
|
"category", parse_tuple, print_tuple, &cfg_rep_tuple, category_fields };
|
|
|
|
|
|
/*
|
|
* A trusted key, as used in the "trusted-keys" statement.
|
|
*/
|
|
static cfg_tuplefielddef_t trustedkey_fields[] = {
|
|
{ "name", &cfg_type_astring, 0 },
|
|
{ "flags", &cfg_type_uint32, 0 },
|
|
{ "protocol", &cfg_type_uint32, 0 },
|
|
{ "algorithm", &cfg_type_uint32, 0 },
|
|
{ "key", &cfg_type_qstring, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_trustedkey = {
|
|
"trustedkey", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
trustedkey_fields
|
|
};
|
|
|
|
|
|
static keyword_type_t wild_class_kw = { "class", &cfg_type_ustring };
|
|
|
|
static cfg_type_t cfg_type_optional_wild_class = {
|
|
"optional_wild_class", parse_optional_keyvalue,
|
|
print_keyvalue, &cfg_rep_string, &wild_class_kw
|
|
};
|
|
|
|
static keyword_type_t wild_type_kw = { "type", &cfg_type_ustring };
|
|
|
|
static cfg_type_t cfg_type_optional_wild_type = {
|
|
"optional_wild_type", parse_optional_keyvalue,
|
|
print_keyvalue, &cfg_rep_string, &wild_type_kw
|
|
};
|
|
|
|
static keyword_type_t wild_name_kw = { "name", &cfg_type_qstring };
|
|
|
|
static cfg_type_t cfg_type_optional_wild_name = {
|
|
"optional_wild_name", parse_optional_keyvalue,
|
|
print_keyvalue, &cfg_rep_string, &wild_name_kw
|
|
};
|
|
|
|
/*
|
|
* An rrset ordering element.
|
|
*/
|
|
static cfg_tuplefielddef_t rrsetorderingelement_fields[] = {
|
|
{ "class", &cfg_type_optional_wild_class, 0 },
|
|
{ "type", &cfg_type_optional_wild_type, 0 },
|
|
{ "name", &cfg_type_optional_wild_name, 0 },
|
|
{ "order", &cfg_type_ustring, 0 }, /* must be literal "order" */
|
|
{ "ordering", &cfg_type_ustring, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_rrsetorderingelement = {
|
|
"rrsetorderingelement", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
rrsetorderingelement_fields
|
|
};
|
|
|
|
/*
|
|
* A global or view "check-names" option. Note that the zone
|
|
* "check-names" option has a different syntax.
|
|
*/
|
|
static cfg_tuplefielddef_t checknames_fields[] = {
|
|
{ "type", &cfg_type_ustring, 0 },
|
|
{ "mode", &cfg_type_ustring, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_checknames = {
|
|
"checknames", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
checknames_fields
|
|
};
|
|
|
|
static cfg_type_t cfg_type_bracketed_sockaddrlist = {
|
|
"bracketed_sockaddrlist", parse_bracketed_list, print_bracketed_list,
|
|
&cfg_rep_list, &cfg_type_sockaddr
|
|
};
|
|
|
|
static cfg_type_t cfg_type_rrsetorder = {
|
|
"rrsetorder", parse_bracketed_list, print_bracketed_list,
|
|
&cfg_rep_list, &cfg_type_rrsetorderingelement
|
|
};
|
|
|
|
static keyword_type_t port_kw = { "port", &cfg_type_uint32 };
|
|
|
|
static cfg_type_t cfg_type_optional_port = {
|
|
"optional_port", parse_optional_keyvalue, print_keyvalue,
|
|
&cfg_rep_uint32, &port_kw
|
|
};
|
|
|
|
/* A list of keys, as in the "key" clause of the controls statement. */
|
|
static cfg_type_t cfg_type_keylist = {
|
|
"keylist", parse_bracketed_list, print_bracketed_list, &cfg_rep_list,
|
|
&cfg_type_astring
|
|
};
|
|
|
|
static cfg_type_t cfg_type_trustedkeys = {
|
|
"trusted-keys", parse_bracketed_list, print_bracketed_list, &cfg_rep_list,
|
|
&cfg_type_trustedkey
|
|
};
|
|
|
|
/*
|
|
* An implicit list. These are formed by clauses that occur multiple times.
|
|
*/
|
|
static cfg_type_t cfg_type_implicitlist = {
|
|
"implicitlist", NULL, print_list, &cfg_rep_list, NULL };
|
|
|
|
static const char *forwardtype_enums[] = { "first", "only", NULL };
|
|
static cfg_type_t cfg_type_forwardtype = {
|
|
"forwardtype", parse_enum, print_ustring, &cfg_rep_string,
|
|
&forwardtype_enums
|
|
};
|
|
|
|
static const char *zonetype_enums[] = {
|
|
"master", "slave", "stub", "hint", "forward", "delegation-only", NULL };
|
|
static cfg_type_t cfg_type_zonetype = {
|
|
"zonetype", parse_enum, print_ustring, &cfg_rep_string,
|
|
&zonetype_enums
|
|
};
|
|
|
|
static const char *loglevel_enums[] = {
|
|
"critical", "error", "warning", "notice", "info", "dynamic", NULL };
|
|
static cfg_type_t cfg_type_loglevel = {
|
|
"loglevel", parse_enum, print_ustring, &cfg_rep_string,
|
|
&loglevel_enums
|
|
};
|
|
|
|
static const char *transferformat_enums[] = {
|
|
"many-answers", "one-answer", NULL };
|
|
static cfg_type_t cfg_type_transferformat = {
|
|
"transferformat", parse_enum, print_ustring, &cfg_rep_string,
|
|
&transferformat_enums
|
|
};
|
|
|
|
/*
|
|
* Clauses that can be found within the top level of the named.conf
|
|
* file only.
|
|
*/
|
|
static cfg_clausedef_t
|
|
namedconf_clauses[] = {
|
|
{ "options", &cfg_type_options, 0 },
|
|
{ "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI },
|
|
{ "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI },
|
|
{ "logging", &cfg_type_logging, 0 },
|
|
{ "view", &cfg_type_view, CFG_CLAUSEFLAG_MULTI },
|
|
{ "lwres", &cfg_type_lwres, CFG_CLAUSEFLAG_MULTI },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
/*
|
|
* Clauses that can occur at the top level or in the view
|
|
* statement, but not in the options block.
|
|
*/
|
|
static cfg_clausedef_t
|
|
namedconf_or_view_clauses[] = {
|
|
{ "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI },
|
|
{ "zone", &cfg_type_zone, CFG_CLAUSEFLAG_MULTI },
|
|
{ "server", &cfg_type_server, CFG_CLAUSEFLAG_MULTI },
|
|
#ifdef ISC_RFC2535
|
|
{ "trusted-keys", &cfg_type_trustedkeys, CFG_CLAUSEFLAG_MULTI },
|
|
#else
|
|
{ "trusted-keys", &cfg_type_trustedkeys,
|
|
CFG_CLAUSEFLAG_MULTI|CFG_CLAUSEFLAG_OBSOLETE },
|
|
#endif
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
/*
|
|
* Clauses that can be found within the 'options' statement.
|
|
*/
|
|
static cfg_clausedef_t
|
|
options_clauses[] = {
|
|
{ "blackhole", &cfg_type_bracketed_aml, 0 },
|
|
{ "coresize", &cfg_type_size, 0 },
|
|
{ "datasize", &cfg_type_size, 0 },
|
|
{ "deallocate-on-exit", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "directory", &cfg_type_qstring, CFG_CLAUSEFLAG_CALLBACK },
|
|
{ "dump-file", &cfg_type_qstring, 0 },
|
|
{ "fake-iquery", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "files", &cfg_type_size, 0 },
|
|
{ "has-old-clients", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "heartbeat-interval", &cfg_type_uint32, 0 },
|
|
{ "host-statistics", &cfg_type_boolean, CFG_CLAUSEFLAG_NOTIMP },
|
|
{ "interface-interval", &cfg_type_uint32, 0 },
|
|
{ "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI },
|
|
{ "listen-on-v6", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI },
|
|
{ "match-mapped-addresses", &cfg_type_boolean, 0 },
|
|
{ "memstatistics-file", &cfg_type_qstring, CFG_CLAUSEFLAG_NOTIMP },
|
|
{ "multiple-cnames", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "named-xfer", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "pid-file", &cfg_type_qstring, 0 },
|
|
{ "port", &cfg_type_uint32, 0 },
|
|
{ "random-device", &cfg_type_qstring, 0 },
|
|
{ "recursive-clients", &cfg_type_uint32, 0 },
|
|
{ "rrset-order", &cfg_type_rrsetorder, CFG_CLAUSEFLAG_NOTIMP },
|
|
{ "serial-queries", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "serial-query-rate", &cfg_type_uint32, 0 },
|
|
{ "stacksize", &cfg_type_size, 0 },
|
|
{ "statistics-file", &cfg_type_qstring, 0 },
|
|
{ "statistics-interval", &cfg_type_uint32, CFG_CLAUSEFLAG_NYI },
|
|
{ "tcp-clients", &cfg_type_uint32, 0 },
|
|
{ "tkey-dhkey", &cfg_type_tkey_dhkey, 0 },
|
|
{ "tkey-gssapi-credential", &cfg_type_qstring, 0 },
|
|
{ "tkey-domain", &cfg_type_qstring, 0 },
|
|
{ "transfers-per-ns", &cfg_type_uint32, 0 },
|
|
{ "transfers-in", &cfg_type_uint32, 0 },
|
|
{ "transfers-out", &cfg_type_uint32, 0 },
|
|
{ "treat-cr-as-space", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "use-id-pool", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "use-ixfr", &cfg_type_boolean, 0 },
|
|
{ "version", &cfg_type_qstring, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
|
|
static cfg_type_t cfg_type_namelist = {
|
|
"namelist", parse_bracketed_list, print_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,
|
|
&cfg_rep_list, &exclude_kw };
|
|
|
|
/*
|
|
* Clauses that can be found within the 'view' statement,
|
|
* with defaults in the 'options' statement.
|
|
*/
|
|
|
|
static cfg_clausedef_t
|
|
view_clauses[] = {
|
|
{ "allow-recursion", &cfg_type_bracketed_aml, 0 },
|
|
{ "allow-v6-synthesis", &cfg_type_bracketed_aml, 0 },
|
|
{ "sortlist", &cfg_type_bracketed_aml, 0 },
|
|
{ "topology", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_NOTIMP },
|
|
{ "auth-nxdomain", &cfg_type_boolean, CFG_CLAUSEFLAG_NEWDEFAULT },
|
|
{ "minimal-responses", &cfg_type_boolean, 0 },
|
|
{ "recursion", &cfg_type_boolean, 0 },
|
|
{ "provide-ixfr", &cfg_type_boolean, 0 },
|
|
{ "request-ixfr", &cfg_type_boolean, 0 },
|
|
{ "fetch-glue", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "rfc2308-type1", &cfg_type_boolean, CFG_CLAUSEFLAG_NYI },
|
|
{ "additional-from-auth", &cfg_type_boolean, 0 },
|
|
{ "additional-from-cache", &cfg_type_boolean, 0 },
|
|
/*
|
|
* Note that the query-source option syntax is different
|
|
* from the other -source options.
|
|
*/
|
|
{ "query-source", &cfg_type_querysource4, 0 },
|
|
{ "query-source-v6", &cfg_type_querysource6, 0 },
|
|
{ "cleaning-interval", &cfg_type_uint32, 0 },
|
|
{ "min-roots", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTIMP },
|
|
{ "lame-ttl", &cfg_type_uint32, 0 },
|
|
{ "max-ncache-ttl", &cfg_type_uint32, 0 },
|
|
{ "max-cache-ttl", &cfg_type_uint32, 0 },
|
|
{ "transfer-format", &cfg_type_transferformat, 0 },
|
|
{ "max-cache-size", &cfg_type_sizenodefault, 0 },
|
|
{ "check-names", &cfg_type_checknames,
|
|
CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NOTIMP },
|
|
{ "cache-file", &cfg_type_qstring, 0 },
|
|
{ "root-delegation-only", &cfg_type_optional_exclude, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
/*
|
|
* Clauses that can be found within the 'view' statement only.
|
|
*/
|
|
static cfg_clausedef_t
|
|
view_only_clauses[] = {
|
|
{ "match-clients", &cfg_type_bracketed_aml, 0 },
|
|
{ "match-destinations", &cfg_type_bracketed_aml, 0 },
|
|
{ "match-recursive-only", &cfg_type_boolean, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
/*
|
|
* Clauses that can be found in a 'zone' statement,
|
|
* with defaults in the 'view' or 'options' statement.
|
|
*/
|
|
static cfg_clausedef_t
|
|
zone_clauses[] = {
|
|
{ "allow-query", &cfg_type_bracketed_aml, 0 },
|
|
{ "allow-transfer", &cfg_type_bracketed_aml, 0 },
|
|
{ "allow-update-forwarding", &cfg_type_bracketed_aml, 0 },
|
|
{ "allow-notify", &cfg_type_bracketed_aml, 0 },
|
|
{ "notify", &cfg_type_notifytype, 0 },
|
|
{ "notify-source", &cfg_type_sockaddr4wild, 0 },
|
|
{ "notify-source-v6", &cfg_type_sockaddr6wild, 0 },
|
|
{ "also-notify", &cfg_type_portiplist, 0 },
|
|
{ "dialup", &cfg_type_dialuptype, 0 },
|
|
{ "forward", &cfg_type_forwardtype, 0 },
|
|
{ "forwarders", &cfg_type_portiplist, 0 },
|
|
{ "maintain-ixfr-base", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "max-ixfr-log-size", &cfg_type_size, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "transfer-source", &cfg_type_sockaddr4wild, 0 },
|
|
{ "transfer-source-v6", &cfg_type_sockaddr6wild, 0 },
|
|
{ "max-transfer-time-in", &cfg_type_uint32, 0 },
|
|
{ "max-transfer-time-out", &cfg_type_uint32, 0 },
|
|
{ "max-transfer-idle-in", &cfg_type_uint32, 0 },
|
|
{ "max-transfer-idle-out", &cfg_type_uint32, 0 },
|
|
{ "max-retry-time", &cfg_type_uint32, 0 },
|
|
{ "min-retry-time", &cfg_type_uint32, 0 },
|
|
{ "max-refresh-time", &cfg_type_uint32, 0 },
|
|
{ "min-refresh-time", &cfg_type_uint32, 0 },
|
|
{ "sig-validity-interval", &cfg_type_uint32, 0 },
|
|
{ "zone-statistics", &cfg_type_boolean, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
/*
|
|
* Clauses that can be found in a 'zone' statement
|
|
* only.
|
|
*/
|
|
static cfg_clausedef_t
|
|
zone_only_clauses[] = {
|
|
{ "type", &cfg_type_zonetype, 0 },
|
|
{ "allow-update", &cfg_type_bracketed_aml, 0 },
|
|
{ "file", &cfg_type_qstring, 0 },
|
|
{ "ixfr-base", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "ixfr-tmp-file", &cfg_type_qstring, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "masters", &cfg_type_sockaddrkeylist, 0 },
|
|
{ "pubkey", &cfg_type_pubkey,
|
|
CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "update-policy", &cfg_type_updatepolicy, 0 },
|
|
{ "database", &cfg_type_astring, 0 },
|
|
{ "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_ustring, CFG_CLAUSEFLAG_NOTIMP },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
|
|
/* The top-level named.conf syntax. */
|
|
|
|
static cfg_clausedef_t *
|
|
namedconf_clausesets[] = {
|
|
namedconf_clauses,
|
|
namedconf_or_view_clauses,
|
|
NULL
|
|
};
|
|
|
|
LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_namedconf = {
|
|
"namedconf", parse_mapbody, print_mapbody, &cfg_rep_map,
|
|
namedconf_clausesets
|
|
};
|
|
|
|
/* The "options" statement syntax. */
|
|
|
|
static cfg_clausedef_t *
|
|
options_clausesets[] = {
|
|
options_clauses,
|
|
view_clauses,
|
|
zone_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_options = {
|
|
"options", parse_map, print_map, &cfg_rep_map, options_clausesets };
|
|
|
|
/* The "view" statement syntax. */
|
|
|
|
static cfg_clausedef_t *
|
|
view_clausesets[] = {
|
|
view_only_clauses,
|
|
namedconf_or_view_clauses,
|
|
view_clauses,
|
|
zone_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_viewopts = {
|
|
"view", parse_map, print_map, &cfg_rep_map, view_clausesets };
|
|
|
|
/* The "zone" statement syntax. */
|
|
|
|
static cfg_clausedef_t *
|
|
zone_clausesets[] = {
|
|
zone_only_clauses,
|
|
zone_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_zoneopts = {
|
|
"zoneopts", parse_map, print_map, &cfg_rep_map, zone_clausesets };
|
|
|
|
/*
|
|
* Clauses that can be found within the 'key' statement.
|
|
*/
|
|
static cfg_clausedef_t
|
|
key_clauses[] = {
|
|
{ "algorithm", &cfg_type_astring, 0 },
|
|
{ "secret", &cfg_type_astring, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_clausedef_t *
|
|
key_clausesets[] = {
|
|
key_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_key = {
|
|
"key", parse_named_map, print_map, &cfg_rep_map, key_clausesets };
|
|
|
|
|
|
/*
|
|
* Clauses that can be found in a 'server' statement.
|
|
*/
|
|
static cfg_clausedef_t
|
|
server_clauses[] = {
|
|
{ "bogus", &cfg_type_boolean, 0 },
|
|
{ "provide-ixfr", &cfg_type_boolean, 0 },
|
|
{ "request-ixfr", &cfg_type_boolean, 0 },
|
|
{ "support-ixfr", &cfg_type_boolean, CFG_CLAUSEFLAG_OBSOLETE },
|
|
{ "transfers", &cfg_type_uint32, 0 },
|
|
{ "transfer-format", &cfg_type_transferformat, 0 },
|
|
{ "keys", &cfg_type_server_key_kludge, 0 },
|
|
{ "edns", &cfg_type_boolean, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_clausedef_t *
|
|
server_clausesets[] = {
|
|
server_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_server = {
|
|
"server", parse_addressed_map, print_map, &cfg_rep_map,
|
|
server_clausesets
|
|
};
|
|
|
|
|
|
/*
|
|
* Clauses that can be found in a 'channel' clause in the
|
|
* 'logging' statement.
|
|
*
|
|
* These have some additional constraints that need to be
|
|
* checked after parsing:
|
|
* - There must exactly one of file/syslog/null/stderr
|
|
*
|
|
*/
|
|
static cfg_clausedef_t
|
|
channel_clauses[] = {
|
|
/* Destinations. We no longer require these to be first. */
|
|
{ "file", &cfg_type_logfile, 0 },
|
|
{ "syslog", &cfg_type_optional_facility, 0 },
|
|
{ "null", &cfg_type_void, 0 },
|
|
{ "stderr", &cfg_type_void, 0 },
|
|
/* Options. We now accept these for the null channel, too. */
|
|
{ "severity", &cfg_type_logseverity, 0 },
|
|
{ "print-time", &cfg_type_boolean, 0 },
|
|
{ "print-severity", &cfg_type_boolean, 0 },
|
|
{ "print-category", &cfg_type_boolean, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_clausedef_t *
|
|
channel_clausesets[] = {
|
|
channel_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_channel = {
|
|
"channel", parse_named_map, print_map,
|
|
&cfg_rep_map, channel_clausesets
|
|
};
|
|
|
|
/* A list of log destination, used in the "category" clause. */
|
|
static cfg_type_t cfg_type_destinationlist = {
|
|
"destinationlist", parse_bracketed_list, print_bracketed_list,
|
|
&cfg_rep_list, &cfg_type_astring };
|
|
|
|
/*
|
|
* Clauses that can be found in a 'logging' statement.
|
|
*/
|
|
static cfg_clausedef_t
|
|
logging_clauses[] = {
|
|
{ "channel", &cfg_type_channel, CFG_CLAUSEFLAG_MULTI },
|
|
{ "category", &cfg_type_category, CFG_CLAUSEFLAG_MULTI },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_clausedef_t *
|
|
logging_clausesets[] = {
|
|
logging_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_logging = {
|
|
"logging", parse_map, print_map, &cfg_rep_map, logging_clausesets };
|
|
|
|
|
|
/* Functions. */
|
|
|
|
static void
|
|
print_obj(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
obj->type->print(pctx, obj);
|
|
}
|
|
|
|
static void
|
|
print(cfg_printer_t *pctx, const char *text, int len) {
|
|
pctx->f(pctx->closure, text, len);
|
|
}
|
|
|
|
static void
|
|
print_open(cfg_printer_t *pctx) {
|
|
print(pctx, "{\n", 2);
|
|
pctx->indent++;
|
|
}
|
|
|
|
static void
|
|
print_indent(cfg_printer_t *pctx) {
|
|
int indent = pctx->indent;
|
|
while (indent > 0) {
|
|
print(pctx, "\t", 1);
|
|
indent--;
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_close(cfg_printer_t *pctx) {
|
|
pctx->indent--;
|
|
print_indent(pctx);
|
|
print(pctx, "}", 1);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
INSIST(ret != NULL && *ret == NULL);
|
|
result = type->parse(pctx, type, ret);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
INSIST(*ret != NULL);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
void
|
|
cfg_print(cfg_obj_t *obj,
|
|
void (*f)(void *closure, const char *text, int textlen),
|
|
void *closure)
|
|
{
|
|
cfg_printer_t pctx;
|
|
pctx.f = f;
|
|
pctx.closure = closure;
|
|
pctx.indent = 0;
|
|
obj->type->print(&pctx, obj);
|
|
}
|
|
|
|
|
|
/* Tuples. */
|
|
|
|
static isc_result_t
|
|
create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
const cfg_tuplefielddef_t *fields = type->of;
|
|
const cfg_tuplefielddef_t *f;
|
|
cfg_obj_t *obj = NULL;
|
|
unsigned int nfields = 0;
|
|
int i;
|
|
|
|
for (f = fields; f->name != NULL; f++)
|
|
nfields++;
|
|
|
|
CHECK(create_cfgobj(pctx, type, &obj));
|
|
obj->value.tuple = isc_mem_get(pctx->mctx,
|
|
nfields * sizeof(cfg_obj_t *));
|
|
if (obj->value.tuple == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup;
|
|
}
|
|
for (f = fields, i = 0; f->name != NULL; f++, i++)
|
|
obj->value.tuple[i] = NULL;
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
if (obj != NULL)
|
|
isc_mem_put(pctx->mctx, obj, sizeof(*obj));
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
const cfg_tuplefielddef_t *fields = type->of;
|
|
const cfg_tuplefielddef_t *f;
|
|
cfg_obj_t *obj = NULL;
|
|
unsigned int i;
|
|
|
|
CHECK(create_tuple(pctx, type, &obj));
|
|
for (f = fields, i = 0; f->name != NULL; f++, i++)
|
|
CHECK(parse(pctx, f->type, &obj->value.tuple[i]));
|
|
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_tuple(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
unsigned int i;
|
|
const cfg_tuplefielddef_t *fields = obj->type->of;
|
|
const cfg_tuplefielddef_t *f;
|
|
isc_boolean_t need_space = ISC_FALSE;
|
|
|
|
for (f = fields, i = 0; f->name != NULL; f++, i++) {
|
|
cfg_obj_t *fieldobj = obj->value.tuple[i];
|
|
if (need_space)
|
|
print(pctx, " ", 1);
|
|
print_obj(pctx, fieldobj);
|
|
need_space = ISC_TF(fieldobj->type->print != print_void);
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj) {
|
|
unsigned int i;
|
|
const cfg_tuplefielddef_t *fields = obj->type->of;
|
|
const cfg_tuplefielddef_t *f;
|
|
unsigned int nfields = 0;
|
|
|
|
if (obj->value.tuple == NULL)
|
|
return;
|
|
|
|
for (f = fields, i = 0; f->name != NULL; f++, i++) {
|
|
CLEANUP_OBJ(obj->value.tuple[i]);
|
|
nfields++;
|
|
}
|
|
isc_mem_put(pctx->mctx, obj->value.tuple,
|
|
nfields * sizeof(cfg_obj_t *));
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_istuple(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_tuple));
|
|
}
|
|
|
|
cfg_obj_t *
|
|
cfg_tuple_get(cfg_obj_t *tupleobj, const char* name) {
|
|
unsigned int i;
|
|
const cfg_tuplefielddef_t *fields;
|
|
const cfg_tuplefielddef_t *f;
|
|
|
|
REQUIRE(tupleobj != NULL && tupleobj->type->rep == &cfg_rep_tuple);
|
|
|
|
fields = tupleobj->type->of;
|
|
for (f = fields, i = 0; f->name != NULL; f++, i++) {
|
|
if (strcmp(f->name, name) == 0)
|
|
return (tupleobj->value.tuple[i]);
|
|
}
|
|
INSIST(0);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Parse a required special character.
|
|
*/
|
|
static isc_result_t
|
|
parse_special(cfg_parser_t *pctx, int special) {
|
|
isc_result_t result;
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special &&
|
|
pctx->token.value.as_char == special)
|
|
return (ISC_R_SUCCESS);
|
|
|
|
parser_error(pctx, LOG_NEAR, "'%c' expected", special);
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Parse a required semicolon. If it is not there, log
|
|
* an error and increment the error count but continue
|
|
* parsing. Since the next token is pushed back,
|
|
* care must be taken to make sure it is eventually
|
|
* consumed or an infinite loop may result.
|
|
*/
|
|
static isc_result_t
|
|
parse_semicolon(cfg_parser_t *pctx) {
|
|
isc_result_t result;
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special &&
|
|
pctx->token.value.as_char == ';')
|
|
return (ISC_R_SUCCESS);
|
|
|
|
parser_error(pctx, LOG_BEFORE, "missing ';'");
|
|
cfg_ungettoken(pctx);
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Parse EOF, logging and returning an error if not there.
|
|
*/
|
|
static isc_result_t
|
|
parse_eof(cfg_parser_t *pctx) {
|
|
isc_result_t result;
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
|
|
if (pctx->token.type == isc_tokentype_eof)
|
|
return (ISC_R_SUCCESS);
|
|
|
|
parser_error(pctx, LOG_NEAR, "syntax error");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
cleanup:
|
|
return(result);
|
|
}
|
|
|
|
/* A list of files, used internally for pctx->files. */
|
|
|
|
static cfg_type_t cfg_type_filelist = {
|
|
"filelist", NULL, print_list, &cfg_rep_list,
|
|
&cfg_type_qstring
|
|
};
|
|
|
|
isc_result_t
|
|
cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
cfg_parser_t *pctx;
|
|
isc_lexspecials_t specials;
|
|
|
|
REQUIRE(mctx != NULL);
|
|
REQUIRE(ret != NULL && *ret == NULL);
|
|
|
|
pctx = isc_mem_get(mctx, sizeof(*pctx));
|
|
if (pctx == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
pctx->mctx = mctx;
|
|
pctx->lctx = lctx;
|
|
pctx->lexer = NULL;
|
|
pctx->seen_eof = ISC_FALSE;
|
|
pctx->ungotten = ISC_FALSE;
|
|
pctx->errors = 0;
|
|
pctx->warnings = 0;
|
|
pctx->open_files = NULL;
|
|
pctx->closed_files = NULL;
|
|
pctx->line = 0;
|
|
pctx->callback = NULL;
|
|
pctx->callbackarg = NULL;
|
|
pctx->token.type = isc_tokentype_unknown;
|
|
|
|
memset(specials, 0, sizeof(specials));
|
|
specials['{'] = 1;
|
|
specials['}'] = 1;
|
|
specials[';'] = 1;
|
|
specials['/'] = 1;
|
|
specials['"'] = 1;
|
|
specials['!'] = 1;
|
|
|
|
CHECK(isc_lex_create(pctx->mctx, 1024, &pctx->lexer));
|
|
|
|
isc_lex_setspecials(pctx->lexer, specials);
|
|
isc_lex_setcomments(pctx->lexer, (ISC_LEXCOMMENT_C |
|
|
ISC_LEXCOMMENT_CPLUSPLUS |
|
|
ISC_LEXCOMMENT_SHELL));
|
|
|
|
CHECK(create_list(pctx, &cfg_type_filelist, &pctx->open_files));
|
|
CHECK(create_list(pctx, &cfg_type_filelist, &pctx->closed_files));
|
|
|
|
*ret = pctx;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
if (pctx->lexer != NULL)
|
|
isc_lex_destroy(&pctx->lexer);
|
|
CLEANUP_OBJ(pctx->open_files);
|
|
CLEANUP_OBJ(pctx->closed_files);
|
|
isc_mem_put(mctx, pctx, sizeof(*pctx));
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parser_openfile(cfg_parser_t *pctx, const char *filename) {
|
|
isc_result_t result;
|
|
cfg_listelt_t *elt = NULL;
|
|
cfg_obj_t *stringobj = NULL;
|
|
|
|
result = isc_lex_openfile(pctx->lexer, filename);
|
|
if (result != ISC_R_SUCCESS) {
|
|
parser_error(pctx, 0, "open: %s: %s",
|
|
filename, isc_result_totext(result));
|
|
goto cleanup;
|
|
}
|
|
|
|
CHECK(create_string(pctx, filename, &cfg_type_qstring, &stringobj));
|
|
CHECK(create_listelt(pctx, &elt));
|
|
elt->obj = stringobj;
|
|
ISC_LIST_APPEND(pctx->open_files->value.list, elt, link);
|
|
|
|
return (ISC_R_SUCCESS);
|
|
cleanup:
|
|
CLEANUP_OBJ(stringobj);
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
cfg_parser_setcallback(cfg_parser_t *pctx,
|
|
cfg_parsecallback_t callback,
|
|
void *arg)
|
|
{
|
|
pctx->callback = callback;
|
|
pctx->callbackarg = arg;
|
|
}
|
|
|
|
/*
|
|
* Parse a configuration using a pctx where a lexer has already
|
|
* been set up with a source.
|
|
*/
|
|
static isc_result_t
|
|
parse2(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
|
|
result = parse(pctx, type, &obj);
|
|
|
|
if (pctx->errors != 0) {
|
|
/* Errors have been logged. */
|
|
if (result == ISC_R_SUCCESS)
|
|
result = ISC_R_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
/* Parsing failed but no errors have been logged. */
|
|
parser_error(pctx, 0, "parsing failed");
|
|
goto cleanup;
|
|
}
|
|
|
|
CHECK(parse_eof(pctx));
|
|
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
cfg_parse_file(cfg_parser_t *pctx, const char *filename,
|
|
const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
|
|
REQUIRE(filename != NULL);
|
|
|
|
CHECK(parser_openfile(pctx, filename));
|
|
CHECK(parse2(pctx, type, ret));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
|
|
isc_result_t
|
|
cfg_parse_buffer(cfg_parser_t *pctx, isc_buffer_t *buffer,
|
|
const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
REQUIRE(buffer != NULL);
|
|
CHECK(isc_lex_openbuffer(pctx->lexer, buffer));
|
|
CHECK(parse2(pctx, type, ret));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
void
|
|
cfg_parser_destroy(cfg_parser_t **pctxp) {
|
|
cfg_parser_t *pctx = *pctxp;
|
|
isc_lex_destroy(&pctx->lexer);
|
|
/*
|
|
* Cleaning up open_files does not
|
|
* close the files; that was already done
|
|
* by closing the lexer.
|
|
*/
|
|
CLEANUP_OBJ(pctx->open_files);
|
|
CLEANUP_OBJ(pctx->closed_files);
|
|
isc_mem_put(pctx->mctx, pctx, sizeof(*pctx));
|
|
*pctxp = NULL;
|
|
}
|
|
|
|
/*
|
|
* void
|
|
*/
|
|
static isc_result_t
|
|
parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
UNUSED(type);
|
|
return (create_cfgobj(pctx, &cfg_type_void, ret));
|
|
}
|
|
|
|
static void
|
|
print_void(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
UNUSED(pctx);
|
|
UNUSED(obj);
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_isvoid(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_void));
|
|
}
|
|
|
|
static cfg_type_t cfg_type_void = {
|
|
"void", parse_void, print_void, &cfg_rep_void, NULL };
|
|
|
|
|
|
/*
|
|
* uint32
|
|
*/
|
|
static isc_result_t
|
|
parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER));
|
|
if (pctx->token.type != isc_tokentype_number) {
|
|
parser_error(pctx, LOG_NEAR, "expected number");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
|
|
CHECK(create_cfgobj(pctx, &cfg_type_uint32, &obj));
|
|
|
|
obj->value.uint32 = pctx->token.value.as_ulong;
|
|
*ret = obj;
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_cstr(cfg_printer_t *pctx, const char *s) {
|
|
print(pctx, s, strlen(s));
|
|
}
|
|
|
|
static void
|
|
print_uint(cfg_printer_t *pctx, unsigned int u) {
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "%u", u);
|
|
print_cstr(pctx, buf);
|
|
}
|
|
|
|
static void
|
|
print_uint32(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
print_uint(pctx, obj->value.uint32);
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_isuint32(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_uint32));
|
|
}
|
|
|
|
isc_uint32_t
|
|
cfg_obj_asuint32(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint32);
|
|
return (obj->value.uint32);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_uint32 = {
|
|
"integer", parse_uint32, print_uint32, &cfg_rep_uint32, NULL };
|
|
|
|
|
|
/*
|
|
* uint64
|
|
*/
|
|
isc_boolean_t
|
|
cfg_obj_isuint64(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_uint64));
|
|
}
|
|
|
|
isc_uint64_t
|
|
cfg_obj_asuint64(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint64);
|
|
return (obj->value.uint64);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_unitstring(char *str, isc_resourcevalue_t *valuep) {
|
|
char *endp;
|
|
unsigned int len;
|
|
isc_uint64_t value;
|
|
isc_uint64_t unit;
|
|
|
|
value = isc_string_touint64(str, &endp, 10);
|
|
if (*endp == 0) {
|
|
*valuep = value;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
len = strlen(str);
|
|
if (len < 2 || endp[1] != '\0')
|
|
return (ISC_R_FAILURE);
|
|
|
|
switch (str[len - 1]) {
|
|
case 'k':
|
|
case 'K':
|
|
unit = 1024;
|
|
break;
|
|
case 'm':
|
|
case 'M':
|
|
unit = 1024 * 1024;
|
|
break;
|
|
case 'g':
|
|
case 'G':
|
|
unit = 1024 * 1024 * 1024;
|
|
break;
|
|
default:
|
|
return (ISC_R_FAILURE);
|
|
}
|
|
if (value > ISC_UINT64_MAX / unit)
|
|
return (ISC_R_FAILURE);
|
|
*valuep = value * unit;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
print_uint64(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
char buf[32];
|
|
sprintf(buf, "%" ISC_PRINT_QUADFORMAT "u", obj->value.uint64);
|
|
print_cstr(pctx, buf);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_uint64 = {
|
|
"64_bit_integer", NULL, print_uint64, &cfg_rep_uint64, NULL };
|
|
|
|
static isc_result_t
|
|
parse_sizeval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
isc_uint64_t val;
|
|
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
if (pctx->token.type != isc_tokentype_string) {
|
|
result = ISC_R_UNEXPECTEDTOKEN;
|
|
goto cleanup;
|
|
}
|
|
CHECK(parse_unitstring(pctx->token.value.as_pointer, &val));
|
|
|
|
CHECK(create_cfgobj(pctx, &cfg_type_uint64, &obj));
|
|
obj->value.uint64 = val;
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
parser_error(pctx, LOG_NEAR, "expected integer and optional unit");
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* A size value (number + optional unit).
|
|
*/
|
|
static cfg_type_t cfg_type_sizeval = {
|
|
"sizeval", parse_sizeval, print_uint64, &cfg_rep_uint64, NULL };
|
|
|
|
/*
|
|
* A size, "unlimited", or "default".
|
|
*/
|
|
|
|
static isc_result_t
|
|
parse_size(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_enum_or_other(pctx, type, &cfg_type_sizeval, ret));
|
|
}
|
|
|
|
static const char *size_enums[] = { "unlimited", "default", NULL };
|
|
static cfg_type_t cfg_type_size = {
|
|
"size", parse_size, print_ustring, &cfg_rep_string, size_enums
|
|
};
|
|
|
|
/*
|
|
* A size or "unlimited", but not "default".
|
|
*/
|
|
static const char *sizenodefault_enums[] = { "unlimited", NULL };
|
|
static cfg_type_t cfg_type_sizenodefault = {
|
|
"size_no_default", parse_size, print_ustring, &cfg_rep_string,
|
|
sizenodefault_enums
|
|
};
|
|
|
|
/*
|
|
* optional_keyvalue
|
|
*/
|
|
static isc_result_t
|
|
parse_maybe_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type,
|
|
isc_boolean_t optional, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
const keyword_type_t *kw = type->of;
|
|
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_string &&
|
|
strcasecmp(pctx->token.value.as_pointer, kw->name) == 0) {
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
CHECK(kw->type->parse(pctx, kw->type, &obj));
|
|
obj->type = type; /* XXX kludge */
|
|
} else {
|
|
if (optional) {
|
|
CHECK(parse_void(pctx, NULL, &obj));
|
|
} else {
|
|
parser_error(pctx, LOG_NEAR, "expected '%s'",
|
|
kw->name);
|
|
result = ISC_R_UNEXPECTEDTOKEN;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
*ret = obj;
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_maybe_optional_keyvalue(pctx, type, ISC_FALSE, ret));
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_maybe_optional_keyvalue(pctx, type, ISC_TRUE, ret));
|
|
}
|
|
|
|
static void
|
|
print_keyvalue(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
const keyword_type_t *kw = obj->type->of;
|
|
print_cstr(pctx, kw->name);
|
|
print(pctx, " ", 1);
|
|
kw->type->print(pctx, obj);
|
|
}
|
|
|
|
/*
|
|
* qstring, ustring, astring
|
|
*/
|
|
|
|
/* Create a string object from a null-terminated C string. */
|
|
static isc_result_t
|
|
create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
|
|
cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
int len;
|
|
|
|
CHECK(create_cfgobj(pctx, type, &obj));
|
|
len = strlen(contents);
|
|
obj->value.string.length = len;
|
|
obj->value.string.base = isc_mem_get(pctx->mctx, len + 1);
|
|
if (obj->value.string.base == 0) {
|
|
isc_mem_put(pctx->mctx, obj, sizeof(*obj));
|
|
return (ISC_R_NOMEMORY);
|
|
}
|
|
memcpy(obj->value.string.base, contents, len);
|
|
obj->value.string.base[len] = '\0';
|
|
|
|
*ret = obj;
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_gettoken(pctx, QSTRING));
|
|
if (pctx->token.type != isc_tokentype_qstring) {
|
|
parser_error(pctx, LOG_NEAR, "expected quoted string");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
return (create_string(pctx,
|
|
pctx->token.value.as_pointer,
|
|
&cfg_type_qstring,
|
|
ret));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_ustring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
if (pctx->token.type != isc_tokentype_string) {
|
|
parser_error(pctx, LOG_NEAR, "expected unquoted string");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
return (create_string(pctx,
|
|
pctx->token.value.as_pointer,
|
|
&cfg_type_ustring,
|
|
ret));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_getstringtoken(pctx));
|
|
return (create_string(pctx,
|
|
pctx->token.value.as_pointer,
|
|
&cfg_type_qstring,
|
|
ret));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
is_enum(const char *s, const char *const *enums) {
|
|
const char * const *p;
|
|
for (p = enums; *p != NULL; p++) {
|
|
if (strcasecmp(*p, s) == 0)
|
|
return (ISC_TRUE);
|
|
}
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
static isc_result_t
|
|
check_enum(cfg_parser_t *pctx, cfg_obj_t *obj, const char *const *enums) {
|
|
const char *s = obj->value.string.base;
|
|
if (is_enum(s, enums))
|
|
return (ISC_R_SUCCESS);
|
|
parser_error(pctx, 0, "'%s' unexpected", s);
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
CHECK(parse_ustring(pctx, NULL, &obj));
|
|
CHECK(check_enum(pctx, obj, type->of));
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
cleanup:
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_enum_or_other(cfg_parser_t *pctx, const cfg_type_t *enumtype,
|
|
const cfg_type_t *othertype, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_string &&
|
|
is_enum(pctx->token.value.as_pointer, enumtype->of)) {
|
|
CHECK(parse_enum(pctx, enumtype, ret));
|
|
} else {
|
|
CHECK(parse(pctx, othertype, ret));
|
|
}
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
|
|
/*
|
|
* Print a string object.
|
|
*/
|
|
static void
|
|
print_ustring(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
print(pctx, obj->value.string.base, obj->value.string.length);
|
|
}
|
|
|
|
static void
|
|
print_qstring(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
print(pctx, "\"", 1);
|
|
print_ustring(pctx, obj);
|
|
print(pctx, "\"", 1);
|
|
}
|
|
|
|
static void
|
|
free_string(cfg_parser_t *pctx, cfg_obj_t *obj) {
|
|
isc_mem_put(pctx->mctx, obj->value.string.base,
|
|
obj->value.string.length + 1);
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_isstring(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_string));
|
|
}
|
|
|
|
char *
|
|
cfg_obj_asstring(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string);
|
|
return (obj->value.string.base);
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_isboolean(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_boolean));
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_asboolean(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_boolean);
|
|
return (obj->value.boolean);
|
|
}
|
|
|
|
/* Quoted string only */
|
|
static cfg_type_t cfg_type_qstring = {
|
|
"quoted_string", parse_qstring, print_qstring, &cfg_rep_string, NULL };
|
|
|
|
/* Unquoted string only */
|
|
static cfg_type_t cfg_type_ustring = {
|
|
"string", parse_ustring, print_ustring, &cfg_rep_string, NULL };
|
|
|
|
/* Any string (quoted or unquoted); printed with quotes */
|
|
static cfg_type_t cfg_type_astring = {
|
|
"string", parse_astring, print_qstring, &cfg_rep_string, NULL };
|
|
|
|
|
|
/*
|
|
* boolean
|
|
*/
|
|
static isc_result_t
|
|
parse_boolean(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
isc_boolean_t value;
|
|
cfg_obj_t *obj = NULL;
|
|
UNUSED(type);
|
|
|
|
result = cfg_gettoken(pctx, 0);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
if (pctx->token.type != isc_tokentype_string)
|
|
goto bad_boolean;
|
|
|
|
if ((strcasecmp(pctx->token.value.as_pointer, "true") == 0) ||
|
|
(strcasecmp(pctx->token.value.as_pointer, "yes") == 0) ||
|
|
(strcmp(pctx->token.value.as_pointer, "1") == 0)) {
|
|
value = ISC_TRUE;
|
|
} else if ((strcasecmp(pctx->token.value.as_pointer, "false") == 0) ||
|
|
(strcasecmp(pctx->token.value.as_pointer, "no") == 0) ||
|
|
(strcmp(pctx->token.value.as_pointer, "0") == 0)) {
|
|
value = ISC_FALSE;
|
|
} else {
|
|
goto bad_boolean;
|
|
}
|
|
|
|
CHECK(create_cfgobj(pctx, &cfg_type_boolean, &obj));
|
|
obj->value.boolean = value;
|
|
*ret = obj;
|
|
return (result);
|
|
|
|
bad_boolean:
|
|
parser_error(pctx, LOG_NEAR, "boolean expected");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_boolean(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
if (obj->value.boolean)
|
|
print(pctx, "yes", 3);
|
|
else
|
|
print(pctx, "no", 2);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_boolean = {
|
|
"boolean", parse_boolean, print_boolean, &cfg_rep_boolean, NULL };
|
|
|
|
static const char *dialup_enums[] = {
|
|
"notify", "notify-passive", "refresh", "passive", NULL };
|
|
static isc_result_t
|
|
parse_dialup_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
|
|
}
|
|
static cfg_type_t cfg_type_dialuptype = {
|
|
"dialuptype", parse_dialup_type, print_ustring,
|
|
&cfg_rep_string, dialup_enums
|
|
};
|
|
|
|
static const char *notify_enums[] = { "explicit", NULL };
|
|
static isc_result_t
|
|
parse_notify_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
|
|
}
|
|
static cfg_type_t cfg_type_notifytype = {
|
|
"notifytype", parse_notify_type, print_ustring,
|
|
&cfg_rep_string, notify_enums,
|
|
};
|
|
|
|
static keyword_type_t key_kw = { "key", &cfg_type_astring };
|
|
|
|
LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_keyref = {
|
|
"keyref", parse_keyvalue, print_keyvalue,
|
|
&cfg_rep_string, &key_kw
|
|
};
|
|
|
|
static cfg_type_t cfg_type_optional_keyref = {
|
|
"optional_keyref", parse_optional_keyvalue, print_keyvalue,
|
|
&cfg_rep_string, &key_kw
|
|
};
|
|
|
|
|
|
/*
|
|
* Lists.
|
|
*/
|
|
|
|
static isc_result_t
|
|
create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) {
|
|
isc_result_t result;
|
|
CHECK(create_cfgobj(pctx, type, obj));
|
|
ISC_LIST_INIT((*obj)->value.list);
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp) {
|
|
cfg_listelt_t *elt;
|
|
elt = isc_mem_get(pctx->mctx, sizeof(*elt));
|
|
if (elt == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
elt->obj = NULL;
|
|
ISC_LINK_INIT(elt, link);
|
|
*eltp = elt;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
free_list_elt(cfg_parser_t *pctx, cfg_listelt_t *elt) {
|
|
cfg_obj_destroy(pctx, &elt->obj);
|
|
isc_mem_put(pctx->mctx, elt, sizeof(*elt));
|
|
}
|
|
|
|
static void
|
|
free_list(cfg_parser_t *pctx, cfg_obj_t *obj) {
|
|
cfg_listelt_t *elt, *next;
|
|
for (elt = ISC_LIST_HEAD(obj->value.list);
|
|
elt != NULL;
|
|
elt = next)
|
|
{
|
|
next = ISC_LIST_NEXT(elt, link);
|
|
free_list_elt(pctx, elt);
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_list_elt(cfg_parser_t *pctx, const cfg_type_t *elttype,
|
|
cfg_listelt_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
cfg_listelt_t *elt = NULL;
|
|
cfg_obj_t *value = NULL;
|
|
|
|
CHECK(create_listelt(pctx, &elt));
|
|
|
|
result = parse(pctx, elttype, &value);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto cleanup;
|
|
|
|
elt->obj = value;
|
|
|
|
*ret = elt;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
isc_mem_put(pctx->mctx, elt, sizeof(*elt));
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Parse a homogeneous list whose elements are of type 'elttype'
|
|
* and where each element is terminated by a semicolon.
|
|
*/
|
|
static isc_result_t
|
|
parse_list(cfg_parser_t *pctx, const cfg_type_t *listtype, cfg_obj_t **ret)
|
|
{
|
|
cfg_obj_t *listobj = NULL;
|
|
const cfg_type_t *listof = listtype->of;
|
|
isc_result_t result;
|
|
cfg_listelt_t *elt = NULL;
|
|
|
|
CHECK(create_list(pctx, listtype, &listobj));
|
|
|
|
for (;;) {
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special &&
|
|
pctx->token.value.as_char == /*{*/ '}')
|
|
break;
|
|
CHECK(parse_list_elt(pctx, listof, &elt));
|
|
CHECK(parse_semicolon(pctx));
|
|
ISC_LIST_APPEND(listobj->value.list, elt, link);
|
|
elt = NULL;
|
|
}
|
|
*ret = listobj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
if (elt != NULL)
|
|
free_list_elt(pctx, elt);
|
|
CLEANUP_OBJ(listobj);
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_list(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
cfg_list_t *list = &obj->value.list;
|
|
cfg_listelt_t *elt;
|
|
|
|
for (elt = ISC_LIST_HEAD(*list);
|
|
elt != NULL;
|
|
elt = ISC_LIST_NEXT(elt, link)) {
|
|
print_indent(pctx);
|
|
print_obj(pctx, elt->obj);
|
|
print(pctx, ";\n", 2);
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
CHECK(parse_special(pctx, '{'));
|
|
CHECK(parse_list(pctx, type, ret));
|
|
CHECK(parse_special(pctx, '}'));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_bracketed_list(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
print_open(pctx);
|
|
print_list(pctx, obj);
|
|
print_close(pctx);
|
|
}
|
|
|
|
/*
|
|
* Parse a homogeneous list whose elements are of type 'elttype'
|
|
* and where elements are separated by space. The list ends
|
|
* before the first semicolon.
|
|
*/
|
|
static isc_result_t
|
|
parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *listtype, cfg_obj_t **ret)
|
|
{
|
|
cfg_obj_t *listobj = NULL;
|
|
const cfg_type_t *listof = listtype->of;
|
|
isc_result_t result;
|
|
|
|
CHECK(create_list(pctx, listtype, &listobj));
|
|
|
|
for (;;) {
|
|
cfg_listelt_t *elt = NULL;
|
|
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special &&
|
|
pctx->token.value.as_char == ';')
|
|
break;
|
|
CHECK(parse_list_elt(pctx, listof, &elt));
|
|
ISC_LIST_APPEND(listobj->value.list, elt, link);
|
|
}
|
|
*ret = listobj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(listobj);
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_spacelist(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
cfg_list_t *list = &obj->value.list;
|
|
cfg_listelt_t *elt;
|
|
|
|
for (elt = ISC_LIST_HEAD(*list);
|
|
elt != NULL;
|
|
elt = ISC_LIST_NEXT(elt, link)) {
|
|
print_obj(pctx, elt->obj);
|
|
if (ISC_LIST_NEXT(elt, link) != NULL)
|
|
print(pctx, " ", 1);
|
|
}
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_islist(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_list));
|
|
}
|
|
|
|
cfg_listelt_t *
|
|
cfg_list_first(cfg_obj_t *obj) {
|
|
REQUIRE(obj == NULL || obj->type->rep == &cfg_rep_list);
|
|
if (obj == NULL)
|
|
return (NULL);
|
|
return (ISC_LIST_HEAD(obj->value.list));
|
|
}
|
|
|
|
cfg_listelt_t *
|
|
cfg_list_next(cfg_listelt_t *elt) {
|
|
REQUIRE(elt != NULL);
|
|
return (ISC_LIST_NEXT(elt, link));
|
|
}
|
|
|
|
cfg_obj_t *
|
|
cfg_listelt_value(cfg_listelt_t *elt) {
|
|
REQUIRE(elt != NULL);
|
|
return (elt->obj);
|
|
}
|
|
|
|
/*
|
|
* Maps.
|
|
*/
|
|
|
|
/*
|
|
* Parse a map body. That's something like
|
|
*
|
|
* "foo 1; bar { glub; }; zap true; zap false;"
|
|
*
|
|
* i.e., a sequence of option names followed by values and
|
|
* terminated by semicolons. Used for the top level of
|
|
* the named.conf syntax, as well as for the body of the
|
|
* options, view, zone, and other statements.
|
|
*/
|
|
static isc_result_t
|
|
parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
const cfg_clausedef_t * const *clausesets = type->of;
|
|
isc_result_t result;
|
|
const cfg_clausedef_t * const *clauseset;
|
|
const cfg_clausedef_t *clause;
|
|
cfg_obj_t *value = NULL;
|
|
cfg_obj_t *obj = NULL;
|
|
cfg_obj_t *eltobj = NULL;
|
|
cfg_obj_t *includename = NULL;
|
|
isc_symvalue_t symval;
|
|
cfg_list_t *list = NULL;
|
|
|
|
CHECK(create_map(pctx, type, &obj));
|
|
|
|
obj->value.map.clausesets = clausesets;
|
|
|
|
for (;;) {
|
|
cfg_listelt_t *elt;
|
|
|
|
redo:
|
|
/*
|
|
* Parse the option name and see if it is known.
|
|
*/
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
|
|
if (pctx->token.type != isc_tokentype_string) {
|
|
cfg_ungettoken(pctx);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We accept "include" statements wherever a map body
|
|
* clause can occur.
|
|
*/
|
|
if (strcasecmp(pctx->token.value.as_pointer, "include") == 0) {
|
|
/*
|
|
* Turn the file name into a temporary configuration
|
|
* object just so that it is not overwritten by the
|
|
* semicolon token.
|
|
*/
|
|
CHECK(parse(pctx, &cfg_type_qstring, &includename));
|
|
CHECK(parse_semicolon(pctx));
|
|
CHECK(parser_openfile(pctx, includename->
|
|
value.string.base));
|
|
cfg_obj_destroy(pctx, &includename);
|
|
goto redo;
|
|
}
|
|
|
|
clause = NULL;
|
|
for (clauseset = clausesets; *clauseset != NULL; clauseset++) {
|
|
for (clause = *clauseset;
|
|
clause->name != NULL;
|
|
clause++) {
|
|
if (strcasecmp(pctx->token.value.as_pointer,
|
|
clause->name) == 0)
|
|
goto done;
|
|
}
|
|
}
|
|
done:
|
|
if (clause == NULL || clause->name == NULL) {
|
|
parser_error(pctx, LOG_NOPREP, "unknown option");
|
|
/*
|
|
* Try to recover by parsing this option as an unknown
|
|
* option and discarding it.
|
|
*/
|
|
CHECK(parse(pctx, &cfg_type_unsupported, &eltobj));
|
|
cfg_obj_destroy(pctx, &eltobj);
|
|
CHECK(parse_semicolon(pctx));
|
|
continue;
|
|
}
|
|
|
|
/* Clause is known. */
|
|
|
|
/* Issue warnings if appropriate */
|
|
if ((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0)
|
|
parser_warning(pctx, 0, "option '%s' is obsolete",
|
|
clause->name);
|
|
if ((clause->flags & CFG_CLAUSEFLAG_NOTIMP) != 0)
|
|
parser_warning(pctx, 0, "option '%s' is "
|
|
"not implemented", clause->name);
|
|
if ((clause->flags & CFG_CLAUSEFLAG_NYI) != 0)
|
|
parser_warning(pctx, 0, "option '%s' is "
|
|
"not implemented", clause->name);
|
|
/*
|
|
* Don't log options with CFG_CLAUSEFLAG_NEWDEFAULT
|
|
* set here - we need to log the *lack* of such an option,
|
|
* not its presence.
|
|
*/
|
|
|
|
/* See if the clause already has a value; if not create one. */
|
|
result = isc_symtab_lookup(obj->value.map.symtab,
|
|
clause->name, 0, &symval);
|
|
|
|
if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
|
|
/* Multivalued clause */
|
|
cfg_obj_t *listobj = NULL;
|
|
if (result == ISC_R_NOTFOUND) {
|
|
CHECK(create_list(pctx,
|
|
&cfg_type_implicitlist,
|
|
&listobj));
|
|
symval.as_pointer = listobj;
|
|
result = isc_symtab_define(obj->value.
|
|
map.symtab,
|
|
clause->name,
|
|
1, symval,
|
|
isc_symexists_reject);
|
|
if (result != ISC_R_SUCCESS) {
|
|
parser_error(pctx, LOG_NEAR,
|
|
"isc_symtab_define(%s) "
|
|
"failed", clause->name);
|
|
isc_mem_put(pctx->mctx, list,
|
|
sizeof(cfg_list_t));
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
INSIST(result == ISC_R_SUCCESS);
|
|
listobj = symval.as_pointer;
|
|
}
|
|
|
|
elt = NULL;
|
|
CHECK(parse_list_elt(pctx, clause->type, &elt));
|
|
CHECK(parse_semicolon(pctx));
|
|
|
|
ISC_LIST_APPEND(listobj->value.list, elt, link);
|
|
} else {
|
|
/* Single-valued clause */
|
|
if (result == ISC_R_NOTFOUND) {
|
|
isc_boolean_t callback =
|
|
ISC_TF((clause->flags &
|
|
CFG_CLAUSEFLAG_CALLBACK) != 0);
|
|
CHECK(parse_symtab_elt(pctx, clause->name,
|
|
clause->type,
|
|
obj->value.map.symtab,
|
|
callback));
|
|
CHECK(parse_semicolon(pctx));
|
|
} else if (result == ISC_R_SUCCESS) {
|
|
parser_error(pctx, LOG_NEAR, "'%s' redefined",
|
|
clause->name);
|
|
result = ISC_R_EXISTS;
|
|
goto cleanup;
|
|
} else {
|
|
parser_error(pctx, LOG_NEAR,
|
|
"isc_symtab_define() failed");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(value);
|
|
CLEANUP_OBJ(obj);
|
|
CLEANUP_OBJ(eltobj);
|
|
CLEANUP_OBJ(includename);
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_symtab_elt(cfg_parser_t *pctx, const char *name,
|
|
cfg_type_t *elttype, isc_symtab_t *symtab,
|
|
isc_boolean_t callback)
|
|
{
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
isc_symvalue_t symval;
|
|
|
|
CHECK(parse(pctx, elttype, &obj));
|
|
|
|
if (callback && pctx->callback != NULL)
|
|
CHECK(pctx->callback(name, obj, pctx->callbackarg));
|
|
|
|
symval.as_pointer = obj;
|
|
CHECK(isc_symtab_define(symtab, name,
|
|
1, symval,
|
|
isc_symexists_reject));
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Parse a map; e.g., "{ foo 1; bar { glub; }; zap true; zap false; }"
|
|
*/
|
|
static isc_result_t
|
|
parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
CHECK(parse_special(pctx, '{'));
|
|
CHECK(parse_mapbody(pctx, type, ret));
|
|
CHECK(parse_special(pctx, '}'));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Subroutine for parse_named_map() and parse_addressed_map().
|
|
*/
|
|
static isc_result_t
|
|
parse_any_named_map(cfg_parser_t *pctx, cfg_type_t *nametype, const cfg_type_t *type,
|
|
cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
cfg_obj_t *idobj = NULL;
|
|
cfg_obj_t *mapobj = NULL;
|
|
|
|
CHECK(parse(pctx, nametype, &idobj));
|
|
CHECK(parse_map(pctx, type, &mapobj));
|
|
mapobj->value.map.id = idobj;
|
|
idobj = NULL;
|
|
*ret = mapobj;
|
|
cleanup:
|
|
CLEANUP_OBJ(idobj);
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Parse a map identified by a string name. E.g., "name { foo 1; }".
|
|
* Used for the "key" and "channel" statements.
|
|
*/
|
|
static isc_result_t
|
|
parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_any_named_map(pctx, &cfg_type_astring, type, ret));
|
|
}
|
|
|
|
/*
|
|
* Parse a map identified by a network address.
|
|
* Used for the "server" statement.
|
|
*/
|
|
static isc_result_t
|
|
parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_any_named_map(pctx, &cfg_type_netaddr, type, ret));
|
|
}
|
|
|
|
static void
|
|
print_mapbody(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
|
|
const cfg_clausedef_t * const *clauseset;
|
|
|
|
for (clauseset = obj->value.map.clausesets;
|
|
*clauseset != NULL;
|
|
clauseset++)
|
|
{
|
|
isc_symvalue_t symval;
|
|
const cfg_clausedef_t *clause;
|
|
|
|
for (clause = *clauseset;
|
|
clause->name != NULL;
|
|
clause++) {
|
|
result = isc_symtab_lookup(obj->value.map.symtab,
|
|
clause->name, 0, &symval);
|
|
if (result == ISC_R_SUCCESS) {
|
|
cfg_obj_t *obj = symval.as_pointer;
|
|
if (obj->type == &cfg_type_implicitlist) {
|
|
/* Multivalued. */
|
|
cfg_list_t *list = &obj->value.list;
|
|
cfg_listelt_t *elt;
|
|
for (elt = ISC_LIST_HEAD(*list);
|
|
elt != NULL;
|
|
elt = ISC_LIST_NEXT(elt, link)) {
|
|
print_indent(pctx);
|
|
print_cstr(pctx, clause->name);
|
|
print(pctx, " ", 1);
|
|
print_obj(pctx, elt->obj);
|
|
print(pctx, ";\n", 2);
|
|
}
|
|
} else {
|
|
/* Single-valued. */
|
|
print_indent(pctx);
|
|
print_cstr(pctx, clause->name);
|
|
print(pctx, " ", 1);
|
|
print_obj(pctx, obj);
|
|
print(pctx, ";\n", 2);
|
|
}
|
|
} else if (result == ISC_R_NOTFOUND) {
|
|
; /* do nothing */
|
|
} else {
|
|
INSIST(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_map(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
if (obj->value.map.id != NULL) {
|
|
print_obj(pctx, obj->value.map.id);
|
|
print(pctx, " ", 1);
|
|
}
|
|
print_open(pctx);
|
|
print_mapbody(pctx, obj);
|
|
print_close(pctx);
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_ismap(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_map));
|
|
}
|
|
|
|
isc_result_t
|
|
cfg_map_get(cfg_obj_t *mapobj, const char* name, cfg_obj_t **obj) {
|
|
isc_result_t result;
|
|
isc_symvalue_t val;
|
|
cfg_map_t *map;
|
|
|
|
REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
|
|
REQUIRE(name != NULL);
|
|
REQUIRE(obj != NULL && *obj == NULL);
|
|
|
|
map = &mapobj->value.map;
|
|
|
|
result = isc_symtab_lookup(map->symtab, name, MAP_SYM, &val);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
*obj = val.as_pointer;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
cfg_obj_t *
|
|
cfg_map_getname(cfg_obj_t *mapobj) {
|
|
REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
|
|
return (mapobj->value.map.id);
|
|
}
|
|
|
|
|
|
/* Parse an arbitrary token, storing its raw text representation. */
|
|
static isc_result_t
|
|
parse_token(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
cfg_obj_t *obj = NULL;
|
|
isc_result_t result;
|
|
isc_region_t r;
|
|
|
|
UNUSED(type);
|
|
|
|
CHECK(create_cfgobj(pctx, &cfg_type_token, &obj));
|
|
CHECK(cfg_gettoken(pctx, QSTRING));
|
|
if (pctx->token.type == isc_tokentype_eof) {
|
|
cfg_ungettoken(pctx);
|
|
result = ISC_R_EOF;
|
|
goto cleanup;
|
|
}
|
|
|
|
isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r);
|
|
|
|
obj->value.string.base = isc_mem_get(pctx->mctx, r.length + 1);
|
|
obj->value.string.length = r.length;
|
|
memcpy(obj->value.string.base, r.base, r.length);
|
|
obj->value.string.base[r.length] = '\0';
|
|
*ret = obj;
|
|
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_token = {
|
|
"token", parse_token, print_ustring, &cfg_rep_string, NULL };
|
|
|
|
/*
|
|
* An unsupported option. This is just a list of tokens with balanced braces
|
|
* ending in a semicolon.
|
|
*/
|
|
|
|
static isc_result_t
|
|
parse_unsupported(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
cfg_obj_t *listobj = NULL;
|
|
isc_result_t result;
|
|
int braces = 0;
|
|
|
|
CHECK(create_list(pctx, type, &listobj));
|
|
|
|
for (;;) {
|
|
cfg_listelt_t *elt = NULL;
|
|
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special) {
|
|
if (pctx->token.value.as_char == '{')
|
|
braces++;
|
|
else if (pctx->token.value.as_char == '}')
|
|
braces--;
|
|
else if (pctx->token.value.as_char == ';')
|
|
if (braces == 0)
|
|
break;
|
|
}
|
|
if (pctx->token.type == isc_tokentype_eof || braces < 0) {
|
|
parser_error(pctx, LOG_NEAR, "unexpected token");
|
|
result = ISC_R_UNEXPECTEDTOKEN;
|
|
goto cleanup;
|
|
}
|
|
|
|
CHECK(parse_list_elt(pctx, &cfg_type_token, &elt));
|
|
ISC_LIST_APPEND(listobj->value.list, elt, link);
|
|
}
|
|
INSIST(braces == 0);
|
|
*ret = listobj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(listobj);
|
|
return (result);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_unsupported = {
|
|
"unsupported", parse_unsupported, print_spacelist,
|
|
&cfg_rep_list, NULL
|
|
};
|
|
|
|
/*
|
|
* A "controls" statement is represented as a map with the multivalued
|
|
* "inet" and "unix" clauses. Inet controls are tuples; unix controls
|
|
* are cfg_unsupported_t objects.
|
|
*/
|
|
|
|
static keyword_type_t controls_allow_kw = {
|
|
"allow", &cfg_type_bracketed_aml };
|
|
static cfg_type_t cfg_type_controls_allow = {
|
|
"controls_allow", parse_keyvalue,
|
|
print_keyvalue, &cfg_rep_list, &controls_allow_kw
|
|
};
|
|
|
|
static keyword_type_t controls_keys_kw = {
|
|
"keys", &cfg_type_keylist };
|
|
static cfg_type_t cfg_type_controls_keys = {
|
|
"controls_keys", parse_optional_keyvalue,
|
|
print_keyvalue, &cfg_rep_list, &controls_keys_kw
|
|
};
|
|
|
|
static cfg_tuplefielddef_t inetcontrol_fields[] = {
|
|
{ "address", &cfg_type_controls_sockaddr, 0 },
|
|
{ "allow", &cfg_type_controls_allow, 0 },
|
|
{ "keys", &cfg_type_controls_keys, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_inetcontrol = {
|
|
"inetcontrol", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
inetcontrol_fields
|
|
};
|
|
|
|
static cfg_clausedef_t
|
|
controls_clauses[] = {
|
|
{ "inet", &cfg_type_inetcontrol, CFG_CLAUSEFLAG_MULTI },
|
|
{ "unix", &cfg_type_unsupported,
|
|
CFG_CLAUSEFLAG_MULTI|CFG_CLAUSEFLAG_NOTIMP },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_clausedef_t *
|
|
controls_clausesets[] = {
|
|
controls_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_controls = {
|
|
"controls", parse_map, print_map, &cfg_rep_map, &controls_clausesets
|
|
};
|
|
|
|
/*
|
|
* An optional class, as used in view and zone statements.
|
|
*/
|
|
static isc_result_t
|
|
parse_optional_class(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
UNUSED(type);
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_string)
|
|
CHECK(parse(pctx, &cfg_type_ustring, ret));
|
|
else
|
|
CHECK(parse(pctx, &cfg_type_void, ret));
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_optional_class = {
|
|
"optional_class", parse_optional_class, NULL, NULL, NULL };
|
|
|
|
|
|
/*
|
|
* Try interpreting the current token as a network address.
|
|
*
|
|
* If WILDOK is set in flags, "*" can be used as a wildcard
|
|
* and at least one of V4OK and V6OK must also be set. The
|
|
* "*" is interpreted as the IPv4 wildcard address if V4OK is
|
|
* set (including the case where V4OK and V6OK are both set),
|
|
* and the IPv6 wildcard address otherwise.
|
|
*/
|
|
static isc_result_t
|
|
token_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
|
|
char *s;
|
|
struct in_addr in4a;
|
|
struct in6_addr in6a;
|
|
|
|
if (pctx->token.type != isc_tokentype_string)
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
|
|
s = pctx->token.value.as_pointer;
|
|
if ((flags & WILDOK) != 0 && strcmp(s, "*") == 0) {
|
|
if ((flags & V4OK) != 0) {
|
|
isc_netaddr_any(na);
|
|
return (ISC_R_SUCCESS);
|
|
} else if ((flags & V6OK) != 0) {
|
|
isc_netaddr_any6(na);
|
|
return (ISC_R_SUCCESS);
|
|
} else {
|
|
INSIST(0);
|
|
}
|
|
} else {
|
|
if ((flags & (V4OK | V4PREFIXOK)) != 0) {
|
|
if (inet_pton(AF_INET, s, &in4a) == 1) {
|
|
isc_netaddr_fromin(na, &in4a);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
if ((flags & V4PREFIXOK) != 0 &&
|
|
strlen(s) <= 15U) {
|
|
char buf[64];
|
|
int i;
|
|
|
|
strcpy(buf, s);
|
|
for (i = 0; i < 3; i++) {
|
|
strcat(buf, ".0");
|
|
if (inet_pton(AF_INET, buf, &in4a) == 1) {
|
|
isc_netaddr_fromin(na, &in4a);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
}
|
|
if (flags & V6OK) {
|
|
if (inet_pton(AF_INET6, s, &in6a) == 1) {
|
|
isc_netaddr_fromin6(na, &in6a);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
}
|
|
}
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
|
|
static isc_result_t
|
|
get_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
|
|
isc_result_t result;
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
result = token_addr(pctx, flags, na);
|
|
if (result == ISC_R_UNEXPECTEDTOKEN)
|
|
parser_error(pctx, LOG_NEAR, "expected IP address");
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_boolean_t
|
|
looking_at_netaddr(cfg_parser_t *pctx, unsigned int flags) {
|
|
isc_result_t result;
|
|
isc_netaddr_t na_dummy;
|
|
result = token_addr(pctx, flags, &na_dummy);
|
|
return (ISC_TF(result == ISC_R_SUCCESS));
|
|
}
|
|
|
|
static isc_result_t
|
|
get_port(cfg_parser_t *pctx, unsigned int flags, in_port_t *port) {
|
|
isc_result_t result;
|
|
|
|
CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER));
|
|
|
|
if ((flags & WILDOK) != 0 &&
|
|
pctx->token.type == isc_tokentype_string &&
|
|
strcmp(pctx->token.value.as_pointer, "*") == 0) {
|
|
*port = 0;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
if (pctx->token.type != isc_tokentype_number) {
|
|
parser_error(pctx, LOG_NEAR,
|
|
"expected port number or '*'");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
if (pctx->token.value.as_ulong >= 65536U) {
|
|
parser_error(pctx, LOG_NEAR,
|
|
"port number out of range");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
*port = (in_port_t)(pctx->token.value.as_ulong);
|
|
return (ISC_R_SUCCESS);
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_querysource(cfg_parser_t *pctx, int flags, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
isc_netaddr_t netaddr;
|
|
in_port_t port;
|
|
unsigned int have_address = 0;
|
|
unsigned int have_port = 0;
|
|
|
|
if ((flags & V4OK) != 0)
|
|
isc_netaddr_any(&netaddr);
|
|
else if ((flags & V6OK) != 0)
|
|
isc_netaddr_any6(&netaddr);
|
|
else
|
|
INSIST(0);
|
|
|
|
port = 0;
|
|
|
|
CHECK(create_cfgobj(pctx, &cfg_type_querysource, &obj));
|
|
for (;;) {
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_string) {
|
|
if (strcasecmp(pctx->token.value.as_pointer,
|
|
"address") == 0)
|
|
{
|
|
/* read "address" */
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
CHECK(get_addr(pctx, flags|WILDOK, &netaddr));
|
|
have_address++;
|
|
} else if (strcasecmp(pctx->token.value.as_pointer,
|
|
"port") == 0)
|
|
{
|
|
/* read "port" */
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
CHECK(get_port(pctx, WILDOK, &port));
|
|
have_port++;
|
|
} else {
|
|
parser_error(pctx, LOG_NEAR,
|
|
"expected 'address' or 'port'");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
} else
|
|
break;
|
|
}
|
|
if (have_address > 1 || have_port > 1 ||
|
|
have_address + have_port == 0) {
|
|
parser_error(pctx, 0, "expected one address and/or port");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
|
|
isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port);
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
parser_error(pctx, LOG_NEAR, "invalid query source");
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_querysource4(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
UNUSED(type);
|
|
return (parse_querysource(pctx, V4OK, ret));
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_querysource6(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
UNUSED(type);
|
|
return (parse_querysource(pctx, V6OK, ret));
|
|
}
|
|
|
|
static void
|
|
print_isc_netaddr(cfg_printer_t *pctx, isc_netaddr_t *na) {
|
|
isc_result_t result;
|
|
char text[128];
|
|
isc_buffer_t buf;
|
|
|
|
isc_buffer_init(&buf, text, sizeof(text));
|
|
result = isc_netaddr_totext(na, &buf);
|
|
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
|
print(pctx, isc_buffer_base(&buf), isc_buffer_usedlength(&buf));
|
|
}
|
|
|
|
static void
|
|
print_querysource(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
isc_netaddr_t na;
|
|
isc_netaddr_fromsockaddr(&na, &obj->value.sockaddr);
|
|
print(pctx, "address ", 8);
|
|
print_isc_netaddr(pctx, &na);
|
|
print(pctx, " port ", 6);
|
|
print_uint(pctx, isc_sockaddr_getport(&obj->value.sockaddr));
|
|
}
|
|
|
|
static cfg_type_t cfg_type_querysource4 = {
|
|
"querysource4", parse_querysource4, NULL, NULL, NULL };
|
|
static cfg_type_t cfg_type_querysource6 = {
|
|
"querysource6", parse_querysource6, NULL, NULL, NULL };
|
|
static cfg_type_t cfg_type_querysource = {
|
|
"querysource", NULL, print_querysource, &cfg_rep_sockaddr, NULL };
|
|
|
|
/* netaddr */
|
|
|
|
static isc_result_t
|
|
parse_netaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
isc_netaddr_t netaddr;
|
|
UNUSED(type);
|
|
CHECK(create_cfgobj(pctx, type, &obj));
|
|
CHECK(get_addr(pctx, V4OK|V6OK, &netaddr));
|
|
isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, 0);
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
cleanup:
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_netaddr = {
|
|
"netaddr", parse_netaddr, print_sockaddr, &cfg_rep_sockaddr, NULL };
|
|
|
|
/* netprefix */
|
|
|
|
static isc_result_t
|
|
parse_netprefix(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
cfg_obj_t *obj = NULL;
|
|
isc_result_t result;
|
|
isc_netaddr_t netaddr;
|
|
unsigned int addrlen, prefixlen;
|
|
UNUSED(type);
|
|
|
|
CHECK(get_addr(pctx, V4OK|V4PREFIXOK|V6OK, &netaddr));
|
|
switch (netaddr.family) {
|
|
case AF_INET:
|
|
addrlen = 32;
|
|
break;
|
|
case AF_INET6:
|
|
addrlen = 128;
|
|
break;
|
|
default:
|
|
addrlen = 0;
|
|
INSIST(0);
|
|
break;
|
|
}
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special &&
|
|
pctx->token.value.as_char == '/') {
|
|
CHECK(cfg_gettoken(pctx, 0)); /* read "/" */
|
|
CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER));
|
|
if (pctx->token.type != isc_tokentype_number) {
|
|
parser_error(pctx, LOG_NEAR,
|
|
"expected prefix length");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
prefixlen = pctx->token.value.as_ulong;
|
|
if (prefixlen > addrlen) {
|
|
parser_error(pctx, LOG_NOPREP,
|
|
"invalid prefix length");
|
|
return (ISC_R_RANGE);
|
|
}
|
|
} else {
|
|
prefixlen = addrlen;
|
|
}
|
|
CHECK(create_cfgobj(pctx, &cfg_type_netprefix, &obj));
|
|
obj->value.netprefix.address = netaddr;
|
|
obj->value.netprefix.prefixlen = prefixlen;
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
cleanup:
|
|
parser_error(pctx, LOG_NEAR, "expected network prefix");
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_netprefix(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
cfg_netprefix_t *p = &obj->value.netprefix;
|
|
print_isc_netaddr(pctx, &p->address);
|
|
print(pctx, "/", 1);
|
|
print_uint(pctx, p->prefixlen);
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_isnetprefix(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_netprefix));
|
|
}
|
|
|
|
void
|
|
cfg_obj_asnetprefix(cfg_obj_t *obj, isc_netaddr_t *netaddr,
|
|
unsigned int *prefixlen) {
|
|
REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_netprefix);
|
|
*netaddr = obj->value.netprefix.address;
|
|
*prefixlen = obj->value.netprefix.prefixlen;
|
|
}
|
|
|
|
static cfg_type_t cfg_type_netprefix = {
|
|
"netprefix", parse_netprefix, print_netprefix, &cfg_rep_netprefix, NULL };
|
|
|
|
/* addrmatchelt */
|
|
|
|
static isc_result_t
|
|
parse_addrmatchelt(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_peektoken(pctx, QSTRING));
|
|
|
|
if (pctx->token.type == isc_tokentype_string ||
|
|
pctx->token.type == isc_tokentype_qstring) {
|
|
if (pctx->token.type == isc_tokentype_string &&
|
|
(strcasecmp(pctx->token.value.as_pointer, "key") == 0)) {
|
|
CHECK(parse(pctx, &cfg_type_keyref, ret));
|
|
} else {
|
|
if (looking_at_netaddr(pctx, V4OK|V4PREFIXOK|V6OK)) {
|
|
CHECK(parse_netprefix(pctx, NULL, ret));
|
|
} else {
|
|
CHECK(parse_astring(pctx, NULL, ret));
|
|
}
|
|
}
|
|
} else if (pctx->token.type == isc_tokentype_special) {
|
|
if (pctx->token.value.as_char == '{') {
|
|
/* Nested match list. */
|
|
CHECK(parse(pctx, &cfg_type_bracketed_aml, ret));
|
|
} else if (pctx->token.value.as_char == '!') {
|
|
CHECK(cfg_gettoken(pctx, 0)); /* read "!" */
|
|
CHECK(parse(pctx, &cfg_type_negated, ret));
|
|
} else {
|
|
goto bad;
|
|
}
|
|
} else {
|
|
bad:
|
|
parser_error(pctx, LOG_NEAR,
|
|
"expected IP match list element");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* A negated address match list element (like "! 10.0.0.1").
|
|
* Somewhat sneakily, the caller is expected to parse the
|
|
* "!", but not to print it.
|
|
*/
|
|
|
|
static cfg_tuplefielddef_t negated_fields[] = {
|
|
{ "value", &cfg_type_addrmatchelt, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static void
|
|
print_negated(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
print(pctx, "!", 1);
|
|
print_tuple(pctx, obj);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_negated = {
|
|
"negated", parse_tuple, print_negated, &cfg_rep_tuple,
|
|
&negated_fields
|
|
};
|
|
|
|
/* an address match list element */
|
|
|
|
static cfg_type_t cfg_type_addrmatchelt = {
|
|
"address_match_element", parse_addrmatchelt, NULL, NULL, NULL };
|
|
static cfg_type_t cfg_type_bracketed_aml = {
|
|
"bracketed_aml", parse_bracketed_list, print_bracketed_list,
|
|
&cfg_rep_list, &cfg_type_addrmatchelt
|
|
};
|
|
|
|
static isc_result_t
|
|
parse_sockaddrsub(cfg_parser_t *pctx, const cfg_type_t *type,
|
|
int flags, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
isc_netaddr_t netaddr;
|
|
in_port_t port = 0;
|
|
cfg_obj_t *obj = NULL;
|
|
|
|
CHECK(create_cfgobj(pctx, type, &obj));
|
|
CHECK(get_addr(pctx, flags, &netaddr));
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_string &&
|
|
strcasecmp(pctx->token.value.as_pointer, "port") == 0) {
|
|
CHECK(cfg_gettoken(pctx, 0)); /* read "port" */
|
|
CHECK(get_port(pctx, flags, &port));
|
|
}
|
|
isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port);
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_sockaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
const unsigned int *flagp = type->of;
|
|
return (parse_sockaddrsub(pctx, &cfg_type_sockaddr4wild, *flagp, ret));
|
|
}
|
|
|
|
static void
|
|
print_sockaddr(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
isc_netaddr_t netaddr;
|
|
in_port_t port;
|
|
char buf[ISC_NETADDR_FORMATSIZE];
|
|
|
|
isc_netaddr_fromsockaddr(&netaddr, &obj->value.sockaddr);
|
|
isc_netaddr_format(&netaddr, buf, sizeof(buf));
|
|
print_cstr(pctx, buf);
|
|
port = isc_sockaddr_getport(&obj->value.sockaddr);
|
|
if (port != 0) {
|
|
print(pctx, " port ", 6);
|
|
print_uint(pctx, port);
|
|
}
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_issockaddr(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL);
|
|
return (ISC_TF(obj->type->rep == &cfg_rep_sockaddr));
|
|
}
|
|
|
|
isc_sockaddr_t *
|
|
cfg_obj_assockaddr(cfg_obj_t *obj) {
|
|
REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_sockaddr);
|
|
return (&obj->value.sockaddr);
|
|
}
|
|
|
|
/* An IPv4/IPv6 address with optional port, "*" accepted as wildcard. */
|
|
static unsigned int sockaddr4wild_flags = WILDOK|V4OK;
|
|
static cfg_type_t cfg_type_sockaddr4wild = {
|
|
"sockaddr4wild", parse_sockaddr, print_sockaddr,
|
|
&cfg_rep_sockaddr, &sockaddr4wild_flags
|
|
};
|
|
|
|
static unsigned int sockaddr6wild_flags = WILDOK|V6OK;
|
|
static cfg_type_t cfg_type_sockaddr6wild = {
|
|
"v6addrportwild", parse_sockaddr, print_sockaddr,
|
|
&cfg_rep_sockaddr, &sockaddr6wild_flags
|
|
};
|
|
|
|
static unsigned int sockaddr_flags = V4OK|V6OK;
|
|
static cfg_type_t cfg_type_sockaddr = {
|
|
"sockaddr", parse_sockaddr, print_sockaddr,
|
|
&cfg_rep_sockaddr, &sockaddr_flags
|
|
};
|
|
|
|
/*
|
|
* The socket address syntax in the "controls" statement is silly.
|
|
* It allows both socket address families, but also allows "*",
|
|
* whis is gratuitously interpreted as the IPv4 wildcard address.
|
|
*/
|
|
static unsigned int controls_sockaddr_flags = V4OK|V6OK|WILDOK;
|
|
static cfg_type_t cfg_type_controls_sockaddr = {
|
|
"controls_sockaddr", parse_sockaddr, print_sockaddr,
|
|
&cfg_rep_sockaddr, &controls_sockaddr_flags };
|
|
|
|
|
|
/*
|
|
* Handle the special kludge syntax of the "keys" clause in the "server"
|
|
* statement, which takes a single key with our without braces and semicolon.
|
|
*/
|
|
static isc_result_t
|
|
parse_server_key_kludge(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
isc_boolean_t braces = ISC_FALSE;
|
|
UNUSED(type);
|
|
|
|
/* Allow opening brace. */
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special &&
|
|
pctx->token.value.as_char == '{') {
|
|
result = cfg_gettoken(pctx, 0);
|
|
braces = ISC_TRUE;
|
|
}
|
|
|
|
CHECK(parse(pctx, &cfg_type_astring, ret));
|
|
|
|
if (braces) {
|
|
/* Skip semicolon if present. */
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_special &&
|
|
pctx->token.value.as_char == ';')
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
|
|
CHECK(parse_special(pctx, '}'));
|
|
}
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
static cfg_type_t cfg_type_server_key_kludge = {
|
|
"server_key", parse_server_key_kludge, NULL, NULL, NULL };
|
|
|
|
|
|
/*
|
|
* An optional logging facility.
|
|
*/
|
|
|
|
static isc_result_t
|
|
parse_optional_facility(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret)
|
|
{
|
|
isc_result_t result;
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_peektoken(pctx, QSTRING));
|
|
if (pctx->token.type == isc_tokentype_string ||
|
|
pctx->token.type == isc_tokentype_qstring) {
|
|
CHECK(parse(pctx, &cfg_type_astring, ret));
|
|
} else {
|
|
CHECK(parse(pctx, &cfg_type_void, ret));
|
|
}
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_optional_facility = {
|
|
"optional_facility", parse_optional_facility, NULL, NULL, NULL };
|
|
|
|
|
|
/*
|
|
* A log severity. Return as a string, except "debug N",
|
|
* which is returned as a keyword object.
|
|
*/
|
|
|
|
static keyword_type_t debug_kw = { "debug", &cfg_type_uint32 };
|
|
static cfg_type_t cfg_type_debuglevel = {
|
|
"debuglevel", parse_keyvalue,
|
|
print_keyvalue, &cfg_rep_uint32, &debug_kw
|
|
};
|
|
|
|
static isc_result_t
|
|
parse_logseverity(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
UNUSED(type);
|
|
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_string &&
|
|
strcasecmp(pctx->token.value.as_pointer, "debug") == 0) {
|
|
CHECK(cfg_gettoken(pctx, 0)); /* read "debug" */
|
|
CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER));
|
|
if (pctx->token.type == isc_tokentype_number) {
|
|
CHECK(parse_uint32(pctx, NULL, ret));
|
|
} else {
|
|
/*
|
|
* The debug level is optional and defaults to 1.
|
|
* This makes little sense, but we support it for
|
|
* compatibility with BIND 8.
|
|
*/
|
|
CHECK(create_cfgobj(pctx, &cfg_type_uint32, ret));
|
|
(*ret)->value.uint32 = 1;
|
|
}
|
|
(*ret)->type = &cfg_type_debuglevel; /* XXX kludge */
|
|
} else {
|
|
CHECK(parse(pctx, &cfg_type_loglevel, ret));
|
|
}
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
static cfg_type_t cfg_type_logseverity = {
|
|
"logseverity", parse_logseverity, NULL, NULL, NULL };
|
|
|
|
/*
|
|
* The "file" clause of the "channel" statement.
|
|
* This is yet another special case.
|
|
*/
|
|
|
|
static const char *logversions_enums[] = { "unlimited", NULL };
|
|
static isc_result_t
|
|
parse_logversions(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
return (parse_enum_or_other(pctx, type, &cfg_type_uint32, ret));
|
|
}
|
|
static cfg_type_t cfg_type_logversions = {
|
|
"logversions", parse_logversions, print_ustring,
|
|
&cfg_rep_string, logversions_enums
|
|
};
|
|
|
|
static cfg_tuplefielddef_t logfile_fields[] = {
|
|
{ "file", &cfg_type_qstring, 0 },
|
|
{ "versions", &cfg_type_logversions, 0 },
|
|
{ "size", &cfg_type_size, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static isc_result_t
|
|
parse_logfile(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
cfg_obj_t *obj = NULL;
|
|
const cfg_tuplefielddef_t *fields = type->of;
|
|
|
|
CHECK(create_tuple(pctx, type, &obj));
|
|
|
|
/* Parse the mandatory "file" field */
|
|
CHECK(parse(pctx, fields[0].type, &obj->value.tuple[0]));
|
|
|
|
/* Parse "versions" and "size" fields in any order. */
|
|
for (;;) {
|
|
CHECK(cfg_peektoken(pctx, 0));
|
|
if (pctx->token.type == isc_tokentype_string) {
|
|
CHECK(cfg_gettoken(pctx, 0));
|
|
if (strcasecmp(pctx->token.value.as_pointer,
|
|
"versions") == 0 &&
|
|
obj->value.tuple[1] == NULL) {
|
|
CHECK(parse(pctx, fields[1].type,
|
|
&obj->value.tuple[1]));
|
|
} else if (strcasecmp(pctx->token.value.as_pointer,
|
|
"size") == 0 &&
|
|
obj->value.tuple[2] == NULL) {
|
|
CHECK(parse(pctx, fields[2].type,
|
|
&obj->value.tuple[2]));
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Create void objects for missing optional values. */
|
|
if (obj->value.tuple[1] == NULL)
|
|
CHECK(parse_void(pctx, NULL, &obj->value.tuple[1]));
|
|
if (obj->value.tuple[2] == NULL)
|
|
CHECK(parse_void(pctx, NULL, &obj->value.tuple[2]));
|
|
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
CLEANUP_OBJ(obj);
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
print_logfile(cfg_printer_t *pctx, cfg_obj_t *obj) {
|
|
print_obj(pctx, obj->value.tuple[0]); /* file */
|
|
if (obj->value.tuple[1]->type->print != print_void) {
|
|
print(pctx, " versions ", 10);
|
|
print_obj(pctx, obj->value.tuple[1]);
|
|
}
|
|
if (obj->value.tuple[2]->type->print != print_void) {
|
|
print(pctx, " size ", 6);
|
|
print_obj(pctx, obj->value.tuple[2]);
|
|
}
|
|
}
|
|
|
|
static cfg_type_t cfg_type_logfile = {
|
|
"logfile", parse_logfile, print_logfile, &cfg_rep_tuple,
|
|
logfile_fields
|
|
};
|
|
|
|
|
|
/*
|
|
* lwres
|
|
*/
|
|
|
|
static cfg_tuplefielddef_t lwres_view_fields[] = {
|
|
{ "name", &cfg_type_astring, 0 },
|
|
{ "class", &cfg_type_optional_class, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
static cfg_type_t cfg_type_lwres_view = {
|
|
"lwres_view", parse_tuple, print_tuple, &cfg_rep_tuple,
|
|
lwres_view_fields
|
|
};
|
|
|
|
static cfg_type_t cfg_type_lwres_searchlist = {
|
|
"lwres_searchlist", parse_bracketed_list, print_bracketed_list,
|
|
&cfg_rep_list, &cfg_type_astring };
|
|
|
|
static cfg_clausedef_t
|
|
lwres_clauses[] = {
|
|
{ "listen-on", &cfg_type_portiplist, 0 },
|
|
{ "view", &cfg_type_lwres_view, 0 },
|
|
{ "search", &cfg_type_lwres_searchlist, 0 },
|
|
{ "ndots", &cfg_type_uint32, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_clausedef_t *
|
|
lwres_clausesets[] = {
|
|
lwres_clauses,
|
|
NULL
|
|
};
|
|
static cfg_type_t cfg_type_lwres = {
|
|
"lwres", parse_map, print_map, &cfg_rep_map, lwres_clausesets };
|
|
|
|
/*
|
|
* rndc
|
|
*/
|
|
|
|
static cfg_clausedef_t
|
|
rndcconf_options_clauses[] = {
|
|
{ "default-server", &cfg_type_astring, 0 },
|
|
{ "default-key", &cfg_type_astring, 0 },
|
|
{ "default-port", &cfg_type_uint32, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_clausedef_t *
|
|
rndcconf_options_clausesets[] = {
|
|
rndcconf_options_clauses,
|
|
NULL
|
|
};
|
|
|
|
static cfg_type_t cfg_type_rndcconf_options = {
|
|
"rndcconf_options", parse_map, print_map, &cfg_rep_map,
|
|
rndcconf_options_clausesets
|
|
};
|
|
|
|
static cfg_clausedef_t
|
|
rndcconf_server_clauses[] = {
|
|
{ "key", &cfg_type_astring, 0 },
|
|
{ "port", &cfg_type_uint32, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_clausedef_t *
|
|
rndcconf_server_clausesets[] = {
|
|
rndcconf_server_clauses,
|
|
NULL
|
|
};
|
|
|
|
static cfg_type_t cfg_type_rndcconf_server = {
|
|
"rndcconf_server", parse_named_map, print_map, &cfg_rep_map,
|
|
rndcconf_server_clausesets
|
|
};
|
|
|
|
static cfg_clausedef_t
|
|
rndcconf_clauses[] = {
|
|
{ "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI },
|
|
{ "server", &cfg_type_rndcconf_server, CFG_CLAUSEFLAG_MULTI },
|
|
{ "options", &cfg_type_rndcconf_options, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_clausedef_t *
|
|
rndcconf_clausesets[] = {
|
|
rndcconf_clauses,
|
|
NULL
|
|
};
|
|
|
|
LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_rndcconf = {
|
|
"rndcconf", parse_mapbody, print_mapbody, &cfg_rep_map,
|
|
rndcconf_clausesets
|
|
};
|
|
|
|
static cfg_clausedef_t
|
|
rndckey_clauses[] = {
|
|
{ "key", &cfg_type_key, 0 },
|
|
{ NULL, NULL, 0 }
|
|
};
|
|
|
|
static cfg_clausedef_t *
|
|
rndckey_clausesets[] = {
|
|
rndckey_clauses,
|
|
NULL
|
|
};
|
|
|
|
LIBISCCFG_EXTERNAL_DATA cfg_type_t cfg_type_rndckey = {
|
|
"rndckey", parse_mapbody, print_mapbody, &cfg_rep_map,
|
|
rndckey_clausesets
|
|
};
|
|
|
|
|
|
static isc_result_t
|
|
cfg_gettoken(cfg_parser_t *pctx, int options) {
|
|
isc_result_t result;
|
|
|
|
if (pctx->seen_eof)
|
|
return (ISC_R_SUCCESS);
|
|
|
|
options |= (ISC_LEXOPT_EOF | ISC_LEXOPT_NOMORE);
|
|
|
|
redo:
|
|
pctx->token.type = isc_tokentype_unknown;
|
|
result = isc_lex_gettoken(pctx->lexer, options, &pctx->token);
|
|
pctx->ungotten = ISC_FALSE;
|
|
pctx->line = isc_lex_getsourceline(pctx->lexer);
|
|
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
if (pctx->token.type == isc_tokentype_eof) {
|
|
result = isc_lex_close(pctx->lexer);
|
|
INSIST(result == ISC_R_NOMORE ||
|
|
result == ISC_R_SUCCESS);
|
|
|
|
if (isc_lex_getsourcename(pctx->lexer) != NULL) {
|
|
/*
|
|
* Closed an included file, not the main file.
|
|
*/
|
|
cfg_listelt_t *elt;
|
|
elt = ISC_LIST_TAIL(pctx->open_files->
|
|
value.list);
|
|
INSIST(elt != NULL);
|
|
ISC_LIST_UNLINK(pctx->open_files->
|
|
value.list, elt, link);
|
|
ISC_LIST_APPEND(pctx->closed_files->
|
|
value.list, elt, link);
|
|
goto redo;
|
|
}
|
|
pctx->seen_eof = ISC_TRUE;
|
|
}
|
|
break;
|
|
|
|
case ISC_R_NOSPACE:
|
|
/* More understandable than "ran out of space". */
|
|
parser_error(pctx, LOG_NEAR, "token too big");
|
|
break;
|
|
|
|
case ISC_R_IOERROR:
|
|
parser_error(pctx, 0, "%s",
|
|
isc_result_totext(result));
|
|
break;
|
|
|
|
default:
|
|
parser_error(pctx, LOG_NEAR, "%s",
|
|
isc_result_totext(result));
|
|
break;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
cfg_ungettoken(cfg_parser_t *pctx) {
|
|
if (pctx->seen_eof)
|
|
return;
|
|
isc_lex_ungettoken(pctx->lexer, &pctx->token);
|
|
pctx->ungotten = ISC_TRUE;
|
|
}
|
|
|
|
static isc_result_t
|
|
cfg_peektoken(cfg_parser_t *pctx, int options) {
|
|
isc_result_t result;
|
|
CHECK(cfg_gettoken(pctx, options));
|
|
cfg_ungettoken(pctx);
|
|
cleanup:
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Get a string token, accepting both the quoted and the unquoted form.
|
|
* Log an error if the next token is not a string.
|
|
*/
|
|
static isc_result_t
|
|
cfg_getstringtoken(cfg_parser_t *pctx) {
|
|
isc_result_t result;
|
|
|
|
result = cfg_gettoken(pctx, QSTRING);
|
|
if (result != ISC_R_SUCCESS)
|
|
return (result);
|
|
|
|
if (pctx->token.type != isc_tokentype_string &&
|
|
pctx->token.type != isc_tokentype_qstring) {
|
|
parser_error(pctx, LOG_NEAR, "expected string");
|
|
return (ISC_R_UNEXPECTEDTOKEN);
|
|
}
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
parser_complain(pctx, ISC_FALSE, flags, fmt, args);
|
|
va_end(args);
|
|
pctx->errors++;
|
|
}
|
|
|
|
static void
|
|
parser_warning(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
parser_complain(pctx, ISC_TRUE, flags, fmt, args);
|
|
va_end(args);
|
|
pctx->warnings++;
|
|
}
|
|
|
|
#define MAX_LOG_TOKEN 30 /* How much of a token to quote in log messages. */
|
|
|
|
static char *
|
|
current_file(cfg_parser_t *pctx) {
|
|
static char none[] = "none";
|
|
cfg_listelt_t *elt;
|
|
cfg_obj_t *fileobj;
|
|
|
|
if (pctx->open_files == NULL)
|
|
return (none);
|
|
elt = ISC_LIST_TAIL(pctx->open_files->value.list);
|
|
if (elt == NULL)
|
|
return (none);
|
|
|
|
fileobj = elt->obj;
|
|
INSIST(fileobj->type == &cfg_type_qstring);
|
|
return (fileobj->value.string.base);
|
|
}
|
|
|
|
static void
|
|
parser_complain(cfg_parser_t *pctx, isc_boolean_t is_warning,
|
|
unsigned int flags, const char *format,
|
|
va_list args)
|
|
{
|
|
char tokenbuf[MAX_LOG_TOKEN + 10];
|
|
static char where[ISC_DIR_PATHMAX + 100];
|
|
static char message[2048];
|
|
int level = ISC_LOG_ERROR;
|
|
const char *prep = "";
|
|
|
|
if (is_warning)
|
|
level = ISC_LOG_WARNING;
|
|
|
|
sprintf(where, "%s:%u: ", current_file(pctx), pctx->line);
|
|
|
|
if ((unsigned int)vsprintf(message, format, args) >= sizeof message)
|
|
FATAL_ERROR(__FILE__, __LINE__,
|
|
"error message would overflow");
|
|
|
|
if ((flags & (LOG_NEAR|LOG_BEFORE|LOG_NOPREP)) != 0) {
|
|
isc_region_t r;
|
|
|
|
if (pctx->ungotten)
|
|
(void)cfg_gettoken(pctx, 0);
|
|
|
|
if (pctx->token.type == isc_tokentype_eof) {
|
|
snprintf(tokenbuf, sizeof(tokenbuf), "end of file");
|
|
} else if (pctx->token.type == isc_tokentype_unknown) {
|
|
flags = 0;
|
|
tokenbuf[0] = '\0';
|
|
} else {
|
|
isc_lex_getlasttokentext(pctx->lexer,
|
|
&pctx->token, &r);
|
|
if (r.length > MAX_LOG_TOKEN)
|
|
snprintf(tokenbuf, sizeof(tokenbuf),
|
|
"'%.*s...'", MAX_LOG_TOKEN, r.base);
|
|
else
|
|
snprintf(tokenbuf, sizeof(tokenbuf),
|
|
"'%.*s'", (int)r.length, r.base);
|
|
}
|
|
|
|
/* Choose a preposition. */
|
|
if (flags & LOG_NEAR)
|
|
prep = " near ";
|
|
else if (flags & LOG_BEFORE)
|
|
prep = " before ";
|
|
else
|
|
prep = " ";
|
|
} else {
|
|
tokenbuf[0] = '\0';
|
|
}
|
|
isc_log_write(pctx->lctx, CAT, MOD, level,
|
|
"%s%s%s%s", where, message, prep, tokenbuf);
|
|
}
|
|
|
|
void
|
|
cfg_obj_log(cfg_obj_t *obj, isc_log_t *lctx, int level, const char *fmt, ...) {
|
|
va_list ap;
|
|
char msgbuf[2048];
|
|
|
|
if (! isc_log_wouldlog(lctx, level))
|
|
return;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
|
|
isc_log_write(lctx, CAT, MOD, level,
|
|
"%s:%u: %s",
|
|
obj->file == NULL ? "<unknown file>" : obj->file,
|
|
obj->line, msgbuf);
|
|
va_end(ap);
|
|
}
|
|
|
|
static isc_result_t
|
|
create_cfgobj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
cfg_obj_t *obj;
|
|
|
|
obj = isc_mem_get(pctx->mctx, sizeof(cfg_obj_t));
|
|
if (obj == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
obj->type = type;
|
|
obj->file = current_file(pctx);
|
|
obj->line = pctx->line;
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
map_symtabitem_destroy(char *key, unsigned int type,
|
|
isc_symvalue_t symval, void *userarg)
|
|
{
|
|
cfg_obj_t *obj = symval.as_pointer;
|
|
cfg_parser_t *pctx = (cfg_parser_t *)userarg;
|
|
|
|
UNUSED(key);
|
|
UNUSED(type);
|
|
|
|
cfg_obj_destroy(pctx, &obj);
|
|
}
|
|
|
|
|
|
static isc_result_t
|
|
create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
|
|
isc_result_t result;
|
|
isc_symtab_t *symtab = NULL;
|
|
cfg_obj_t *obj = NULL;
|
|
|
|
CHECK(create_cfgobj(pctx, type, &obj));
|
|
CHECK(isc_symtab_create(pctx->mctx, 5, /* XXX */
|
|
map_symtabitem_destroy,
|
|
pctx, ISC_FALSE, &symtab));
|
|
|
|
obj->value.map.symtab = symtab;
|
|
obj->value.map.id = NULL;
|
|
|
|
*ret = obj;
|
|
return (ISC_R_SUCCESS);
|
|
|
|
cleanup:
|
|
if (obj != NULL)
|
|
isc_mem_put(pctx->mctx, obj, sizeof(*obj));
|
|
return (result);
|
|
}
|
|
|
|
static void
|
|
free_map(cfg_parser_t *pctx, cfg_obj_t *obj) {
|
|
CLEANUP_OBJ(obj->value.map.id);
|
|
isc_symtab_destroy(&obj->value.map.symtab);
|
|
}
|
|
|
|
isc_boolean_t
|
|
cfg_obj_istype(cfg_obj_t *obj, const cfg_type_t *type) {
|
|
return (ISC_TF(obj->type == type));
|
|
}
|
|
|
|
/*
|
|
* Destroy 'obj', a configuration object created in 'pctx'.
|
|
*/
|
|
void
|
|
cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **objp) {
|
|
cfg_obj_t *obj = *objp;
|
|
obj->type->rep->free(pctx, obj);
|
|
isc_mem_put(pctx->mctx, obj, sizeof(cfg_obj_t));
|
|
*objp = NULL;
|
|
}
|
|
|
|
static void
|
|
free_noop(cfg_parser_t *pctx, cfg_obj_t *obj) {
|
|
UNUSED(pctx);
|
|
UNUSED(obj);
|
|
}
|
|
|
|
/*
|
|
* Data and functions for printing grammar summaries.
|
|
*/
|
|
static struct flagtext {
|
|
unsigned int flag;
|
|
const char *text;
|
|
} flagtexts[] = {
|
|
{ CFG_CLAUSEFLAG_NOTIMP, "not implemented" },
|
|
{ CFG_CLAUSEFLAG_NYI, "not yet implemented" },
|
|
{ CFG_CLAUSEFLAG_OBSOLETE, "obsolete" },
|
|
{ CFG_CLAUSEFLAG_NEWDEFAULT, "default changed" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
static void
|
|
print_clause_flags(cfg_printer_t *pctx, unsigned int flags) {
|
|
struct flagtext *p;
|
|
isc_boolean_t first = ISC_TRUE;
|
|
for (p = flagtexts; p->flag != 0; p++) {
|
|
if ((flags & p->flag) != 0) {
|
|
if (first)
|
|
print(pctx, " // ", 4);
|
|
else
|
|
print(pctx, ", ", 2);
|
|
print_cstr(pctx, p->text);
|
|
first = ISC_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_grammar(cfg_printer_t *pctx, const cfg_type_t *type) {
|
|
if (type->print == print_mapbody) {
|
|
const cfg_clausedef_t * const *clauseset;
|
|
const cfg_clausedef_t *clause;
|
|
|
|
for (clauseset = type->of; *clauseset != NULL; clauseset++) {
|
|
for (clause = *clauseset;
|
|
clause->name != NULL;
|
|
clause++) {
|
|
print_cstr(pctx, clause->name);
|
|
print(pctx, " ", 1);
|
|
print_grammar(pctx, clause->type);
|
|
print(pctx, ";", 1);
|
|
/* XXX print flags here? */
|
|
print(pctx, "\n\n", 2);
|
|
}
|
|
}
|
|
} else if (type->print == print_map) {
|
|
const cfg_clausedef_t * const *clauseset;
|
|
const cfg_clausedef_t *clause;
|
|
|
|
if (type->parse == parse_named_map) {
|
|
print_grammar(pctx, &cfg_type_astring);
|
|
print(pctx, " ", 1);
|
|
}
|
|
|
|
print_open(pctx);
|
|
|
|
for (clauseset = type->of; *clauseset != NULL; clauseset++) {
|
|
for (clause = *clauseset;
|
|
clause->name != NULL;
|
|
clause++) {
|
|
print_indent(pctx);
|
|
print_cstr(pctx, clause->name);
|
|
if (clause->type->print != print_void)
|
|
print(pctx, " ", 1);
|
|
print_grammar(pctx, clause->type);
|
|
print(pctx, ";", 1);
|
|
print_clause_flags(pctx, clause->flags);
|
|
print(pctx, "\n", 1);
|
|
}
|
|
}
|
|
print_close(pctx);
|
|
} else if (type->print == print_tuple) {
|
|
const cfg_tuplefielddef_t *fields = type->of;
|
|
const cfg_tuplefielddef_t *f;
|
|
isc_boolean_t need_space = ISC_FALSE;
|
|
|
|
for (f = fields; f->name != NULL; f++) {
|
|
if (need_space)
|
|
print(pctx, " ", 1);
|
|
print_grammar(pctx, f->type);
|
|
need_space = ISC_TF(f->type->print != print_void);
|
|
}
|
|
} else if (type->parse == parse_enum) {
|
|
const char * const *p;
|
|
print(pctx, "( ", 2);
|
|
for (p = type->of; *p != NULL; p++) {
|
|
print_cstr(pctx, *p);
|
|
if (p[1] != NULL)
|
|
print(pctx, " | ", 3);
|
|
}
|
|
print(pctx, " )", 2);
|
|
} else if (type->print == print_bracketed_list) {
|
|
print(pctx, "{ ", 2);
|
|
print_grammar(pctx, type->of);
|
|
print(pctx, "; ... }", 7);
|
|
} else if (type->parse == parse_keyvalue) {
|
|
const keyword_type_t *kw = type->of;
|
|
print_cstr(pctx, kw->name);
|
|
print(pctx, " ", 1);
|
|
print_grammar(pctx, kw->type);
|
|
} else if (type->parse == parse_optional_keyvalue) {
|
|
const keyword_type_t *kw = type->of;
|
|
print(pctx, "[ ", 2);
|
|
print_cstr(pctx, kw->name);
|
|
print(pctx, " ", 1);
|
|
print_grammar(pctx, kw->type);
|
|
print(pctx, " ]", 2);
|
|
} else if (type->parse == parse_sockaddr) {
|
|
const unsigned int *flagp = type->of;
|
|
int n = 0;
|
|
print(pctx, "( ", 2);
|
|
if (*flagp & V4OK) {
|
|
if (n != 0)
|
|
print(pctx, " | ", 3);
|
|
print_cstr(pctx, "<ipv4_address>");
|
|
n++;
|
|
}
|
|
if (*flagp & V6OK) {
|
|
if (n != 0)
|
|
print(pctx, " | ", 3);
|
|
print_cstr(pctx, "<ipv6_address>");
|
|
n++;
|
|
}
|
|
if (*flagp & WILDOK) {
|
|
if (n != 0)
|
|
print(pctx, " | ", 3);
|
|
print(pctx, "*", 1);
|
|
n++;
|
|
}
|
|
print(pctx, " ) ", 3);
|
|
if (*flagp & WILDOK) {
|
|
print_cstr(pctx, "[ port ( <integer> | * ) ]");
|
|
} else {
|
|
print_cstr(pctx, "[ port <integer> ]");
|
|
}
|
|
} else if (type->print == print_void) {
|
|
/* Print nothing. */
|
|
} else {
|
|
print(pctx, "<", 1);
|
|
print_cstr(pctx, type->name);
|
|
print(pctx, ">", 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
cfg_print_grammar(const cfg_type_t *type,
|
|
void (*f)(void *closure, const char *text, int textlen),
|
|
void *closure)
|
|
{
|
|
cfg_printer_t pctx;
|
|
pctx.f = f;
|
|
pctx.closure = closure;
|
|
pctx.indent = 0;
|
|
print_grammar(&pctx, type);
|
|
}
|