From 10e561767fdac63c930287abe18716d6574d2330 Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Fri, 4 Feb 2022 07:58:09 -0800 Subject: [PATCH] SOCK: Add support for parsing and evaluating Access Control Lists --- sim_sock.c | 190 ++++++++++++++++++++++++++++++++++++++++++++++++----- sim_sock.h | 8 ++- 2 files changed, 179 insertions(+), 19 deletions(-) diff --git a/sim_sock.c b/sim_sock.c index 9b291c92..724c82f8 100644 --- a/sim_sock.c +++ b/sim_sock.c @@ -501,27 +501,38 @@ int load_ws2(void) { /* OS independent routines - sim_parse_addr parse a hostname/ipaddress from port and apply defaults and - optionally validate an address match + sim_parse_addr parse a hostname/ipaddress from port and apply defaults + and optionally validate an address match + sim_addr_acl_check parse a hostname/ipaddress (possibly in CIDR form) and + test against an acl */ /* sim_parse_addr host:port - Presumption is that the input, if it doesn't contain a ':' character is a port specifier. - If the host field contains one or more colon characters (i.e. it is an IPv6 address), - the IPv6 address MUST be enclosed in square bracket characters (i.e. Domain Literal format) + Presumption is that the cptr input, if it doesn't contain a ':' character + is a port specifier. If the host field contains one or more colon characters + (i.e. it is an IPv6 address), the IPv6 address MUST be enclosed in square + bracket characters (i.e. Domain Literal format) Inputs: cptr = pointer to input string - default_host - = optional pointer to default host if none specified + host = optional pointer to host buffer host_len = length of host buffer - default_port - = optional pointer to default port if none specified + default_host = optional pointer to default host if none specified + in cptr + port = optional pointer to port buffer port_len = length of port buffer + default_port = optional pointer to default port if none specified + in cptr validate_addr = optional name/addr which is checked to be equivalent to the host result of parsing the other input. This address would usually be returned by sim_accept_conn. + The validate_addr can also be a CIDR address specifier + which will match against the provided host. + If the validate_addr is provided with cptr as NULL, + the validate_addr is parsed for reasonableness and + the result returned with 0 indicating a reasonable + value and -1 indicating a parsing error. Outputs: host = pointer to buffer for IP address (may be NULL), 0 = none port = pointer to buffer for IP port (may be NULL), 0 = none @@ -532,7 +543,9 @@ int load_ws2(void) { doesn't match the parsed host) */ -int sim_parse_addr (const char *cptr, char *host, size_t host_len, const char *default_host, char *port, size_t port_len, const char *default_port, const char *validate_addr) +int sim_parse_addr (const char *cptr, char *host, size_t host_len, const char *default_host, + char *port, size_t port_len, const char *default_port, + const char *validate_addr) { char gbuf[CBUFSIZE], default_pbuf[CBUFSIZE]; const char *hostp; @@ -545,7 +558,8 @@ if ((host != NULL) && (host_len != 0)) if ((port != NULL) && (port_len != 0)) memset (port, 0, port_len); if ((cptr == NULL) || (*cptr == 0)) { - if (((default_host == NULL) || (*default_host == 0)) || ((default_port == NULL) || (*default_port == 0))) + if (((default_host == NULL) || (*default_host == 0)) || + ((default_port == NULL) || (*default_port == 0))) return -1; if ((host == NULL) || (port == NULL)) return -1; /* no place */ @@ -572,7 +586,7 @@ else { /* No colon in input */ portp = gbuf; /* Input is the port specifier */ hostp = (const char *)default_host; /* host is defaulted if provided */ } -if (portp != NULL) { +if ((portp != NULL) && (*portp != '\0')) { portval = strtoul(portp, &endc, 10); if ((*endc == '\0') && ((portval == 0) || (portval > 65535))) return -1; /* numeric value too big */ @@ -590,7 +604,7 @@ if (port) /* port wanted? */ else strcpy (port, portp); } -if (hostp != NULL) { +if ((hostp != NULL) && (*hostp != '\0')) { if (']' == hostp[strlen(hostp)-1]) { if ('[' != hostp[0]) return -1; /* invalid domain literal */ @@ -626,16 +640,15 @@ if (validate_addr) { struct addrinfo *ai_host, *ai_validate, *ai, *aiv; int status; - if (hostp == NULL) - return -1; - if (p_getaddrinfo(hostp, NULL, NULL, &ai_host)) + if ((hostp == NULL) || + (0 != p_getaddrinfo(hostp, NULL, NULL, &ai_host))) return -1; if (p_getaddrinfo(validate_addr, NULL, NULL, &ai_validate)) { p_freeaddrinfo (ai_host); return -1; } status = -1; - for (ai = ai_host; ai != NULL; ai = ai->ai_next) { + for (ai = ai_host; (ai != NULL) && (status == -1); ai = ai->ai_next) { for (aiv = ai_validate; aiv != NULL; aiv = aiv->ai_next) { if ((ai->ai_addrlen == aiv->ai_addrlen) && (ai->ai_family == aiv->ai_family) && @@ -660,6 +673,149 @@ if (validate_addr) { return 0; } +/* sim_addr_acl_check host:port,acl + + parse a hostname/ipaddress (possibly in CIDR form) and + test against an acl + + Inputs: + validate_addr = This address would usually be returned by + sim_accept_conn. The validate_addr can also be a + CIDR address specifier and in that mode, acl should + be NULL so that we're just validating the syntax + of what will likely become an entry in an acl list. + If the validate_addr is provided with cptr as NULL, + the validate_addr is parsed for reasonableness and + the result returned with 0 indicating a reasonable + value and -1 indicating a parsing error. + acl = pointer to acl string which is comprised of comma + separated entries each which may have a + or - + prefix that indicated a permit or deny status when + the entry matches. Each entry may specify a CIDR + form match criteria. + Outputs: + result = status (0 on complete success or -1 if + parsing can't happen due to bad syntax, a value is + out of range or the validate_addr matches a reject + entry in the acl or it is not mentioned at all in + the acl. +*/ + +int sim_addr_acl_check (const char *validate_addr, const char *acl) +{ +int status = -1; +int done = 0; +struct addrinfo *ai_validate; +unsigned long bits = 0; +char *c, *c1, v_cpy[256]; + +if (validate_addr == NULL) + return status; + +c = strchr (validate_addr, '/'); +if (c != NULL) { + bits = strtoul (c + 1, &c1, 10); + if ((bits == 0) || (bits > 128) || (*c1 != '\0')) + return status; + if ((c - validate_addr) > sizeof (v_cpy) - 1) + return status; + memcpy (v_cpy, validate_addr, c - validate_addr); /* Copy everything before the / */ + v_cpy[1 + c - validate_addr] = '\0'; /* NUL terminate the result */ + validate_addr = v_cpy; /* Use the original string minus the prefix specifier */ + } +if (p_getaddrinfo(validate_addr, NULL, NULL, &ai_validate)) + return status; +if (acl == NULL) { /* Just checking validate_addr syntax? */ + status = 0; + if ((ai_validate->ai_family == AF_INET) && (bits > 32)) + status = -1; + p_freeaddrinfo (ai_validate); + return status; + } +status = -1; +while ((*acl != '\0') && !done) { + struct addrinfo *ai_rule, *ai, *aiv; + int permit; + unsigned long bits = 0; + char *c, *c1, rule[260]; + + permit = (*acl == '+'); + c = strchr (acl, ','); + if (c != NULL) { + if ((c - acl) > sizeof (rule)) + break; /* Too big - error */ + memcpy (rule, acl + 1, c - (acl + 1)); + rule[c - (acl + 1)] = '\0'; + } + else { + if (strlen (acl) > sizeof (rule)) + break; /* Too big - error */ + strcpy (rule, acl + 1); + } + acl += strlen (rule) + 1 + (c != NULL); + c = strchr (rule, '/'); + if (c != NULL) { + bits = strtoul (c + 1, &c1, 10); + if ((bits == 0) || (bits > 128) || (*c1 != '\0')) + break; + *c = '\0'; + } + + if (p_getaddrinfo(rule, NULL, NULL, &ai_rule)) + break; + + for (ai = ai_rule; (ai != NULL) && (done == 0); ai = ai->ai_next) { + for (aiv = ai_validate; aiv != NULL; aiv = aiv->ai_next) { + if ((ai->ai_addrlen == aiv->ai_addrlen) && + (ai->ai_family == aiv->ai_family)) { + unsigned int bit, addr_bits; + unsigned char *da, *dav; + + if (ai->ai_family == AF_INET) { + da = (unsigned char *)&((struct sockaddr_in *)ai->ai_addr)->sin_addr; + dav = (unsigned char *)&((struct sockaddr_in *)aiv->ai_addr)->sin_addr; + addr_bits = 32; + } + #if !defined(AF_INET6) + else { + done = 1; + break; + } + #else + else { + if (ai->ai_family == AF_INET6) { + da = (unsigned char *)&((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; + dav = (unsigned char *)&((struct sockaddr_in6 *)aiv->ai_addr)->sin6_addr; + addr_bits = 128; + } + else { + done = 1; + break; + } + } + #endif + if (bits == 0) /* Bits not specified? */ + bits = addr_bits; /* Use them all */ + for (bit=0; (bit < bits) && (bit < addr_bits); bit++) { + unsigned int bitmask = 1 << (7 - (bit & 7)); + + if ((da[bit>>3] & bitmask) != (dav[bit>>3] & bitmask)) + break; + } + if (bit == bits) { /* All desired bits matched? */ + done = 1; + status = permit ? 0 : -1; + break; + } + } + } + } + p_freeaddrinfo (ai_rule); + } +p_freeaddrinfo (ai_validate); +return status; +} + /* sim_parse_addr_ex localport:host:port Presumption is that the input, if it doesn't contain a ':' character is a port specifier. diff --git a/sim_sock.h b/sim_sock.h index f42e8f16..1a0ee450 100644 --- a/sim_sock.h +++ b/sim_sock.h @@ -111,8 +111,12 @@ extern "C" { #define sim_printf printf #endif -int sim_parse_addr (const char *cptr, char *host, size_t hostlen, const char *default_host, char *port, size_t port_len, const char *default_port, const char *validate_addr); -int sim_parse_addr_ex (const char *cptr, char *host, size_t hostlen, const char *default_host, char *port, size_t port_len, char *localport, size_t local_port_len, const char *default_port); +int sim_parse_addr (const char *cptr, char *host, size_t hostlen, const char *default_host, + char *port, size_t port_len, const char *default_port, + const char *validate_addr); +int sim_parse_addr_ex (const char *cptr, char *host, size_t hostlen, const char *default_host, + char *port, size_t port_len, char *localport, size_t local_port_len, const char *default_port); +int sim_addr_acl_check (const char *validate_addr, const char *acl); #define SIM_SOCK_OPT_REUSEADDR 0x0001 #define SIM_SOCK_OPT_DATAGRAM 0x0002 #define SIM_SOCK_OPT_NODELAY 0x0004