Marcus Meissner
a4faf550b7
Fix breakage of IPv6 address handling [bsc#914527, bsc#899185] OBS-URL: https://build.opensuse.org/request/show/319256 OBS-URL: https://build.opensuse.org/package/show/network:utilities/tcpd?expand=0&rev=23
365 lines
9.9 KiB
Diff
365 lines
9.9 KiB
Diff
---
|
|
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 <rouilj@cs.umb.edu>.
|
|
+ */
|
|
+
|
|
+ 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. */
|