--- hosts_access.c | 271 ++++++++++++++++++++++++++++++++++----------------------- misc.c | 18 +++ tcpd.h | 2 3 files changed, 183 insertions(+), 108 deletions(-) Index: tcp_wrappers_7.6/hosts_access.c =================================================================== --- tcp_wrappers_7.6.orig/hosts_access.c +++ tcp_wrappers_7.6/hosts_access.c @@ -88,6 +88,10 @@ static int server_match(); static int client_match(); static int host_match(); static int string_match(); +static int masked_match(); +#ifdef INET6 +static void ipv6_mask(); +#endif /* Size of logical line buffer. */ @@ -293,6 +297,74 @@ struct hosts_info *host; return (match); } +static inline int +host_info_ipv6addr(const struct host_info *host, struct in6_addr *addrbuf) +{ + /* + * In some cases we don't get the sockaddr, only the addr. + * We use inet_pton to convert it to its binary representation + * and match against that. + */ + if (host->sin == NULL) { + if (host->addr == NULL || inet_pton(AF_INET6, host->addr, addrbuf) != 1) + return 0; + + return 1; + } else + if (host->sin->ss_family == AF_INET6) { + *addrbuf = ((struct sockaddr_in6 *) host->sin)->sin6_addr; + return 1; + } + return 0; +} + +static inline int +token_ipv6addr_and_mask(char *tok, struct in6_addr *addrbuf, unsigned int *maskbits) +{ + char *cbr; + char *slash; + + if (*tok != '[') + return 0; + + *maskbits = 128; + + ++tok; /* now points to the beginning of the IPv6 addr string */ + if ((cbr = strchr(tok, ']')) == NULL) { + tcpd_warn("bad IP6 address specification"); + return 0; + } + *cbr++ = '\0'; + + /* + * A /nnn prefix specifies how many bits of the address we + * need to check. + * Note, we support both [x::y/64] and [x::y]/64 + */ + if ((slash = strchr(tok, '/')) == NULL && *cbr == '/') + slash = cbr; + + if (slash != NULL) { + int mask; + + *slash++ = '\0'; + mask = atoi(slash); + if (mask < 0 || mask > 128) { + tcpd_warn("bad IP6 prefix specification"); + return 0; + } + + *maskbits = mask; + } + + if (inet_pton(AF_INET6, tok, addrbuf) != 1) { + tcpd_warn("bad IP6 address specification"); + return 0; + } + + return 1; +} + /* host_match - match host name and/or address against pattern */ static int host_match(tok, host) @@ -328,12 +400,68 @@ struct host_info *host; } else if (STR_EQ(tok, "LOCAL")) { /* local: no dots in name */ char *name = eval_hostname(host); return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name)); + } else if (tok[0] == '[') { /* IPv6 address */ +#ifdef INET6 + struct in6_addr match_addr, host_addr; + unsigned int mask = 128; + + if (!host_info_ipv6addr(host, &host_addr)) + return (NO); + + if (!token_ipv6addr_and_mask(tok, &match_addr, &mask)) + return (NO); + + /* + * Zero the bits we're not interested in in both addresses + * then compare. Note that we take a copy of the host info + * in that case. + */ + if (mask != 128) { + ipv6_mask(&match_addr, mask); + ipv6_mask(&host_addr, mask); + } + if (memcmp(&match_addr, &host_addr, sizeof(struct in6_addr)) == 0) + return (YES); +#endif + return (NO); + } else if ((mask = split_at(tok, '/')) != 0) { /* net/mask */ + return (masked_match(tok, mask, eval_hostaddr(host))); } else { /* anything else */ - return (string_match(tok, eval_hostaddr(host)) - || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host)))); + return (string_match(tok, eval_hostaddr(host)) + || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host)))); } } +static int masked_match(net_tok, mask_tok, string) +char *net_tok; +char *mask_tok; +char *string; +{ + unsigned long net; + unsigned long mask; + unsigned long addr; + + /* + * Disallow forms other than dotted quad: the treatment that inet_addr() + * gives to forms with less than four components is inconsistent with the + * access control language. John P. Rouillard . + */ + + if ((addr = dot_quad_addr(string)) == INADDR_NONE) + return (NO); + if ((net = dot_quad_addr(net_tok)) == INADDR_NONE + || ((mask = dot_quad_addr(mask_tok)) == INADDR_NONE + && strcmp(mask_tok, "255.255.255.255") + && (mask = prefix_to_netmask(mask_tok)) == INADDR_NONE + && strcmp(mask_tok, "32"))) { + /* 255.255.255.255 == INADDR_NONE, separate check needed. TJ. */ + /* 32 == INADDR_NONE, separate check needed. philipp */ + tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok); + return (NO); /* not tcpd_jump() */ + } + return ((addr & mask) == net); +} + /* string_match - match string against pattern * * tok = data read from /etc/hosts.* @@ -359,113 +487,14 @@ char *string; return (YES); } else if (STR_EQ(tok, "KNOWN")) { /* not unknown */ return (STR_NE(string, unknown)); - } else if (STR_EQ(tok, string)) /* exact match */ - return (YES); -#ifdef INET6 - else /* IP addresses match - not needed for IPv4 */ - { - /* For simplicity we convert everything to IPv6 (or v4 mapped) */ - struct in6_addr pat, addr; - int len, ret, prefixlen=128, nof_periods = 0; - char ch, token[INET6_ADDRSTRLEN+1], *mask, *addition; - - /* If prefix was given, handle it */ - if ((mask = split_at(tok, '/')) != 0) - { - if (strchr(mask, '.') != NULL) /* We have something - like 255.255.0.0 */ - { - int b1, b2, b3, b4; - uint32_t netmask; - - if (sscanf(mask, "%d.%d.%d.%d", &b1, &b2, &b3, &b4) != 4) - { - tcpd_warn ("Wrong netmask in %s", tok); - return (NO); - } - netmask = (((((b1 * 256) + b2) * 256) + b3) * 256) + b4; - prefixlen = 0; - while (netmask > 0) - { - ++prefixlen; - netmask <<= 1; - } - } - else if (sscanf(mask, "%d", &prefixlen) != 1 || prefixlen < 0) - { - tcpd_warn ("Wrong prefix length in %s", tok); - return (NO); - } - - if (is_v4_string (tok)) - prefixlen += 96; /* extend to v4mapped */ - - if (prefixlen > 128) - { - tcpd_warn ("Prefix too long in %s", tok); - return (NO); - } - } - - len = strlen(tok); - if (tok[len - 1] == '.') { /* prefix */ - char *ptok = tok; - - while ((ptok = strchr(ptok, '.')) != NULL){ - nof_periods++; - ptok++; - } - switch(nof_periods){ - case 1: - addition = "0.0.0"; - prefixlen = 8; - break; - case 2: - addition = "0.0"; - prefixlen = 16; - break; - case 3: - addition = "0"; - prefixlen = 24; - break; - default: - tcpd_warn ("Wrong prefix %s", tok); - return (NO); - } - snprintf(token, sizeof(token), "%s%s", tok, addition); - prefixlen += 96; /* extend to v4mapped */ - } - else if (*tok == '[' && tok[len - 1] == ']') - { - ch = tok[len - 1]; - tok[len - 1] = '\0'; - snprintf(token, sizeof(token), "%s", tok+1); - tok[len - 1] = ch; - } - else - snprintf(token, sizeof(token), "%s", tok); - - memset (&pat, 0, sizeof(pat)); - memset (&addr, 0, sizeof(addr)); - - if (inet_pton_mapped(AF_INET6, token, &pat) != 1) - return (NO); - - if (inet_pton_mapped(AF_INET6, string, &addr) != 1) - { - tcpd_warn("Unable to handle client address: %s", string); - return (NO); - } - - if (prefixlen < 128) - { - apply_v6_prefix (&pat, prefixlen); - apply_v6_prefix (&addr, prefixlen); - } - - return (!memcmp(&pat, &addr, sizeof(struct in6_addr))); + } else if (tok[(n = strlen(tok)) - 1] == '.') { /* prefix */ + return (STRN_EQ(tok, string, n)); + } else if ((STR_EQ(tok, "localhost") || STR_EQ(tok, "localhost.localdomain")) + && (STR_EQ(string, "localhost") || STR_EQ(string, "localhost.localdomain"))) { + return (YES); /* these localhosts are equivalent */ + } else { /* exact match */ + return (STR_EQ(tok, string)); } -#endif } #ifndef DISABLE_WILDCARD_MATCHING @@ -535,3 +564,29 @@ int match_pattern_ylo(const char *s, con /*NOTREACHED*/ } #endif /* DISABLE_WILDCARD_MATCHING */ + +#ifdef INET6 +/* + * Function that zeros all but the first "maskbits" bits of the IPV6 address + * This function can be made generic by specifying an address length as + * extra parameter. (So Wietse can implement 1.2.3.4/16) + */ +static void ipv6_mask(in6p, maskbits) +struct in6_addr *in6p; +int maskbits; +{ + unsigned char *p = (unsigned char*) in6p; + + if (maskbits < 0 || maskbits >= 128) + return; + + p += maskbits / 8; + maskbits %= 8; + + if (maskbits != 0) + *p++ &= 0xff << (8 - maskbits); + + while (p < (((unsigned char*) in6p)) + sizeof(*in6p)) + *p++ = 0; +} +#endif Index: tcp_wrappers_7.6/misc.c =================================================================== --- tcp_wrappers_7.6.orig/misc.c +++ tcp_wrappers_7.6/misc.c @@ -107,3 +107,21 @@ char *str; } return (runs == 4 ? inet_addr(str) : INADDR_NONE); } + +/* prefix_to_netmask - convert prefix (0-32) to netmask */ + +unsigned long prefix_to_netmask(str) +char *str; +{ + unsigned long prefix; + char *endptr; + + if (!isdigit(str[0])) + return INADDR_NONE; + + prefix = strtoul(str, &endptr, 10); + if ((endptr == str) || (*endptr != '\0') || (prefix > 32)) + return INADDR_NONE; + + return htonl(~0UL << (32 - prefix)); +} Index: tcp_wrappers_7.6/tcpd.h =================================================================== --- tcp_wrappers_7.6.orig/tcpd.h +++ tcp_wrappers_7.6/tcpd.h @@ -88,6 +88,7 @@ extern void refuse(struct request_info * extern char *xgets(char *, int, FILE *); extern char *split_at(char *, int); extern unsigned long dot_quad_addr(char *); +extern char *skip_ipv6_addrs(char *); #else extern int hosts_access(); /* access control */ extern void shell_cmd(); /* execute shell command */ @@ -98,6 +99,7 @@ extern void refuse(); /* clean up and extern char *xgets(); /* fgets() on steroids */ extern char *split_at(); /* strchr() and split */ extern unsigned long dot_quad_addr(); /* restricted inet_addr() */ +extern char *skip_ipv6_addrs(); #endif /* Global variables. */