/* 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); }
/* 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]); } }
/* 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/"); }
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); }
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; }
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 === "); }
/** * 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; }
/** * 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; }