Exemplo n.º 1
0
/* Only search in HOSTNAME_STRLIST because it only makes sense to search for
 * longest suffix with hostnames and subdomains.  */
static void test_StrList_SearchLongestSuffix()
{
    /* REMINDER: HOSTNAME_STRINGS[] =
       {  "*", ".*", ".", "com", "cfengine.com", ".allowed.cfengine.com", "www.cfengine.com", ".no" }; */

    size_t ret, ret2, ret3, ret4, ret5, ret6, ret7, ret8, ret9, ret10, ret11;

    ret  = StrList_SearchLongestPrefix(HOSTNAME_SL, "cfengine.com", 0, '.', false);
    ret2 = StrList_SearchLongestPrefix(HOSTNAME_SL, "google.com", 0, '.', false);
    ret3 = StrList_SearchLongestPrefix(HOSTNAME_SL, "yr.no", 0, '.', false);
    ret4 = StrList_SearchLongestPrefix(HOSTNAME_SL, "ntua.gr", 0, '.', false);
    ret5 = StrList_SearchLongestPrefix(HOSTNAME_SL, "disallowed.cfengine.com", 0, '.', false);
    ret6 = StrList_SearchLongestPrefix(HOSTNAME_SL, "allowed.cfengine.com", 0, '.', false);
    ret7 = StrList_SearchLongestPrefix(HOSTNAME_SL, "blah.allowed.cfengine.com", 0, '.', false);
    ret8 = StrList_SearchLongestPrefix(HOSTNAME_SL, "www.cfengine.com", 0, '.', false);
    ret9 = StrList_SearchLongestPrefix(HOSTNAME_SL, "www1.cfengine.com", 0, '.', false);
    ret10 = StrList_SearchLongestPrefix(HOSTNAME_SL, "1www.cfengine.com", 0, '.', false);
    ret11 = StrList_SearchLongestPrefix(HOSTNAME_SL, "no", 0, '.', false);

    assert_string_equal(StrList_At(HOSTNAME_SL, ret), "cfengine.com");
    assert_int_equal(ret2, (size_t) -1);
    assert_string_equal(StrList_At(HOSTNAME_SL, ret3), ".no");
    assert_int_equal(ret4, (size_t) -1);
    assert_int_equal(ret5, (size_t) -1);
    assert_int_equal(ret6, (size_t) -1);
    assert_string_equal(StrList_At(HOSTNAME_SL, ret7), ".allowed.cfengine.com");
    assert_string_equal(StrList_At(HOSTNAME_SL, ret8), "www.cfengine.com");
    assert_int_equal(ret9, (size_t) -1);
    assert_int_equal(ret10, (size_t) -1);
    assert_int_equal(ret11, (size_t) -1);
}
Exemplo n.º 2
0
/* Sort PATH_STRLIST using the common way, and HOSTNAME_STRLIST in the order
 * of reading the strings backwards. */
static void test_StrList_Sort()
{
    StrList_Sort(PATH_SL, string_Compare);
    assert_int_equal(StrList_Len(PATH_SL), PATH_STRINGS_LEN);

    for (size_t i = 0; i < PATH_STRINGS_LEN; i++)
    {
        assert_string_equal(StrList_At(PATH_SL, i),
                            PATH_STRINGS[i]);
    }

    StrList_Sort(HOSTNAME_SL, string_CompareFromEnd);
    assert_int_equal(StrList_Len(HOSTNAME_SL), HOSTNAME_STRINGS_LEN);

    for (size_t i = 0; i < HOSTNAME_STRINGS_LEN; i++)
    {
        assert_string_equal(StrList_At(HOSTNAME_SL, i),
                            HOSTNAME_STRINGS[i]);
    }
}
Exemplo n.º 3
0
/* Only search in PATH_STRLIST because it makes sense to search longest prefix
 * for paths. */
