diff options
Diffstat (limited to 'src/remoteip.c')
-rw-r--r-- | src/remoteip.c | 379 |
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); +} |