mirror of
git://anongit.mindrot.org/openssh.git
synced 2024-12-24 19:02:06 +00:00
0a80ca190a
- djm@cvs.openbsd.org 2010/02/26 20:29:54 [PROTOCOL PROTOCOL.agent PROTOCOL.certkeys addrmatch.c auth-options.c] [auth-options.h auth.h auth2-pubkey.c authfd.c dns.c dns.h hostfile.c] [hostfile.h kex.h kexdhs.c kexgexs.c key.c key.h match.h monitor.c] [myproposal.h servconf.c servconf.h ssh-add.c ssh-agent.c ssh-dss.c] [ssh-keygen.1 ssh-keygen.c ssh-rsa.c ssh.1 ssh.c ssh2.h sshconnect.c] [sshconnect2.c sshd.8 sshd.c sshd_config.5] Add support for certificate key types for users and hosts. OpenSSH certificate key types are not X.509 certificates, but a much simpler format that encodes a public key, identity information and some validity constraints and signs it with a CA key. CA keys are regular SSH keys. This certificate style avoids the attack surface of X.509 certificates and is very easy to deploy. Certified host keys allow automatic acceptance of new host keys when a CA certificate is marked as sh/known_hosts. see VERIFYING HOST KEYS in ssh(1) for details. Certified user keys allow authentication of users when the signing CA key is marked as trusted in authorized_keys. See "AUTHORIZED_KEYS FILE FORMAT" in sshd(8) for details. Certificates are minted using ssh-keygen(1), documentation is in the "CERTIFICATES" section of that manpage. Documentation on the format of certificates is in the file PROTOCOL.certkeys feedback and ok markus@
501 lines
11 KiB
C
501 lines
11 KiB
C
/* $OpenBSD: addrmatch.c,v 1.5 2010/02/26 20:29:54 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"
|
|
#include "xmalloc.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));
|
|
#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
|
|
xa->scope_id = in6->sin6_scope_id;
|
|
#endif
|
|
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;
|
|
if (l == 0)
|
|
return 0;
|
|
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;
|
|
}
|
|
}
|
|
xfree(o);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Match "addr" against list CIDR list "_list". Lexical wildcards and
|
|
* negation are not supported. If "addr" == NULL, will verify structure
|
|
* of "_list".
|
|
*
|
|
* 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 error
|
|
*/
|
|
int
|
|
addr_match_cidr_list(const char *addr, const char *_list)
|
|
{
|
|
char *list, *cp, *o;
|
|
struct xaddr try_addr, match_addr;
|
|
u_int masklen;
|
|
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) {
|
|
if (*cp == '\0') {
|
|
error("%s: empty entry in list \"%.100s\"",
|
|
__func__, o);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* NB. This function is called in pre-auth with untrusted data,
|
|
* so be extra paranoid about junk reaching getaddrino (via
|
|
* addr_pton_cidr).
|
|
*/
|
|
|
|
/* Stop junk from reaching getaddrinfo. +3 is for masklen */
|
|
if (strlen(cp) > INET6_ADDRSTRLEN + 3) {
|
|
error("%s: list entry \"%.100s\" too long",
|
|
__func__, cp);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/"
|
|
if (strspn(cp, VALID_CIDR_CHARS) != strlen(cp)) {
|
|
error("%s: list entry \"%.100s\" contains invalid "
|
|
"characters", __func__, cp);
|
|
ret = -1;
|
|
}
|
|
|
|
/* Prefer CIDR address matching */
|
|
r = addr_pton_cidr(cp, &match_addr, &masklen);
|
|
if (r == -1) {
|
|
error("Invalid network entry \"%.100s\"", cp);
|
|
ret = -1;
|
|
break;
|
|
} else if (r == -2) {
|
|
error("Inconsistent mask length for "
|
|
"network \"%.100s\"", cp);
|
|
ret = -1;
|
|
break;
|
|
} else if (r == 0 && addr != NULL) {
|
|
if (addr_netmatch(&try_addr, &match_addr,
|
|
masklen) == 0)
|
|
ret = 1;
|
|
continue;
|
|
}
|
|
}
|
|
xfree(o);
|
|
|
|
return ret;
|
|
}
|