#include "config.h"
#include <ctype.h>
#include <stdbool.h>
#include <sys/types.h>
#ifndef _WIN32
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 46
#endif
#include "libssh/priv.h"
#define MAX_MATCH_RECURSION 16
static int match_pattern(const char *s, const char *pattern, size_t limit)
{
bool had_asterisk = false;
if (s == NULL || pattern == NULL || limit <= 0) {
return 0;
}
for (;;) {
if (*pattern == '\0') {
return (*s == '\0');
}
while (*pattern == '*' || (had_asterisk && *pattern == '?')) {
if (*pattern == '*') {
had_asterisk = true;
}
pattern++;
}
if (had_asterisk) {
if (!*pattern)
return 1;
if (*pattern != '?') {
for (; *s; s++)
if (*s == *pattern && match_pattern(s + 1, pattern + 1, limit - 1)) {
return 1;
}
return 0;
}
for (; *s; s++) {
if (match_pattern(s, pattern, limit - 1)) {
return 1;
}
}
return 0;
}
if (!*s) {
return 0;
}
if (*pattern != '?' && *pattern != *s) {
return 0;
}
s++;
pattern++;
}
return 0;
}
int match_pattern_list(const char *string, const char *pattern,
size_t len, int dolower) {
char sub[1024];
int negated;
int got_positive;
size_t i, subi;
got_positive = 0;
for (i = 0; i < len;) {
if (pattern[i] == '!') {
negated = 1;
i++;
} else {
negated = 0;
}
for (subi = 0;
i < len && subi < sizeof(sub) - 1 && pattern[i] != ',';
subi++, i++) {
sub[subi] = dolower && isupper(pattern[i]) ?
(char)tolower(pattern[i]) : pattern[i];
}
if (subi >= sizeof(sub) - 1) {
return 0;
}
if (i < len && pattern[i] == ',') {
i++;
}
sub[subi] = '\0';
if (match_pattern(string, sub, MAX_MATCH_RECURSION)) {
if (negated) {
return -1;
} else {
got_positive = 1;
}
}
}
return got_positive;
}
int match_hostname(const char *host, const char *pattern, unsigned int len) {
return match_pattern_list(host, pattern, len, 1);
}
#ifndef _WIN32
static int
cidr_match_6(struct in6_addr *host_addr,
struct in6_addr *net_addr,
unsigned int bits)
{
const uint8_t *a = host_addr->s6_addr;
const uint8_t *b = net_addr->s6_addr;
unsigned int byte_whole, bits_left;
byte_whole = bits / 8;
bits_left = bits % 8;
if (byte_whole) {
if (memcmp(a, b, byte_whole) != 0) {
return 0;
}
}
if (bits_left) {
if ((a[byte_whole] ^ b[byte_whole]) & (0xFFu << (8 - bits_left))) {
return 0;
}
}
return 1;
}
static int
cidr_match_4(struct in_addr *host_addr,
struct in_addr *net_addr,
unsigned int bits)
{
if (bits == 0) {
return 1;
}
return !((host_addr->s_addr ^ net_addr->s_addr) &
htonl((0xFFFFFFFFu << (32 - bits)) & 0xFFFFFFFFu));
}
static bool
masklen_valid(int family, unsigned int mask)
{
switch (family) {
case AF_INET:
return mask <= 32;
case AF_INET6:
return mask <= 128;
default:
return false;
}
}
static int
get_address_family(const char *address)
{
struct addrinfo hints, *ai = NULL;
int rc = -1, rv;
ZERO_STRUCT(hints);
if (address == NULL) {
SSH_LOG(SSH_LOG_TRACE, "Bad arguments");
goto out;
}
hints.ai_flags = AI_NUMERICHOST;
rv = getaddrinfo(address, NULL, &hints, &ai);
if (rv != 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't get address information - getaddrinfo() failed: %s",
gai_strerror(rv));
goto out;
}
rc = ai->ai_family;
freeaddrinfo(ai);
out:
return rc;
}
int
match_cidr_address_list(const char *address,
const char *addrlist,
int sa_family)
{
char *list = NULL, *cp = NULL, *a = NULL, *b = NULL, *sp = NULL;
char addr_buffer[64], addr[NI_MAXHOST];
struct in_addr try_addr, match_addr;
struct in6_addr try_addr6, match_addr6;
unsigned long mask_len;
size_t addr_len, tmp_len;
int rc = 0, r, ai_family;
ZERO_STRUCT(try_addr);
ZERO_STRUCT(try_addr6);
ZERO_STRUCT(match_addr);
ZERO_STRUCT(match_addr6);
if (sa_family != AF_INET && sa_family != AF_INET6 && sa_family != -1) {
SSH_LOG(SSH_LOG_TRACE,
"Invalid argument: sa_family %d is not valid",
sa_family);
return -1;
}
if (address != NULL) {
strncpy(addr, address, NI_MAXHOST - 1);
a = strchr(addr, '%');
if (a != NULL) {
*a = '\0';
}
if (sa_family == -1) {
r = get_address_family(addr);
if (r == -1) {
SSH_LOG(SSH_LOG_TRACE,
"Failed to derive address family for address "
"\"%.100s\"",
addr);
return -1;
}
sa_family = r;
}
if (sa_family == AF_INET) {
if (inet_pton(AF_INET, addr, &try_addr) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv4 address \"%.100s\"",
addr);
return -1;
}
} else if (sa_family == AF_INET6) {
if (inet_pton(AF_INET6, addr, &try_addr6) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv6 address \"%.100s\"",
addr);
return -1;
}
} else {
SSH_LOG(SSH_LOG_TRACE,
"Address family %d for address \"%.100s\" "
"is not recognized",
sa_family,
addr);
return -1;
}
}
b = list = strdup(addrlist);
if (b == NULL) {
return -1;
}
while ((cp = strsep(&list, ",")) != NULL) {
if (*cp == '\0') {
SSH_LOG(SSH_LOG_TRACE, "Empty entry in list \"%.100s\"", b);
rc = -1;
break;
}
addr_len = strlen(cp);
if (addr_len > INET6_ADDRSTRLEN + 3) {
SSH_LOG(SSH_LOG_TRACE,
"List entry \"%.100s\" too long: %zu > %d (MAX ALLOWED)",
cp,
addr_len,
INET6_ADDRSTRLEN + 3);
rc = -1;
break;
}
#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/"
tmp_len = strspn(cp, VALID_CIDR_CHARS);
if (tmp_len != addr_len) {
SSH_LOG(SSH_LOG_TRACE,
"List entry \"%.100s\" contains invalid characters "
"-> \"%c\" is an invalid character",
cp,
cp[tmp_len]);
rc = -1;
break;
}
#undef VALID_CIDR_CHARS
strncpy(addr_buffer, cp, sizeof(addr_buffer) - 1);
sp = strchr(addr_buffer, '/');
if (sp != NULL) {
*sp = '\0';
sp++;
mask_len = strtoul(sp, &cp, 10);
if (*sp < '0' || *sp > '9' || *cp != '\0') {
SSH_LOG(SSH_LOG_TRACE, "Error while parsing prefix: %s", sp);
rc = -1;
break;
}
if (mask_len > 128) {
SSH_LOG(SSH_LOG_TRACE,
"Invalid prefix: %lu exceeds the maximum allowed "
"(>128)",
mask_len);
rc = -1;
break;
}
} else {
SSH_LOG(SSH_LOG_TRACE,
"Missing prefix length for list entry \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
ai_family = get_address_family(addr_buffer);
if (ai_family == -1) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't get address family for \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
if (ai_family == AF_INET) {
if (inet_pton(AF_INET, addr_buffer, &match_addr) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv4 address \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
} else if (ai_family == AF_INET6) {
if (inet_pton(AF_INET6, addr_buffer, &match_addr6) == 0) {
SSH_LOG(SSH_LOG_TRACE,
"Couldn't parse IPv6 address \"%.100s\"",
addr_buffer);
rc = -1;
break;
}
} else {
SSH_LOG(SSH_LOG_TRACE,
"Address family %d for address \"%.100s\" "
"is not recognized",
ai_family,
addr_buffer);
rc = -1;
break;
}
if (masklen_valid(ai_family, mask_len) != true) {
SSH_LOG(SSH_LOG_TRACE,
"Invalid mask length %lu for list entry \"%.100s\"",
mask_len,
addr_buffer);
rc = -1;
break;
}
if (((ai_family == AF_INET && sa_family == AF_INET) &&
cidr_match_4(&try_addr, &match_addr, mask_len)) ||
((ai_family == AF_INET6 && sa_family == AF_INET6) &&
cidr_match_6(&try_addr6, &match_addr6, mask_len))) {
rc = 1;
break;
}
}
SAFE_FREE(b);
return rc;
}
#endif
int match_group(const char *group, const char *object)
{
const char *a = NULL;
const char *z = NULL;
if (group == NULL || object == NULL) {
return 0;
}
z = group;
do {
a = strchr(z, ',');
if (a == NULL) {
if (strcmp(z, object) == 0) {
return 1;
}
return 0;
} else {
if (strncmp(z, object, a - z) == 0) {
return 1;
}
}
z = a + 1;
} while (1);
return 0;
}