diff options
author | Robert James Kaes <rjkaes@users.sourceforge.net> | 2004-08-11 20:09:20 +0000 |
---|---|---|
committer | Robert James Kaes <rjkaes@users.sourceforge.net> | 2004-08-11 20:09:20 +0000 |
commit | 924da17c1771f95509d5e1c64286fa8f6e4ba76f (patch) | |
tree | 7f2394c43d27d019df2887309767f7e7d4e52b65 /src | |
parent | fd4b67bbb4982683bfd6c137696b9fd4c37122cd (diff) | |
download | tinyproxy-924da17c1771f95509d5e1c64286fa8f6e4ba76f.tar.gz tinyproxy-924da17c1771f95509d5e1c64286fa8f6e4ba76f.zip |
Completely rewrote the ACL functionality. The new system is intended
to handle IPv6 style addresses along with the existing IPv4 and string
addresses. In addition, the hand-rolled "list" code has been replaced
with a vector (code reuse.) Also, the code should be a little easier
to understand (relatively speaking.)
I do need to add some kind of testing framework (in general) to check
that the new code does work with all the formats that will be thrown
at it.
Diffstat (limited to 'src')
-rw-r--r-- | src/acl.c | 342 |
1 files changed, 184 insertions, 158 deletions
@@ -1,4 +1,4 @@ -/* $Id: acl.c,v 1.18 2004-02-13 21:27:42 rjkaes Exp $ +/* $Id: acl.c,v 1.19 2004-08-11 20:09:20 rjkaes Exp $ * * This system handles Access Control for use of this daemon. A list of * domains, or IP addresses (including IP blocks) are stored in a list @@ -22,31 +22,36 @@ #include "acl.h" #include "heap.h" #include "log.h" +#include "network.h" #include "sock.h" +#include "vector.h" + +/* Define how long an IPv6 address is in bytes (128 bits, 16 bytes) */ +#define IPV6_LEN 16 enum acl_type { ACL_STRING, ACL_NUMERIC }; +/* + * Hold the information about a particular access control. We store + * whether it's an ALLOW or DENY entry, and also whether it's a string + * entry (like a domain name) or an IP entry. + */ struct acl_s { - acl_access_t acl_access; + acl_access_t access; enum acl_type type; - char *location; - int netmask; - struct acl_s *next; + union { + char* addr; + struct { + unsigned char addr[IPV6_LEN]; + unsigned char mask[IPV6_LEN]; + } ip; + }; }; -static struct acl_s *access_list = NULL; - /* - * Take a netmask number (between 0 and 32) and returns a network ordered - * value for comparison. + * All the access lists are stored in a vector. */ -static in_addr_t -make_netmask(int netmask_num) -{ - assert(netmask_num >= 0 && netmask_num <= 32); - - return htonl(~((1 << (32 - netmask_num)) - 1)); -} +static vector_t access_list = NULL; /* * Inserts a new access control into the list. The function will figure out @@ -60,80 +65,80 @@ make_netmask(int netmask_num) int insert_acl(char *location, acl_access_t access_type) { - size_t i; - struct acl_s **rev_acl_ptr, *acl_ptr, *new_acl_ptr; - char *nptr; + struct acl_s acl; + int i, ret, mask; + char *p, ip_dst[IPV6_LEN]; assert(location != NULL); - /* - * First check to see if the location is a string or numeric. - */ - for (i = 0; location[i] != '\0'; i++) { - /* - * Numeric strings can not contain letters, so test on it. - */ - if (isalpha((unsigned char) location[i])) { - break; - } - } - - /* - * Add a new ACL to the list. - */ - rev_acl_ptr = &access_list; - acl_ptr = access_list; - while (acl_ptr) { - rev_acl_ptr = &acl_ptr->next; - acl_ptr = acl_ptr->next; - } - new_acl_ptr = safemalloc(sizeof(struct acl_s)); - if (!new_acl_ptr) { - return -1; - } - - new_acl_ptr->acl_access = access_type; - - if (location[i] == '\0') { - DEBUG2("ACL \"%s\" is a number.", location); - - /* - * We did not break early, so this a numeric location. - * Check for a netmask. - */ - new_acl_ptr->type = ACL_NUMERIC; - nptr = strchr(location, '/'); - if (nptr) { - *nptr++ = '\0'; - - new_acl_ptr->netmask = strtol(nptr, NULL, 10); - if (new_acl_ptr->netmask < 0 - || new_acl_ptr->netmask > 32) { - safefree(new_acl_ptr); - return -1; - } - } else { - new_acl_ptr->netmask = 32; - } - } else { - DEBUG2("ACL \"%s\" is a string.", location); - - new_acl_ptr->type = ACL_STRING; - new_acl_ptr->netmask = 32; - } - - new_acl_ptr->location = safestrdup(location); - if (!new_acl_ptr->location) { - safefree(new_acl_ptr); - return -1; - } - - *rev_acl_ptr = new_acl_ptr; - new_acl_ptr->next = acl_ptr; - - return 0; + /* + * If the access list has not been set up, create it. + */ + if (!access_list) { + access_list = vector_create(); + if (!access_list) { + log_message(LOG_ERR, + "Unable to allocate memory for access list"); + return -1; + } + } + + /* + * Start populating the access control structure. + */ + memset(&acl, 0, sizeof(struct acl_s)); + acl.access = access_type; + + /* + * Check for a valid IP address (the simplest case) first. + */ + if (full_inet_pton(location, ip_dst) > 0) { + acl.type = ACL_NUMERIC; + memcpy(acl.ip.addr, ip_dst, IPV6_LEN); + memset(acl.ip.mask, 0xff, IPV6_LEN); + } else { + /* + * At this point we're either a hostname or an + * IP address with a slash. + */ + p = strchr(location, '/'); + if (p != NULL) { + /* + * We have a slash, so it's intended to be an + * IP address with mask + */ + *p = '\0'; + if (full_inet_pton(location, ip_dst) <= 0) + return -1; + + acl.type = ACL_NUMERIC; + memcpy(acl.ip.addr, ip_dst, IPV6_LEN); + + mask = strtol(p + 1, NULL, 10); + for (i = 0; i != IPV6_LEN; ++i) { + if (mask >= ((i + 1) * 8)) + acl.ip.mask[i] = 0xff; + else + acl.ip.mask[i] = 0xff << (8 - (mask - i * 8)); + } + } else { + /* In all likelihood a string */ + acl.type = ACL_STRING; + acl.addr = safestrdup(location); + if (!acl.addr) + return -1; + } + } + + /* + * Add the entry and then clean up. + */ + ret = vector_append(access_list, &acl, sizeof(struct acl_s)); + safefree(acl.addr); + return ret; } + /* * This function is called whenever a "string" access control is found in * the ACL. From here we do both a text based string comparison, along with @@ -143,48 +148,56 @@ insert_acl(char *location, acl_access_t access_type) * 1 if host is allowed * -1 if no tests match, so skip */ -static inline int -acl_string_processing(struct acl_s* aclptr, +static int +acl_string_processing(struct acl_s* acl, const char* ip_address, const char* string_address) { - int i; - struct hostent* result; + int match; + struct addrinfo hints, *res, *ressave; size_t test_length, match_length; + char ipbuf[512]; + + assert(acl && acl->type == ACL_STRING); + assert(ip_address && strlen(ip_address) > 0); + assert(string_address && strlen(string_address) > 0); /* * If the first character of the ACL string is a period, we need to * do a string based test only; otherwise, we can do a reverse * lookup test as well. */ - if (aclptr->location[0] != '.') { - /* It is not a partial domain, so do a reverse lookup. */ - result = gethostbyname(aclptr->location); - if (!result) + if (acl->addr[0] != '.') { + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(acl->addr, NULL, &hints, &res) != 0) goto STRING_TEST; - - for (i = 0; result->h_addr_list[i]; ++i) { - if (strcmp(ip_address, - inet_ntoa(*((struct in_addr*)result->h_addr_list[i]))) == 0) { - /* We have a match */ - if (aclptr->acl_access == ACL_DENY) { - return 0; - } else { - DEBUG2("Matched using reverse domain lookup: %s", ip_address); - return 1; - } + + ressave = res; + + match = FALSE; + do { + get_ip_string(res->ai_addr, ipbuf, sizeof(ipbuf)); + if (strcmp(ip_address, ipbuf) == 0) { + match = TRUE; + break; } - } + } while ((res = res->ai_next) != NULL); - /* - * If we got this far, the reverse didn't match, so drop down - * to a standard string test. - */ + freeaddrinfo(ressave); + + if (match) { + if (acl->access == ACL_DENY) + return 0; + else + return 1; + } } STRING_TEST: test_length = strlen(string_address); - match_length = strlen(aclptr->location); + match_length = strlen(acl->addr); /* * If the string length is shorter than AC string, return a -1 so @@ -193,8 +206,8 @@ STRING_TEST: if (test_length < match_length) return -1; - if (strcasecmp(string_address + (test_length - match_length), aclptr->location) == 0) { - if (aclptr->acl_access == ACL_DENY) + if (strcasecmp(string_address + (test_length - match_length), acl->addr) == 0) { + if (acl->access == ACL_DENY) return 0; else return 1; @@ -205,6 +218,39 @@ STRING_TEST: } /* + * Compare the supplied numeric IP address with the supplied ACL structure. + * + * Return: + * 1 IP address is allowed + * 0 IP address is denied + * -1 neither allowed nor denied. + */ +static int +check_numeric_acl(const struct acl_s* acl, const char* ip) +{ + uint8_t addr[IPV6_LEN], x, y; + int i; + + assert(acl && acl->type == ACL_NUMERIC); + assert(ip && strlen(ip) > 0); + + if (full_inet_pton(ip, &addr) <= 0) return -1; + + for (i = 0; i != IPV6_LEN; ++i) { + x = addr[i] & acl->ip.mask[i]; + y = acl->ip.addr[i] & acl->ip.mask[i]; + + /* If x and y don't match, the IP addresses don't match */ + if (x != y) + return 0; + } + + /* The addresses match, return the permission */ + return (acl->access == ACL_ALLOW); +} + + +/* * Checks whether file descriptor is allowed. * * Returns: @@ -212,68 +258,48 @@ STRING_TEST: * 0 if denied */ int -check_acl(int fd, const char* ip_address, const char* string_address) +check_acl(int fd, const char* ip, const char* host) { - struct acl_s* aclptr; - int ret; + struct acl_s* acl; + int perm; + int i; assert(fd >= 0); - assert(ip_address != NULL); - assert(string_address != NULL); + assert(ip != NULL); + assert(host != NULL); /* * If there is no access list allow everything. */ - aclptr = access_list; - if (!aclptr) - return 1; - - while (aclptr) { - if (aclptr->type == ACL_STRING) { - ret = acl_string_processing(aclptr, - ip_address, - string_address); - if (ret == 0) - goto UNAUTHORIZED; - else if (ret == 1) - return 1; - - aclptr = aclptr->next; - continue; - } else { - struct in_addr test_addr, match_addr; - in_addr_t netmask_addr; + if (!access_list) return 1; - if (ip_address[0] == 0) { - aclptr = aclptr->next; - continue; - } - - inet_aton(ip_address, &test_addr); - inet_aton(aclptr->location, &match_addr); - - netmask_addr = make_netmask(aclptr->netmask); + for (i = 0; i != vector_length(access_list); ++i) { + acl = vector_getentry(access_list, i, NULL); + switch (acl->type) { + case ACL_STRING: + perm = acl_string_processing(acl, ip, host); + break; - if ((test_addr.s_addr & netmask_addr) == - (match_addr.s_addr & netmask_addr)) { - if (aclptr->acl_access == ACL_DENY) - goto UNAUTHORIZED; - else - return 1; - } + case ACL_NUMERIC: + if (ip[0] == '\0') continue; + perm = check_numeric_acl(acl, ip); + break; } - /* - * Dropped through... go on to the next one. + /* + * Check the return value too see if the IP address is + * allowed or denied. */ - aclptr = aclptr->next; - } + if (perm == 0) + break; + else if (perm == 1) + return perm; + } /* * Deny all connections by default. */ -UNAUTHORIZED: log_message(LOG_NOTICE, "Unauthorized connection from \"%s\" [%s].", - string_address, ip_address); + host, ip); return 0; } |