diff options
Diffstat (limited to 'src/vmod_remoteip.c')
-rw-r--r-- | src/vmod_remoteip.c | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/vmod_remoteip.c b/src/vmod_remoteip.c new file mode 100644 index 0000000..528b0a4 --- /dev/null +++ b/src/vmod_remoteip.c @@ -0,0 +1,370 @@ +/* This file is part of vmod_remoteip. + * Copyright (C) 2020 Sergey Poznyakoff + * + * Vmod_remoteip is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Vmod_remoteip is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with vmod_remoteip. If not, see <http://www.gnu.org/licenses/>. + */ +#include <config.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <syslog.h> +#include <ctype.h> +#include <netdb.h> +#include <arpa/inet.h> + +#include <cache/cache.h> +#include <vcl.h> +#include <vcc_if.h> +#include <vqueue.h> +#ifdef VPFX +# define VEVENT(a) VPFX(a) +#else +/* For compatibility with varnish prior to 6.2 */ +# define VEVENT(a) a +#endif + +/* Max. number of bytes in an IPv6 address */ +#define MU_INADDR_BYTES 16 + +/* CIDR representation */ +struct cidr { + int family; /* Address family */ + int len; /* Number of bytes in the address */ + unsigned char address[MU_INADDR_BYTES]; + unsigned char netmask[MU_INADDR_BYTES]; + VSLIST_ENTRY(cidr) next; +}; + +typedef VSLIST_HEAD(,cidr) CIDRHEAD; + +/* Returns 1 if ADDR is a valid string representation of IPv4 address */ +static int +str_is_ipv4(const char *addr, size_t len) +{ + int dot_count = 0; + int digit_count = 0; + + for (; len; addr++, len--) { + if (!isascii(*addr)) + return 0; + if (*addr == '.') { + if (++dot_count > 3) + break; + digit_count = 0; + } + else if (!(isdigit(*addr) && ++digit_count <= 3)) + return 0; + } + return (dot_count == 3); +} + +#define PFXSTR_IPV4_MAPPED "::ffff:" +#define PFXLEN_IPV4_MAPPED (sizeof PFXSTR_IPV4_MAPPED - 1) + +static int +str_is_ipv4mapped(const char *addr, size_t len) +{ + return len > PFXLEN_IPV4_MAPPED + && strncasecmp(PFXSTR_IPV4_MAPPED, addr, PFXLEN_IPV4_MAPPED) + == 0 + && str_is_ipv4(addr + PFXLEN_IPV4_MAPPED, len - PFXLEN_IPV4_MAPPED); +} + +/* Returns 1 if ADDR is a valid IPv6 address */ +static int +str_is_ipv6(const char *addr, size_t len) +{ + int col_count = 0; /* Number of colons */ + int dcol = 0; /* Did we encounter a double-colon? */ + int dig_count = 0; /* Number of digits in the last group */ + + for (; len; addr++, len--) { + if (!isascii (*addr)) + return 0; + else if (isxdigit(*addr)) { + if (++dig_count > 4) + return 0; + } else if (*addr == ':') { + if (col_count && dig_count == 0 && ++dcol > 1) + return 0; + if (++col_count > 7) + return 0; + dig_count = 0; + } + else + return 0; + } + return (col_count == 7 || dcol); +} + +static void +uint32_to_bytes(unsigned char *bytes, uint32_t u) +{ + int i; + + for (i = 0; i < 4; i++) { + bytes[i] = u & 0xff; + u >>= 8; + } +} + +static int +inaddr_to_bytes(int af, void *buf, unsigned char *bytes) +{ + uint32_t u; + + switch (af) { + case AF_INET: + memcpy(&u, buf, sizeof u); + uint32_to_bytes(bytes, u); + return 4; + + case AF_INET6: + memcpy(bytes, buf, 16); + return 16; + } + + abort(); +} + +/* Fill in the BUF (LEN bytes long) with a network mask corresponding to + the mask length MASKLEN */ +static void +masklen_to_netmask(unsigned char *buf, size_t len, size_t masklen) +{ + int i, cnt; + + cnt = masklen / 8; + for (i = 0; i < cnt; i++) + buf[i] = 0xff; + if (i == len) + return; + cnt = 8 - masklen % 8; + buf[i++] = (0xff >> cnt) << cnt; + for (; i < len; i++) + buf[i] = 0; +} + +#define CIDR_MAXBUFSIZE 81 + +/* Convert string STR to CIDR. Return 0 on success and -1 on failure. */ +static int +str_to_cidr(char const *str, struct cidr *cidr, char **endp) +{ + int rc; + char *ipbuf, *ipstart; + union { + struct in_addr in; + struct in6_addr in6; + } inaddr; + size_t len; + + len = strspn(str, "0123456789abcdefABCDEF.:"); + ipbuf = malloc(len + 1); + AN(ipbuf); + memcpy(ipbuf, str, len); + ipbuf[len] = 0; + str += len; + + ipstart = ipbuf; + + if (str_is_ipv4(ipstart, len)) + cidr->family = AF_INET; + else if (str_is_ipv4mapped(ipstart, len)) { + cidr->family = AF_INET; + ipstart += PFXLEN_IPV4_MAPPED; + } else if (str_is_ipv6(ipbuf, len)) + cidr->family = AF_INET6; + else { + free(ipbuf); + syslog(LOG_DAEMON|LOG_ERR, "invalid CIDR: %s", ipstart); + return -1; + } + + rc = inet_pton(cidr->family, ipstart, &inaddr); + free(ipbuf); + + if (rc != 1) { + if (rc == -1) { + syslog(LOG_DAEMON|LOG_ERR, "invalid address family"); + return -1; + } + if (rc == 0) { + syslog(LOG_DAEMON|LOG_ERR, + "invalid IPv%s address: %s", + cidr->family == AF_INET ? "4" : "6", + str - len); + return -1; + } + } + + cidr->len = inaddr_to_bytes(cidr->family, &inaddr, cidr->address); + + if (*str == '/') { + char *end; + unsigned long masklen; + + str++; + + masklen = strtoul(str, &end, 10); + if (*end == 0) { + masklen_to_netmask(cidr->netmask, cidr->len, masklen); + str = end; + } else { + size_t k = strspn(str, "0123456789abcdefABCDEF.:"); + + if ((cidr->family == AF_INET + && str_is_ipv4(str, k)) + || (cidr->family == AF_INET6 + && str_is_ipv6(str, k))) { + char mbuf[CIDR_MAXBUFSIZE]; + memcpy(mbuf, str, k); + mbuf[k] = 0; + rc = inet_pton(cidr->family, mbuf, &inaddr); + if (rc != 1) { + syslog(LOG_DAEMON|LOG_ERR, + "bad netmask: %s", mbuf); + return -1; + } + inaddr_to_bytes(cidr->family, &inaddr, + cidr->netmask); + } else { + syslog(LOG_DAEMON|LOG_ERR, + "bad CIDR (near %s)", str); + return -1; + } + } + } else + masklen_to_netmask(cidr->netmask, cidr->len, cidr->len * 8); + *endp = (char*) str; + return 0; +} + +/* A is a valid CIDR, and B is a valid IP address represented as CIDR. + Return 0 if B falls within A */ +static int +cidr_match(struct cidr *a, struct cidr *b) +{ + int i; + + if (a->family != b->family) + return 1; + for (i = 0; i < a->len; i++) { + if (a->address[i] != (b->address[i] & a->netmask[i])) + return 1; + } + return 0; +} + +static int +is_trusted_ip(CIDRHEAD *cl, char const *ipstr) +{ + struct cidr cidr, *cp; + char *endp; + + VSLIST_FOREACH(cp, cl, next) { + if (str_to_cidr(ipstr, &cidr, &endp) || *endp) + return 0; + if (cidr_match(cp, &cidr) == 0) + return 1; + } + return 0; +} + +int +VEVENT(remoteip_event)(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e) +{ + CIDRHEAD *p; + + switch (e) { + case VCL_EVENT_LOAD: + p = calloc(1, sizeof(*p)); + AN(p); + priv->priv = p; + break; + default: + break; + } + return 0; +} + +void +vmod_init(VRT_CTX, struct vmod_priv *priv, VCL_STRING s) +{ + CIDRHEAD *cl = priv->priv; + struct cidr *cidr; + char *endp; + + while (*s) { + cidr = calloc(1, sizeof(cidr[0])); + if (str_to_cidr(s, cidr, &endp)) { + free(cidr); + break; + } + VSLIST_INSERT_HEAD(cl, cidr, next); + while (*endp && isspace(*endp)) + ++endp; + if (*endp == 0) + break; + AN(*endp == ','); + ++endp; + while (*endp && isspace(*endp)) + ++endp; + s = endp; + } +} + +VCL_STRING +vmod_get(VRT_CTX, struct vmod_priv *priv, VCL_STRING hdr) +{ + CIDRHEAD *cl = priv->priv; + unsigned u; + char const *end; + char *buf; + int i = 0; + + u = WS_ReserveAll(ctx->ws); + buf = ctx->ws->f; + + end = hdr + strlen(hdr); + while (end > hdr) { + char const *p; + size_t len, j; + + for (p = end - 1; p > hdr && *p != ','; p--) + ; + len = end - p; + AN(len + 1 < u); + + i = 0; + j = 0; + if (*p == ',') + j++; + while (j < len && isspace(p[j])) + j++; + while (j < len) { + if (isspace(p[j])) + break; + buf[i++] = p[j++]; + } + buf[i++] = 0; + + if (!is_trusted_ip(cl, buf)) { + break; + } + end = p; + } + WS_Release(ctx->ws, i); + return buf; +} |