b7aa600c41
I have worked hard to reduce diffs against the vendor branch. One notable change in that respect is that we no longer prefer DSA over RSA - the reasons for doing so went away years ago. This may cause some surprises, as ssh will warn about unknown host keys even for hosts whose keys haven't changed. MFC after: 6 weeks
422 lines
9.0 KiB
C
422 lines
9.0 KiB
C
/* $OpenBSD: addrmatch.c,v 1.3 2008/06/10 23:06:19 djm Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2004-2008 Damien Miller <djm@mindrot.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <netdb.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "match.h"
|
|
#include "log.h"
|
|
|
|
struct xaddr {
|
|
sa_family_t af;
|
|
union {
|
|
struct in_addr v4;
|
|
struct in6_addr v6;
|
|
u_int8_t addr8[16];
|
|
u_int32_t addr32[4];
|
|
} xa; /* 128-bit address */
|
|
u_int32_t scope_id; /* iface scope id for v6 */
|
|
#define v4 xa.v4
|
|
#define v6 xa.v6
|
|
#define addr8 xa.addr8
|
|
#define addr32 xa.addr32
|
|
};
|
|
|
|
static int
|
|
addr_unicast_masklen(int af)
|
|
{
|
|
switch (af) {
|
|
case AF_INET:
|
|
return 32;
|
|
case AF_INET6:
|
|
return 128;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
masklen_valid(int af, u_int masklen)
|
|
{
|
|
switch (af) {
|
|
case AF_INET:
|
|
return masklen <= 32 ? 0 : -1;
|
|
case AF_INET6:
|
|
return masklen <= 128 ? 0 : -1;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert struct sockaddr to struct xaddr
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
addr_sa_to_xaddr(struct sockaddr *sa, socklen_t slen, struct xaddr *xa)
|
|
{
|
|
struct sockaddr_in *in4 = (struct sockaddr_in *)sa;
|
|
struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa;
|
|
|
|
memset(xa, '\0', sizeof(*xa));
|
|
|
|
switch (sa->sa_family) {
|
|
case AF_INET:
|
|
if (slen < sizeof(*in4))
|
|
return -1;
|
|
xa->af = AF_INET;
|
|
memcpy(&xa->v4, &in4->sin_addr, sizeof(xa->v4));
|
|
break;
|
|
case AF_INET6:
|
|
if (slen < sizeof(*in6))
|
|
return -1;
|
|
xa->af = AF_INET6;
|
|
memcpy(&xa->v6, &in6->sin6_addr, sizeof(xa->v6));
|
|
xa->scope_id = in6->sin6_scope_id;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculate a netmask of length 'l' for address family 'af' and
|
|
* store it in 'n'.
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
addr_netmask(int af, u_int l, struct xaddr *n)
|
|
{
|
|
int i;
|
|
|
|
if (masklen_valid(af, l) != 0 || n == NULL)
|
|
return -1;
|
|
|
|
memset(n, '\0', sizeof(*n));
|
|
switch (af) {
|
|
case AF_INET:
|
|
n->af = AF_INET;
|
|
n->v4.s_addr = htonl((0xffffffff << (32 - l)) & 0xffffffff);
|
|
return 0;
|
|
case AF_INET6:
|
|
n->af = AF_INET6;
|
|
for (i = 0; i < 4 && l >= 32; i++, l -= 32)
|
|
n->addr32[i] = 0xffffffffU;
|
|
if (i < 4 && l != 0)
|
|
n->addr32[i] = htonl((0xffffffff << (32 - l)) &
|
|
0xffffffff);
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Perform logical AND of addresses 'a' and 'b', storing result in 'dst'.
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
addr_and(struct xaddr *dst, const struct xaddr *a, const struct xaddr *b)
|
|
{
|
|
int i;
|
|
|
|
if (dst == NULL || a == NULL || b == NULL || a->af != b->af)
|
|
return -1;
|
|
|
|
memcpy(dst, a, sizeof(*dst));
|
|
switch (a->af) {
|
|
case AF_INET:
|
|
dst->v4.s_addr &= b->v4.s_addr;
|
|
return 0;
|
|
case AF_INET6:
|
|
dst->scope_id = a->scope_id;
|
|
for (i = 0; i < 4; i++)
|
|
dst->addr32[i] &= b->addr32[i];
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compare addresses 'a' and 'b'
|
|
* Return 0 if addresses are identical, -1 if (a < b) or 1 if (a > b)
|
|
*/
|
|
static int
|
|
addr_cmp(const struct xaddr *a, const struct xaddr *b)
|
|
{
|
|
int i;
|
|
|
|
if (a->af != b->af)
|
|
return a->af == AF_INET6 ? 1 : -1;
|
|
|
|
switch (a->af) {
|
|
case AF_INET:
|
|
if (a->v4.s_addr == b->v4.s_addr)
|
|
return 0;
|
|
return ntohl(a->v4.s_addr) > ntohl(b->v4.s_addr) ? 1 : -1;
|
|
case AF_INET6:
|
|
for (i = 0; i < 16; i++)
|
|
if (a->addr8[i] - b->addr8[i] != 0)
|
|
return a->addr8[i] > b->addr8[i] ? 1 : -1;
|
|
if (a->scope_id == b->scope_id)
|
|
return 0;
|
|
return a->scope_id > b->scope_id ? 1 : -1;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parse string address 'p' into 'n'
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
addr_pton(const char *p, struct xaddr *n)
|
|
{
|
|
struct addrinfo hints, *ai;
|
|
|
|
memset(&hints, '\0', sizeof(hints));
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
|
|
if (p == NULL || getaddrinfo(p, NULL, &hints, &ai) != 0)
|
|
return -1;
|
|
|
|
if (ai == NULL || ai->ai_addr == NULL)
|
|
return -1;
|
|
|
|
if (n != NULL &&
|
|
addr_sa_to_xaddr(ai->ai_addr, ai->ai_addrlen, n) == -1) {
|
|
freeaddrinfo(ai);
|
|
return -1;
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Perform bitwise negation of address
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
addr_invert(struct xaddr *n)
|
|
{
|
|
int i;
|
|
|
|
if (n == NULL)
|
|
return (-1);
|
|
|
|
switch (n->af) {
|
|
case AF_INET:
|
|
n->v4.s_addr = ~n->v4.s_addr;
|
|
return (0);
|
|
case AF_INET6:
|
|
for (i = 0; i < 4; i++)
|
|
n->addr32[i] = ~n->addr32[i];
|
|
return (0);
|
|
default:
|
|
return (-1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Calculate a netmask of length 'l' for address family 'af' and
|
|
* store it in 'n'.
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
addr_hostmask(int af, u_int l, struct xaddr *n)
|
|
{
|
|
if (addr_netmask(af, l, n) == -1 || addr_invert(n) == -1)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Test whether address 'a' is all zeros (i.e. 0.0.0.0 or ::)
|
|
* Returns 0 on if address is all-zeros, -1 if not all zeros or on failure.
|
|
*/
|
|
static int
|
|
addr_is_all0s(const struct xaddr *a)
|
|
{
|
|
int i;
|
|
|
|
switch (a->af) {
|
|
case AF_INET:
|
|
return (a->v4.s_addr == 0 ? 0 : -1);
|
|
case AF_INET6:;
|
|
for (i = 0; i < 4; i++)
|
|
if (a->addr32[i] != 0)
|
|
return (-1);
|
|
return (0);
|
|
default:
|
|
return (-1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test whether host portion of address 'a', as determined by 'masklen'
|
|
* is all zeros.
|
|
* Returns 0 on if host portion of address is all-zeros,
|
|
* -1 if not all zeros or on failure.
|
|
*/
|
|
static int
|
|
addr_host_is_all0s(const struct xaddr *a, u_int masklen)
|
|
{
|
|
struct xaddr tmp_addr, tmp_mask, tmp_result;
|
|
|
|
memcpy(&tmp_addr, a, sizeof(tmp_addr));
|
|
if (addr_hostmask(a->af, masklen, &tmp_mask) == -1)
|
|
return (-1);
|
|
if (addr_and(&tmp_result, &tmp_addr, &tmp_mask) == -1)
|
|
return (-1);
|
|
return (addr_is_all0s(&tmp_result));
|
|
}
|
|
|
|
/*
|
|
* Parse a CIDR address (x.x.x.x/y or xxxx:yyyy::/z).
|
|
* Return -1 on parse error, -2 on inconsistency or 0 on success.
|
|
*/
|
|
static int
|
|
addr_pton_cidr(const char *p, struct xaddr *n, u_int *l)
|
|
{
|
|
struct xaddr tmp;
|
|
long unsigned int masklen = 999;
|
|
char addrbuf[64], *mp, *cp;
|
|
|
|
/* Don't modify argument */
|
|
if (p == NULL || strlcpy(addrbuf, p, sizeof(addrbuf)) > sizeof(addrbuf))
|
|
return -1;
|
|
|
|
if ((mp = strchr(addrbuf, '/')) != NULL) {
|
|
*mp = '\0';
|
|
mp++;
|
|
masklen = strtoul(mp, &cp, 10);
|
|
if (*mp == '\0' || *cp != '\0' || masklen > 128)
|
|
return -1;
|
|
}
|
|
|
|
if (addr_pton(addrbuf, &tmp) == -1)
|
|
return -1;
|
|
|
|
if (mp == NULL)
|
|
masklen = addr_unicast_masklen(tmp.af);
|
|
if (masklen_valid(tmp.af, masklen) == -1)
|
|
return -2;
|
|
if (addr_host_is_all0s(&tmp, masklen) != 0)
|
|
return -2;
|
|
|
|
if (n != NULL)
|
|
memcpy(n, &tmp, sizeof(*n));
|
|
if (l != NULL)
|
|
*l = masklen;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
addr_netmatch(const struct xaddr *host, const struct xaddr *net, u_int masklen)
|
|
{
|
|
struct xaddr tmp_mask, tmp_result;
|
|
|
|
if (host->af != net->af)
|
|
return -1;
|
|
|
|
if (addr_netmask(host->af, masklen, &tmp_mask) == -1)
|
|
return -1;
|
|
if (addr_and(&tmp_result, host, &tmp_mask) == -1)
|
|
return -1;
|
|
return addr_cmp(&tmp_result, net);
|
|
}
|
|
|
|
/*
|
|
* Match "addr" against list pattern list "_list", which may contain a
|
|
* mix of CIDR addresses and old-school wildcards.
|
|
*
|
|
* If addr is NULL, then no matching is performed, but _list is parsed
|
|
* and checked for well-formedness.
|
|
*
|
|
* Returns 1 on match found (never returned when addr == NULL).
|
|
* Returns 0 on if no match found, or no errors found when addr == NULL.
|
|
* Returns -1 on negated match found (never returned when addr == NULL).
|
|
* Returns -2 on invalid list entry.
|
|
*/
|
|
int
|
|
addr_match_list(const char *addr, const char *_list)
|
|
{
|
|
char *list, *cp, *o;
|
|
struct xaddr try_addr, match_addr;
|
|
u_int masklen, neg;
|
|
int ret = 0, r;
|
|
|
|
if (addr != NULL && addr_pton(addr, &try_addr) != 0) {
|
|
debug2("%s: couldn't parse address %.100s", __func__, addr);
|
|
return 0;
|
|
}
|
|
if ((o = list = strdup(_list)) == NULL)
|
|
return -1;
|
|
while ((cp = strsep(&list, ",")) != NULL) {
|
|
neg = *cp == '!';
|
|
if (neg)
|
|
cp++;
|
|
if (*cp == '\0') {
|
|
ret = -2;
|
|
break;
|
|
}
|
|
/* Prefer CIDR address matching */
|
|
r = addr_pton_cidr(cp, &match_addr, &masklen);
|
|
if (r == -2) {
|
|
error("Inconsistent mask length for "
|
|
"network \"%.100s\"", cp);
|
|
ret = -2;
|
|
break;
|
|
} else if (r == 0) {
|
|
if (addr != NULL && addr_netmatch(&try_addr,
|
|
&match_addr, masklen) == 0) {
|
|
foundit:
|
|
if (neg) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
ret = 1;
|
|
}
|
|
continue;
|
|
} else {
|
|
/* If CIDR parse failed, try wildcard string match */
|
|
if (addr != NULL && match_pattern(addr, cp) == 1)
|
|
goto foundit;
|
|
}
|
|
}
|
|
free(o);
|
|
|
|
return ret;
|
|
}
|