aboutsummaryrefslogtreecommitdiff
path: root/src/remoteip.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/remoteip.c')
-rw-r--r--src/remoteip.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/src/remoteip.c b/src/remoteip.c
new file mode 100644
index 0000000..44f0eaf
--- /dev/null
+++ b/src/remoteip.c
@@ -0,0 +1,379 @@
+/* This file is part of Ping903
+ Copyright (C) 2020 Sergey Poznyakoff
+
+ Ping903 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.
+
+ Ping903 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 Ping903. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <microhttpd.h>
+#include "ping903.h"
+#include "json.h"
+
+/* Returns 1 if ADDR is a valid string representation of IPv4 address */
+static int
+str_is_ipv4(const char *addr)
+{
+ int dot_count = 0;
+ int digit_count = 0;
+
+ for (; *addr; addr++) {
+ 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)
+{
+ return strlen(addr) > PFXLEN_IPV4_MAPPED
+ && strncasecmp(PFXSTR_IPV4_MAPPED, addr, PFXLEN_IPV4_MAPPED)
+ == 0
+ && str_is_ipv4(addr + PFXLEN_IPV4_MAPPED);
+}
+
+/* Returns 1 if ADDR is a valid IPv6 address */
+static int
+str_is_ipv6(const char *addr)
+{
+ 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 (; *addr; addr++) {
+ 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);
+}
+
+/* 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];
+ char *str; /* String representation */
+ struct cidr *next; /* Next CIDR in list */
+};
+
+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;
+}
+
+/* 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 const *file, unsigned line)
+{
+ int rc;
+ char *ipbuf, *ipstart;
+ union {
+ struct in_addr in;
+ struct in6_addr in6;
+ } inaddr;
+ size_t len;
+ char *p;
+
+ cidr->str = estrdup(str);
+ p = strrchr(str, '/');
+ if (p)
+ len = p - str;
+ else
+ len = strlen (str);
+
+ ipbuf = emalloc(len+1);
+ memcpy(ipbuf, str, len);
+ ipbuf[len] = 0;
+ ipstart = ipbuf;
+
+ if (str_is_ipv4(ipstart))
+ cidr->family = AF_INET;
+ else if (str_is_ipv4mapped(ipstart)) {
+ cidr->family = AF_INET;
+ ipstart += PFXLEN_IPV4_MAPPED;
+ } else if (str_is_ipv6(ipbuf))
+ cidr->family = AF_INET6;
+ else {
+ free(ipbuf);
+ error("%s:%d: invalid CIDR: %s", file, line, str);
+ return -1;
+ }
+
+ rc = inet_pton(cidr->family, ipstart, &inaddr);
+ free(ipbuf);
+
+ if (rc != 1) {
+ if (rc == -1) {
+ error("%s:%d: invalid address family", file, line);
+ return -1;
+ }
+ if (rc == 0) {
+ error("%s:%d: invalid IPv%s address: %s",
+ file, line,
+ cidr->family == AF_INET ? "4" : "6",
+ str);
+ return -1;
+ }
+ }
+
+ cidr->len = inaddr_to_bytes(cidr->family, &inaddr, cidr->address);
+
+ if (p) {
+ char *end;
+ unsigned long masklen;
+
+ p++;
+
+ masklen = strtoul(p, &end, 10);
+ if (*end == 0)
+ masklen_to_netmask(cidr->netmask, cidr->len, masklen);
+ else if ((cidr->family == AF_INET && str_is_ipv4(p))
+ || (cidr->family == AF_INET6 && str_is_ipv6(ipbuf))) {
+ rc = inet_pton(cidr->family, p, &inaddr);
+ if (rc != 1) {
+ error(file, line, "bad netmask: %s",
+ file, line, p);
+ return -1;
+ }
+ inaddr_to_bytes(cidr->family, &inaddr, cidr->netmask);
+ } else {
+ error("%s:%d: bad CIDR (near %s)", file, line, p);
+ return -1;
+ }
+ } else
+ masklen_to_netmask(cidr->netmask, cidr->len, cidr->len * 8);
+ 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;
+}
+
+struct cidr *trusted_ip_list;
+
+int
+cf_trusted_ip(int mode, union cf_callback_arg *arg, void *data)
+{
+ if (mode == CF_PARSE) {
+ struct cidr *cidr = emalloc(sizeof(cidr[0]));
+
+ if (str_to_cidr(arg->input.val, cidr,
+ arg->input.file, arg->input.line)) {
+ free(cidr);
+ return CF_RET_FAIL;
+ }
+ cidr->next = trusted_ip_list;
+ trusted_ip_list = cidr;
+ } else {
+ struct cidr *cidr;
+ struct json_value *ar = json_new_array();
+
+ for (cidr = trusted_ip_list; cidr; cidr = cidr->next) {
+ struct json_value *v;
+
+ if (!(v = json_new_string(cidr->str)))
+ goto err;
+ if (json_array_append(ar, v)) {
+ json_value_free(v);
+ goto err;
+ }
+ }
+
+ arg->output = ar;
+ return CF_RET_OK;
+ err:
+ json_value_free(ar);
+ return CF_RET_FAIL;
+ }
+ return CF_RET_OK;
+}
+
+static int
+is_trusted_ip(char const *ipstr)
+{
+ struct cidr cidr;
+ struct cidr *p;
+
+ if (str_to_cidr(ipstr, &cidr, __FILE__, __LINE__))
+ return 0;
+ for (p = trusted_ip_list; p; p = p->next) {
+ if (cidr_match(p, &cidr) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static char *
+scan_forwarded_header(char const *hdr)
+{
+ char const *end;
+ char *buf = NULL;
+ size_t bufsize = 0;
+
+ end = hdr + strlen(hdr);
+ while (end > hdr) {
+ char const *p;
+ size_t len, i, j;
+
+ for (p = end - 1; p > hdr && *p != ','; p--)
+ ;
+ len = end - p;
+ if (len + 1 > bufsize) {
+ char *newbuf = realloc(buf, len + 1);
+ if (newbuf) {
+ buf = newbuf;
+ bufsize = len + 1;
+ } else {
+ free(buf);
+ return NULL;
+ }
+ }
+
+ 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(buf))
+ return buf;
+
+ end = p;
+ }
+ free(buf);
+ return 0;
+}
+
+char *
+get_remote_ip(struct MHD_Connection *conn)
+{
+ union MHD_ConnectionInfo const *ci;
+ char ipstr[NI_MAXHOST];
+ char const *hdr;
+ char *ret;
+
+ hdr = MHD_lookup_connection_value(conn,
+ MHD_HEADER_KIND,
+ "x-forwarded-from");
+ if (hdr && (ret = scan_forwarded_header(hdr)) != NULL)
+ return ret;
+
+ ci = MHD_get_connection_info(conn,
+ MHD_CONNECTION_INFO_CLIENT_ADDRESS,
+ NULL);
+ if (!ci)
+ return NULL;
+
+ if (getnameinfo(ci->client_addr, sizeof(struct sockaddr),
+ ipstr, sizeof(ipstr), NULL, 0,
+ NI_NUMERICHOST))
+ return NULL;
+ return strdup(ipstr);
+}

Return to:

Send suggestions and report system problems to the System administrator.