]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Duplicate cfg_map
authorAlessio Podda <alessio@isc.org>
Thu, 19 Mar 2026 16:33:52 +0000 (17:33 +0100)
committerAlessio Podda <alessio@isc.org>
Thu, 19 Mar 2026 16:33:52 +0000 (17:33 +0100)
bin/plugins/filter-a.c
bin/plugins/filter-aaaa.c
bin/plugins/synthrecord.c
bin/tests/system/hooks/driver/test-syncplugin.c
lib/isccfg/include/isccfg/cfg.h
lib/isccfg/include/isccfg/grammar.h
lib/isccfg/parser.c

index f722fdcb813945a29e552ad35c31c7a2efb71b77..0d94f41ae9e550774bb21a610f54ee94be3a509c 100644 (file)
@@ -191,9 +191,11 @@ static cfg_clausedef_t param_clauses[] = {
 
 static cfg_clausedef_t *param_clausesets[] = { param_clauses, NULL };
 
-static cfg_type_t cfg_type_parameters = { "filter-a-params", cfg_parse_mapbody,
-                                         cfg_print_mapbody, cfg_doc_mapbody,
-                                         &cfg_rep_map,      param_clausesets };
+static cfg_type_t cfg_type_parameters = {
+       "filter-a-params",          cfg_parse_mapbody_external,
+       cfg_print_mapbody_external, cfg_doc_mapbody_external,
+       &cfg_rep_map_external,      param_clausesets
+};
 
 static isc_result_t
 parse_filter_a_on(const cfg_obj_t *param_obj, const char *param_name,
@@ -201,7 +203,7 @@ parse_filter_a_on(const cfg_obj_t *param_obj, const char *param_name,
        const cfg_obj_t *obj = NULL;
        isc_result_t result;
 
-       result = cfg_map_get(param_obj, param_name, &obj);
+       result = cfg_map_external_get(param_obj, param_name, &obj);
        if (result != ISC_R_SUCCESS) {
                return ISC_R_SUCCESS;
        }
@@ -278,7 +280,7 @@ parse_parameters(filter_instance_t *inst, const char *parameters,
        CHECK(parse_filter_a_on(param_obj, "filter-a-on-v6", &inst->v6_a));
        CHECK(parse_filter_a_on(param_obj, "filter-a-on-v4", &inst->v4_a));
 
-       result = cfg_map_get(param_obj, "filter-a", &obj);
+       result = cfg_map_external_get(param_obj, "filter-a", &obj);
        if (result == ISC_R_SUCCESS) {
                CHECK(cfg_acl_fromconfig(obj, (const cfg_obj_t *)cfg,
                                         (cfg_aclconfctx_t *)aclctx, mctx, 0,
index 6a7a8d25e9b47c8920db30af2b963cc9af2c5d36..32ded4f63a83bcff40782cf7b2b8aff1eab128d7 100644 (file)
@@ -192,8 +192,9 @@ static cfg_clausedef_t param_clauses[] = {
 static cfg_clausedef_t *param_clausesets[] = { param_clauses, NULL };
 
 static cfg_type_t cfg_type_parameters = {
-       "filter-aaaa-params", cfg_parse_mapbody, cfg_print_mapbody,
-       cfg_doc_mapbody,      &cfg_rep_map,      param_clausesets
+       "filter-aaaa-params",       cfg_parse_mapbody_external,
+       cfg_print_mapbody_external, cfg_doc_mapbody_external,
+       &cfg_rep_map_external,      param_clausesets
 };
 
 static isc_result_t
@@ -202,7 +203,7 @@ parse_filter_aaaa_on(const cfg_obj_t *param_obj, const char *param_name,
        const cfg_obj_t *obj = NULL;
        isc_result_t result;
 
-       result = cfg_map_get(param_obj, param_name, &obj);
+       result = cfg_map_external_get(param_obj, param_name, &obj);
        if (result != ISC_R_SUCCESS) {
                return ISC_R_SUCCESS;
        }
@@ -281,7 +282,7 @@ parse_parameters(filter_instance_t *inst, const char *parameters,
        CHECK(parse_filter_aaaa_on(param_obj, "filter-aaaa-on-v6",
                                   &inst->v6_aaaa));
 
-       result = cfg_map_get(param_obj, "filter-aaaa", &obj);
+       result = cfg_map_external_get(param_obj, "filter-aaaa", &obj);
        if (result == ISC_R_SUCCESS) {
                CHECK(cfg_acl_fromconfig(obj, (const cfg_obj_t *)cfg,
                                         (cfg_aclconfctx_t *)aclctx, mctx, 0,
index fc2f79450bbdce39e60b4c46f28fa09318b47415..8d1470af32d2c72b14d15829bc207f254ab68e96 100644 (file)
@@ -401,8 +401,9 @@ static cfg_clausedef_t *synthrecord_cfgparamsclausesets[] = {
 };
 
 static cfg_type_t synthrecord_cfgparams = {
-       "synthrecord-params", cfg_parse_mapbody, cfg_print_mapbody,
-       cfg_doc_mapbody,      &cfg_rep_map,      synthrecord_cfgparamsclausesets
+       "synthrecord-params",      cfg_parse_mapbody_external,
+       cfg_print_mapbody_external, cfg_doc_mapbody_external,
+       &cfg_rep_map_external,     synthrecord_cfgparamsclausesets
 };
 
 static isc_result_t
@@ -411,7 +412,7 @@ synthrecord_initprefix(synthrecord_t *inst, const cfg_obj_t *synthrecordcfg) {
        const char *base = NULL;
        const cfg_obj_t *obj = NULL;
 
-       result = cfg_map_get(synthrecordcfg, "prefix", &obj);
+       result = cfg_map_external_get(synthrecordcfg, "prefix", &obj);
        if (result != ISC_R_SUCCESS) {
                isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
                              ISC_LOG_ERROR, "synthrecord: prefix not found");
@@ -448,7 +449,7 @@ synthrecord_initorigin(synthrecord_t *inst, const cfg_obj_t *synthrecordcfg,
        const cfg_obj_t *obj = NULL;
        const char *originstr = NULL;
 
-       result = cfg_map_get(synthrecordcfg, "origin", &obj);
+       result = cfg_map_external_get(synthrecordcfg, "origin", &obj);
        if (inst->mode == REVERSE && result != ISC_R_SUCCESS) {
                isc_log_write(NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
                              ISC_LOG_ERROR,
@@ -496,7 +497,7 @@ synthrecord_parseallowsynth(synthrecord_t *inst, const cfg_obj_t *cfg,
        const cfg_obj_t *obj = NULL;
 
        INSIST(inst->allowedsynth == NULL);
-       result = cfg_map_get(synthrecordcfg, "allow-synth", &obj);
+       result = cfg_map_external_get(synthrecordcfg, "allow-synth", &obj);
 
        if (result == ISC_R_NOTFOUND) {
                return dns_acl_any(inst->mctx, &inst->allowedsynth);
@@ -532,7 +533,7 @@ synthrecord_parsettl(synthrecord_t *inst, const cfg_obj_t *synthrecordcfg) {
        isc_result_t result;
        const cfg_obj_t *obj = NULL;
 
-       result = cfg_map_get(synthrecordcfg, "ttl", &obj);
+       result = cfg_map_external_get(synthrecordcfg, "ttl", &obj);
 
        if (result == ISC_R_NOTFOUND) {
                inst->ttl = DEFAULT_TTL;
index 3ac08a2dc99e310768afb2b6a440ca27859094e7..62bca1842be09e50482e07b82597a8f289fef21a 100644 (file)
@@ -68,8 +68,9 @@ static cfg_clausedef_t *syncplugin__cfgparamsclausesets[] = {
 };
 
 static cfg_type_t syncplugin__cfgparams = {
-       "syncplugin-params", cfg_parse_mapbody, cfg_print_mapbody,
-       cfg_doc_mapbody,     &cfg_rep_map,      syncplugin__cfgparamsclausesets
+       "syncplugin-params",        cfg_parse_mapbody_external,
+       cfg_print_mapbody_external, cfg_doc_mapbody_external,
+       &cfg_rep_map_external,      syncplugin__cfgparamsclausesets
 };
 
 static isc_result_t
@@ -78,7 +79,7 @@ syncplugin__parse_rcode(const cfg_obj_t *syncplugincfg, uint8_t *rcode) {
        const cfg_obj_t *obj = NULL;
        const char *rcodestr = NULL;
 
-       RETERR(cfg_map_get(syncplugincfg, "rcode", &obj));
+       RETERR(cfg_map_external_get(syncplugincfg, "rcode", &obj));
 
        rcodestr = obj->value.string;
 
@@ -131,7 +132,7 @@ plugin_register(const char *parameters, const void *cfg, const char *cfgfile,
 
        CHECK(syncplugin__parse_rcode(syncplugincfg, &inst->rcode));
 
-       if (cfg_map_get(syncplugincfg, "firstlbl", &obj) == ISC_R_SUCCESS) {
+       if (cfg_map_external_get(syncplugincfg, "firstlbl", &obj) == ISC_R_SUCCESS) {
                const char *firstlbl = cfg_obj_asstring(obj);
                size_t len = strlen(firstlbl) + 1;
 
@@ -140,7 +141,7 @@ plugin_register(const char *parameters, const void *cfg, const char *cfgfile,
        }
 
        obj = NULL;
-       CHECK(cfg_map_get(syncplugincfg, "source", &obj));
+       CHECK(cfg_map_external_get(syncplugincfg, "source", &obj));
        sourcestr = obj->value.string;
 
        if (strcmp(sourcestr, "zone") == 0) {
index 5012f492171730c6cd36a2e168d241944cb748e7..035dd2d0dfcde652f16ffc492fb982f70f352ba5 100644 (file)
@@ -580,6 +580,40 @@ cfg_map_nextclause(const cfg_type_t *map, const void **clauses,
 const cfg_clausedef_t *
 cfg_map_findclause(const cfg_type_t *map, const char *name);
 
+bool
+cfg_obj_ismap_external(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a map_external type.
+ */
+
+isc_result_t
+cfg_map_external_get(const cfg_obj_t *mapobj, const char *name,
+                    const cfg_obj_t **obj);
+
+const cfg_obj_t *
+cfg_map_external_getname(const cfg_obj_t *mapobj);
+
+unsigned int
+cfg_map_external_count(const cfg_obj_t *mapobj);
+
+isc_result_t
+cfg_map_external_add(cfg_obj_t *mapobj, cfg_obj_t *obj, const char *clause);
+
+isc_result_t
+cfg_map_external_addclone(cfg_obj_t *map, const cfg_obj_t *obj,
+                         const cfg_clausedef_t *clause);
+
+const cfg_clausedef_t *
+cfg_map_external_firstclause(const cfg_type_t *map, const void **clauses,
+                            unsigned int *idx);
+
+const cfg_clausedef_t *
+cfg_map_external_nextclause(const cfg_type_t *map, const void **clauses,
+                           unsigned int *idx);
+
+const cfg_clausedef_t *
+cfg_map_external_findclause(const cfg_type_t *map, const char *name);
+
 typedef isc_result_t(pluginlist_cb_t)(
        const cfg_obj_t *config, const cfg_obj_t *obj, cfg_aclconfctx_t *aclctx,
        const char *plugin_path, const char *parameters, void *callback_data);
index 9ad371b7e06065141ed40c2779f12fc83f26eb37..98c7aaad419454d9293f85f841ea4b9ab0b6da80 100644 (file)
@@ -230,6 +230,7 @@ struct cfg_obj {
                char              *string; /*%< null terminated */
                bool               boolean;
                cfg_map_t         *map;
+               cfg_map_t         *map_external;
                cfg_list_t        *list;
                cfg_obj_t        **tuple;
                isc_sockaddr_t    *sockaddr;
@@ -329,6 +330,7 @@ extern cfg_rep_t cfg_rep_uint64;
 extern cfg_rep_t cfg_rep_string;
 extern cfg_rep_t cfg_rep_boolean;
 extern cfg_rep_t cfg_rep_map;
+extern cfg_rep_t cfg_rep_map_external;
 extern cfg_rep_t cfg_rep_list;
 extern cfg_rep_t cfg_rep_tuple;
 extern cfg_rep_t cfg_rep_sockaddr;
@@ -545,6 +547,26 @@ cfg_print_mapbody(cfg_printer_t *pctx, const cfg_obj_t *obj);
 void
 cfg_doc_mapbody(cfg_printer_t *pctx, const cfg_type_t *type);
 
+isc_result_t
+cfg_parse_map_external(cfg_parser_t *pctx, const cfg_type_t *type,
+                      cfg_obj_t **ret);
+
+void
+cfg_print_map_external(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_map_external(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_mapbody_external(cfg_parser_t *pctx, const cfg_type_t *type,
+                          cfg_obj_t **ret);
+
+void
+cfg_print_mapbody_external(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_mapbody_external(cfg_printer_t *pctx, const cfg_type_t *type);
+
 isc_result_t
 cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
 
index 83722cb858f4827ead7c5952086fdcd076b6c626..be7e33650283d5b97b872f5498cc732b1eea29a9 100644 (file)
@@ -133,6 +133,13 @@ create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
 static void
 free_map(cfg_obj_t *obj);
 
+static void
+create_map_external(cfg_parser_t *pctx, const cfg_type_t *type,
+                   cfg_obj_t **objp);
+
+static void
+free_map_external(cfg_obj_t *obj);
+
 static isc_result_t
 parse_symtab_elt(cfg_parser_t *pctx, const cfg_clausedef_t *clause,
                 isc_symtab_t *symtab);
@@ -284,6 +291,42 @@ copy_map(cfg_obj_t *to, const cfg_obj_t *from) {
        to->value.map->clausesets = from->value.map->clausesets;
 }
 
+static bool
+copy_map_external_add(char *key, unsigned int type, isc_symvalue_t value,
+                     void *arg) {
+       cfg_obj_t *to = arg;
+       cfg_obj_t *toelt = NULL;
+
+       REQUIRE(VALID_CFGOBJ(value.as_pointer));
+
+       cfg_obj_clone(value.as_pointer, &toelt);
+       value.as_pointer = toelt;
+
+       INSIST(isc_symtab_define(to->value.map_external->symtab, key, type,
+                                value, isc_symexists_reject) == ISC_R_SUCCESS);
+
+       return false;
+}
+
+static void
+copy_map_external(cfg_obj_t *to, const cfg_obj_t *from) {
+       to->value.map_external =
+               isc_mem_cget(isc_g_mctx, 1, sizeof(*to->value.map_external));
+
+       if (from->value.map_external->id != NULL) {
+               cfg_obj_clone(from->value.map_external->id,
+                             &to->value.map_external->id);
+       }
+
+       isc_symtab_create(isc_g_mctx, copy_map_destroy, NULL, false,
+                         &to->value.map_external->symtab);
+       isc_symtab_foreach(from->value.map_external->symtab,
+                          copy_map_external_add, to);
+
+       to->value.map_external->clausesets =
+               from->value.map_external->clausesets;
+}
+
 static void
 copy_list(cfg_obj_t *to, const cfg_obj_t *from) {
        const cfg_listelt_t *fromelt = cfg_list_first(from);
@@ -337,6 +380,8 @@ cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop, copy_uint64 };
 cfg_rep_t cfg_rep_string = { "string", free_string, copy_string };
 cfg_rep_t cfg_rep_boolean = { "boolean", free_noop, copy_boolean };
 cfg_rep_t cfg_rep_map = { "map", free_map, copy_map };
+cfg_rep_t cfg_rep_map_external = { "map_external", free_map_external,
+                                  copy_map_external };
 cfg_rep_t cfg_rep_list = { "list", free_list, copy_list };
 cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple, copy_tuple };
 cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_sockaddr, copy_sockaddr };
@@ -2955,193 +3000,650 @@ cfg_map_findclause(const cfg_type_t *map, const char *name) {
        return ((cfg_clausedef_t *)clauses) + idx;
 }
 
-static char *
-region_to_string(isc_region_t region) {
-       size_t len = region.length + 1;
-       char *str = isc_mem_allocate(isc_g_mctx, len);
-
-       memmove(str, region.base, region.length);
-       str[region.length] = 0;
-
-       return str;
-}
-
-/* Parse an arbitrary token, storing its raw text representation. */
-static isc_result_t
-parse_token(cfg_parser_t *pctx, const cfg_type_t *type ISC_ATTR_UNUSED,
-           cfg_obj_t **ret) {
-       cfg_obj_t *obj = NULL;
-       isc_result_t result;
-       isc_region_t r;
-
-       cfg_obj_create(cfg_parser_currentfile(pctx), pctx->line,
-                      &cfg_type_token, &obj);
-       CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
-       if (pctx->token.type == isc_tokentype_eof) {
-               cfg_ungettoken(pctx);
-               CLEANUP(ISC_R_EOF);
-       }
-
-       isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r);
-       obj->value.string = region_to_string(r);
-
-       *ret = obj;
-       return result;
-
-cleanup:
-       if (obj != NULL) {
-               isc_mem_put(isc_g_mctx, obj, sizeof(*obj));
-       }
-       return result;
-}
-
-cfg_type_t cfg_type_token = { "token",          parse_token,
-                             cfg_print_ustring, cfg_doc_terminal,
-                             &cfg_rep_string,   NULL };
-
 /*
- * An unsupported option.  This is just a list of tokens with balanced braces
- * ending in a semicolon.
+ * map_external variants â€” identical logic to map, but access
+ * obj->value.map_external and check &cfg_rep_map_external.
  */
 
-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
+cfg_parse_mapbody_external(cfg_parser_t *pctx, const cfg_type_t *type,
+                          cfg_obj_t **ret) {
+       const cfg_clausedef_t *const *clausesets;
        isc_result_t result;
-       int braces = 0;
+       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;
 
-       create_list(cfg_parser_currentfile(pctx), pctx->line, type, &listobj);
+       REQUIRE(pctx != NULL);
+       REQUIRE(type != NULL);
+       REQUIRE(ret != NULL && *ret == NULL);
 
-       for (;;) {
-               cfg_listelt_t *elt = NULL;
+       clausesets = type->of;
 
-               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");
-                       CLEANUP(ISC_R_UNEXPECTEDTOKEN);
-               }
+       create_map_external(pctx, type, &obj);
 
-               CHECK(cfg_parse_listelt(pctx, listobj, &cfg_type_token, &elt));
-               ISC_LIST_APPEND(*listobj->value.list, elt, link);
-       }
-       INSIST(braces == 0);
-       *ret = listobj;
-       return ISC_R_SUCCESS;
+       obj->value.map_external->clausesets = clausesets;
 
-cleanup:
-       CLEANUP_OBJ(listobj);
-       return result;
-}
+       for (;;) {
+               CHECK(cfg_gettoken(pctx, 0));
 
-cfg_type_t cfg_type_unsupported = { "unsupported",      parse_unsupported,
-                                   cfg_print_spacelist, cfg_doc_terminal,
-                                   &cfg_rep_list,       NULL };
+               if (pctx->token.type != isc_tokentype_string) {
+                       cfg_ungettoken(pctx);
+                       break;
+               }
 
-/*
- * Try interpreting the current token as a network address.
- *
- * If CFG_ADDR_WILDOK is set in flags, "*" can be used as a wildcard
- * and at least one of CFG_ADDR_V4OK and CFG_ADDR_V6OK must also be set.  The
- * "*" is interpreted as the IPv4 wildcard address if CFG_ADDR_V4OK is
- * set (including the case where CFG_ADDR_V4OK and CFG_ADDR_V6OK are both set),
- * and the IPv6 wildcard address otherwise.
- */
-static isc_result_t
-token_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
-       char *s;
-       struct in_addr in4a;
-       struct in6_addr in6a;
+               if (strcasecmp(TOKEN_STRING(pctx), "include") == 0) {
+                       glob_t g;
+                       int rc;
 
-       if (pctx->token.type != isc_tokentype_string) {
-               return ISC_R_UNEXPECTEDTOKEN;
-       }
+                       CHECK(cfg_parse_obj(pctx, &cfg_type_qstring,
+                                           &includename));
+                       CHECK(parse_semicolon(pctx));
 
-       s = TOKEN_STRING(pctx);
-       if ((flags & CFG_ADDR_WILDOK) != 0 && strcmp(s, "*") == 0) {
-               if ((flags & CFG_ADDR_V4OK) != 0) {
-                       isc_netaddr_any(na);
-                       return ISC_R_SUCCESS;
-               } else if ((flags & CFG_ADDR_V6OK) != 0) {
-                       isc_netaddr_any6(na);
-                       return ISC_R_SUCCESS;
-               } else {
-                       UNREACHABLE();
-               }
-       } else {
-               if ((flags & (CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK)) != 0) {
-                       if (inet_pton(AF_INET, s, &in4a) == 1) {
-                               isc_netaddr_fromin(na, &in4a);
-                               return ISC_R_SUCCESS;
+                       if (includename->value.string[0] == 0) {
+                               CLEANUP(ISC_R_FILENOTFOUND);
                        }
-               }
-               if ((flags & CFG_ADDR_V4PREFIXOK) != 0 && strlen(s) <= 15U) {
-                       char buf[64];
-                       int i;
 
-                       strlcpy(buf, s, sizeof(buf));
-                       for (i = 0; i < 3; i++) {
-                               strlcat(buf, ".0", sizeof(buf));
-                               if (inet_pton(AF_INET, buf, &in4a) == 1) {
-                                       isc_netaddr_fromin(na, &in4a);
-                                       return ISC_R_IPV4PREFIX;
+                       rc = glob(cfg_obj_asstring(includename), GLOB_ERR, NULL,
+                                 &g);
+
+                       switch (rc) {
+                       case 0:
+                               break;
+                       case GLOB_NOMATCH:
+                               CLEANUP(ISC_R_FILENOTFOUND);
+                               break;
+                       case GLOB_NOSPACE:
+                               CLEANUP(ISC_R_NOMEMORY);
+                               break;
+                       default:
+                               if (errno == 0) {
+                                       CLEANUP(ISC_R_IOERROR);
                                }
+                               CHECK(isc_errno_toresult(errno));
                        }
-               }
-               if ((flags & CFG_ADDR_V6OK) != 0 && strlen(s) <= 127U) {
-                       char buf[128];     /* see isc_getaddresses() */
-                       char *d;           /* zone delimiter */
-                       uint32_t zone = 0; /* scope zone ID */
 
-                       strlcpy(buf, s, sizeof(buf));
-                       d = strchr(buf, '%');
-                       if (d != NULL) {
-                               *d = '\0';
+                       for (size_t i = 0; i < g.gl_pathc; ++i) {
+                               CHECK(parser_openfile(pctx, g.gl_pathv[i]));
                        }
 
-                       if (inet_pton(AF_INET6, buf, &in6a) == 1) {
-                               if (d != NULL) {
-                                       RETERR(isc_netscope_pton(
-                                               AF_INET6, d + 1, &in6a, &zone));
-                               }
+                       cfg_obj_detach(&includename);
+                       globfree(&g);
 
-                               isc_netaddr_fromin6(na, &in6a);
-                               isc_netaddr_setzone(na, zone);
-                               return ISC_R_SUCCESS;
-                       }
+                       continue;
                }
-       }
-       return ISC_R_UNEXPECTEDTOKEN;
-}
-
-isc_result_t
-cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
-       isc_result_t result;
-       const char *wild = "";
-       const char *prefix = "";
-
-       REQUIRE(pctx != NULL);
-       REQUIRE(na != NULL);
 
-       CHECK(cfg_gettoken(pctx, 0));
-       result = token_addr(pctx, flags, na);
-       if (result == ISC_R_UNEXPECTEDTOKEN) {
-               if ((flags & CFG_ADDR_WILDOK) != 0) {
-                       wild = " or '*'";
+               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;
+                               }
+                       }
                }
-               if ((flags & CFG_ADDR_V4PREFIXOK) != 0) {
-                       wild = " or IPv4 prefix";
+       done:
+               if (clause == NULL || clause->name == NULL) {
+                       cfg_parser_error(pctx, CFG_LOG_NOPREP,
+                                        "unknown option");
+                       CHECK(cfg_parse_obj(pctx, &cfg_type_unsupported,
+                                           &eltobj));
+                       cfg_obj_detach(&eltobj);
+                       CHECK(parse_semicolon(pctx));
+                       continue;
+               }
+
+               if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0) {
+                       cfg_parser_error(pctx, 0,
+                                        "option '%s' no longer exists",
+                                        clause->name);
+                       CLEANUP(ISC_R_FAILURE);
+               }
+               if ((pctx->flags & CFG_PCTX_ALLCONFIGS) == 0 &&
+                   (clause->flags & CFG_CLAUSEFLAG_NOTCONFIGURED) != 0)
+               {
+                       cfg_parser_error(pctx, 0,
+                                        "option '%s' was not "
+                                        "enabled at compile time",
+                                        clause->name);
+                       CLEANUP(ISC_R_FAILURE);
+               }
+               if ((pctx->flags & CFG_PCTX_BUILTIN) == 0 &&
+                   (clause->flags & CFG_CLAUSEFLAG_BUILTINONLY) != 0)
+               {
+                       cfg_parser_error(pctx, 0,
+                                        "option '%s' is allowed in the "
+                                        "builtin configuration only",
+                                        clause->name);
+                       CHECK(ISC_R_FAILURE);
+               }
+
+               if ((pctx->flags & CFG_PCTX_NODEPRECATED) == 0 &&
+                   (clause->flags & CFG_CLAUSEFLAG_DEPRECATED) != 0)
+               {
+                       cfg_parser_warning(pctx, 0, "option '%s' is deprecated",
+                                          clause->name);
+               }
+               if ((pctx->flags & CFG_PCTX_NOOBSOLETE) == 0 &&
+                   (clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0)
+               {
+                       cfg_parser_warning(pctx, 0,
+                                          "option '%s' is obsolete and "
+                                          "should be removed ",
+                                          clause->name);
+               }
+               if ((pctx->flags & CFG_PCTX_NOEXPERIMENTAL) == 0 &&
+                   (clause->flags & CFG_CLAUSEFLAG_EXPERIMENTAL) != 0)
+               {
+                       cfg_parser_warning(pctx, 0,
+                                          "option '%s' is experimental and "
+                                          "subject to change in the future",
+                                          clause->name);
+               }
+
+               if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
+                       cfg_obj_t *listobj = NULL;
+                       cfg_listelt_t *elt = NULL;
+
+                       create_list(cfg_parser_currentfile(pctx), pctx->line,
+                                   &cfg_type_implicitlist, &listobj);
+                       symval.as_pointer = listobj;
+                       result = isc_symtab_define_and_return(
+                               obj->value.map_external->symtab, clause->name,
+                               SYMTAB_DUMMY_TYPE, symval, isc_symexists_reject,
+                               &symval);
+                       if (result == ISC_R_EXISTS) {
+                               CLEANUP_OBJ(listobj);
+                               listobj = symval.as_pointer;
+                       }
+
+                       CHECK(cfg_parse_listelt(pctx, listobj, clause->type,
+                                               &elt));
+                       ISC_LIST_APPEND(*listobj->value.list, elt, link);
+                       CHECK(parse_semicolon(pctx));
+               } else {
+                       result = parse_symtab_elt(
+                               pctx, clause,
+                               obj->value.map_external->symtab);
+                       if (result == ISC_R_EXISTS) {
+                               cfg_parser_error(pctx, CFG_LOG_NEAR,
+                                                "'%s' redefined",
+                                                clause->name);
+                               CHECK(result);
+                       } else if (result != ISC_R_SUCCESS) {
+                               cfg_parser_error(pctx, CFG_LOG_NEAR,
+                                                "isc_symtab_define() failed");
+                               CHECK(result);
+                       }
+                       CHECK(parse_semicolon(pctx));
+               }
+       }
+
+       *ret = obj;
+       return ISC_R_SUCCESS;
+
+cleanup:
+       CLEANUP_OBJ(value);
+       CLEANUP_OBJ(obj);
+       CLEANUP_OBJ(eltobj);
+       CLEANUP_OBJ(includename);
+       return result;
+}
+
+isc_result_t
+cfg_parse_map_external(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_external(pctx, type, ret));
+       CHECK(cfg_parse_special(pctx, '}'));
+cleanup:
+       return result;
+}
+
+void
+cfg_print_mapbody_external(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+       const cfg_clausedef_t *const *clauseset;
+
+       REQUIRE(pctx != NULL);
+       REQUIRE(VALID_CFGOBJ(obj));
+
+       for (clauseset = obj->value.map_external->clausesets;
+            *clauseset != NULL; clauseset++)
+       {
+               isc_symvalue_t symval;
+               const cfg_clausedef_t *clause;
+
+               for (clause = *clauseset; clause->name != NULL; clause++) {
+                       isc_result_t result;
+
+                       if ((clause->flags & CFG_CLAUSEFLAG_BUILTINONLY) != 0) {
+                               continue;
+                       }
+
+                       result = isc_symtab_lookup(
+                               obj->value.map_external->symtab, clause->name,
+                               SYMTAB_DUMMY_TYPE, &symval);
+                       if (result == ISC_R_SUCCESS) {
+                               cfg_obj_t *symobj = symval.as_pointer;
+                               if (symobj->type == &cfg_type_implicitlist) {
+                                       cfg_list_t *list = symobj->value.list;
+                                       ISC_LIST_FOREACH(*list, elt, link) {
+                                               print_symval(pctx, clause->name,
+                                                            elt->obj);
+                                       }
+                               } else {
+                                       print_symval(pctx, clause->name,
+                                                    symobj);
+                               }
+                       } else if (result == ISC_R_NOTFOUND) {
+                               /* do nothing */
+                       } else {
+                               UNREACHABLE();
+                       }
+               }
+       }
+}
+
+void
+cfg_print_map_external(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+       REQUIRE(pctx != NULL);
+       REQUIRE(VALID_CFGOBJ(obj));
+
+       if (obj->value.map_external->id != NULL) {
+               cfg_print_obj(pctx, obj->value.map_external->id);
+               cfg_print_cstr(pctx, " ");
+       }
+       print_open(pctx);
+       cfg_print_mapbody_external(pctx, obj);
+       print_close(pctx);
+}
+
+void
+cfg_doc_mapbody_external(cfg_printer_t *pctx, const cfg_type_t *type) {
+       const cfg_clausedef_t *const *clauseset;
+       const cfg_clausedef_t *clause;
+
+       REQUIRE(pctx != NULL);
+       REQUIRE(type != NULL);
+
+       for (clauseset = type->of; *clauseset != NULL; clauseset++) {
+               for (clause = *clauseset; clause->name != NULL; clause++) {
+                       if (((pctx->flags & CFG_PRINTER_ACTIVEONLY) != 0) &&
+                           (((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) ||
+                            ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 0)))
+                       {
+                               continue;
+                       }
+                       if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0 ||
+                           (clause->flags & CFG_CLAUSEFLAG_NODOC) != 0)
+                       {
+                               continue;
+                       }
+                       cfg_print_cstr(pctx, clause->name);
+                       cfg_print_cstr(pctx, " ");
+                       cfg_doc_obj(pctx, clause->type);
+                       cfg_print_cstr(pctx, ";");
+                       cfg_print_clauseflags(pctx, clause->flags);
+                       cfg_print_cstr(pctx, "\n\n");
+               }
+       }
+}
+
+void
+cfg_doc_map_external(cfg_printer_t *pctx, const cfg_type_t *type) {
+       const cfg_clausedef_t *const *clauseset;
+       const cfg_clausedef_t *clause;
+
+       REQUIRE(pctx != NULL);
+       REQUIRE(type != NULL);
+
+       if (type->parse == cfg_parse_named_map) {
+               cfg_doc_obj(pctx, &cfg_type_astring);
+               cfg_print_cstr(pctx, " ");
+       } else if (type->parse == cfg_parse_addressed_map) {
+               cfg_doc_obj(pctx, &cfg_type_netaddr);
+               cfg_print_cstr(pctx, " ");
+       } else if (type->parse == cfg_parse_netprefix_map) {
+               cfg_doc_obj(pctx, &cfg_type_netprefix);
+               cfg_print_cstr(pctx, " ");
+       }
+
+       print_open(pctx);
+
+       for (clauseset = type->of; *clauseset != NULL; clauseset++) {
+               for (clause = *clauseset; clause->name != NULL; clause++) {
+                       if (((pctx->flags & CFG_PRINTER_ACTIVEONLY) != 0) &&
+                           (((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) ||
+                            ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 0)))
+                       {
+                               continue;
+                       }
+                       if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0 ||
+                           (clause->flags & CFG_CLAUSEFLAG_NODOC) != 0)
+                       {
+                               continue;
+                       }
+                       cfg_print_indent(pctx);
+                       cfg_print_cstr(pctx, clause->name);
+                       if (clause->type->print != cfg_print_void) {
+                               cfg_print_cstr(pctx, " ");
+                       }
+                       cfg_doc_obj(pctx, clause->type);
+                       cfg_print_cstr(pctx, ";");
+                       cfg_print_clauseflags(pctx, clause->flags);
+                       cfg_print_cstr(pctx, "\n");
+               }
+       }
+       print_close(pctx);
+}
+
+bool
+cfg_obj_ismap_external(const cfg_obj_t *obj) {
+       REQUIRE(VALID_CFGOBJ(obj));
+       return obj->type->rep == &cfg_rep_map_external;
+}
+
+isc_result_t
+cfg_map_external_get(const cfg_obj_t *mapobj, const char *name,
+                    const cfg_obj_t **obj) {
+       isc_symvalue_t val;
+       const cfg_map_t *map;
+
+       REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map_external);
+       REQUIRE(name != NULL);
+       REQUIRE(obj != NULL && *obj == NULL);
+
+       map = mapobj->value.map_external;
+
+       RETERR(isc_symtab_lookup(map->symtab, name, SYMTAB_DUMMY_TYPE, &val));
+       *obj = val.as_pointer;
+       return ISC_R_SUCCESS;
+}
+
+const cfg_obj_t *
+cfg_map_external_getname(const cfg_obj_t *mapobj) {
+       REQUIRE(VALID_CFGOBJ(mapobj));
+       REQUIRE(mapobj->type->rep == &cfg_rep_map_external);
+       return mapobj->value.map_external->id;
+}
+
+unsigned int
+cfg_map_external_count(const cfg_obj_t *mapobj) {
+       const cfg_map_t *map;
+
+       REQUIRE(VALID_CFGOBJ(mapobj));
+       REQUIRE(mapobj->type->rep == &cfg_rep_map_external);
+
+       map = mapobj->value.map_external;
+       return isc_symtab_count(map->symtab);
+}
+
+const cfg_clausedef_t *
+cfg_map_external_firstclause(const cfg_type_t *map, const void **clauses,
+                            unsigned int *idx) {
+       cfg_clausedef_t *const *clauseset;
+
+       REQUIRE(map != NULL && map->rep == &cfg_rep_map_external);
+       REQUIRE(idx != NULL);
+       REQUIRE(clauses != NULL && *clauses == NULL);
+
+       clauseset = map->of;
+       if (*clauseset == NULL) {
+               return NULL;
+       }
+       *clauses = *clauseset;
+       *idx = 0;
+       while ((*clauseset)[*idx].name == NULL) {
+               *clauses = (*++clauseset);
+               if (*clauses == NULL) {
+                       return NULL;
+               }
+       }
+       return &(*clauseset)[*idx];
+}
+
+const cfg_clausedef_t *
+cfg_map_external_nextclause(const cfg_type_t *map, const void **clauses,
+                           unsigned int *idx) {
+       cfg_clausedef_t *const *clauseset;
+
+       REQUIRE(map != NULL && map->rep == &cfg_rep_map_external);
+       REQUIRE(idx != NULL);
+       REQUIRE(clauses != NULL && *clauses != NULL);
+
+       clauseset = map->of;
+       while (*clauseset != NULL && *clauseset != *clauses) {
+               clauseset++;
+       }
+       INSIST(*clauseset == *clauses);
+       (*idx)++;
+       while ((*clauseset)[*idx].name == NULL) {
+               *idx = 0;
+               *clauses = (*++clauseset);
+               if (*clauses == NULL) {
+                       return NULL;
+               }
+       }
+       return &(*clauseset)[*idx];
+}
+
+const cfg_clausedef_t *
+cfg_map_external_findclause(const cfg_type_t *map, const char *name) {
+       const cfg_clausedef_t *found = NULL;
+       const void *clauses = NULL;
+       unsigned int idx;
+
+       REQUIRE(map != NULL && map->rep == &cfg_rep_map_external);
+       REQUIRE(name != NULL);
+
+       found = cfg_map_external_firstclause(map, &clauses, &idx);
+       while (name != NULL && strcasecmp(name, found->name)) {
+               found = cfg_map_external_nextclause(map, &clauses, &idx);
+       }
+
+       return ((cfg_clausedef_t *)clauses) + idx;
+}
+
+static char *
+region_to_string(isc_region_t region) {
+       size_t len = region.length + 1;
+       char *str = isc_mem_allocate(isc_g_mctx, len);
+
+       memmove(str, region.base, region.length);
+       str[region.length] = 0;
+
+       return str;
+}
+
+/* Parse an arbitrary token, storing its raw text representation. */
+static isc_result_t
+parse_token(cfg_parser_t *pctx, const cfg_type_t *type ISC_ATTR_UNUSED,
+           cfg_obj_t **ret) {
+       cfg_obj_t *obj = NULL;
+       isc_result_t result;
+       isc_region_t r;
+
+       cfg_obj_create(cfg_parser_currentfile(pctx), pctx->line,
+                      &cfg_type_token, &obj);
+       CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+       if (pctx->token.type == isc_tokentype_eof) {
+               cfg_ungettoken(pctx);
+               CLEANUP(ISC_R_EOF);
+       }
+
+       isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r);
+       obj->value.string = region_to_string(r);
+
+       *ret = obj;
+       return result;
+
+cleanup:
+       if (obj != NULL) {
+               isc_mem_put(isc_g_mctx, obj, sizeof(*obj));
+       }
+       return result;
+}
+
+cfg_type_t cfg_type_token = { "token",          parse_token,
+                             cfg_print_ustring, cfg_doc_terminal,
+                             &cfg_rep_string,   NULL };
+
+/*
+ * An unsupported option.  This is just a list of tokens with balanced braces
+ * ending in a semicolon.
+ */
+
+static isc_result_t
+parse_unsupported(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+       cfg_obj_t *listobj = NULL;
+       isc_result_t result;
+       int braces = 0;
+
+       create_list(cfg_parser_currentfile(pctx), pctx->line, 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");
+                       CLEANUP(ISC_R_UNEXPECTEDTOKEN);
+               }
+
+               CHECK(cfg_parse_listelt(pctx, listobj, &cfg_type_token, &elt));
+               ISC_LIST_APPEND(*listobj->value.list, elt, link);
+       }
+       INSIST(braces == 0);
+       *ret = listobj;
+       return ISC_R_SUCCESS;
+
+cleanup:
+       CLEANUP_OBJ(listobj);
+       return result;
+}
+
+cfg_type_t cfg_type_unsupported = { "unsupported",      parse_unsupported,
+                                   cfg_print_spacelist, cfg_doc_terminal,
+                                   &cfg_rep_list,       NULL };
+
+/*
+ * Try interpreting the current token as a network address.
+ *
+ * If CFG_ADDR_WILDOK is set in flags, "*" can be used as a wildcard
+ * and at least one of CFG_ADDR_V4OK and CFG_ADDR_V6OK must also be set.  The
+ * "*" is interpreted as the IPv4 wildcard address if CFG_ADDR_V4OK is
+ * set (including the case where CFG_ADDR_V4OK and CFG_ADDR_V6OK are both set),
+ * and the IPv6 wildcard address otherwise.
+ */
+static isc_result_t
+token_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
+       char *s;
+       struct in_addr in4a;
+       struct in6_addr in6a;
+
+       if (pctx->token.type != isc_tokentype_string) {
+               return ISC_R_UNEXPECTEDTOKEN;
+       }
+
+       s = TOKEN_STRING(pctx);
+       if ((flags & CFG_ADDR_WILDOK) != 0 && strcmp(s, "*") == 0) {
+               if ((flags & CFG_ADDR_V4OK) != 0) {
+                       isc_netaddr_any(na);
+                       return ISC_R_SUCCESS;
+               } else if ((flags & CFG_ADDR_V6OK) != 0) {
+                       isc_netaddr_any6(na);
+                       return ISC_R_SUCCESS;
+               } else {
+                       UNREACHABLE();
+               }
+       } else {
+               if ((flags & (CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK)) != 0) {
+                       if (inet_pton(AF_INET, s, &in4a) == 1) {
+                               isc_netaddr_fromin(na, &in4a);
+                               return ISC_R_SUCCESS;
+                       }
+               }
+               if ((flags & CFG_ADDR_V4PREFIXOK) != 0 && strlen(s) <= 15U) {
+                       char buf[64];
+                       int i;
+
+                       strlcpy(buf, s, sizeof(buf));
+                       for (i = 0; i < 3; i++) {
+                               strlcat(buf, ".0", sizeof(buf));
+                               if (inet_pton(AF_INET, buf, &in4a) == 1) {
+                                       isc_netaddr_fromin(na, &in4a);
+                                       return ISC_R_IPV4PREFIX;
+                               }
+                       }
+               }
+               if ((flags & CFG_ADDR_V6OK) != 0 && strlen(s) <= 127U) {
+                       char buf[128];     /* see isc_getaddresses() */
+                       char *d;           /* zone delimiter */
+                       uint32_t zone = 0; /* scope zone ID */
+
+                       strlcpy(buf, s, sizeof(buf));
+                       d = strchr(buf, '%');
+                       if (d != NULL) {
+                               *d = '\0';
+                       }
+
+                       if (inet_pton(AF_INET6, buf, &in6a) == 1) {
+                               if (d != NULL) {
+                                       RETERR(isc_netscope_pton(
+                                               AF_INET6, d + 1, &in6a, &zone));
+                               }
+
+                               isc_netaddr_fromin6(na, &in6a);
+                               isc_netaddr_setzone(na, zone);
+                               return ISC_R_SUCCESS;
+                       }
+               }
+       }
+       return ISC_R_UNEXPECTEDTOKEN;
+}
+
+isc_result_t
+cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
+       isc_result_t result;
+       const char *wild = "";
+       const char *prefix = "";
+
+       REQUIRE(pctx != NULL);
+       REQUIRE(na != NULL);
+
+       CHECK(cfg_gettoken(pctx, 0));
+       result = token_addr(pctx, flags, na);
+       if (result == ISC_R_UNEXPECTEDTOKEN) {
+               if ((flags & CFG_ADDR_WILDOK) != 0) {
+                       wild = " or '*'";
+               }
+               if ((flags & CFG_ADDR_V4PREFIXOK) != 0) {
+                       wild = " or IPv4 prefix";
                }
                if ((flags & CFG_ADDR_MASK) == CFG_ADDR_V4OK) {
                        cfg_parser_error(pctx, CFG_LOG_NEAR,
@@ -3937,6 +4439,31 @@ free_map(cfg_obj_t *obj) {
        isc_mem_put(isc_g_mctx, obj->value.map, sizeof(*obj->value.map));
 }
 
+static void
+create_map_external(cfg_parser_t *pctx, const cfg_type_t *type,
+                   cfg_obj_t **ret) {
+       isc_symtab_t *symtab = NULL;
+       cfg_obj_t *obj = NULL;
+
+       cfg_obj_create(cfg_parser_currentfile(pctx), pctx->line, type, &obj);
+       isc_symtab_create(isc_g_mctx, map_symtabitem_destroy, pctx, false,
+                         &symtab);
+
+       obj->value.map_external =
+               isc_mem_cget(isc_g_mctx, 1, sizeof(*obj->value.map_external));
+       obj->value.map_external->symtab = symtab;
+
+       *ret = obj;
+}
+
+static void
+free_map_external(cfg_obj_t *obj) {
+       CLEANUP_OBJ(obj->value.map_external->id);
+       isc_symtab_destroy(&obj->value.map_external->symtab);
+       isc_mem_put(isc_g_mctx, obj->value.map_external,
+                   sizeof(*obj->value.map_external));
+}
+
 bool
 cfg_obj_istype(const cfg_obj_t *obj, const cfg_type_t *type) {
        REQUIRE(VALID_CFGOBJ(obj));
@@ -4103,6 +4630,106 @@ cfg_map_addclone(cfg_obj_t *map, const cfg_obj_t *obj,
        return result;
 }
 
+static isc_result_t
+map_define_external(cfg_obj_t *mapobj, cfg_obj_t *obj,
+                   const cfg_clausedef_t *clause) {
+       isc_result_t result;
+       const cfg_map_t *map;
+       isc_symvalue_t symval;
+
+       map = mapobj->value.map_external;
+       result = isc_symtab_lookup(map->symtab, clause->name, SYMTAB_DUMMY_TYPE,
+                                  &symval);
+       if (result == ISC_R_NOTFOUND) {
+               if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
+                       cfg_obj_t *destobj = NULL;
+                       cfg_listelt_t *elt = NULL;
+
+                       create_list(obj->file, obj->line,
+                                   &cfg_type_implicitlist, &destobj);
+                       cfg_listelt_create(&elt);
+                       cfg_obj_attach(obj, &elt->obj);
+                       ISC_LIST_APPEND(*destobj->value.list, elt, link);
+                       symval.as_pointer = destobj;
+               } else {
+                       symval.as_pointer = obj;
+               }
+
+               result = isc_symtab_define(map->symtab, clause->name,
+                                          SYMTAB_DUMMY_TYPE, symval,
+                                          isc_symexists_reject);
+               INSIST(result == ISC_R_SUCCESS);
+       } else {
+               cfg_obj_t *destobj = symval.as_pointer;
+               cfg_listelt_t *elt = NULL;
+
+               INSIST(result == ISC_R_SUCCESS);
+
+               if (destobj->type == &cfg_type_implicitlist) {
+                       cfg_listelt_create(&elt);
+                       cfg_obj_attach(obj, &elt->obj);
+                       ISC_LIST_APPEND(*destobj->value.list, elt, link);
+               } else {
+                       result = ISC_R_EXISTS;
+               }
+       }
+
+       return result;
+}
+
+isc_result_t
+cfg_map_external_add(cfg_obj_t *mapobj, cfg_obj_t *obj,
+                    const char *clausename) {
+       const cfg_clausedef_t *clause;
+
+       REQUIRE(VALID_CFGOBJ(obj));
+       REQUIRE(VALID_CFGOBJ(mapobj));
+       REQUIRE(mapobj->type->rep == &cfg_rep_map_external);
+       REQUIRE(clausename != NULL);
+
+       clause = cfg_map_external_findclause(mapobj->type, clausename);
+       if (clause == NULL || clause->name == NULL) {
+               return ISC_R_FAILURE;
+       }
+
+       return map_define_external(mapobj, obj, clause);
+}
+
+isc_result_t
+cfg_map_external_addclone(cfg_obj_t *map, const cfg_obj_t *obj,
+                         const cfg_clausedef_t *clause) {
+       isc_result_t result = ISC_R_SUCCESS;
+       cfg_obj_t *clone = NULL;
+
+       REQUIRE(VALID_CFGOBJ(obj));
+       REQUIRE(VALID_CFGOBJ(map));
+       REQUIRE(map->type->rep == &cfg_rep_map_external);
+       REQUIRE(clause != NULL && clause->name != NULL);
+
+       if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
+               const cfg_listelt_t *elt = NULL;
+
+               REQUIRE(cfg_obj_islist(obj));
+
+               elt = cfg_list_first(obj);
+               while (elt != NULL && result == ISC_R_SUCCESS) {
+                       cfg_obj_clone(elt->obj, &clone);
+                       clone->cloned = true;
+
+                       result = map_define_external(map, clone, clause);
+                       elt = cfg_list_next(elt);
+
+                       cfg_obj_detach(&clone);
+               }
+       } else {
+               cfg_obj_clone(obj, &clone);
+               clone->cloned = true;
+               result = map_define_external(map, clone, clause);
+       }
+
+       return result;
+}
+
 void
 cfg_list_addclone(cfg_obj_t *dst, const cfg_obj_t *src, bool prepend) {
        const cfg_listelt_t *srcelt = NULL;