aboutsummaryrefslogtreecommitdiff
path: root/src/vmod_remoteip.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/vmod_remoteip.c')
-rw-r--r--src/vmod_remoteip.c370
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;
+}

Return to:

Send suggestions and report system problems to the System administrator.