/* 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 .
*/
#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(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);
}