[core] parse $HTTP["remote-ip"] CIDR mask at start

parse $HTTP["remote-ip"] CIDR mask into structured data at startup

note: adds buffer_move() to configparser.y to reduce memory copying
for all config values, and is required for remote-ip to preserve the
structured data added after the config value string.  (Alternatively,
could have normalized the remote-ip value after copying into dc->string)
master
Glenn Strauss 2 years ago
parent e34ce5f217
commit 4b9da9f1e8

@ -379,57 +379,6 @@ static cond_result_t config_check_cond_cached(request_st * const r, const data_c
return config_check_cond_nocache_calc(r, dc, debug_cond, cache);
}
static int config_addrstr_eq_remote_ip_mask(request_st * const r, const char * const addrstr, const int nm_bits, sock_addr * const rmt) {
/* special-case 0 == nm_bits to mean "all bits of the address" in addrstr */
sock_addr addr;
if (1 == sock_addr_inet_pton(&addr, addrstr, AF_INET, 0)) {
if (nm_bits > 32) {
log_error(r->conf.errh, __FILE__, __LINE__, "ERROR: ipv4 netmask too large: %d", nm_bits);
return -1;
}
} else if (1 == sock_addr_inet_pton(&addr, addrstr, AF_INET6, 0)) {
if (nm_bits > 128) {
log_error(r->conf.errh, __FILE__, __LINE__, "ERROR: ipv6 netmask too large: %d", nm_bits);
return -1;
}
} else {
log_error(r->conf.errh, __FILE__, __LINE__, "ERROR: ip addr is invalid: %s", addrstr);
return -1;
}
return sock_addr_is_addr_eq_bits(&addr, rmt, nm_bits);
}
static int config_addrbuf_eq_remote_ip_mask(request_st * const r, const buffer * const string, char * const nm_slash, sock_addr * const 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(r->conf.errh, __FILE__, __LINE__, "ERROR: non-digit found in netmask: %s %s", string->ptr, err);
return -1;
}
if (nm_bits <= 0) {
if (*(nm_slash+1) == '\0') {
log_error(r->conf.errh, __FILE__, __LINE__, "ERROR: no number after / %s", string->ptr);
} else {
log_error(r->conf.errh, __FILE__, __LINE__, "ERROR: invalid netmask <= 0: %s %s", string->ptr, err);
}
return -1;
}
if (addrstrlen >= sizeof(addrstr)) {
log_error(r->conf.errh, __FILE__, __LINE__, "ERROR: address string too long: %s", string->ptr);
return -1;
}
memcpy(addrstr, string->ptr, addrstrlen);
addrstr[addrstrlen] = '\0';
return config_addrstr_eq_remote_ip_mask(r, addrstr, nm_bits, rmt);
}
static int data_config_pcre_exec(const data_config *dc, cond_cache_t *cache, const buffer *b, cond_match_t *cond_match);
static cond_result_t config_check_cond_nocache(request_st * const r, const data_config * const dc, const int debug_cond, cond_cache_t * const cache) {
@ -551,7 +500,6 @@ static cond_result_t config_check_cond_nocache(request_st * const r, const data_
break;
case COMP_HTTP_REMOTE_IP: {
char *nm_slash;
/* handle remoteip limitations
*
* "10.0.0.1" is provided for all comparisons
@ -560,15 +508,22 @@ static cond_result_t config_check_cond_nocache(request_st * const r, const data_
*
* "10.0.0.1/24"
*/
if ((dc->cond == CONFIG_COND_EQ ||
dc->cond == CONFIG_COND_NE) &&
(NULL != (nm_slash = strchr(dc->string.ptr, '/')))) {
switch (config_addrbuf_eq_remote_ip_mask(r, &dc->string, nm_slash, &r->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)*/
}
if ((dc->cond == CONFIG_COND_EQ || dc->cond == CONFIG_COND_NE)
&& dc->string.ptr[0] != '/') { /*(AF_UNIX addr is string)*/
/* compare using structure data at end of string
* (generated at startup when parsing config) */
if (debug_cond)
log_error(r->conf.errh, __FILE__, __LINE__,
"%s compare to %s", dc->comp_key, r->con->dst_addr_buf.ptr);
const sock_addr * const addr = (sock_addr *)
(((uintptr_t)dc->string.ptr + dc->string.used + 1 + 7) & ~7);
int bits = ((unsigned char *)dc->string.ptr)[dc->string.used];
int match = bits
? sock_addr_is_addr_eq_bits(addr, &r->con->dst_addr, bits)
: sock_addr_is_addr_eq(addr, &r->con->dst_addr);
return match ^ (dc->cond == CONFIG_COND_EQ)
? COND_RESULT_FALSE
: COND_RESULT_TRUE;
}
l = &r->con->dst_addr_buf;
break;

@ -13,6 +13,7 @@
#include "configfile.h"
#include "plugin.h"
#include "reqpool.h"
#include "sock_addr.h"
#include "stat_cache.h"
#include "sys-crypto.h"
@ -2393,6 +2394,74 @@ int config_parse_cmd(server *srv, config_t *context, const char *cmd) {
return ret;
}
static int config_remoteip_normalize_ipv6(buffer * const b, buffer * const tb) {
/* $HTTP["remote-ip"] IPv6 accepted with or without '[]' for config compat
* http_request_host_normalize() expects IPv6 with '[]',
* and config processing at runtime expects COMP_HTTP_REMOTE_IP
* compared without '[]', so strip '[]' after normalization */
buffer_clear(tb);
if (b->ptr[0] != '[')
buffer_append_str3(tb,
CONST_STR_LEN("["),
BUF_PTR_LEN(b),
CONST_STR_LEN("]"));
else
buffer_append_string_buffer(tb, b);
int rc = http_request_host_normalize(tb, 0);
if (0 == rc) {
/* remove surrounding '[]' */
size_t blen = buffer_clen(tb);
if (blen > 1) buffer_copy_string_len(b, tb->ptr+1, blen-2);
}
return rc;
}
int config_remoteip_normalize(buffer * const b, buffer * const tb) {
if (b->ptr[0] == '/') return 1; /*(skip AF_UNIX /path/file)*/
const char * const slash = strchr(b->ptr, '/'); /* CIDR mask */
const char * const colon = strchr(b->ptr, ':'); /* IPv6 */
unsigned long nm_bits = 0;
if (NULL != slash) {
char *nptr;
nm_bits = strtoul(slash + 1, &nptr, 10);
if (*nptr || 0 == nm_bits || nm_bits > (NULL != colon ? 128 : 32)) {
/*(also rejects (slash+1 == nptr) which results in nm_bits = 0)*/
return -1;
}
buffer_truncate(b, (size_t)(slash - b->ptr));
}
int family = colon ? AF_INET6 : AF_INET;
int rc = (family == AF_INET)
? http_request_host_normalize(b, 0)
: config_remoteip_normalize_ipv6(b, tb);
uint32_t len = buffer_clen(b); /*(save len before adding CIDR mask)*/
if (nm_bits) {
buffer_append_string_len(b, CONST_STR_LEN("/"));
buffer_append_int(b, (int)nm_bits);
}
if (0 != rc) {
return -1;
}
/* extend b to hold structured data after end of string:
* nm_bits and memory-aligned sock_addr for AF_INET or AF_INET6 (28 bytes)*/
char *after = buffer_string_prepare_append(b, 1 + 7 + 28);
++after; /*(increment to pos after string end '\0')*/
*(unsigned char *)after = (unsigned char)nm_bits;
sock_addr * const addr = (sock_addr *)(((uintptr_t)after+1+7) & ~7);
if (nm_bits) b->ptr[len] = '\0'; /*(sock_addr_inet_pton() w/o CIDR mask)*/
rc = sock_addr_inet_pton(addr, b->ptr, family, 0);
if (nm_bits) b->ptr[len] = '/';
return (1 == rc);
}
static void context_init(server *srv, config_t *context) {
context->srv = srv;
context->ok = 1;

@ -78,4 +78,7 @@ int config_parse_file(server *srv, config_t *context, const char *fn);
__attribute_cold__
int config_parse_cmd(server *srv, config_t *context, const char *cmd);
__attribute_cold__
int config_remoteip_normalize(buffer *b, buffer *tb);
#endif

@ -134,34 +134,6 @@ static data_unset *configparser_merge_data(data_unset *op1, const data_unset *op
return op1;
}
static int configparser_remoteip_normalize_compat(buffer *rvalue) {
/* $HTTP["remoteip"] IPv6 accepted with or without '[]' for config compat
* http_request_host_normalize() expects IPv6 with '[]',
* and config processing at runtime expects COMP_HTTP_REMOTE_IP
* compared without '[]', so strip '[]' after normalization */
buffer *b = buffer_init();
int rc;
if (rvalue->ptr[0] != '[') {
buffer_append_str3(b, CONST_STR_LEN("["),
BUF_PTR_LEN(rvalue),
CONST_STR_LEN("]"));
} else {
buffer_append_string_buffer(b, rvalue);
}
rc = http_request_host_normalize(b, 0);
if (0 == rc) {
/* remove surrounding '[]' */
size_t blen = buffer_clen(b);
if (blen > 1) buffer_copy_string_len(rvalue, b->ptr+1, blen-2);
}
buffer_free(b);
return rc;
}
__attribute_pure__
static comp_key_t
configparser_comp_key_id(const buffer * const obj_tag, const buffer * const comp_tag)
@ -264,40 +236,9 @@ configparser_parse_condition(config_t * const ctx, const buffer * const obj_tag,
}
else if (COMP_HTTP_REMOTE_IP == dc->comp
&& (dc->cond == CONFIG_COND_EQ || dc->cond == CONFIG_COND_NE)) {
char * const slash = strchr(rvalue->ptr, '/'); /* CIDR mask */
char * const colon = strchr(rvalue->ptr, ':'); /* IPv6 */
if (NULL != slash && slash == rvalue->ptr){/*(skip AF_UNIX /path/file)*/
}
else if (NULL != slash) {
char *nptr;
const unsigned long nm_bits = strtoul(slash + 1, &nptr, 10);
if (*nptr || 0 == nm_bits || nm_bits > (NULL != colon ? 128 : 32)) {
/*(also rejects (slash+1 == nptr) which results in nm_bits = 0)*/
fprintf(stderr, "invalid or missing netmask: %s\n", rvalue->ptr);
ctx->ok = 0;
}
else {
int rc;
buffer_truncate(rvalue, (size_t)(slash - rvalue->ptr));
rc = (NULL == colon)
? http_request_host_normalize(rvalue, 0)
: configparser_remoteip_normalize_compat(rvalue);
buffer_append_string_len(rvalue, CONST_STR_LEN("/"));
buffer_append_int(rvalue, (int)nm_bits);
if (0 != rc) {
fprintf(stderr, "invalid IP addr: %s\n", rvalue->ptr);
ctx->ok = 0;
}
}
}
else {
int rc = (NULL == colon)
? http_request_host_normalize(rvalue, 0)
: configparser_remoteip_normalize_compat(rvalue);
if (0 != rc) {
fprintf(stderr, "invalid IP addr: %s\n", rvalue->ptr);
ctx->ok = 0;
}
if (!config_remoteip_normalize(rvalue, tb)) {
fprintf(stderr, "invalid IP addr: %s\n", rvalue->ptr);
ctx->ok = 0;
}
}
else if (COMP_SERVER_SOCKET == dc->comp) {
@ -323,7 +264,7 @@ configparser_parse_condition(config_t * const ctx, const buffer * const obj_tag,
dc->ext = http_header_hkey_get(BUF_PTR_LEN(&dc->comp_tag));
}
buffer_copy_buffer(&dc->string, rvalue);
buffer_move(&dc->string, rvalue);
if (ctx->ok)
configparser_push(ctx, dc, 1);

@ -49,16 +49,36 @@ static void test_configfile_addrbuf_eq_remote_ip_mask (void) {
r.conf.errh = log_error_st_init();
r.conf.errh->errorlog_fd = -1; /* (disable) */
int i, m;
buffer * const s = buffer_init();
char *slash;
buffer * const b = buffer_init();
buffer * const tb = buffer_init();
sock_addr rmt;
for (i = 0; i < (int)(sizeof(rmtmask)/sizeof(rmtmask[0])); ++i) {
for (int i = 0; i < (int)(sizeof(rmtmask)/sizeof(rmtmask[0])); ++i) {
if (1 != sock_addr_inet_pton(&rmt, rmtmask[i].rmtstr, rmtmask[i].rmtfamily, 0)) exit(-1); /*(bad test)*/
buffer_copy_string(s, rmtmask[i].string);
slash = strchr(s->ptr,'/'); assert(slash);
m = config_addrbuf_eq_remote_ip_mask(&r, s, slash, &rmt);
buffer_copy_string(b, rmtmask[i].string);
#if 0
if (!config_remoteip_normalize(b, tb)) exit(-1); /*(bad test)*/
#else
/* modified from configfile.c:config_remoteip_normalize()
* to avoid pulling in configfile.c and all dependencies */
char *slash = strchr(b->ptr, '/');
if (NULL == slash) exit(-1); /*(bad test)*/
unsigned long nm_bits = strtoul(slash+1, NULL, 10);
uint32_t len = slash - b->ptr;
int family = strchr(b->ptr, ':') ? AF_INET6 : AF_INET;
char *after = buffer_string_prepare_append(b, 1 + 7 + 28);
++after; /*(increment to pos after string end '\0')*/
*(unsigned char *)after = (unsigned char)nm_bits;
sock_addr * const saddr = (sock_addr *)(((uintptr_t)after+1+7) & ~7);
if (nm_bits) b->ptr[len]='\0'; /*(sock_addr_inet_pton() w/o CIDR mask)*/
int rc = sock_addr_inet_pton(saddr, b->ptr, family, 0);
if (nm_bits) b->ptr[len]='/';
if (1 != rc) exit(-1); /*(bad test)*/
#endif
const sock_addr * const addr = (sock_addr *)
(((uintptr_t)b->ptr + b->used + 1 + 7) & ~7);
int bits = ((unsigned char *)b->ptr)[b->used];
int m = sock_addr_is_addr_eq_bits(addr, &rmt, bits);
if (m != rmtmask[i].expect) {
fprintf(stderr, "failed assertion: %s %s %s\n",
rmtmask[i].string,
@ -68,7 +88,8 @@ static void test_configfile_addrbuf_eq_remote_ip_mask (void) {
}
}
buffer_free(s);
buffer_free(tb);
buffer_free(b);
log_error_st_free(r.conf.errh);
}

Loading…
Cancel
Save