/* This file is part of Ping903 Copyright (C) 2020-2023 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 . */ #include #include #include #include #include #include #include #include #include #include "ping903.h" #include "json.h" #include "defs.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(p))) { 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; } int cf_trusted_ip_heredoc(int mode, union cf_callback_arg *arg, void *data) { struct cidr *cidr; if (mode == CF_SERIALIZE) return CF_RET_IGNORE; cidr = emalloc(sizeof(cidr[0])); if (str_to_cidr(arg->heredoc.val, cidr, arg->heredoc.file, arg->heredoc.line)) { free(cidr); return CF_RET_FAIL; } cidr->next = trusted_ip_list; trusted_ip_list = cidr; 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-for"); 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); }