parser.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 2000-2016 Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*! \file */
#include <config.h>
#include <stdlib.h>
#include <isc/formatcheck.h>
#include <isc/netscope.h>
#include <isc/sockaddr.h>
/* Shorthand */
#define CAT CFG_LOGCATEGORY_CONFIG
#define MOD CFG_LOGMODULE_PARSER
/* Check a return value. */
} while (0)
/* Clean up a configuration object if non-NULL. */
#define CLEANUP_OBJ(obj) \
/*
* Forward declarations of static functions.
*/
static void
static isc_result_t
static void
static void
static isc_result_t
static isc_result_t
static void
static isc_result_t
static void
static isc_result_t
static void
static isc_result_t
static void
/*
* Data representations. These correspond to members of the
* "value" union in struct cfg_obj (except "void", which does
* not need a union member).
*/
/*
* Configuration type definitions.
*/
/*%
* An implicit list. These are formed by clauses that occur multiple times.
*/
static cfg_type_t cfg_type_implicitlist = {
/* Functions. */
void
}
void
}
static void
else {
}
}
static void
return;
}
while (indent > 0) {
indent--;
}
}
static void
}
}
if (result != ISC_R_SUCCESS)
return (result);
return (ISC_R_SUCCESS);
}
void
void *closure)
{
}
void
void *closure)
{
pctx.f = f;
}
/* Tuples. */
const cfg_tuplefielddef_t *f;
unsigned int nfields = 0;
int i;
nfields++;
goto cleanup;
}
return (ISC_R_SUCCESS);
return (result);
}
{
const cfg_tuplefielddef_t *f;
unsigned int i;
return (ISC_R_SUCCESS);
return (result);
}
void
unsigned int i;
const cfg_tuplefielddef_t *f;
}
}
void
const cfg_tuplefielddef_t *f;
if (need_space)
}
}
static void
unsigned int i;
const cfg_tuplefielddef_t *f;
unsigned int nfields = 0;
return;
nfields++;
}
}
}
const cfg_obj_t *
unsigned int i;
const cfg_tuplefielddef_t *fields;
const cfg_tuplefielddef_t *f;
}
INSIST(0);
return (NULL);
}
return (ISC_R_SUCCESS);
return (ISC_R_UNEXPECTEDTOKEN);
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
return (ISC_R_SUCCESS);
return (result);
}
/*
* Parse EOF, logging and returning an error if not there.
*/
static isc_result_t
return (ISC_R_SUCCESS);
return (ISC_R_UNEXPECTEDTOKEN);
return (result);
}
/* A list of files, used internally for pctx->files. */
static cfg_type_t cfg_type_filelist = {
};
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
return (ISC_R_SUCCESS);
return (result);
}
static isc_result_t
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
return (ISC_R_SUCCESS);
return (result);
}
void
void *arg)
{
}
void
}
/*
* Parse a configuration using a pctx where a lexer has already
* been set up with a source.
*/
static isc_result_t
/* Errors have been logged. */
if (result == ISC_R_SUCCESS)
goto cleanup;
}
if (result != ISC_R_SUCCESS) {
/* Parsing failed but no errors have been logged. */
goto cleanup;
}
return (ISC_R_SUCCESS);
return (result);
}
{
/* Clean up the opened file */
return (result);
}
{
}
{
return (result);
}
void
}
void
unsigned int refs;
if (refs == 0) {
/*
* Cleaning up open_files does not
* close the files; that was already done
* by closing the lexer.
*/
}
}
/*
* void
*/
}
void
}
void
}
}
NULL };
/*
* percentage
*/
{
char *endp;
"expected percentage");
return (ISC_R_UNEXPECTEDTOKEN);
}
"expected percentage");
return (ISC_R_UNEXPECTEDTOKEN);
}
return (result);
}
void
char buf[64];
int n;
}
}
};
}
/*
* Fixed point
*/
{
const char *p;
"expected fixed point number");
return (ISC_R_UNEXPECTEDTOKEN);
}
p = TOKEN_STRING(pctx);
l = strlen(p);
"expected fixed point number");
return (ISC_R_UNEXPECTEDTOKEN);
}
switch (n3) {
case 2:
break;
case 1:
break;
}
return (result);
}
void
char buf[64];
int n;
}
}
};
}
/*
* uint32
*/
return (ISC_R_UNEXPECTEDTOKEN);
}
return (result);
}
void
}
void
char buf[32];
}
void
}
}
}
};
/*
* uint64
*/
}
}
void
char buf[32];
}
};
/*
* qstring (quoted string), ustring (unquoted string), astring
* (any string)
*/
/* Create a string object from a null-terminated C string. */
static isc_result_t
{
int len;
return (ISC_R_NOMEMORY);
}
return (result);
}
return (ISC_R_UNEXPECTEDTOKEN);
}
return (create_string(pctx,
ret));
return (result);
}
static isc_result_t
return (ISC_R_UNEXPECTEDTOKEN);
}
return (create_string(pctx,
ret));
return (result);
}
{
return (create_string(pctx,
ret));
return (result);
}
{
return (create_string(pctx,
ret));
return (result);
}
static isc_result_t
{
"expected bracketed text");
return (ISC_R_UNEXPECTEDTOKEN);
}
return (create_string(pctx,
ret));
return (result);
}
static void
}
static void
}
cfg_is_enum(const char *s, const char *const *enums) {
const char * const *p;
if (strcasecmp(*p, s) == 0)
return (ISC_TRUE);
}
return (ISC_FALSE);
}
static isc_result_t
if (cfg_is_enum(s, enums))
return (ISC_R_SUCCESS);
return (ISC_R_UNEXPECTEDTOKEN);
}
return (ISC_R_SUCCESS);
return (result);
}
void
const char * const *p;
cfg_print_cstr(pctx, *p);
if (p[1] != NULL)
}
}
void
}
static void
}
static void
while (len-- > 0)
} else
}
static void
}
}
const char *
}
/* Quoted string only */
};
/* Unquoted string only */
};
/* Any string (quoted or unquoted); printed with quotes */
};
/*
* Any string (quoted or unquoted); printed with quotes.
* If CFG_PRINTER_XKEY is set when printing the string will be '?' out.
*/
};
/*
* Text enclosed in brackets. Used to pass a block of configuration
* text to dynamic library or external application. Checked for
* bracket balance, but not otherwise parsed.
*/
};
/*
* Booleans
*/
}
}
{
if (result != ISC_R_SUCCESS)
return (result);
goto bad_boolean;
} else {
goto bad_boolean;
}
return (result);
return (ISC_R_UNEXPECTEDTOKEN);
return (result);
}
void
else
}
};
/*
* Lists.
*/
return (result);
}
static isc_result_t
return (ISC_R_NOMEMORY);
return (ISC_R_SUCCESS);
}
static void
}
static void
{
}
}
cfg_listelt_t **ret)
{
if (result != ISC_R_SUCCESS)
goto cleanup;
return (ISC_R_SUCCESS);
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
{
for (;;) {
break;
}
return (ISC_R_SUCCESS);
return (result);
}
static void
const cfg_listelt_t *elt;
{
} else {
}
}
}
{
return (result);
}
void
}
void
}
/*
* Parse a homogeneous list whose elements are of type 'elttype'
* and where elements are separated by space. The list ends
* before the first semicolon.
*/
{
for (;;) {
break;
}
return (ISC_R_SUCCESS);
return (result);
}
void
const cfg_listelt_t *elt;
}
}
}
const cfg_listelt_t *
return (NULL);
}
const cfg_listelt_t *
}
/*
* Return the length of a list object. If obj is NULL or is not
* a list, return 0.
*/
unsigned int
const cfg_listelt_t *elt;
unsigned int count = 0;
return (0U);
} else {
count++;
}
}
return (count);
}
}
/*
* 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.
*/
{
const cfg_clausedef_t * const *clauseset;
const cfg_clausedef_t *clause;
for (;;) {
redo:
/*
* Parse the option name and see if it is known.
*/
break;
}
/*
* We accept "include" statements wherever a map body
* clause can occur.
*/
/*
* Turn the file name into a temporary configuration
* object just so that it is not overwritten by the
* semicolon token.
*/
goto redo;
}
clause++) {
goto done;
}
}
done:
/*
* Try to recover by parsing this option as an unknown
* option and discarding it.
*/
continue;
}
/* Clause is known. */
/* Issue warnings if appropriate */
"enabled at compile time",
goto cleanup;
}
/*
* 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. */
/* Multivalued clause */
if (result == ISC_R_NOTFOUND) {
&listobj));
1, symval,
if (result != ISC_R_SUCCESS) {
"isc_symtab_define(%s) "
sizeof(cfg_list_t));
goto cleanup;
}
} else {
}
} else {
/* Single-valued clause */
if (result == ISC_R_NOTFOUND) {
CFG_CLAUSEFLAG_CALLBACK) != 0);
callback));
} else if (result == ISC_R_SUCCESS) {
goto cleanup;
} else {
"isc_symtab_define() failed");
goto cleanup;
}
}
}
return (ISC_R_SUCCESS);
return (result);
}
static isc_result_t
{
1, symval,
return (ISC_R_SUCCESS);
return (result);
}
/*
* Parse a map; e.g., "{ foo 1; bar { glub; }; zap true; zap false; }"
*/
return (result);
}
/*
* Subroutine for cfg_parse_named_map() and cfg_parse_addressed_map().
*/
static isc_result_t
{
return (result);
return (result);
}
/*
* Parse a map identified by a string name. E.g., "name { foo 1; }".
* Used for the "key" and "channel" statements.
*/
}
/*
* Parse a map identified by a network address.
* Used to be used for the "server" statement.
*/
}
/*
* Parse a map identified by a network prefix.
* Used for the "server" statement.
*/
}
static void
else
}
void
const cfg_clausedef_t * const *clauseset;
clauseset++)
{
const cfg_clausedef_t *clause;
clause++) {
if (result == ISC_R_SUCCESS) {
/* Multivalued. */
}
} else {
/* Single-valued. */
symobj);
}
} else if (result == ISC_R_NOTFOUND) {
; /* do nothing */
} else {
INSIST(0);
}
}
}
}
void
const cfg_clausedef_t * const *clauseset;
const cfg_clausedef_t *clause;
clause++) {
/* XXX print flags here? */
}
}
}
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" },
{ CFG_CLAUSEFLAG_TESTONLY, "test only" },
{ CFG_CLAUSEFLAG_NOTCONFIGURED, "not configured" },
{ 0, NULL }
};
void
}
}
static void
struct flagtext *p;
if (first)
else
}
}
}
void
const cfg_clausedef_t * const *clauseset;
const cfg_clausedef_t *clause;
}
clause++) {
}
}
}
}
if (result != ISC_R_SUCCESS)
return (result);
return (ISC_R_SUCCESS);
}
const cfg_obj_t *
}
unsigned int
}
/* Parse an arbitrary token, storing its raw text representation. */
static isc_result_t
isc_region_t r;
goto cleanup;
}
goto cleanup;
}
return (result);
return (result);
}
};
/*
* An unsupported option. This is just a list of tokens with balanced braces
* ending in a semicolon.
*/
static isc_result_t
int braces = 0;
for (;;) {
braces++;
braces--;
if (braces == 0)
break;
}
goto cleanup;
}
}
return (ISC_R_SUCCESS);
return (result);
}
};
/*
* Try interpreting the current token as a network address.
*
* If CFG_ADDR_WILDOK is set in flags, "*" can be used as a wildcard
* and at least one of CFG_ADDR_V4OK and CFG_ADDR_V6OK must also be set. The
* "*" is interpreted as the IPv4 wildcard address if CFG_ADDR_V4OK is
* set (including the case where CFG_ADDR_V4OK and CFG_ADDR_V6OK are both set),
* and the IPv6 wildcard address otherwise.
*/
static isc_result_t
char *s;
return (ISC_R_UNEXPECTEDTOKEN);
s = TOKEN_STRING(pctx);
if ((flags & CFG_ADDR_V4OK) != 0) {
return (ISC_R_SUCCESS);
} else if ((flags & CFG_ADDR_V6OK) != 0) {
return (ISC_R_SUCCESS);
} else {
INSIST(0);
}
} else {
return (ISC_R_SUCCESS);
}
}
if ((flags & CFG_ADDR_V4PREFIXOK) != 0 &&
strlen(s) <= 15U) {
char buf[64];
int i;
for (i = 0; i < 3; i++) {
return (ISC_R_SUCCESS);
}
}
}
if ((flags & CFG_ADDR_V6OK) != 0 &&
strlen(s) <= 127U) {
char *d; /* zone delimiter */
if (d != NULL)
*d = '\0';
if (d != NULL) {
#ifdef ISC_PLATFORM_HAVESCOPEID
d + 1,
&in6a,
&zone);
if (result != ISC_R_SUCCESS)
return (result);
#else
return (ISC_R_BADADDRESSFORM);
#endif
}
return (ISC_R_SUCCESS);
}
}
}
return (ISC_R_UNEXPECTEDTOKEN);
}
const char *wild = "";
const char *prefix = "";
if (result == ISC_R_UNEXPECTEDTOKEN) {
if ((flags & CFG_ADDR_WILDOK) != 0)
wild = " or '*'";
if ((flags & CFG_ADDR_V4PREFIXOK) != 0)
wild = " or IPv4 prefix";
"expected IPv4 address%s%s",
"expected IPv6 address%s%s",
else
"expected IP address%s%s",
}
return (result);
}
}
if ((flags & CFG_ADDR_WILDOK) != 0 &&
*port = 0;
return (ISC_R_SUCCESS);
}
"expected port number or '*'");
return (ISC_R_UNEXPECTEDTOKEN);
}
"port number out of range");
return (ISC_R_UNEXPECTEDTOKEN);
}
return (ISC_R_SUCCESS);
return (result);
}
void
char text[128];
}
"expected number");
return (ISC_R_UNEXPECTEDTOKEN);
}
"dscp out of range");
return (ISC_R_RANGE);
}
return (ISC_R_SUCCESS);
return (result);
}
/* netaddr */
static unsigned int netaddr4_flags = CFG_ADDR_V4OK;
static unsigned int netaddr6_flags = CFG_ADDR_V6OK;
static isc_result_t
return (ISC_R_SUCCESS);
return (result);
}
static void
int n = 0;
if (*flagp & CFG_ADDR_V4OK) {
n++;
}
if (*flagp & CFG_ADDR_V6OK) {
if (n != 0)
n++;
}
if (*flagp & CFG_ADDR_WILDOK) {
if (n != 0)
n++;
POST(n);
}
}
};
};
};
};
};
/* netprefix */
{
CFG_ADDR_V6OK, &netaddr));
case AF_INET:
addrlen = 32;
break;
case AF_INET6:
addrlen = 128;
break;
default:
INSIST(0);
break;
}
"expected prefix length");
return (ISC_R_UNEXPECTEDTOKEN);
}
"invalid prefix length");
return (ISC_R_RANGE);
}
} else {
}
return (ISC_R_SUCCESS);
return (result);
}
static void
}
}
void
unsigned int *prefixlen)
{
}
};
static isc_result_t
{
for (;;) {
++have_port;
} else if ((flags & CFG_ADDR_DSCPOK) != 0 &&
{
++have_dscp;
} else
break;
} else
break;
}
if (have_port > 1) {
goto cleanup;
}
if (have_dscp > 1) {
goto cleanup;
}
return (ISC_R_SUCCESS);
return (result);
}
};
};
}
void
char buf[ISC_NETADDR_FORMATSIZE];
if (port != 0) {
}
}
}
void
int n = 0;
if (*flagp & CFG_ADDR_V4OK) {
n++;
}
if (*flagp & CFG_ADDR_V6OK) {
if (n != 0)
n++;
}
if (*flagp & CFG_ADDR_WILDOK) {
if (n != 0)
n++;
POST(n);
}
if (*flagp & CFG_ADDR_WILDOK) {
} else {
}
if ((*flagp & CFG_ADDR_DSCPOK) != 0) {
}
}
}
const isc_sockaddr_t *
}
}
return (ISC_R_SUCCESS);
redo:
switch (result) {
case ISC_R_SUCCESS:
result == ISC_R_SUCCESS);
/*
* Closed an included file, not the main file.
*/
goto redo;
}
}
break;
case ISC_R_NOSPACE:
/* More understandable than "ran out of space". */
break;
case ISC_R_IOERROR:
break;
default:
break;
}
return (result);
}
void
return;
}
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
if (result != ISC_R_SUCCESS)
return (result);
return (ISC_R_UNEXPECTEDTOKEN);
}
return (ISC_R_SUCCESS);
}
void
}
void
}
static isc_boolean_t
return (ISC_FALSE);
return (ISC_FALSE);
return (ISC_TRUE);
}
static char *
static char none[] = "none";
if (!have_current_file(pctx))
return (none);
return (none);
}
static void
{
static char message[2048];
int level = ISC_LOG_ERROR;
const char *prep = "";
if (is_warning)
where[0] = '\0';
if (have_current_file(pctx))
#define ELIPSIS " ... "
ELIPSIS);
isc_region_t r;
(void)cfg_gettoken(pctx, 0);
flags = 0;
tokenbuf[0] = '\0';
} else {
if (r.length > MAX_LOG_TOKEN)
else
}
/* Choose a preposition. */
if (flags & CFG_LOG_NEAR)
prep = " near ";
else if (flags & CFG_LOG_BEFORE)
prep = " before ";
else
prep = " ";
} else {
tokenbuf[0] = '\0';
}
}
void
const char *fmt, ...) {
char msgbuf[2048];
return;
} else {
}
}
const char *
}
unsigned int
}
return (ISC_R_NOMEMORY);
if (result != ISC_R_SUCCESS) {
return (result);
}
return (ISC_R_SUCCESS);
}
static void
{
}
static isc_result_t
return (ISC_R_SUCCESS);
return (result);
}
static void
}
}
/*
* Destroy 'obj', a configuration object created in 'pctx'.
*/
void
unsigned int refs;
if (refs == 0) {
}
}
void
}
static void
}
void
}
void
}
void
void *closure)
{
pctx.f = f;
}
{
const cfg_clausedef_t * const *clauseset;
const cfg_clausedef_t *clause;
goto breakout;
}
}
}
return (ISC_R_FAILURE);
if (result == ISC_R_NOTFOUND) {
&destobj));
} else
} else {
} else
}
return (result);
}