static void test_StrList_SearchLongestPrefix()
{
    /* REMINDER: PATH_STRINGS[] =
       { " ", "  ", "/", "/path", "/path/to/file.name", "blah", "waza" }; */

    size_t ret, ret2, ret3;

    /* These searches all search for  "/path", since length is the same. */
    ret  = StrList_SearchLongestPrefix(PATH_SL, "/path", 0, '/', true);
    ret2 = StrList_SearchLongestPrefix(PATH_SL, "/path/", 5, '/', true);
    ret3 = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name", 5, '/', true);
    assert_string_equal(StrList_At(PATH_SL, ret), "/path");
    assert_string_equal(StrList_At(PATH_SL, ret2), "/path");
    assert_string_equal(StrList_At(PATH_SL, ret3), "/path");

    /* Searching for "/path/" does not bring up "/path", but "/", since
     * directories *must* have a trailing slash. */
    ret = StrList_SearchLongestPrefix(PATH_SL, "/path/", 0, '/', true);
    assert_string_equal(StrList_At(PATH_SL, ret), "/");

    ret = StrList_SearchLongestPrefix(PATH_SL, "/path.json", 0, '/', true);
    assert_string_equal(StrList_At(PATH_SL, ret), "/");


    /* We insert a couple more directories and sort again. */
    StrList_Append(&PATH_SL, "/path/to/file.namewhatever/whatever");
    StrList_Append(&PATH_SL, "/path/to/file.name/whatever/");
    StrList_Append(&PATH_SL, "/path/to/");
    StrList_Sort(PATH_SL, string_Compare);

    ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name",
                                      0, '/', true);
    assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/file.name");

    ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file",
                                      0, '/', true);
    assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/");

    ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name/whatever/blah",
                                      0, '/', true);
    assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/file.name/whatever/");

    ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/",
                                      0, '/', true);
    assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/");
}
Exemplo n.º 4
0
static void test_StrList_SearchForSuffix()
{
    /* REMINDER: HOSTNAME_STRINGS[] =
       {  "*", ".*", ".", "com", "cfengine.com", ".allowed.cfengine.com", "www.cfengine.com", ".no" }; */

    size_t ret, ret2, ret3, ret4, ret5, ret6, ret7, ret8, ret9, ret10, ret11;

    ret  = StrList_SearchForPrefix(HOSTNAME_SL, "cfengine.com", 0, false);
    ret2 = StrList_SearchForPrefix(HOSTNAME_SL, "google.com", 0, false);
    ret3 = StrList_SearchForPrefix(HOSTNAME_SL, "yr.no", 0, false);
    ret4 = StrList_SearchForPrefix(HOSTNAME_SL, "ntua.gr", 0, false);
    ret5 = StrList_SearchForPrefix(HOSTNAME_SL, "disallowed.cfengine.com", 0, false);
    ret6 = StrList_SearchForPrefix(HOSTNAME_SL, "allowed.cfengine.com", 0, false);
    ret7 = StrList_SearchForPrefix(HOSTNAME_SL, "blah.allowed.cfengine.com", 0, false);
    ret8 = StrList_SearchForPrefix(HOSTNAME_SL, "www.cfengine.com", 0, false);
    ret9 = StrList_SearchForPrefix(HOSTNAME_SL, "www1.cfengine.com", 0, false);
    ret10 = StrList_SearchForPrefix(HOSTNAME_SL, "1www.cfengine.com", 0, false);
    ret11 = StrList_SearchForPrefix(HOSTNAME_SL, "no", 0, false);

    /* SearchForPrefix() does not guarantee which one of all the matches it
     * will return if there are many matches. */

    /* E.g. the first search might return "cfengine.com" or "com" entry. */
    LargestIntegralType set[] = {3, 4};
    assert_in_set(ret, set, 2);
    assert_string_equal(StrList_At(HOSTNAME_SL, ret2),  "com");
    assert_string_equal(StrList_At(HOSTNAME_SL, ret3),  ".no");
    assert_int_equal(ret4,  (size_t) -1);
    assert_in_set(ret5, set, 2);
    assert_in_set(ret6, set, 2);
    LargestIntegralType set2[] = {3, 4, 5};
    assert_in_set(ret7, set2, 3);
    assert_in_set(ret8, set, 2);
    assert_in_set(ret9, set, 2);
    LargestIntegralType set3[] = {3, 4, 6};
    assert_in_set(ret10, set3, 3);
    assert_int_equal(ret11, (size_t) -1);
}
Exemplo n.º 5
0
static StrList *init_strlist(char ** strings, size_t strings_len,
                             size_t *insert_order)
{
    StrList *sl = calloc(1, sizeof(*sl));
    /* Insert in random order. */
    for (size_t i = 0; i < strings_len; i++)
    {
        size_t ret = StrList_Append(&sl, strings[ insert_order[i] ]);
        assert_int_equal(ret, i);
        assert_string_equal(StrList_At(sl, i), strings[ insert_order[i] ]);
    }
    assert_int_equal(StrList_Len(sl), strings_len);
    StrList_Finalise(&sl);

    return sl;
}
Exemplo n.º 6
0
void Summarize()
{
    Log(LOG_LEVEL_VERBOSE, " === BEGIN summary of access promises === ");

    size_t i, j;
    for (i = 0; i < paths_acl->len; i++)
    {
        Log(LOG_LEVEL_VERBOSE, "\tPath: %s",
            StrList_At(paths_acl->resource_names, i));
        const struct resource_acl *racl = &paths_acl->acls[i];

        for (j = 0; j < StrList_Len(racl->admit.ips); j++)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tadmit_ips: %s",
                StrList_At(racl->admit.ips, j));
        }
        for (j = 0; j < StrList_Len(racl->admit.hostnames); j++)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tadmit_hostnames: %s",
                StrList_At(racl->admit.hostnames, j));
        }
        for (j = 0; j < StrList_Len(racl->admit.keys); j++)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tadmit_keys: %s",
                StrList_At(racl->admit.keys, j));
        }
        for (j = 0; j < StrList_Len(racl->deny.ips); j++)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tdeny_ips: %s",
                StrList_At(racl->deny.ips, j));
        }
        for (j = 0; j < StrList_Len(racl->deny.hostnames); j++)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tdeny_hostnames: %s",
                StrList_At(racl->deny.hostnames, j));
        }
        for (j = 0; j < StrList_Len(racl->deny.keys); j++)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tdeny_keys: %s",
                StrList_At(racl->deny.keys, j));
        }
    }

    Auth *ptr;
    Item *ip, *ipr;

    Log(LOG_LEVEL_VERBOSE, "Granted access to paths for classic protocol:");

    for (ptr = SV.admit; ptr != NULL; ptr = ptr->next)
    {
        /* Don't report empty entries. */
        if (ptr->maproot != NULL || ptr->accesslist != NULL)
        {
            Log(LOG_LEVEL_VERBOSE, "\tPath: %s", ptr->path);
        }

        for (ipr = ptr->maproot; ipr != NULL; ipr = ipr->next)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tmaproot user: %s,", ipr->name);
        }

        for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tadmit: %s", ip->name);
        }
    }

    Log(LOG_LEVEL_VERBOSE, "Denied access to paths for classic protocol:");

    for (ptr = SV.deny; ptr != NULL; ptr = ptr->next)
    {
        /* Don't report empty entries. */
        if (ptr->accesslist != NULL)
        {
            Log(LOG_LEVEL_VERBOSE, "\tPath: %s", ptr->path);
        }

        for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
        {
            Log(LOG_LEVEL_VERBOSE, "\t\tdeny: %s", ip->name);
        }
    }

    Log(LOG_LEVEL_VERBOSE, "Granted access to literal/variable/query data :");

    for (ptr = SV.varadmit; ptr != NULL; ptr = ptr->next)
    {
        Log(LOG_LEVEL_VERBOSE, "Object: %s", ptr->path);

        for (ipr = ptr->maproot; ipr != NULL; ipr = ipr->next)
        {
            Log(LOG_LEVEL_VERBOSE, "%s,", ipr->name);
        }
        for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
        {
            Log(LOG_LEVEL_VERBOSE, "Admit '%s' root=", ip->name);
        }
    }

    Log(LOG_LEVEL_VERBOSE, "Denied access to literal/variable/query data:");

    for (ptr = SV.vardeny; ptr != NULL; ptr = ptr->next)
    {
        Log(LOG_LEVEL_VERBOSE, "Object %s", ptr->path);

        for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
        {
            Log(LOG_LEVEL_VERBOSE, "Deny '%s'", ip->name);
        }
    }

    Log(LOG_LEVEL_VERBOSE, "Host IPs allowed connection access:");

    for (ip = SV.nonattackerlist; ip != NULL; ip = ip->next)
    {
        Log(LOG_LEVEL_VERBOSE, "IP '%s'", ip->name);
    }

    Log(LOG_LEVEL_VERBOSE, "Host IPs denied connection access:");

    for (ip = SV.attackerlist; ip != NULL; ip = ip->next)
    {
        Log(LOG_LEVEL_VERBOSE, "IP '%s'", ip->name);
    }

    Log(LOG_LEVEL_VERBOSE, "Host IPs allowed multiple connection access:");

    for (ip = SV.multiconnlist; ip != NULL; ip = ip->next)
    {
        Log(LOG_LEVEL_VERBOSE, "IP '%s'", ip->name);
    }

    Log(LOG_LEVEL_VERBOSE, "Host IPs whose keys we shall establish trust to:");

    for (ip = SV.trustkeylist; ip != NULL; ip = ip->next)
    {
        Log(LOG_LEVEL_VERBOSE, "IP '%s'", ip->name);
    }

    Log(LOG_LEVEL_VERBOSE, "Host IPs allowed legacy connections:");

    for (ip = SV.allowlegacyconnects; ip != NULL; ip = ip->next)
    {
        Log(LOG_LEVEL_VERBOSE, "IP '%s'", ip->name);
    }

    Log(LOG_LEVEL_VERBOSE, "Users from whom we accept connections:");

    for (ip = SV.allowuserlist; ip != NULL; ip = ip->next)
    {
        Log(LOG_LEVEL_VERBOSE, "USERS '%s'", ip->name);
    }

    Log(LOG_LEVEL_VERBOSE, " === END summary of access promises === ");
}
Exemplo n.º 7
0
/**
 * Run this function on every resource (file, class, var etc) access to
 * grant/deny rights. Currently it checks if:
 *  1. #ipaddr matches the subnet expression in {admit,deny}_ips
 *  2. #hostname matches the subdomain expression in {admit,deny}_hostnames
 *  3. #key is found exactly as it is in {admit,deny}_keys
 *
 * @return Default is false, i.e. deny. If a match is found in #acl->admit.*
 *         then return true, unless a match is also found in #acl->deny.* in
 *         which case return false.
 *
 * @TODO preprocess our global ACL the moment a client connects, and store in
 *       ServerConnectionState a list of objects that he can access. That way
 *       only his relevant resources will be stored in e.g. {admit,deny}_paths
 *       lists, and running through these two lists on every file request will
 *       be much faster.
 */
