/** * Search the list of resources for the handle. If found return the index of * the resource ACL that corresponds to that handle, else add the handle with * empty ACL, reallocating if necessary. The new handle is inserted in the * proper position to keep the acl->resource_names list sorted. * * @note acl->resource_names list should already be sorted, no problem if all * inserts are done with this function. * * @return the index of the resource_acl corresponding to handle. -1 means * reallocation failed, but existing values are still valid. */ size_t acl_SortedInsert(struct acl **a, const char *handle) { assert(handle != NULL); struct acl *acl = *a; /* for clarity */ size_t position = (size_t) -1; bool found = StrList_BinarySearch(acl->resource_names, handle, &position); if (found) { /* Found it, return existing entry. */ assert(position < acl->len); return position; } /* handle is not in acl, we must insert at the position returned. */ assert(position <= acl->len); /* 1. Check if reallocation is needed. */ if (acl->len == acl->alloc_len) { size_t new_alloc_len = acl->alloc_len * 2; if (new_alloc_len == 0) { new_alloc_len = 1; } struct acl *p = realloc(acl, sizeof(*p) + sizeof(*p->acls) * new_alloc_len); if (p == NULL) { return (size_t) -1; } acl = p; acl->alloc_len = new_alloc_len; *a = acl; /* Change the caller's variable */ } /* 2. We now have enough space, so insert the resource at the proper index. */ size_t ret = StrList_Insert(&acl->resource_names, handle, position); if (ret == (size_t) -1) { /* realloc() failed but the data structure is still valid. */ return (size_t) -1; } /* 3. Make room. */ memmove(&acl->acls[position + 1], &acl->acls[position], (acl->len - position) * sizeof(acl->acls[position])); acl->len++; /* 4. Initialise all ACLs for the resource as empty. */ acl->acls[position] = (struct resource_acl) { {0}, {0} }; /* NULL acls <=> empty */
bool acl_CheckExact(const struct acl *acl, const char *req_string, const char *ipaddr, const char *hostname, const char *key) { bool access = false; size_t pos = -1; bool found = StrList_BinarySearch(acl->resource_names, req_string, &pos); if (found) { const struct resource_acl *racl = &acl->acls[pos]; bool ret = access_CheckResource(racl, ipaddr, hostname, key); if (ret == true) /* entry found that grants access */ { access = true; } } return access; }
/* Only search in PATH_STRLIST which is sorted in the common way. */ static void test_StrList_BinarySearch() { size_t pos; bool found; /* Search for existing strings. */ for (size_t i = 0; i < PATH_STRINGS_LEN; i++) { found = StrList_BinarySearch(PATH_SL, PATH_STRINGS[i], &pos); assert_int_equal(found, true); assert_int_equal(pos, i); } /* Search for inexistent entries, check that the returned position is the * one they should be inserted into. */ found = StrList_BinarySearch(PATH_SL, "", &pos); assert_int_equal(found, false); assert_int_equal(pos, 0); /* empty string should always come first */ found = StrList_BinarySearch(PATH_SL, " ", &pos); assert_int_equal(found, false); assert_int_equal(pos, 2); found = StrList_BinarySearch(PATH_SL, "zzz", &pos); assert_int_equal(found, false); assert_int_equal(pos, PATH_STRINGS_LEN); found = StrList_BinarySearch(PATH_SL, "/path/", &pos); assert_int_equal(found, false); assert_int_equal(pos, 4); found = StrList_BinarySearch(PATH_SL, "/path/to", &pos); assert_int_equal(found, false); assert_int_equal(pos, 4); }
/** * 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; }