From 2b4f908f40909a20c74de8889b997a983f17334f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20=C5=BDidek?= <mzidek@redhat.com>
Date: Fri, 15 Apr 2016 12:13:58 +0200
Subject: [PATCH 4/4] ini: Add internal validator allowed_sections

Ticket:
https://fedorahosted.org/sssd/ticket/133

This validator allows to list all sections
that can be used in config file. Sections
can be specified using string or regular
expression.

Format is:

[rule/allowed_sections]
validator = ini_allowed_sections
section = foo
section = bar
section_re = ^baz/.*
---
 ini/ini_configobj.c           | 222 ++++++++++++++++++++++++++++++++++++++++++
 ini/ini_validators_ut_check.c | 219 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 441 insertions(+)

diff --git a/ini/ini_configobj.c b/ini/ini_configobj.c
index 95a3baa..c53e868 100644
--- a/ini/ini_configobj.c
+++ b/ini/ini_configobj.c
@@ -1095,6 +1095,226 @@ static int ini_dummy_error(const char *rule_name,
     return ini_errobj_add_msg(errobj, "Error");
 }
 
+static int is_allowed_section(const char *tested_section,
+                              char **allowed_sections,
+                              size_t num_sec,
+                              regex_t *allowed_sections_re,
+                              size_t num_sec_re,
+                              int case_insensitive)
+{
+    int ret;
+
+    if (case_insensitive) {
+        for (int i = 0; i < num_sec; i++) {
+            if (strcasecmp(tested_section, allowed_sections[i]) == 0) {
+                return 1;
+            }
+        }
+    } else { /* case insensitive */
+         for (int i = 0; i < num_sec; i++) {
+            if (strcmp(tested_section, allowed_sections[i]) == 0) {
+                return 1;
+            }
+        }
+    }
+
+    for (int i = 0; i < num_sec_re; i++) {
+        ret = regexec(&allowed_sections_re[i], tested_section, 0, NULL, 0);
+        if (ret == 0) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+static int ini_allowed_sections(const char *rule_name,
+                                struct ini_cfgobj *rules_obj,
+                                struct ini_cfgobj *config_obj,
+                                struct ini_errobj *errobj)
+{
+    struct value_obj *vo = NULL;
+    int ret;
+    char *regex_str = NULL;
+    char **allowed_sections = NULL;
+    char *insensitive_str;
+    char **cfg_sections = NULL;
+    int num_cfg_sections;
+    char **attributes = NULL;
+    int num_attributes;
+    size_t num_sec = 0;
+    size_t num_sec_re = 0;
+    regex_t *allowed_sections_re;
+    size_t buf_size;
+    char *err_str = NULL;
+    int reg_err;
+    int is_allowed;
+    int case_insensitive = 0;
+    int regcomp_flags = REG_NOSUB;
+
+    /* Get number of 'section' and 'section_re' attributes
+     * in this rule */
+    attributes = ini_get_attribute_list(rules_obj,
+                                        rule_name,
+                                        &num_attributes,
+                                        NULL);
+    if (attributes == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (int i = 0; i < num_attributes; i++) {
+        if (strcmp("section", attributes[i]) == 0) {
+            num_sec++;
+        }
+
+        if (strcmp("section_re", attributes[i]) == 0) {
+            num_sec_re++;
+        }
+    }
+
+    if (num_sec == 0 && num_sec_re == 0) {
+        /* This rule is empty. */
+        ret = ini_errobj_add_msg(errobj, "No allowed sections specified. "
+                                 "Use 'section = default' to allow only "
+                                 "default section");
+        goto done;
+    }
+
+    ini_free_attribute_list(attributes);
+
+    ret = ini_get_config_valueobj(rule_name,
+                                  "case_insensitive",
+                                   rules_obj,
+                                   INI_GET_NEXT_VALUE,
+                                   &vo);
+    if (ret) {
+        goto done;
+    }
+
+    if (vo) {
+        insensitive_str = ini_get_string_config_value(vo, &ret);
+        if (ret) {
+            goto done;
+        }
+
+        if (strcasecmp(insensitive_str, "yes") == 0
+                || strcasecmp(insensitive_str, "true") == 0
+                || strcmp(insensitive_str, "1") == 0) {
+            case_insensitive = 1;
+            regcomp_flags |= REG_ICASE;
+        }
+
+        free(insensitive_str);
+    }
+
+    /* Create arrays for section_re regexes and section name
+     * strings. */
+    allowed_sections = calloc(num_sec, sizeof(char*));
+    if (allowed_sections == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    allowed_sections_re = calloc(num_sec_re, sizeof(regex_t));
+    if (allowed_sections_re == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Get all allowed section names and store them to
+     * allowed_sections array */
+    for (size_t i = 0; i < num_sec; i++) {
+         ret = ini_get_config_valueobj(rule_name,
+                                       "section",
+                                       rules_obj,
+                                       INI_GET_NEXT_VALUE,
+                                       &vo);
+        if (ret) {
+            goto done;
+        }
+
+        allowed_sections[i] = ini_get_string_config_value(vo, &ret);
+        if (ret) {
+            goto done;
+        }
+    }
+
+    /* Get all regular section_re regular expresions and
+     * store them to allowed_sections_re array */
+    for (int i = 0; i < num_sec_re; i++) {
+         ret = ini_get_config_valueobj(rule_name,
+                                       "section_re",
+                                       rules_obj,
+                                       INI_GET_NEXT_VALUE,
+                                       &vo);
+        if (ret) {
+            goto done;
+        }
+
+        regex_str = ini_get_string_config_value(vo, &ret);
+        if (ret) {
+            goto done;
+        }
+
+        reg_err = regcomp(&allowed_sections_re[i], regex_str, regcomp_flags);
+        if (reg_err) {
+            buf_size = regerror(reg_err, &allowed_sections_re[i], NULL, 0);
+            err_str = malloc(buf_size);
+            if (err_str == NULL) {
+                ret = ENOMEM;
+                goto done;
+            }
+
+            regerror(reg_err, &allowed_sections_re[i], err_str, buf_size);
+            ret = ini_errobj_add_msg(errobj, "Validator failed to use regex "
+                                     "[%s]:[%s]", regex_str, err_str);
+            ret = ret ? ret : EINVAL;
+            goto done;
+        }
+        free(regex_str);
+        regex_str = NULL;
+    }
+
+    /* Finally get list of all sections in configuration and
+     * check if they are matched by some string in allowed_sections
+     * or regex in allowed_sections_re */
+    cfg_sections = ini_get_section_list(config_obj, &num_cfg_sections, &ret);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    for (int i = 0; i < num_cfg_sections; i++) {
+        is_allowed = is_allowed_section(cfg_sections[i],
+                                        allowed_sections,
+                                        num_sec,
+                                        allowed_sections_re,
+                                        num_sec_re,
+                                        case_insensitive);
+        if (!is_allowed) {
+            ret = ini_errobj_add_msg(errobj, "Section [%s] is not allowed. "
+                                     "Check for typos.", cfg_sections[i]);
+            if (ret) {
+                goto done;
+            }
+        }
+    }
+
+    ret = EOK;
+done:
+    for (int i = 0; i < num_sec; i++) {
+        free(allowed_sections[i]);
+    }
+    free(allowed_sections);
+    for (int i = 0; i < num_sec_re; i++) {
+        regfree(&allowed_sections_re[i]);
+    }
+    free(allowed_sections_re);
+    ini_free_section_list(cfg_sections);
+    free(regex_str);
+
+    return ret;
+}
 
 static int check_if_allowed(char *section, char *attr, char **allowed, int num_allowed,
                             struct ini_errobj *errobj)
@@ -1285,6 +1505,8 @@ get_validator(char *validator_name,
         return ini_dummy_error;
     } else if (strcmp(validator_name, "ini_allowed_options") == 0) {
         return ini_allowed_options;
+    } else if (strcmp(validator_name, "ini_allowed_sections") == 0) {
+        return ini_allowed_sections;
     }
 
     /* Now check the custom validators */
diff --git a/ini/ini_validators_ut_check.c b/ini/ini_validators_ut_check.c
index cf208ed..d337d36 100644
--- a/ini/ini_validators_ut_check.c
+++ b/ini/ini_validators_ut_check.c
@@ -510,10 +510,219 @@ START_TEST(test_ini_allowed_sections_str_ok)
     ini_errobj_destroy(&errobj);
     ini_config_destroy(cfg_obj);
     ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_allowed_sections_str_typos)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int num_err;
+    int ret;
+
+    /* Only bar and baz are allowed for foo section */
+    char input_rules[] =
+        "[rule/section_list]\n"
+        "validator = ini_allowed_sections\n"
+        "section = foo\n"
+        "section = bar\n";
+
+    /* Make 4 typos */
+    char input_cfg[] =
+        "[fooo]\n"
+        "br = 0\n"
+        "bra = 0\n"
+        "[baar]\n"
+        "abz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate 2 errors */
+    fail_if(ini_errobj_no_more_msgs(errobj),
+            "Expected 2 errors but none found");
+    num_err = ini_errobj_count(errobj);
+    fail_unless(num_err == 2, "Expected 2 errors, got %d", num_err);
+
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_allowed_sections_str_insensitive)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+
+    /* Only bar and baz are allowed for foo section */
+    char input_rules[] =
+        "[rule/section_list]\n"
+        "validator = ini_allowed_sections\n"
+        "case_insensitive = yes\n"
+        "section = foo\n"
+        "section = bar\n";
+
+    /* Make 4 typos */
+    char input_cfg[] =
+        "[FOo]\n"
+        "br = 0\n"
+        "bra = 0\n"
+        "[baR]\n"
+        "abz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate 0 errors */
+    fail_unless(ini_errobj_no_more_msgs(errobj), "Unexpected errors found");
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_allowed_sections_re_ok)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+
+    char input_rules[] =
+        "[rule/section_list]\n"
+        "validator = ini_allowed_sections\n"
+        "section_re = ^foo*$\n"
+        "section_re = bar\n";
+
+    char input_cfg[] =
+        "[foooooooooooo]\n"
+        "br = 0\n"
+        "bra = 0\n"
+        "[my_bar]\n"
+        "abz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate 0 errors */
+    fail_unless(ini_errobj_no_more_msgs(errobj), "Unexpected errors found");
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+START_TEST(test_ini_allowed_sections_re_typos)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int num_err;
+    int ret;
+
+    char input_rules[] =
+        "[rule/section_list]\n"
+        "validator = ini_allowed_sections\n"
+        "section_re = ^foo*$\n"
+        "section_re = bar\n";
+
+    /* Make 4 typos */
+    char input_cfg[] =
+        "[fooooooOooooo]\n"
+        "br = 0\n"
+        "bra = 0\n"
+        "[my_bra]\n"
+        "abz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
 
+    /* Should generate 2 errors */
+    fail_if(ini_errobj_no_more_msgs(errobj),
+            "Expected 2 errors but none found");
+    num_err = ini_errobj_count(errobj);
+    fail_unless(num_err == 2, "Expected 2 errors, got %d", num_err);
+
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
 }
 END_TEST
 
+START_TEST(test_ini_allowed_sections_re_insensitive)
+{
+    struct ini_cfgobj *rules_obj;
+    struct ini_cfgobj *cfg_obj;
+    struct ini_errobj *errobj;
+    int ret;
+
+    /* Only bar and baz are allowed for foo section */
+    char input_rules[] =
+        "[rule/section_list]\n"
+        "validator = ini_allowed_sections\n"
+        "case_insensitive = yes\n"
+        "section_re = ^foo*$\n"
+        "section_re = bar\n";
+
+    /* Make 4 typos */
+    char input_cfg[] =
+        "[FOoOoOoOoOOOOooo]\n"
+        "br = 0\n"
+        "bra = 0\n"
+        "[my_Bar]\n"
+        "abz = 0\n";
+
+    create_rules_from_str(input_rules, &rules_obj);
+    cfg_obj = get_ini_config_from_str(input_cfg, sizeof(input_cfg));
+
+    ret = ini_errobj_create(&errobj);
+    fail_unless(ret == 0, "ini_errobj_create() failed: %s", strerror(ret));
+
+    ret = ini_rules_check(rules_obj, cfg_obj, NULL, 0, errobj);
+    fail_unless(ret == 0, "ini_rules_check() failed: %s", strerror(ret));
+
+    /* Should generate 0 errors */
+    fail_unless(ini_errobj_no_more_msgs(errobj), "Unexpected errors found");
+
+    ini_errobj_destroy(&errobj);
+    ini_config_destroy(cfg_obj);
+    ini_rules_destroy(rules_obj);
+}
+END_TEST
+
+
 static Suite *ini_validators_utils_suite(void)
 {
     Suite *s = suite_create("ini_validators");
@@ -531,8 +740,18 @@ static Suite *ini_validators_utils_suite(void)
     tcase_add_test(tc_allowed_options, test_ini_allowed_options_typos);
     tcase_add_test(tc_allowed_options, test_ini_allowed_options_no_section);
 
+    TCase *tc_allowed_sections = tcase_create("ini_allowed_sections");
+    tcase_add_test(tc_allowed_options, test_ini_allowed_sections_str_ok);
+    tcase_add_test(tc_allowed_options, test_ini_allowed_sections_str_typos);
+    tcase_add_test(tc_allowed_options, test_ini_allowed_sections_str_insensitive);
+    tcase_add_test(tc_allowed_options, test_ini_allowed_sections_re_ok);
+    tcase_add_test(tc_allowed_options, test_ini_allowed_sections_re_typos);
+    tcase_add_test(tc_allowed_options, test_ini_allowed_sections_re_insensitive);
+
+
     suite_add_tcase(s, tc_infrastructure);
     suite_add_tcase(s, tc_allowed_options);
+    suite_add_tcase(s, tc_allowed_sections);
 
     return s;
 }
-- 
2.5.0