bool access_CheckResource(const struct resource_acl *acl,
                          const char *ipaddr, const char *hostname,
                          const char *key)
{
    /* Only hostname may be NULL in case of resolution failure. */
    assert(ipaddr != NULL);
    assert(key != NULL);

    bool access = false;                                 /* DENY by default */

    /* First we check for admission, secondly for denial, so that denial takes
     * precedence. */

    if (acl->admit.ips)
    {
        /* Still using legacy code here, doing linear search over all IPs in
         * textual representation... too CPU intensive! TODO store the ACL as
         * one list of struct sockaddr_storage, together with CIDR notation
         * subnet length.
         */

        const char *rule = NULL;
        for (int i = 0; i < StrList_Len(acl->admit.ips); i++)
        {
            if (FuzzySetMatch(StrList_At(acl->admit.ips, i), ipaddr) == 0 ||
                /* Legacy regex matching, TODO DEPRECATE */
                StringMatchFull(StrList_At(acl->admit.ips, i), ipaddr))
            {
                rule = StrList_At(acl->admit.ips, i);
                break;
            }
        }

        if (rule != NULL)
        {
            Log(LOG_LEVEL_DEBUG,
                "Admit IP due to rule: %s",
                rule);
            access = true;
        }
    }
    if (!access && acl->admit.hostnames != NULL &&
        hostname != NULL && *hostname != '\0')
    {
        size_t pos = StrList_SearchLongestPrefix(acl->admit.hostnames,
                                                 hostname, 0,
                                                 '.', false);

        /* === Legacy regex matching, slow, TODO DEPRECATE === */
        if (pos == (size_t) -1)
        {
            for (int i = 0; i < StrList_Len(acl->admit.hostnames); i++)
            {
                if (StringMatchFull(StrList_At(acl->admit.hostnames, i),
                                    hostname))
                {
                    pos = i;
                    break;
                }
            }
        }
        /* =================================================== */

        if (pos != (size_t) -1)
        {
            Log(LOG_LEVEL_DEBUG,
                "Admit hostname due to rule: %s",
                StrList_At(acl->admit.hostnames, pos));
            access = true;
        }
    }
    if (!access && acl->admit.keys != NULL)
    {
        size_t pos;
        bool ret = StrList_BinarySearch(acl->admit.keys, key, &pos);
        if (ret)
        {
            Log(LOG_LEVEL_DEBUG,
                "Admit key due to rule: %s",
                StrList_At(acl->admit.keys, pos));
            access = true;
        }
    }


    /* If access has been granted, we might need to deny it based on ACL. */

    if (access && acl->deny.ips != NULL)
    {
        const char *rule = NULL;
        for (int i = 0; i < StrList_Len(acl->deny.ips); i++)
        {
            if (FuzzySetMatch(StrList_At(acl->deny.ips, i), ipaddr) == 0 ||
                /* Legacy regex matching, TODO DEPRECATE */
                StringMatchFull(StrList_At(acl->deny.ips, i), ipaddr))
            {
                rule = StrList_At(acl->deny.ips, i);
                break;
            }
        }

        if (rule != NULL)
        {
            Log(LOG_LEVEL_DEBUG,
                "Deny IP due to rule: %s",
                rule);
            access = false;
        }
    }
    if (access && acl->deny.hostnames != NULL &&
        hostname != NULL && *hostname != '\0')
    {
        size_t pos = StrList_SearchLongestPrefix(acl->deny.hostnames,
                                                 hostname, 0,
                                                 '.', false);

        /* === Legacy regex matching, slow, TODO DEPRECATE === */
        if (pos == (size_t) -1)
        {
            for (int i = 0; i < StrList_Len(acl->deny.hostnames); i++)
            {
                if (StringMatchFull(StrList_At(acl->deny.hostnames, i),
                                    hostname))
                {
                    pos = i;
                    break;
                }
            }
        }
        /* =================================================== */

        if (pos != (size_t) -1)
        {
            Log(LOG_LEVEL_DEBUG,
                "Deny hostname due to rule: %s",
                StrList_At(acl->deny.hostnames, pos));
            access = false;
        }
    }
    if (access && acl->deny.keys != NULL)
    {
        size_t pos;
        bool ret = StrList_BinarySearch(acl->deny.keys, key, &pos);
        if (ret)
        {
            Log(LOG_LEVEL_DEBUG,
                "Deny key due to rule: %s",
                StrList_At(acl->deny.keys, pos));
            access = false;
        }
    }

    return access;
}
Exemplo n.º 8
0
/**
 * Run this function on every resource (file, class, var etc) access to
 * grant/deny rights. Currently it checks if:
 *  1. #ipaddr matches the subnet expression in {admit,deny}_ips
 *  2. #hostname matches the subdomain expression in {admit,deny}_hostnames
 *  3. #key is found exactly as it is in {admit,deny}_keys
 *
 * @return Default is false, i.e. deny. If a match is found in #acl->admit.*
 *         then return true, unless a match is also found in #acl->deny.* in
 *         which case return false.
 *
 * @TODO preprocess our global ACL the moment a client connects, and store in
 *       ServerConnectionState a list of objects that he can access. That way
 *       only his relevant resources will be stored in e.g. {admit,deny}_paths
 *       lists, and running through these two lists on every file request will
 *       be much faster.
 */
