/* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /*! \file */ #include #include #include #include #include #include #include #include #include /*! * Pass one of these flags to cfg_parser_error() to include the * token text in log message. */ #define CFG_LOG_NEAR 0x00000001 /*%< Say "near " */ #define CFG_LOG_BEFORE 0x00000002 /*%< Say "before " */ #define CFG_LOG_NOPREP 0x00000004 /*%< Say just "" */ isc_logcategory_t cfg_category = { "config", 0 }; isc_logmodule_t cfg_module = { "isccfg/parser", 0 }; /* Shorthand */ #define CAT &cfg_category #define MOD &cfg_module #define MAP_SYM 1 /* Unique type for isc_symtab */ #define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base) #define CFG_LEXOPT_QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE) /* Check a return value. */ #define CHECK(op) \ do { result = (op); \ if (result != ISC_R_SUCCESS) goto cleanup; \ } while (0) /* Clean up a configuration object if non-NULL. */ #define CLEANUP_OBJ(obj) \ do { if ((obj) != NULL) cfg_obj_destroy(pctx, &(obj)); } while (0) /* Forward declarations of variables */ cfg_rep_t cfg_rep_string; cfg_rep_t cfg_rep_list; cfg_type_t cfg_type_qstring; cfg_type_t cfg_type_sstring; cfg_type_t cfg_type_token; cfg_type_t cfg_type_unsupported; /* * Forward declarations of static functions. */ static isc_result_t cfg_gettoken(cfg_parser_t *pctx, int options); static isc_result_t cfg_peektoken(cfg_parser_t *pctx, int options); static void cfg_ungettoken(cfg_parser_t *pctx); static isc_result_t cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); static isc_result_t cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); static isc_result_t cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); static isc_result_t cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); static isc_result_t cfg_parse_special(cfg_parser_t *pctx, int special); /*%< Parse a required special character 'special'. */ static isc_result_t cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); static isc_result_t cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype, cfg_listelt_t **ret); static isc_result_t cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); static isc_result_t cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret); static void cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) __attribute__((__format__(__printf__, 3, 4))); static void free_list(cfg_parser_t *pctx, cfg_obj_t *obj); static isc_result_t create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp); static isc_result_t create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type, cfg_obj_t **ret); static void free_string(cfg_parser_t *pctx, cfg_obj_t *obj); static isc_result_t create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp); static void free_map(cfg_parser_t *pctx, cfg_obj_t *obj); static isc_result_t parse_symtab_elt(cfg_parser_t *pctx, const char *name, cfg_type_t *elttype, isc_symtab_t *symtab); static isc_result_t cfg_getstringtoken(cfg_parser_t *pctx); static void parser_complain(cfg_parser_t *pctx, int is_warning, unsigned int flags, const char *format, va_list args); /* * Data representations. These correspond to members of the * "value" union in struct cfg_obj (except "void", which does * not need a union member). */ cfg_rep_t cfg_rep_string = { "string", free_string }; cfg_rep_t cfg_rep_map = { "map", free_map }; cfg_rep_t cfg_rep_list = { "list", free_list }; /* * Configuration type definitions. */ /* Functions. */ static isc_result_t cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; REQUIRE(pctx != NULL); REQUIRE(type != NULL); REQUIRE(ret != NULL && *ret == NULL); result = type->parse(pctx, type, ret); if (result != ISC_R_SUCCESS) return (result); ENSURE(*ret != NULL); return (ISC_R_SUCCESS); } static isc_result_t cfg_parse_special(cfg_parser_t *pctx, int special) { isc_result_t result; REQUIRE(pctx != NULL); CHECK(cfg_gettoken(pctx, 0)); if (pctx->token.type == isc_tokentype_special && pctx->token.value.as_char == special) return (ISC_R_SUCCESS); cfg_parser_error(pctx, CFG_LOG_NEAR, "'%c' expected", special); return (ISC_R_UNEXPECTEDTOKEN); cleanup: return (result); } /* * Parse a required semicolon. If it is not there, log * an error and increment the error count but continue * parsing. Since the next token is pushed back, * care must be taken to make sure it is eventually * consumed or an infinite loop may result. */ static isc_result_t parse_semicolon(cfg_parser_t *pctx) { isc_result_t result; CHECK(cfg_gettoken(pctx, 0)); if (pctx->token.type == isc_tokentype_special && pctx->token.value.as_char == ';') return (ISC_R_SUCCESS); cfg_parser_error(pctx, CFG_LOG_BEFORE, "missing ';'"); cfg_ungettoken(pctx); cleanup: return (result); } /* * Parse EOF, logging and returning an error if not there. */ static isc_result_t parse_eof(cfg_parser_t *pctx) { isc_result_t result; CHECK(cfg_gettoken(pctx, 0)); if (pctx->token.type == isc_tokentype_eof) return (ISC_R_SUCCESS); cfg_parser_error(pctx, CFG_LOG_NEAR, "syntax error"); return (ISC_R_UNEXPECTEDTOKEN); cleanup: return (result); } /* A list of files, used internally for pctx->files. */ static cfg_type_t cfg_type_filelist = { "filelist", NULL, &cfg_rep_list, &cfg_type_qstring }; isc_result_t cfg_parser_create(isc_log_t *lctx, cfg_parser_t **ret) { isc_result_t result; cfg_parser_t *pctx; isc_lexspecials_t specials; REQUIRE(ret != NULL && *ret == NULL); pctx = malloc(sizeof(*pctx)); if (pctx == NULL) return (ISC_R_NOMEMORY); pctx->lctx = lctx; pctx->lexer = NULL; pctx->seen_eof = 0; pctx->ungotten = 0; pctx->errors = 0; pctx->open_files = NULL; pctx->closed_files = NULL; pctx->line = 0; pctx->token.type = isc_tokentype_unknown; pctx->flags = 0; memset(specials, 0, sizeof(specials)); specials['{'] = 1; specials['}'] = 1; specials[';'] = 1; specials['/'] = 1; specials['"'] = 1; specials['!'] = 1; CHECK(isc_lex_create(1024, &pctx->lexer)); isc_lex_setspecials(pctx->lexer, specials); isc_lex_setcomments(pctx->lexer, (ISC_LEXCOMMENT_C | ISC_LEXCOMMENT_CPLUSPLUS | ISC_LEXCOMMENT_SHELL)); CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->open_files)); CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->closed_files)); *ret = pctx; return (ISC_R_SUCCESS); cleanup: if (pctx->lexer != NULL) isc_lex_destroy(&pctx->lexer); CLEANUP_OBJ(pctx->open_files); CLEANUP_OBJ(pctx->closed_files); free(pctx); return (result); } static isc_result_t parser_openfile(cfg_parser_t *pctx, const char *filename) { isc_result_t result; cfg_listelt_t *elt = NULL; cfg_obj_t *stringobj = NULL; result = isc_lex_openfile(pctx->lexer, filename); if (result != ISC_R_SUCCESS) { cfg_parser_error(pctx, 0, "open: %s: %s", filename, isc_result_totext(result)); goto cleanup; } CHECK(create_string(pctx, filename, &cfg_type_qstring, &stringobj)); CHECK(create_listelt(pctx, &elt)); elt->obj = stringobj; ISC_LIST_APPEND(pctx->open_files->value.list, elt, link); return (ISC_R_SUCCESS); cleanup: CLEANUP_OBJ(stringobj); return (result); } /* * Parse a configuration using a pctx where a lexer has already * been set up with a source. */ static isc_result_t parse2(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; cfg_obj_t *obj = NULL; result = cfg_parse_obj(pctx, type, &obj); if (pctx->errors != 0) { /* Errors have been logged. */ if (result == ISC_R_SUCCESS) result = ISC_R_FAILURE; goto cleanup; } if (result != ISC_R_SUCCESS) { /* Parsing failed but no errors have been logged. */ cfg_parser_error(pctx, 0, "parsing failed: %s", isc_result_totext(result)); 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(pctx != NULL); REQUIRE(filename != NULL); REQUIRE(type != NULL); REQUIRE(ret != NULL && *ret == NULL); CHECK(parser_openfile(pctx, filename)); CHECK(parse2(pctx, type, ret)); cleanup: return (result); } void cfg_parser_destroy(cfg_parser_t **pctxp) { cfg_parser_t *pctx; REQUIRE(pctxp != NULL && *pctxp != NULL); pctx = *pctxp; *pctxp = NULL; 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); free(pctx); } /* * qstring (quoted string), ustring (unquoted string), astring * (any string) */ /* Create a string object from a null-terminated C string. */ static isc_result_t create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; cfg_obj_t *obj = NULL; int len; CHECK(cfg_create_obj(pctx, type, &obj)); len = strlen(contents); obj->value.string.length = len; obj->value.string.base = malloc(len + 1); if (obj->value.string.base == NULL) { free(obj); return (ISC_R_NOMEMORY); } memmove(obj->value.string.base, contents, len); obj->value.string.base[len] = '\0'; *ret = obj; cleanup: return (result); } static isc_result_t cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; REQUIRE(pctx != NULL); REQUIRE(ret != NULL && *ret == NULL); UNUSED(type); CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING)); if (pctx->token.type != isc_tokentype_qstring) { cfg_parser_error(pctx, CFG_LOG_NEAR, "expected quoted string"); return (ISC_R_UNEXPECTEDTOKEN); } return (create_string(pctx, TOKEN_STRING(pctx), &cfg_type_qstring, ret)); cleanup: return (result); } static isc_result_t cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; REQUIRE(pctx != NULL); REQUIRE(ret != NULL && *ret == NULL); UNUSED(type); CHECK(cfg_getstringtoken(pctx)); return (create_string(pctx, TOKEN_STRING(pctx), &cfg_type_qstring, ret)); cleanup: return (result); } static isc_result_t cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; REQUIRE(pctx != NULL); REQUIRE(ret != NULL && *ret == NULL); UNUSED(type); CHECK(cfg_getstringtoken(pctx)); return (create_string(pctx, TOKEN_STRING(pctx), &cfg_type_sstring, ret)); cleanup: return (result); } static void free_string(cfg_parser_t *pctx, cfg_obj_t *obj) { UNUSED(pctx); free(obj->value.string.base); } const char * cfg_obj_asstring(const cfg_obj_t *obj) { REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string); return (obj->value.string.base); } /* Quoted string only */ cfg_type_t cfg_type_qstring = { "quoted_string", cfg_parse_qstring, &cfg_rep_string, NULL }; /* Any string (quoted or unquoted); printed with quotes */ cfg_type_t cfg_type_astring = { "string", cfg_parse_astring, &cfg_rep_string, NULL }; /* * Any string (quoted or unquoted); printed with quotes. * If CFG_PRINTER_XKEY is set when printing the string will be '?' out. */ cfg_type_t cfg_type_sstring = { "string", cfg_parse_sstring, &cfg_rep_string, NULL }; /* * Booleans */ /* * Lists. */ static isc_result_t cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) { isc_result_t result; REQUIRE(pctx != NULL); REQUIRE(type != NULL); REQUIRE(obj != NULL && *obj == NULL); CHECK(cfg_create_obj(pctx, type, obj)); ISC_LIST_INIT((*obj)->value.list); cleanup: return (result); } static isc_result_t create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp) { UNUSED(pctx); cfg_listelt_t *elt; elt = malloc(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); free(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 cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype, cfg_listelt_t **ret) { isc_result_t result; cfg_listelt_t *elt = NULL; cfg_obj_t *value = NULL; REQUIRE(pctx != NULL); REQUIRE(elttype != NULL); REQUIRE(ret != NULL && *ret == NULL); CHECK(create_listelt(pctx, &elt)); result = cfg_parse_obj(pctx, elttype, &value); if (result != ISC_R_SUCCESS) goto cleanup; elt->obj = value; *ret = elt; return (ISC_R_SUCCESS); cleanup: free(elt); return (result); } /* * Maps. */ /* * Parse a map body. That's something like * * "foo 1; bar { glub; }; zap true; zap false;" * * i.e., a sequence of option names followed by values and * terminated by semicolons. Used for the top level of * the named.conf syntax, as well as for the body of the * options, view, zone, and other statements. */ isc_result_t cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { const cfg_clausedef_t * const *clausesets = type->of; isc_result_t result; const cfg_clausedef_t * const *clauseset; const cfg_clausedef_t *clause; cfg_obj_t *value = NULL; cfg_obj_t *obj = NULL; cfg_obj_t *eltobj = NULL; cfg_obj_t *includename = NULL; isc_symvalue_t symval; REQUIRE(pctx != NULL); REQUIRE(type != NULL); REQUIRE(ret != NULL && *ret == NULL); CHECK(create_map(pctx, type, &obj)); obj->value.map.clausesets = clausesets; for (;;) { redo: /* * Parse the option name and see if it is known. */ CHECK(cfg_gettoken(pctx, 0)); if (pctx->token.type != isc_tokentype_string) { cfg_ungettoken(pctx); break; } /* * We accept "include" statements wherever a map body * clause can occur. */ if (strcasecmp(TOKEN_STRING(pctx), "include") == 0) { /* * Turn the file name into a temporary configuration * object just so that it is not overwritten by the * semicolon token. */ CHECK(cfg_parse_obj(pctx, &cfg_type_qstring, &includename)); CHECK(parse_semicolon(pctx)); CHECK(parser_openfile(pctx, includename-> value.string.base)); cfg_obj_destroy(pctx, &includename); goto redo; } clause = NULL; for (clauseset = clausesets; *clauseset != NULL; clauseset++) { for (clause = *clauseset; clause->name != NULL; clause++) { if (strcasecmp(TOKEN_STRING(pctx), clause->name) == 0) goto done; } } done: if (clause == NULL || clause->name == NULL) { cfg_parser_error(pctx, CFG_LOG_NOPREP, "unknown option"); /* * Try to recover by parsing this option as an unknown * option and discarding it. */ CHECK(cfg_parse_obj(pctx, &cfg_type_unsupported, &eltobj)); cfg_obj_destroy(pctx, &eltobj); CHECK(parse_semicolon(pctx)); continue; } /* Clause is known. */ /* See if the clause already has a value; if not create one. */ result = isc_symtab_lookup(obj->value.map.symtab, clause->name, 0, &symval); /* Single-valued clause */ if (result == ISC_R_NOTFOUND) { CHECK(parse_symtab_elt(pctx, clause->name, clause->type, obj->value.map.symtab)); CHECK(parse_semicolon(pctx)); } else if (result == ISC_R_SUCCESS) { cfg_parser_error(pctx, CFG_LOG_NEAR, "'%s' redefined", clause->name); result = ISC_R_EXISTS; goto cleanup; } else { cfg_parser_error(pctx, CFG_LOG_NEAR, "isc_symtab_define() failed"); goto cleanup; } } *ret = obj; return (ISC_R_SUCCESS); cleanup: CLEANUP_OBJ(value); CLEANUP_OBJ(obj); CLEANUP_OBJ(eltobj); CLEANUP_OBJ(includename); return (result); } static isc_result_t parse_symtab_elt(cfg_parser_t *pctx, const char *name, cfg_type_t *elttype, isc_symtab_t *symtab) { isc_result_t result; cfg_obj_t *obj = NULL; isc_symvalue_t symval; CHECK(cfg_parse_obj(pctx, elttype, &obj)); 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 cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; REQUIRE(pctx != NULL); REQUIRE(type != NULL); REQUIRE(ret != NULL && *ret == NULL); CHECK(cfg_parse_special(pctx, '{')); CHECK(cfg_parse_mapbody(pctx, type, ret)); CHECK(cfg_parse_special(pctx, '}')); cleanup: return (result); } /* * Subroutine for cfg_parse_named_map() and cfg_parse_addressed_map(). */ static isc_result_t parse_any_named_map(cfg_parser_t *pctx, cfg_type_t *nametype, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; cfg_obj_t *idobj = NULL; cfg_obj_t *mapobj = NULL; REQUIRE(pctx != NULL); REQUIRE(nametype != NULL); REQUIRE(type != NULL); REQUIRE(ret != NULL && *ret == NULL); CHECK(cfg_parse_obj(pctx, nametype, &idobj)); CHECK(cfg_parse_map(pctx, type, &mapobj)); mapobj->value.map.id = idobj; *ret = mapobj; return (result); cleanup: CLEANUP_OBJ(idobj); CLEANUP_OBJ(mapobj); return (result); } /* * Parse a map identified by a string name. E.g., "name { foo 1; }". * Used for the "key" and "channel" statements. */ isc_result_t cfg_parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { return (parse_any_named_map(pctx, &cfg_type_astring, type, ret)); } isc_result_t cfg_map_get(const cfg_obj_t *mapobj, const char* name, const cfg_obj_t **obj) { isc_result_t result; isc_symvalue_t val; const cfg_map_t *map; REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map); REQUIRE(name != NULL); REQUIRE(obj != NULL && *obj == NULL); map = &mapobj->value.map; result = isc_symtab_lookup(map->symtab, name, MAP_SYM, &val); if (result != ISC_R_SUCCESS) return (result); *obj = val.as_pointer; return (ISC_R_SUCCESS); } const cfg_obj_t * cfg_map_getname(const cfg_obj_t *mapobj) { REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map); return (mapobj->value.map.id); } /* Parse an arbitrary token, storing its raw text representation. */ static isc_result_t parse_token(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { cfg_obj_t *obj = NULL; isc_result_t result; isc_region_t r; UNUSED(type); CHECK(cfg_create_obj(pctx, &cfg_type_token, &obj)); CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING)); if (pctx->token.type == isc_tokentype_eof) { cfg_ungettoken(pctx); result = ISC_R_EOF; goto cleanup; } isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r); obj->value.string.base = malloc(r.length + 1); if (obj->value.string.base == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } obj->value.string.length = r.length; memmove(obj->value.string.base, r.base, r.length); obj->value.string.base[r.length] = '\0'; *ret = obj; return (result); cleanup: if (obj != NULL) free(obj); return (result); } cfg_type_t cfg_type_token = { "token", parse_token, &cfg_rep_string, NULL }; /* * An unsupported option. This is just a list of tokens with balanced braces * ending in a semicolon. */ static isc_result_t parse_unsupported(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { cfg_obj_t *listobj = NULL; isc_result_t result; int braces = 0; CHECK(cfg_create_list(pctx, type, &listobj)); for (;;) { cfg_listelt_t *elt = NULL; CHECK(cfg_peektoken(pctx, 0)); if (pctx->token.type == isc_tokentype_special) { if (pctx->token.value.as_char == '{') braces++; else if (pctx->token.value.as_char == '}') braces--; else if (pctx->token.value.as_char == ';') if (braces == 0) break; } if (pctx->token.type == isc_tokentype_eof || braces < 0) { cfg_parser_error(pctx, CFG_LOG_NEAR, "unexpected token"); result = ISC_R_UNEXPECTEDTOKEN; goto cleanup; } CHECK(cfg_parse_listelt(pctx, &cfg_type_token, &elt)); ISC_LIST_APPEND(listobj->value.list, elt, link); } INSIST(braces == 0); *ret = listobj; return (ISC_R_SUCCESS); cleanup: CLEANUP_OBJ(listobj); return (result); } cfg_type_t cfg_type_unsupported = { "unsupported", parse_unsupported, &cfg_rep_list, NULL }; static isc_result_t cfg_gettoken(cfg_parser_t *pctx, int options) { isc_result_t result; REQUIRE(pctx != NULL); 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 = 0; 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 = 1; } break; case ISC_R_NOSPACE: /* More understandable than "ran out of space". */ cfg_parser_error(pctx, CFG_LOG_NEAR, "token too big"); break; case ISC_R_IOERROR: cfg_parser_error(pctx, 0, "%s", isc_result_totext(result)); break; default: cfg_parser_error(pctx, CFG_LOG_NEAR, "%s", isc_result_totext(result)); break; } return (result); } static void cfg_ungettoken(cfg_parser_t *pctx) { REQUIRE(pctx != NULL); if (pctx->seen_eof) return; isc_lex_ungettoken(pctx->lexer, &pctx->token); pctx->ungotten = 1; } static isc_result_t cfg_peektoken(cfg_parser_t *pctx, int options) { isc_result_t result; REQUIRE(pctx != NULL); CHECK(cfg_gettoken(pctx, options)); cfg_ungettoken(pctx); cleanup: return (result); } /* * Get a string token, accepting both the quoted and the unquoted form. * Log an error if the next token is not a string. */ static isc_result_t cfg_getstringtoken(cfg_parser_t *pctx) { isc_result_t result; result = cfg_gettoken(pctx, CFG_LEXOPT_QSTRING); if (result != ISC_R_SUCCESS) return (result); if (pctx->token.type != isc_tokentype_string && pctx->token.type != isc_tokentype_qstring) { cfg_parser_error(pctx, CFG_LOG_NEAR, "expected string"); return (ISC_R_UNEXPECTEDTOKEN); } return (ISC_R_SUCCESS); } static void cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) { va_list args; REQUIRE(pctx != NULL); REQUIRE(fmt != NULL); va_start(args, fmt); parser_complain(pctx, 0, flags, fmt, args); va_end(args); pctx->errors++; } #define MAX_LOG_TOKEN 30 /* How much of a token to quote in log messages. */ static int have_current_file(cfg_parser_t *pctx) { cfg_listelt_t *elt; if (pctx->open_files == NULL) return (0); elt = ISC_LIST_TAIL(pctx->open_files->value.list); if (elt == NULL) return (0); return (1); } static char * current_file(cfg_parser_t *pctx) { static char none[] = "none"; cfg_listelt_t *elt; cfg_obj_t *fileobj; if (!have_current_file(pctx)) return (none); elt = ISC_LIST_TAIL(pctx->open_files->value.list); if (elt == NULL) /* shouldn't be possible, but... */ return (none); fileobj = elt->obj; INSIST(fileobj->type == &cfg_type_qstring); return (fileobj->value.string.base); } static void parser_complain(cfg_parser_t *pctx, int is_warning, unsigned int flags, const char *format, va_list args) { char tokenbuf[MAX_LOG_TOKEN + 10]; static char where[PATH_MAX + 100]; static char message[2048]; int level = ISC_LOG_ERROR; const char *prep = ""; size_t len; if (is_warning) level = ISC_LOG_WARNING; where[0] = '\0'; if (have_current_file(pctx)) snprintf(where, sizeof(where), "%s:%u: ", current_file(pctx), pctx->line); len = vsnprintf(message, sizeof(message), format, args); #define ELIPSIS " ... " if (len >= sizeof(message)) { message[sizeof(message) - sizeof(ELIPSIS)] = 0; strlcat(message, ELIPSIS, sizeof(message)); } if ((flags & (CFG_LOG_NEAR|CFG_LOG_BEFORE|CFG_LOG_NOPREP)) != 0) { isc_region_t r; if (pctx->ungotten) (void)cfg_gettoken(pctx, 0); if (pctx->token.type == isc_tokentype_eof) { snprintf(tokenbuf, sizeof(tokenbuf), "end of file"); } else if (pctx->token.type == isc_tokentype_unknown) { flags = 0; tokenbuf[0] = '\0'; } else { isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r); if (r.length > MAX_LOG_TOKEN) snprintf(tokenbuf, sizeof(tokenbuf), "'%.*s...'", MAX_LOG_TOKEN, r.base); else snprintf(tokenbuf, sizeof(tokenbuf), "'%.*s'", (int)r.length, r.base); } /* Choose a preposition. */ if (flags & CFG_LOG_NEAR) prep = " near "; else if (flags & CFG_LOG_BEFORE) prep = " before "; else prep = " "; } else { tokenbuf[0] = '\0'; } isc_log_write(pctx->lctx, CAT, MOD, level, "%s%s%s%s", where, message, prep, tokenbuf); } static isc_result_t cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { cfg_obj_t *obj; REQUIRE(pctx != NULL); REQUIRE(type != NULL); REQUIRE(ret != NULL && *ret == NULL); obj = malloc(sizeof(cfg_obj_t)); if (obj == NULL) return (ISC_R_NOMEMORY); obj->type = type; obj->file = current_file(pctx); obj->line = pctx->line; *ret = obj; return (ISC_R_SUCCESS); } static void map_symtabitem_destroy(char *key, unsigned int type, isc_symvalue_t symval, void *userarg) { cfg_obj_t *obj = symval.as_pointer; cfg_parser_t *pctx = (cfg_parser_t *)userarg; UNUSED(key); UNUSED(type); cfg_obj_destroy(pctx, &obj); } static isc_result_t create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { isc_result_t result; isc_symtab_t *symtab = NULL; cfg_obj_t *obj = NULL; CHECK(cfg_create_obj(pctx, type, &obj)); CHECK(isc_symtab_create(5, /* XXX */ map_symtabitem_destroy, pctx, 0, &symtab)); obj->value.map.symtab = symtab; obj->value.map.id = NULL; *ret = obj; return (ISC_R_SUCCESS); cleanup: if (obj != NULL) free(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); } /* * Destroy 'obj', a configuration object created in 'pctx'. */ void cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **objp) { cfg_obj_t *obj; REQUIRE(objp != NULL && *objp != NULL); REQUIRE(pctx != NULL); obj = *objp; obj->type->rep->free(pctx, obj); free(obj); *objp = NULL; }