settings: Add reference feature

Similar to the `also` keyword in ipsec.conf, the new syntax allows adding
one or more references to other sections, which means all the settings and
subsections defined there are inherited (values may be overridden, even
with an empty value to clear it).

It's important to note that all subsections are inherited, so if this is
used to reference a connection in swanctl.conf all auth rounds and
children are inherited.  There is currently no syntax to limit the
inclusion level or clear inherited sections (but as mentioned, settings
in those inherited sections may be overridden).

Another property is that inherited settings or sections always follow
explicitly defined entries in the current section when they are enumerated.
This is relevant if the order is important (e.g. for auth rounds if `round`
is not specified).

References are evaluated dynamically at runtime, so referring to
sections later in the config file or included via other files is no
problem.

The colon used as separator to reference other sections may be used in
section names by writing :: (e.g. for Windows log file paths).

This is based on a patch originally written in 2016.
This commit is contained in:
Tobias Brunner
2018-06-27 14:19:35 +02:00
parent 0ca0fa71c0
commit 35ca6a7b2f
7 changed files with 800 additions and 157 deletions
@@ -1037,6 +1037,50 @@ START_TEST(test_add_fallback)
}
END_TEST
START_TEST(test_fallback_resolution)
{
linked_list_t *keys, *values;
settings->destroy(settings);
create_settings(chunk_from_str(
"base {\n"
" sub {\n"
" key1 = val1\n"
" key2 = val2\n"
" key5 = val5\n"
" subsub {\n"
" subkey1 = subval1\n"
" }\n"
" }\n"
"}\n"
"other {\n"
" sub {\n"
" key3 = val3\n"
" key4 = val4\n"
" }\n"
"}\n"
"main {\n"
" sub {\n"
" key4=\n"
" key5 = \n"
" }\n"
"}"));
settings->add_fallback(settings, "other", "base");
settings->add_fallback(settings, "main.sub", "other.sub");
verify_string("val1", "main.sub.key1");
verify_string("val3", "main.sub.key3");
verify_null("main.sub.key4");
verify_null("main.sub.key5");
verify_string("subval1", "main.sub.subsub.subkey1");
keys = linked_list_create_with_items("key3", "key1", "key2", NULL);
values = linked_list_create_with_items("val3", "val1", "val2", NULL);
verify_key_values(keys, values, "main.sub");
}
END_TEST
START_TEST(test_add_fallback_printf)
{
settings->add_fallback(settings, "%s.sub1", "sub", "main");
@@ -1051,6 +1095,264 @@ START_TEST(test_add_fallback_printf)
}
END_TEST
START_TEST(test_references)
{
linked_list_t *keys, *values;
create_settings(chunk_from_str(
"main {\n"
" sub1 {\n"
" key1 = sub1val1\n"
" key2 = sub1val2\n"
" key4 = sub1val4\n"
" subsub {\n"
" subkey1 = sub1subsubval1\n"
" subkey2 = sub1subsubval2\n"
" }\n"
" subsub1 {\n"
" subkey1 = sub1subsub1val1\n"
" }\n"
" }\n"
" sub2 : main.sub1 {\n"
" key2 = sub2val2\n"
" key3 = sub2val3\n"
" key4 =\n"
" subsub {\n"
" subkey1 = sub2subsubval1\n"
" subkey3 = sub2subsubval3\n"
" }\n"
" }\n"
"}"));
verify_string("sub1val1", "main.sub2.key1");
verify_string("sub2val2", "main.sub2.key2");
verify_string("sub2val3", "main.sub2.key3");
verify_null("main.sub2.key4");
verify_string("sub2subsubval1", "main.sub2.subsub.subkey1");
verify_string("sub1subsubval2", "main.sub2.subsub.subkey2");
verify_string("sub2subsubval3", "main.sub2.subsub.subkey3");
verify_string("sub1subsub1val1", "main.sub2.subsub1.subkey1");
keys = linked_list_create_with_items("subsub", "subsub1", NULL);
verify_sections(keys, "main.sub2");
keys = linked_list_create_with_items("key2", "key3", "key1", NULL);
values = linked_list_create_with_items("sub2val2", "sub2val3", "sub1val1", NULL);
verify_key_values(keys, values, "main.sub2");
keys = linked_list_create_with_items("subkey1", "subkey3", "subkey2", NULL);
values = linked_list_create_with_items("sub2subsubval1", "sub2subsubval3", "sub1subsubval2", NULL);
verify_key_values(keys, values, "main.sub2.subsub");
}
END_TEST
START_TEST(test_references_templates)
{
create_settings(chunk_from_str(
"sub-def {\n"
" key1 = sub1val1\n"
" key2 = sub1val2\n"
" subsub {\n"
" subkey1 = sub1subsubval1\n"
" }\n"
"}\n"
"subsub-def {\n"
" subkey1 = sub1subval1\n"
" subkey2 = sub1subval1\n"
"}\n"
"main {\n"
" sub1 : sub-def {\n"
" key1 = mainsub1val1\n"
" subsub : subsub-def {\n"
" subkey1 = mainsub1subval1\n"
" }\n"
" subsub1 {\n"
" subkey1 = mainsub1sub1val1\n"
" }\n"
" }\n"
" sub2 : sub-def {\n"
" key2 = mainsub2val2\n"
" key3 = mainsub2val3\n"
" subsub {\n"
" subkey3 = mainsub2subsubval3\n"
" }\n"
" }\n"
"}"));
verify_string("mainsub1val1", "main.sub1.key1");
verify_string("sub1val2", "main.sub1.key2");
verify_string("mainsub1subval1", "main.sub1.subsub.subkey1");
verify_string("sub1subval1", "main.sub1.subsub.subkey2");
verify_string("mainsub1sub1val1", "main.sub1.subsub1.subkey1");
verify_string("sub1val1", "main.sub2.key1");
verify_string("mainsub2val2", "main.sub2.key2");
verify_string("mainsub2val3", "main.sub2.key3");
verify_string("sub1subsubval1", "main.sub2.subsub.subkey1");
verify_null("main.sub2.subsub.subkey2");
verify_string("mainsub2subsubval3", "main.sub2.subsub.subkey3");
}
END_TEST
START_TEST(test_references_order)
{
linked_list_t *keys, *values;
create_settings(chunk_from_str(
"main {\n"
" sub1 {\n"
" key1 = sub1val1\n"
" key2 = sub1val2\n"
" subsub1 {\n"
" }\n"
" }\n"
" sub2 {\n"
" key2 = sub2val2\n"
" key3 = sub2val3\n"
" subsub2 {\n"
" }\n"
" }\n"
" sub3 : main.sub1, main.sub2 {\n"
" key3 = sub3val3\n"
" }\n"
" sub4 : main.sub2, main.sub1 {\n"
" key3 = sub4val3\n"
" }\n"
"}"));
verify_string("sub1val2", "main.sub3.key2");
verify_string("sub3val3", "main.sub3.key3");
verify_string("sub2val2", "main.sub4.key2");
verify_string("sub4val3", "main.sub4.key3");
/* the order of referenced keys/subsections depends on the reference
* statement's order */
keys = linked_list_create_with_items("subsub1", "subsub2", NULL);
verify_sections(keys, "main.sub3");
keys = linked_list_create_with_items("subsub2", "subsub1", NULL);
verify_sections(keys, "main.sub4");
/* local keys are always enumerated first */
keys = linked_list_create_with_items("key3", "key1", "key2", NULL);
values = linked_list_create_with_items("sub3val3", "sub1val1", "sub1val2", NULL);
verify_key_values(keys, values, "main.sub3");
keys = linked_list_create_with_items("key3", "key2", "key1", NULL);
values = linked_list_create_with_items("sub4val3", "sub2val2", "sub1val1", NULL);
verify_key_values(keys, values, "main.sub4");
}
END_TEST
START_TEST(test_references_resolution)
{
linked_list_t *keys, *values;
create_settings(chunk_from_str(
"sec-a {\n"
" sub1 {\n"
" a1 = val-a1\n"
" key = sec-a-val1\n"
" sub-a {\n"
" }\n"
" }\n"
"}\n"
"sec-b : sec-a {\n"
" sub1 {\n"
" b1 = val-b1\n"
" key = sec-b-val1\n"
" sub-b1 {\n"
" }\n"
" }\n"
" sub2 {\n"
" b2 = val-b2\n"
" key = sec-b-val2\n"
" sub-b2 {\n"
" }\n"
" }\n"
"}\n"
"sec-c : sec-b {\n"
" sub2 : sec-b.sub1 {\n"
" c2 = val-c2\n"
" key = sec-c-val2\n"
" sub-c2 {\n"
" }\n"
" }\n"
"}"));
verify_string("sec-c-val2", "sec-c.sub2.key");
settings_remove_value(settings, "sec-c.sub2.key");
verify_string("sec-b-val1", "sec-c.sub2.key");
settings_remove_value(settings, "sec-b.sub1.key");
verify_string("sec-a-val1", "sec-c.sub2.key");
settings_remove_value(settings, "sec-a.sub1.key");
verify_string("sec-b-val2", "sec-c.sub2.key");
settings_remove_value(settings, "sec-b.sub2.key");
verify_null("sec-c.sub2.key");
keys = linked_list_create_with_items("sub-c2", "sub-b1", "sub-a", "sub-b2", NULL);
verify_sections(keys, "sec-c.sub2");
keys = linked_list_create_with_items("c2", "b1", "a1", "b2", NULL);
values = linked_list_create_with_items("val-c2", "val-b1", "val-a1", "val-b2", NULL);
verify_key_values(keys, values, "sec-c.sub2");
}
END_TEST
START_TEST(test_references_fallback)
{
linked_list_t *keys, *values;
#define test_references_fallback_base_settings \
"lib {\n" \
" key1 = libval1\n" \
" keylib = libval\n" \
" sub {\n" \
" key1 = libsubval1\n" \
" }\n" \
" libsub {\n" \
" }\n" \
"}\n" \
"other {\n" \
" key1 = otherval1\n" \
" keyother = otherval\n" \
" sub {\n" \
" key1 = othersubval1\n" \
" }\n" \
" othersub {\n" \
" }\n" \
"}\n"
create_settings(chunk_from_str(
test_references_fallback_base_settings "app : other {}"));
/* references have precedence over fallbacks */
settings->add_fallback(settings, "app", "lib");
verify_string("otherval1", "app.key1");
verify_string("libval", "app.keylib");
verify_string("othersubval1", "app.sub.key1");
keys = linked_list_create_with_items("sub", "othersub", "libsub", NULL);
verify_sections(keys, "app");
keys = linked_list_create_with_items("key1", "keyother", "keylib", NULL);
values = linked_list_create_with_items("otherval1", "otherval", "libval", NULL);
verify_key_values(keys, values, "app");
/* fallbacks are unaffected when reloading configs with references */
ck_assert(settings->load_string_section(settings,
test_references_fallback_base_settings "app {}", FALSE, ""));
verify_string("libval1", "app.key1");
verify_string("libval", "app.keylib");
verify_string("libsubval1", "app.sub.key1");
ck_assert(settings->load_string_section(settings,
test_references_fallback_base_settings "app : other {}", FALSE, ""));
verify_string("otherval1", "app.key1");
verify_string("libval", "app.keylib");
verify_string("othersubval1", "app.sub.key1");
}
END_TEST
START_SETUP(setup_string_config)
{
create_settings(chunk_from_str(
@@ -1115,6 +1417,37 @@ START_TEST(test_valid)
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(settings->load_files(settings, path, FALSE));
verify_string("a setting with = and { character", "equals");
contents = chunk_from_str(
"ref { key = value }\nvalid:ref {}");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(settings->load_files(settings, path, FALSE));
verify_string("value", "valid.key");
contents = chunk_from_str(
"ref { key = value }\nvalid\n:\nref {}");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(settings->load_files(settings, path, FALSE));
verify_string("value", "valid.key");
contents = chunk_from_str(
"ref { key = value }\nother { key1 = value1 }\nvalid\n:\nref\n\t,\nother {}");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(settings->load_files(settings, path, FALSE));
verify_string("value", "valid.key");
verify_string("value1", "valid.key1");
contents = chunk_from_str(
"c::\\Logfiles\\charon.log { dmn = 1 }");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(settings->load_files(settings, path, FALSE));
verify_string("1", "%s.dmn", "c:\\Logfiles\\charon.log");
contents = chunk_from_str(
"section { c::\\Logfiles\\charon.log = 1 }");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(settings->load_files(settings, path, FALSE));
verify_string("1", "section.%s", "c:\\Logfiles\\charon.log");
}
END_TEST
@@ -1157,6 +1490,11 @@ START_TEST(test_invalid)
"\"unexpected\" = string");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(!settings->load_files(settings, path, FALSE));
contents = chunk_from_str(
"incorrect :: ref {}");
ck_assert(chunk_write(contents, path, 0022, TRUE));
ck_assert(!settings->load_files(settings, path, FALSE));
}
END_TEST
@@ -1331,9 +1669,19 @@ Suite *settings_suite_create()
tc = tcase_create("fallback");
tcase_add_checked_fixture(tc, setup_fallback_config, teardown_config);
tcase_add_test(tc, test_add_fallback);
tcase_add_test(tc, test_fallback_resolution);
tcase_add_test(tc, test_add_fallback_printf);
suite_add_tcase(s, tc);
tc = tcase_create("references");
tcase_add_checked_fixture(tc, NULL, teardown_config);
tcase_add_test(tc, test_references);
tcase_add_test(tc, test_references_templates);
tcase_add_test(tc, test_references_order);
tcase_add_test(tc, test_references_resolution);
tcase_add_test(tc, test_references_fallback);
suite_add_tcase(s, tc);
tc = tcase_create("strings");
tcase_add_checked_fixture(tc, setup_string_config, teardown_config);
tcase_add_test(tc, test_strings);