bool access_CheckResource(const struct resource_acl *acl,
                          const char *ipaddr, const char *hostname,
                          const char *key)
{
    /* Only hostname may be NULL in case of resolution failure. */
    assert(ipaddr != NULL);
    assert(key != NULL);

    size_t pos = (size_t) -1;
    bool access = false;                                 /* DENY by default */

    /* First we check for admission, secondly for denial, so that denial takes
     * precedence. */

    const char *rule;
    if (acl->admit.ips)
    {
        /* Still using legacy code here, doing linear search over all IPs in
         * textual representation... too CPU intensive! TODO store the ACL as
         * one list of struct sockaddr_storage, together with CIDR notation
         * subnet length.
         */

        bool found_rule = false;
        for (int i = 0; i < StrList_Len(acl->admit.ips); i++)
        {
            if (FuzzySetMatch(StrList_At(acl->admit.ips, i),
                              MapAddress(ipaddr))
                == 0 ||
                /* Legacy regex matching, TODO DEPRECATE */
                StringMatchFull(StrList_At(acl->admit.ips, i),
                                MapAddress(ipaddr)))
            {
                found_rule = true;
                rule = StrList_At(acl->admit.ips, i);
                break;
            }
        }

        if (found_rule)
        {
            Log(LOG_LEVEL_DEBUG, "access_Check: admit IP: %s", rule);
            access = true;
        }
    }
    if (!access && acl->admit.keys != NULL)
    {
        bool ret = StrList_BinarySearch(acl->admit.keys, key, &pos);
        if (ret)
        {
            rule = acl->admit.keys->list[pos]->str;
            Log(LOG_LEVEL_DEBUG, "access_Check: admit key: %s", rule);
            access = true;
        }
    }
    if (!access && acl->admit.hostnames != NULL && hostname != NULL)
    {
        size_t hostname_len = strlen(hostname);
        size_t pos = StrList_SearchForPrefix(acl->admit.hostnames,
                                             hostname, hostname_len,
                                             false);

        /* === Legacy regex matching, slow, TODO DEPRECATE === */
        bool regex_match = false;
        if (pos == (size_t) -1)
        {
            for (int i = 0; i < StrList_Len(acl->admit.hostnames); i++)
            {
                if (StringMatchFull(StrList_At(acl->admit.hostnames, i),
                                    hostname))
                {
                    pos = i;
                    break;
                }
            }
        }
        /* ===   === */

        if (pos != (size_t) -1)
        {
            rule            = acl->admit.hostnames->list[pos]->str;
            size_t rule_len = acl->admit.hostnames->list[pos]->len;
            /* The rule in the access list has to be an exact match, or be a
             * subdomain match (i.e. the rule begins with '.') or a regex. */
            if (rule_len == hostname_len || rule[0] == '.' || regex_match)
            {
                Log(LOG_LEVEL_DEBUG, "access_Check: admit hostname: %s", rule);
                access = true;
            }
        }
    }

    /* If access has been granted, we might need to deny it based on ACL. */

    if (access && acl->deny.ips != NULL)
    {
        bool found_rule = false;
        for (int i = 0; i < StrList_Len(acl->deny.ips); i++)
        {
            if (FuzzySetMatch(StrList_At(acl->deny.ips, i),
                              MapAddress(ipaddr))
                == 0 ||
                /* Legacy regex matching, TODO DEPRECATE */
                StringMatchFull(StrList_At(acl->deny.ips, i),
                                MapAddress(ipaddr)))
            {
                found_rule = true;
                rule = acl->deny.ips->list[i]->str;
                break;
            }
        }

        if (found_rule)
        {
            Log(LOG_LEVEL_DEBUG, "access_Check: deny IP: %s", rule);
            access = false;
        }
    }
    if (access && acl->deny.keys != NULL)
    {
        bool ret = StrList_BinarySearch(acl->deny.keys, key, &pos);
        if (ret)
        {
            rule = StrList_At(acl->deny.keys, pos);
            Log(LOG_LEVEL_DEBUG, "access_Check: deny key: %s", rule);
            access = false;
        }
    }
    if (access && acl->deny.hostnames != NULL && hostname != NULL)
    {
        size_t hostname_len = strlen(hostname);
        size_t pos = StrList_SearchForPrefix(acl->deny.hostnames,
                                             hostname, hostname_len,
                                             false);
        /* === Legacy regex matching, slow, TODO DEPRECATE === */
        bool regex_match = false;
        if (pos == (size_t) -1)
        {
            for (int i = 0; i < StrList_Len(acl->deny.hostnames); i++)
            {
                if (StringMatchFull(StrList_At(acl->deny.hostnames, i),
                                    hostname))
                {
                    pos = i;
                    break;
                }
            }
        }
        /* ===   === */

        if (pos != (size_t) -1)
        {
            rule            = acl->deny.hostnames->list[pos]->str;
            size_t rule_len = acl->deny.hostnames->list[pos]->len;
            /* The rule in the access list has to be an exact match, or be a
             * subdomain match (i.e. the rule begins with '.') or a regex. */
            if (rule_len == hostname_len || rule[0] == '.' || regex_match)
            {
                Log(LOG_LEVEL_DEBUG, "access_Check: deny hostname: %s", rule);
                access = false;
            }
        }
    }

    return access;
}