diff options
author | rofl0r <rofl0r@users.noreply.github.com> | 2021-02-13 15:11:42 +0000 |
---|---|---|
committer | rofl0r <rofl0r@users.noreply.github.com> | 2021-04-16 14:46:02 +0100 |
commit | 979c737f9b811c5441ae0573a90b72dc1e44e142 (patch) | |
tree | 44b29c27cffc2a292061ab764449b12af35cd696 | |
parent | 2529597ea0201c147b4bb5fb713040b117368b50 (diff) |
make upstream site-spec ipv6 compatible, refactor acl code
the acl.c code parsing a site-spec has been factored out into a
new TU: hostspec. it was superior to the parsing code in
upstream.c in that it properly deals with both ipv4 and ipv6.
both upstream and acl now use the new code for parsing, and upstream
also for checking for a match.
acl.c still uses the old matching code as it has a lot of special case
code for specifications containing a hostname, and in case such
a spec is encountered, tries to do reverse name lookup to see if
a numeric ip matches that spec.
removing that code could break existing usecases, however since
that was never implemented for upstream nobody will miss it there.
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/acl.c | 147 | ||||
-rw-r--r-- | src/hostspec.c | 166 | ||||
-rw-r--r-- | src/hostspec.h | 26 | ||||
-rw-r--r-- | src/upstream.c | 80 | ||||
-rw-r--r-- | src/upstream.h | 6 |
6 files changed, 237 insertions, 189 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 6d806e0..d132a75 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,6 +24,7 @@ AM_CPPFLAGS = \ -DLOCALSTATEDIR=\"${localstatedir}\" tinyproxy_SOURCES = \ + hostspec.c hostspec.h \ acl.c acl.h \ anonymous.c anonymous.h \ buffer.c buffer.h \ @@ -29,17 +29,10 @@ #include "network.h" #include "sock.h" #include "sblist.h" +#include "hostspec.h" #include <limits.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 @@ -47,66 +40,9 @@ enum acl_type { */ struct acl_s { acl_access_t access; - enum acl_type type; - union { - char *string; - struct { - unsigned char network[IPV6_LEN]; - unsigned char mask[IPV6_LEN]; - } ip; - } address; + struct hostspec h; }; -/* - * Fills in the netmask array given a numeric value. - * - * Returns: - * 0 on success - * -1 on failure (invalid mask value) - * - */ -static int -fill_netmask_array (char *bitmask_string, int v6, - unsigned char array[], size_t len) -{ - unsigned int i; - unsigned long int mask; - char *endptr; - - errno = 0; /* to distinguish success/failure after call */ - mask = strtoul (bitmask_string, &endptr, 10); - - /* check for various conversion errors */ - if ((errno == ERANGE && mask == ULONG_MAX) - || (errno != 0 && mask == 0) || (endptr == bitmask_string)) - return -1; - - if (v6 == 0) { - /* The mask comparison is done as an IPv6 address, so - * convert to a longer mask in the case of IPv4 - * addresses. */ - mask += 12 * 8; - } - - /* check valid range for a bit mask */ - if (mask > (8 * len)) - return -1; - - /* we have a valid range to fill in the array */ - for (i = 0; i != len; ++i) { - if (mask >= 8) { - array[i] = 0xff; - mask -= 8; - } else if (mask > 0) { - array[i] = (unsigned char) (0xff << (8 - mask)); - mask = 0; - } else { - array[i] = 0; - } - } - - return 0; -} /** * If the access list has not been set up, create it. @@ -138,7 +74,6 @@ int insert_acl (char *location, acl_access_t access_type, acl_list_t *access_list) { struct acl_s acl; - char *mask, ip_dst[IPV6_LEN]; assert (location != NULL); @@ -150,55 +85,11 @@ insert_acl (char *location, acl_access_t access_type, acl_list_t *access_list) */ memset (&acl, 0, sizeof (struct acl_s)); acl.access = access_type; - - if ((mask = strrchr(location, '/'))) - *(mask++) = 0; - - /* - * Check for a valid IP address (the simplest case) first. - */ - if (full_inet_pton (location, ip_dst) > 0) { - acl.type = ACL_NUMERIC; - memcpy (acl.address.ip.network, ip_dst, IPV6_LEN); - if(!mask) memset (acl.address.ip.mask, 0xff, IPV6_LEN); - else { - char dst[sizeof(struct in6_addr)]; - int v6, i; - /* Check if the IP address before the netmask is - * an IPv6 address */ - if (inet_pton(AF_INET6, location, dst) > 0) - v6 = 1; - else - v6 = 0; - - if (fill_netmask_array - (mask, v6, &(acl.address.ip.mask[0]), IPV6_LEN) - < 0) - goto err; - - for (i = 0; i < IPV6_LEN; i++) - acl.address.ip.network[i] = ip_dst[i] & - acl.address.ip.mask[i]; - } - } else { - /* either bogus IP or hostname */ - /* bogus ipv6 ? */ - if (mask || strchr (location, ':')) - goto err; - - /* In all likelihood a string */ - acl.type = ACL_STRING; - acl.address.string = safestrdup (location); - if (!acl.address.string) - goto err; - } + if(hostspec_parse(location, &acl.h) || acl.h.type == HST_NONE) + return -1; if(!sblist_add(*access_list, &acl)) return -1; return 0; -err:; - /* restore mask for proper error message */ - if(mask) *(--mask) = '/'; - return -1; } /* @@ -219,7 +110,7 @@ acl_string_processing (struct acl_s *acl, const char *ip_address, size_t test_length, match_length; char ipbuf[512]; - assert (acl && acl->type == ACL_STRING); + assert (acl && acl->h.type == HST_STRING); assert (ip_address && strlen (ip_address) > 0); /* @@ -227,11 +118,11 @@ acl_string_processing (struct acl_s *acl, const char *ip_address, * do a string based test only; otherwise, we can do a reverse * lookup test as well. */ - if (acl->address.string[0] != '.') { + if (acl->h.address.string[0] != '.') { memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - if (getaddrinfo (acl->address.string, NULL, &hints, &res) != 0) + if (getaddrinfo (acl->h.address.string, NULL, &hints, &res) != 0) goto STRING_TEST; ressave = res; @@ -265,7 +156,7 @@ STRING_TEST: } test_length = strlen (string_addr); - match_length = strlen (acl->address.string); + match_length = strlen (acl->h.address.string); /* * If the string length is shorter than AC string, return a -1 so @@ -276,7 +167,7 @@ STRING_TEST: if (strcasecmp (string_addr + (test_length - match_length), - acl->address.string) == 0) { + acl->h.address.string) == 0) { if (acl->access == ACL_DENY) return 0; else @@ -300,11 +191,11 @@ static int check_numeric_acl (const struct acl_s *acl, uint8_t addr[IPV6_LEN]) uint8_t x, y; int i; - assert (acl && acl->type == ACL_NUMERIC); + assert (acl && acl->h.type == HST_NUMERIC); for (i = 0; i != IPV6_LEN; ++i) { - x = addr[i] & acl->address.ip.mask[i]; - y = acl->address.ip.network[i]; + x = addr[i] & acl->h.address.ip.mask[i]; + y = acl->h.address.ip.network[i]; /* If x and y don't match, the IP addresses don't match */ if (x != y) @@ -345,12 +236,12 @@ int check_acl (const char *ip, union sockaddr_union *addr, acl_list_t access_lis for (i = 0; i < sblist_getsize (access_list); ++i) { acl = sblist_get (access_list, i); - switch (acl->type) { - case ACL_STRING: + switch (acl->h.type) { + case HST_STRING: perm = acl_string_processing (acl, ip, addr, string_addr); break; - case ACL_NUMERIC: + case HST_NUMERIC: if (ip[0] == '\0') continue; @@ -358,6 +249,10 @@ int check_acl (const char *ip, union sockaddr_union *addr, acl_list_t access_lis ? check_numeric_acl (acl, numeric_addr) : -1; break; + + case HST_NONE: + perm = -1; + break; } /* @@ -394,8 +289,8 @@ void flush_access_list (acl_list_t access_list) */ for (i = 0; i < sblist_getsize (access_list); ++i) { acl = sblist_get (access_list, i); - if (acl->type == ACL_STRING) { - safefree (acl->address.string); + if (acl->h.type == HST_STRING) { + safefree (acl->h.address.string); } } diff --git a/src/hostspec.c b/src/hostspec.c new file mode 100644 index 0000000..adbad53 --- /dev/null +++ b/src/hostspec.c @@ -0,0 +1,166 @@ +#include "common.h" +#include "hostspec.h" +#include "heap.h" +#include "network.h" + +/* + * Fills in the netmask array given a numeric value. + * + * Returns: + * 0 on success + * -1 on failure (invalid mask value) + * + */ +static int +fill_netmask_array (char *bitmask_string, int v6, + unsigned char array[], size_t len) +{ + unsigned int i; + unsigned long int mask; + char *endptr; + + errno = 0; /* to distinguish success/failure after call */ + mask = strtoul (bitmask_string, &endptr, 10); + + /* check for various conversion errors */ + if ((errno == ERANGE && mask == ULONG_MAX) + || (errno != 0 && mask == 0) || (endptr == bitmask_string)) + return -1; + + if (v6 == 0) { + /* The mask comparison is done as an IPv6 address, so + * convert to a longer mask in the case of IPv4 + * addresses. */ + mask += 12 * 8; + } + + /* check valid range for a bit mask */ + if (mask > (8 * len)) + return -1; + + /* we have a valid range to fill in the array */ + for (i = 0; i != len; ++i) { + if (mask >= 8) { + array[i] = 0xff; + mask -= 8; + } else if (mask > 0) { + array[i] = (unsigned char) (0xff << (8 - mask)); + mask = 0; + } else { + array[i] = 0; + } + } + + return 0; +} + + +/* parse a location string containing either an ipv4/ipv4 + hostmask tuple + or a dnsname into a struct hostspec. + returns 0 on success, non-0 on error (might be memory allocation, bogus + ip address or mask). +*/ +int hostspec_parse(char *location, struct hostspec *h) { + char *mask, ip_dst[IPV6_LEN]; + + h->type = HST_NONE; + if(!location) return 0; + + memset(h, 0, sizeof(*h)); + if ((mask = strrchr(location, '/'))) + *(mask++) = 0; + + /* + * Check for a valid IP address (the simplest case) first. + */ + if (full_inet_pton (location, ip_dst) > 0) { + h->type = HST_NUMERIC; + memcpy (h->address.ip.network, ip_dst, IPV6_LEN); + if(!mask) memset (h->address.ip.mask, 0xff, IPV6_LEN); + else { + char dst[sizeof(struct in6_addr)]; + int v6, i; + /* Check if the IP address before the netmask is + * an IPv6 address */ + if (inet_pton(AF_INET6, location, dst) > 0) + v6 = 1; + else + v6 = 0; + + if (fill_netmask_array + (mask, v6, &(h->address.ip.mask[0]), IPV6_LEN) + < 0) + goto err; + + for (i = 0; i < IPV6_LEN; i++) + h->address.ip.network[i] = ip_dst[i] & + h->address.ip.mask[i]; + } + } else { + /* either bogus IP or hostname */ + /* bogus ipv6 ? */ + if (mask || strchr (location, ':')) + goto err; + + /* In all likelihood a string */ + h->type = HST_STRING; + h->address.string = safestrdup (location); + if (!h->address.string) + goto err; + } + /* restore mask */ + if(mask) *(--mask) = '/'; + return 0; +err:; + if(mask) *(--mask) = '/'; + return -1; +} + +static int string_match(const char *ip, const char *addrspec) +{ + size_t test_length, match_length; + if(!strcasecmp(ip, addrspec)) return 1; + if(addrspec[0] != '.') return 0; + test_length = strlen (ip); + match_length = strlen (addrspec); + if (test_length < match_length) return 0; + return (strcasecmp + (ip + (test_length - match_length), + addrspec) == 0); +} + +static int numeric_match(const uint8_t addr[], const struct hostspec *h) +{ + uint8_t x, y; + int i; + + for (i = 0; i != IPV6_LEN; ++i) { + x = addr[i] & h->address.ip.mask[i]; + y = h->address.ip.network[i]; + + /* If x and y don't match, the IP addresses don't match */ + if (x != y) + return 0; + } + + return 1; +} + +/* check whether ip matches hostspec. + return 1 on match, 0 on non-match */ +int hostspec_match(const char *ip, const struct hostspec *h) { + int is_numeric_addr; + uint8_t numeric_addr[IPV6_LEN]; + if (ip[0] == '\0') return 0; + is_numeric_addr = (full_inet_pton (ip, &numeric_addr) > 0); + switch (h->type) { + case HST_STRING: + if(is_numeric_addr) return 0; + return string_match (ip, h->address.string); + case HST_NUMERIC: + return numeric_match (numeric_addr, h); + case HST_NONE: + return 0; + } + return 0; +} diff --git a/src/hostspec.h b/src/hostspec.h new file mode 100644 index 0000000..9d1d7bf --- /dev/null +++ b/src/hostspec.h @@ -0,0 +1,26 @@ +#ifndef HOSTSPEC_H +#define HOSTSPEC_H + +#define IPV6_LEN 16 + +enum hostspec_type { + HST_NONE, + HST_STRING, + HST_NUMERIC, +}; + +struct hostspec { + enum hostspec_type type; + union { + char *string; + struct { + unsigned char network[IPV6_LEN]; + unsigned char mask[IPV6_LEN]; + } ip; + } address; +}; + +int hostspec_parse(char *domain, struct hostspec *h); +int hostspec_match(const char *ip, const struct hostspec *h); + +#endif diff --git a/src/upstream.c b/src/upstream.c index c8fee22..52dad80 100644 --- a/src/upstream.c +++ b/src/upstream.c @@ -60,11 +60,10 @@ const char* upstream_build_error_string(enum upstream_build_error ube) { /** * Construct an upstream struct from input data. */ -static struct upstream *upstream_build (const char *host, int port, const char *domain, +static struct upstream *upstream_build (const char *host, int port, char *domain, const char *user, const char *pass, proxy_type type, enum upstream_build_error *ube) { - char *ptr; struct upstream *up; *ube = UBE_SUCCESS; @@ -75,8 +74,8 @@ static struct upstream *upstream_build (const char *host, int port, const char * } up->type = type; - up->host = up->domain = up->ua.user = up->pass = NULL; - up->ip = up->mask = 0; + up->target.type = HST_NONE; + up->host = up->ua.user = up->pass = NULL; if (user) { if (type == PT_HTTP) { char b[BASE64ENC_BYTES((256+2)-1) + 1]; @@ -121,30 +120,10 @@ static struct upstream *upstream_build (const char *host, int port, const char * up->port = port; } - ptr = strchr (domain, '/'); - if (ptr) { - struct in_addr addrstruct; - - *ptr = '\0'; - if (inet_aton (domain, &addrstruct) != 0) { - up->ip = ntohl (addrstruct.s_addr); - *ptr++ = '/'; - - if (strchr (ptr, '.')) { - if (inet_aton (ptr, &addrstruct) != 0) - up->mask = - ntohl (addrstruct.s_addr); - } else { - up->mask = - ~((1 << (32 - atoi (ptr))) - 1); - } - up->ip = up->ip & up->mask; - } else { - *ube = UBE_NETMASK; - goto fail; - } - } else { - up->domain = safestrdup (domain); + if (hostspec_parse(domain, &up->target) + || up->target.type == HST_NONE) { + *ube = UBE_NETMASK; + goto fail; } if (type == PT_NONE) @@ -160,7 +139,8 @@ fail: safefree (up->ua.user); safefree (up->pass); safefree (up->host); - safefree (up->domain); + if(up->target.type == HST_STRING) + safefree (up->target.address.string); safefree (up); return NULL; @@ -170,7 +150,7 @@ fail: * Add an entry to the upstream list */ enum upstream_build_error upstream_add ( - const char *host, int port, const char *domain, + const char *host, int port, char *domain, const char *user, const char *pass, proxy_type type, struct upstream **upstream_list) { @@ -182,11 +162,11 @@ enum upstream_build_error upstream_add ( return ube; } - if (!up->domain && !up->ip) { /* always add default to end */ + if (up->target.type == HST_NONE) { /* always add default to end */ struct upstream *tmp = *upstream_list; while (tmp) { - if (!tmp->domain && !tmp->ip) { + if (tmp->target.type == HST_NONE) { log_message (LOG_WARNING, "Duplicate default upstream"); goto upstream_cleanup; @@ -209,7 +189,8 @@ enum upstream_build_error upstream_add ( upstream_cleanup: safefree (up->host); - safefree (up->domain); + if(up->target.type == HST_STRING) + safefree (up->target.address.string); safefree (up); return ube; @@ -220,34 +201,12 @@ upstream_cleanup: */ struct upstream *upstream_get (char *host, struct upstream *up) { - in_addr_t my_ip = INADDR_NONE; - while (up) { - if (up->domain) { - if (strcasecmp (host, up->domain) == 0) - break; /* exact match */ - - if (up->domain[0] == '.') { - char *dot = strchr (host, '.'); + if (up->target.type == HST_NONE) + break; - if (!dot && !up->domain[1]) - break; /* local host matches "." */ - - while (dot && strcasecmp (dot, up->domain)) - dot = strchr (dot + 1, '.'); - - if (dot) - break; /* subdomain match */ - } - } else if (up->ip) { - if (my_ip == INADDR_NONE) - my_ip = ntohl (inet_addr (host)); - - if ((my_ip & up->mask) == up->ip) - break; - } else { - break; /* No domain or IP, default upstream */ - } + if (hostspec_match(host, &up->target)) + break; up = up->next; } @@ -269,7 +228,8 @@ void free_upstream_list (struct upstream *up) while (up) { struct upstream *tmp = up; up = up->next; - safefree (tmp->domain); + if(tmp->target.type == HST_STRING) + safefree (tmp->target.address.string); safefree (tmp->host); safefree (tmp); } diff --git a/src/upstream.h b/src/upstream.h index a611807..9a2314d 100644 --- a/src/upstream.h +++ b/src/upstream.h @@ -26,6 +26,7 @@ #define _TINYPROXY_UPSTREAM_H_ #include "common.h" +#include "hostspec.h" enum upstream_build_error { UBE_SUCCESS = 0, @@ -50,7 +51,6 @@ typedef enum proxy_type { struct upstream { struct upstream *next; - char *domain; /* optional */ char *host; union { char *user; @@ -58,14 +58,14 @@ struct upstream { } ua; char *pass; int port; - in_addr_t ip, mask; + struct hostspec target; proxy_type type; }; #ifdef UPSTREAM_SUPPORT const char *proxy_type_name(proxy_type type); extern enum upstream_build_error upstream_add ( - const char *host, int port, const char *domain, + const char *host, int port, char *domain, const char *user, const char *pass, proxy_type type, struct upstream **upstream_list); extern struct upstream *upstream_get (char *host, struct upstream *up); |