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 \ diff --git a/src/acl.c b/src/acl.c index b989219..63a3d34 100644 --- a/src/acl.c +++ b/src/acl.c @@ -29,17 +29,10 @@ #include "network.h" #include "sock.h" #include "sblist.h" +#include "hostspec.h" #include -/* 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->target.type == HST_NONE) + break; - if (up->domain[0] == '.') { - char *dot = strchr (host, '.'); - - 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);