From bed63b79625cf98f41317b62519fa8abb7a82c1e Mon Sep 17 00:00:00 2001 From: Glenn Strauss Date: Mon, 4 Apr 2016 01:27:43 -0400 Subject: [PATCH] [core] support IPv6 in $HTTP["remote-ip"] CIDR cond match (fixes #2706) x-ref: "Matching IPv6 addresses with $HTTP["remoteip"]" https://redmine.lighttpd.net/issues/2706 github: closes #52 --- src/CMakeLists.txt | 12 ++++ src/Makefile.am | 8 ++- src/configfile-glue.c | 151 +++++++++++++++++++++++++++--------------- src/test_configfile.c | 87 ++++++++++++++++++++++++ 4 files changed, 204 insertions(+), 54 deletions(-) create mode 100644 src/test_configfile.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7f465796..9094b0bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -579,6 +579,16 @@ add_executable(test_base64 ) add_test(NAME test_base64 COMMAND test_base64) +add_executable(test_configfile + test_configfile.c + buffer.c + array.c + data_string.c + keyvalue.c + log.c +) +add_test(NAME test_configfile COMMAND test_configfile) + if(HAVE_PCRE_H) target_link_libraries(lighttpd ${PCRE_LDFLAGS}) add_target_properties(lighttpd COMPILE_FLAGS ${PCRE_CFLAGS}) @@ -703,6 +713,8 @@ if(WITH_LIBUNWIND) add_target_properties(test_buffer COMPILE_FLAGS ${LIBUNWIND_CFLAGS}) target_link_libraries(test_base64 ${LIBUNWIND_LDFLAGS}) add_target_properties(test_base64 COMPILE_FLAGS ${LIBUNWIND_CFLAGS}) + target_link_libraries(test_configfile ${PCRE_LDFLAGS} ${LIBUNWIND_LDFLAGS}) + add_target_properties(test_configfile COMPILE_FLAGS ${PCRE_CFLAGS} ${LIBUNWIND_CFLAGS}) endif() if(NOT WIN32) diff --git a/src/Makefile.am b/src/Makefile.am index eaec8536..d4d0b59a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,12 +1,13 @@ AM_CFLAGS = $(FAM_CFLAGS) $(LIBUNWIND_CFLAGS) -noinst_PROGRAMS=lemon proc_open test_buffer test_base64 +noinst_PROGRAMS=lemon proc_open test_buffer test_base64 test_configfile sbin_PROGRAMS=lighttpd lighttpd-angel LEMON=$(top_builddir)/src/lemon$(EXEEXT) TESTS=\ test_buffer$(EXEEXT) \ - test_base64$(EXEEXT) + test_base64$(EXEEXT) \ + test_configfile$(EXEEXT) lemon_SOURCES=lemon.c @@ -308,6 +309,9 @@ test_buffer_LDADD = $(LIBUNWIND_LIBS) test_base64_SOURCES = test_base64.c base64.c buffer.c test_base64_LDADD = $(LIBUNWIND_LIBS) +test_configfile_SOURCES = test_configfile.c buffer.c array.c data_string.c keyvalue.c log.c +test_configfile_LDADD = $(PCRE_LIB) $(LIBUNWIND_LIBS) + noinst_HEADERS = $(hdr) EXTRA_DIST = \ mod_skeleton.c \ diff --git a/src/configfile-glue.c b/src/configfile-glue.c index f89cfd6f..ff576c3e 100644 --- a/src/configfile-glue.c +++ b/src/configfile-glue.c @@ -10,6 +10,9 @@ #include #include +#ifndef _WIN32 +#include +#endif /** * like all glue code this file contains functions which @@ -237,6 +240,97 @@ static const char* cond_result_to_string(cond_result_t cond_result) { } } +static int config_addrstr_eq_remote_ip_mask(server *srv, const char *addrstr, int nm_bits, sock_addr *rmt) { + /* special-case 0 == nm_bits to mean "all bits of the address" in addrstr */ + sock_addr val; +#ifdef HAVE_INET_PTON + if (1 == inet_pton(AF_INET, addrstr, &val.ipv4.sin_addr)) +#else + if (INADDR_NONE != (val.ipv4.sin_addr = inet_addr(addrstr))) +#endif + { + /* build netmask */ + uint32_t nm; + if (nm_bits > 32) { + log_error_write(srv, __FILE__, __LINE__, "sd", "ERROR: ipv4 netmask too large:", nm_bits); + return -1; + } + nm = htonl(~((1u << (32 - (0 != nm_bits ? nm_bits : 32))) - 1)); + + if (rmt->plain.sa_family == AF_INET) { + return ((val.ipv4.sin_addr.s_addr & nm) == (rmt->ipv4.sin_addr.s_addr & nm)); +#ifdef HAVE_IPV6 + } else if (rmt->plain.sa_family == AF_INET6 + && IN6_IS_ADDR_V4MAPPED(&rmt->ipv6.sin6_addr)) { + in_addr_t x = *(in_addr_t *)(rmt->ipv6.sin6_addr.s6_addr+12); + return ((val.ipv4.sin_addr.s_addr & nm) == (x & nm)); +#endif + } else { + return 0; + } +#if defined(HAVE_INET_PTON) && defined(HAVE_IPV6) + } else if (1 == inet_pton(AF_INET6, addrstr, &val.ipv6.sin6_addr)) { + if (nm_bits > 128) { + log_error_write(srv, __FILE__, __LINE__, "sd", "ERROR: ipv6 netmask too large:", nm_bits); + return -1; + } + if (rmt->plain.sa_family == AF_INET6) { + uint8_t *a = (uint8_t *)&val.ipv6.sin6_addr.s6_addr[0]; + uint8_t *b = (uint8_t *)&rmt->ipv6.sin6_addr.s6_addr[0]; + int match; + do { + match = (nm_bits >= 8) + ? *a++ == *b++ + : (*a >> (8 - nm_bits)) == (*b >> (8 - nm_bits)); + } while (match && (nm_bits -= 8) > 0); + return match; + } else if (rmt->plain.sa_family == AF_INET + && IN6_IS_ADDR_V4MAPPED(&val.ipv6.sin6_addr)) { + in_addr_t x = *(in_addr_t *)(val.ipv6.sin6_addr.s6_addr+12); + uint32_t nm = + htonl(~((1u << (32 - (0 != nm_bits ? (nm_bits > 96 ? nm_bits - 96 : 0) : 32))) - 1)); + return ((x & nm) == (rmt->ipv4.sin_addr.s_addr & nm)); + } else { + return 0; + } +#endif + } else { + log_error_write(srv, __FILE__, __LINE__, "ss", "ERROR: ip addr is invalid:", addrstr); + return -1; + } +} + +static int config_addrbuf_eq_remote_ip_mask(server *srv, buffer *string, char *nm_slash, sock_addr *rmt) { + char *err; + int nm_bits = strtol(nm_slash + 1, &err, 10); + size_t addrstrlen = (size_t)(nm_slash - string->ptr); + char addrstr[64]; /*(larger than INET_ADDRSTRLEN and INET6_ADDRSTRLEN)*/ + + if (*err) { + log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: non-digit found in netmask:", string, err); + return -1; + } + + if (nm_bits <= 0) { + if (*(nm_slash+1) == '\0') { + log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: no number after / ", string); + } else { + log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: invalid netmask <= 0:", string, err); + } + return -1; + } + + if (addrstrlen >= sizeof(addrstr)) { + log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: address string too long:", string); + return -1; + } + + memcpy(addrstr, string->ptr, addrstrlen); + addrstr[addrstrlen] = '\0'; + + return config_addrstr_eq_remote_ip_mask(srv, addrstr, nm_bits, rmt); +} + static cond_result_t config_check_cond_cached(server *srv, connection *con, data_config *dc); static cond_result_t config_check_cond_nocache(server *srv, connection *con, data_config *dc) { @@ -370,61 +464,14 @@ static cond_result_t config_check_cond_nocache(server *srv, connection *con, dat if ((dc->cond == CONFIG_COND_EQ || dc->cond == CONFIG_COND_NE) && - (con->dst_addr.plain.sa_family == AF_INET) && (NULL != (nm_slash = strchr(dc->string->ptr, '/')))) { - int nm_bits; - long nm; - char *err; - struct in_addr val_inp; - - if (*(nm_slash+1) == '\0') { - log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: no number after / ", dc->string); - - return COND_RESULT_FALSE; + switch (config_addrbuf_eq_remote_ip_mask(srv, dc->string, nm_slash, &con->dst_addr)) { + case 1: return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE; + case 0: return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE; + case -1: return COND_RESULT_FALSE; /*(error parsing configfile entry)*/ } - - nm_bits = strtol(nm_slash + 1, &err, 10); - - if (*err) { - log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: non-digit found in netmask:", dc->string, err); - - return COND_RESULT_FALSE; - } - - if (nm_bits > 32 || nm_bits < 0) { - log_error_write(srv, __FILE__, __LINE__, "sbs", "ERROR: invalid netmask:", dc->string, err); - - return COND_RESULT_FALSE; - } - - /* take IP convert to the native */ - buffer_copy_string_len(srv->cond_check_buf, dc->string->ptr, nm_slash - dc->string->ptr); -#ifdef __WIN32 - if (INADDR_NONE == (val_inp.s_addr = inet_addr(srv->cond_check_buf->ptr))) { - log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: ip addr is invalid:", srv->cond_check_buf); - - return COND_RESULT_FALSE; - } - -#else - if (0 == inet_aton(srv->cond_check_buf->ptr, &val_inp)) { - log_error_write(srv, __FILE__, __LINE__, "sb", "ERROR: ip addr is invalid:", srv->cond_check_buf); - - return COND_RESULT_FALSE; - } -#endif - - /* build netmask */ - nm = nm_bits ? htonl(~((1 << (32 - nm_bits)) - 1)) : 0; - - if ((val_inp.s_addr & nm) == (con->dst_addr.ipv4.sin_addr.s_addr & nm)) { - return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_TRUE : COND_RESULT_FALSE; - } else { - return (dc->cond == CONFIG_COND_EQ) ? COND_RESULT_FALSE : COND_RESULT_TRUE; - } - } else { - l = con->dst_addr_buf; } + l = con->dst_addr_buf; break; } case COMP_HTTP_SCHEME: diff --git a/src/test_configfile.c b/src/test_configfile.c new file mode 100644 index 00000000..81bc08c4 --- /dev/null +++ b/src/test_configfile.c @@ -0,0 +1,87 @@ +#include "configfile-glue.c" + +#include +#include + +const struct { + const char *string; + const char *rmtstr; + int rmtfamily; + int expect; +} rmtmask[] = { + { "1.0.0.1/1", "1.0.0.1", AF_INET, 1 } + ,{ "254.254.254.254/1", "254.0.0.1", AF_INET, 1 } + ,{ "254.254.254.252/31", "254.254.254.253", AF_INET, 1 } + ,{ "254.254.254.253/31", "254.254.254.254", AF_INET, 0 } + ,{ "254.254.254.253/32", "254.254.254.254", AF_INET, 0 } + ,{ "254.254.254.254/32", "254.254.254.254", AF_INET, 1 } + #ifdef HAVE_IPV6 + ,{ "2001::/3", "2001::1", AF_INET6, 1 } + ,{ "2f01::/5", "2701::1", AF_INET6, 0 } + ,{ "2f01::/32", "2f01::1", AF_INET6, 1 } + ,{ "2f01::/32", "2f02::1", AF_INET6, 0 } + ,{ "2001::1/127", "2001::1", AF_INET6, 1 } + ,{ "2001::1/127", "2001::2", AF_INET6, 0 } + ,{ "2001::2/128", "2001::2", AF_INET6, 1 } + ,{ "2001::2/128", "2001::3", AF_INET6, 0 } + ,{ "1.0.0.1/1", "::ffff:1.0.0.1", AF_INET6, 1 } + ,{ "254.254.254.254/1", "::ffff:254.0.0.1", AF_INET6, 1 } + ,{ "254.254.254.252/31", "::ffff:254.254.254.253", AF_INET6, 1 } + ,{ "254.254.254.253/31", "::ffff:254.254.254.254", AF_INET6, 0 } + ,{ "254.254.254.253/32", "::ffff:254.254.254.254", AF_INET6, 0 } + ,{ "254.254.254.254/32", "::ffff:254.254.254.254", AF_INET6, 1 } + ,{ "::ffff:1.0.0.1/97", "1.0.0.1", AF_INET, 1 } + ,{ "::ffff:254.254.254.254/97", "254.0.0.1", AF_INET, 1 } + ,{ "::ffff:254.254.254.252/127", "254.254.254.253", AF_INET, 1 } + ,{ "::ffff:254.254.254.253/127", "254.254.254.254", AF_INET, 0 } + ,{ "::ffff:254.254.254.253/128", "254.254.254.254", AF_INET, 0 } + ,{ "::ffff:254.254.254.254/128", "254.254.254.254", AF_INET, 1 } + #endif +}; + +static void test_configfile_addrbuf_eq_remote_ip_mask (void) { + int i, m; + buffer * const s = buffer_init(); + sock_addr rmt; + + for (i = 0; i < (int)(sizeof(rmtmask)/sizeof(rmtmask[0])); ++i) { + #ifndef HAVE_INET_PTON + rmt.ipv4.sin_family = AF_INET; + rmt.ipv4.sin_addr.s_addr = inet_addr(rmtmask[i].rmtstr); + #else + if (rmtmask[i].rmtfamily == AF_INET) { + rmt.ipv4.sin_family = AF_INET; + inet_pton(AF_INET, rmtmask[i].rmtstr, &rmt.ipv4.sin_addr); + #ifdef HAVE_IPV6 + } else if (rmtmask[i].rmtfamily == AF_INET6) { + rmt.ipv6.sin6_family = AF_INET6; + inet_pton(AF_INET6, rmtmask[i].rmtstr, &rmt.ipv6.sin6_addr); + #endif + } else { + continue; + } + #endif + buffer_copy_string(s, rmtmask[i].string); + m = config_addrbuf_eq_remote_ip_mask(NULL,s,strchr(s->ptr,'/'),&rmt); + if (m != rmtmask[i].expect) { + fprintf(stderr, "failed assertion: %s %s %s\n", + rmtmask[i].string, + rmtmask[i].expect ? "==" : "!=", + rmtmask[i].rmtstr); + exit(-1); + } + } + + buffer_free(s); +} + +int main (void) { + test_configfile_addrbuf_eq_remote_ip_mask(); + + return 0; +} + +/* + * stub functions (for linking) + */ +void fd_close_on_exec(int fd) { UNUSED(fd); };