Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable partial masking of IP addresses in access logs #124

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/config/conf.d/access_log.conf
Expand Up @@ -13,7 +13,8 @@ accesslog.filename = log_root + "/access.log"

##
## The default format produces CLF compatible output.
## For available parameters see access.txt
## For available parameters see
## https://wiki.lighttpd.net/mod_accesslog
##
#accesslog.format = "%h %l %u %t \"%r\" %b %>s \"%{User-Agent}i\" \"%{Referer}i\""

Expand Down
74 changes: 72 additions & 2 deletions src/mod_accesslog.c
Expand Up @@ -18,6 +18,7 @@

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

Expand Down Expand Up @@ -570,8 +571,49 @@ static format_fields * mod_accesslog_process_format(const char * const format, c
} else if (FORMAT_HEADER == f->field
|| FORMAT_RESPONSE_HEADER == f->field) {
f->opt = http_header_hkey_get(BUF_PTR_LEN(fstr));
} else if (FORMAT_REMOTE_HOST == f->field) {
} else if (FORMAT_REMOTE_HOST == f->field
|| FORMAT_REMOTE_ADDR == f->field) {
f->field = FORMAT_REMOTE_ADDR;
if (!buffer_is_blank(fstr)) {
const char * const ptr = fstr->ptr;
char * tmp;
uint8_t shift = 0;
uint32_t i = 0;
uint32_t len = buffer_clen(fstr);
int32_t v;
while (i < len) {
if (0 == strncmp(ptr + i, "v4:", 3)) {
shift = 0;
} else if (0 == strncmp(ptr + i, "v6:", 3)) {
shift = 8;
} else {
log_error(srv->errh, __FILE__, __LINE__,
"invalid format %%{v[46]:numbits[,...]}a: %s", format);
mod_accesslog_free_format_fields(parsed_format);
return NULL;
}
i += 3;
v = strtol(ptr + i, &tmp, 0);
if (ERANGE == errno || EINVAL == errno || v < 0 || v > (shift ? 128 : 32)) {
log_error(srv->errh, __FILE__, __LINE__,
"invalid numeric value %%{v[46]:numbits[,...]}a: %s", format);
mod_accesslog_free_format_fields(parsed_format);
return NULL;
}
i = tmp - ptr;
if (i < len) {
if (',' != ptr[i]) {
log_error(srv->errh, __FILE__, __LINE__,
"expected , after numeric value %%{v[46]:numbits[,...]}a: %s", format);
mod_accesslog_free_format_fields(parsed_format);
return NULL;
}
++i;
}
f->opt &= ~(255 << shift);
f->opt |= v << shift;
}
}
} else if (FORMAT_REMOTE_USER == f->field) {
f->field = FORMAT_ENV;
buffer_copy_string_len(fstr, CONST_STR_LEN("REMOTE_USER"));
Expand Down Expand Up @@ -833,6 +875,31 @@ log_access_record_cold (buffer * const b, const request_st * const r,
}
}

__attribute_noinline__
static void
accesslog_append_addr_masked (buffer * const b, const request_st * const r,
int bits)
{
buffer tmp = {0};
sock_addr masked;

switch (((struct sockaddr*)r->dst_addr)->sa_family) {
case AF_INET:
#ifdef HAVE_IPV6
case AF_INET6:
#endif
if (0 != sock_addr_mask_lower_bits(&masked, (sock_addr*)r->dst_addr, bits & 0xff, (bits >> 8) & 0xff)
&& 0 == sock_addr_inet_ntop_copy_buffer(&tmp, &masked)) {
buffer_append_string_buffer(b, &tmp);
buffer_free_ptr(&tmp);
break;
}
// else fallthrough
default:
buffer_append_string_buffer(b, r->dst_addr_buf);
}
}

static int log_access_record (const request_st * const r, buffer * const b, format_fields * const parsed_format, esc_fn_t esc) {
const buffer *vb;
unix_timespec64_t ts = { 0, 0 };
Expand Down Expand Up @@ -875,7 +942,10 @@ static int log_access_record (const request_st * const r, buffer * const b, form
/*case FORMAT_REMOTE_HOST:*/
#endif
case FORMAT_REMOTE_ADDR:
buffer_append_string_buffer(b, r->dst_addr_buf);
if (!f->opt)
buffer_append_string_buffer(b, r->dst_addr_buf);
else
accesslog_append_addr_masked(b, r, f->opt);
break;
case FORMAT_HTTP_HOST:
accesslog_append_buffer(b, &r->uri.authority, esc);
Expand Down
64 changes: 64 additions & 0 deletions src/sock_addr.c
Expand Up @@ -190,6 +190,70 @@ int sock_addr_is_addr_eq_bits(const sock_addr *a, const sock_addr *b, int bits)
}
}

#ifdef HAVE_IPV6
static const sock_addr * SIXTO4_PREFIX()
{
static sock_addr prefix = {0};
if (0 == sock_addr_get_family(&prefix)) {
prefix.ipv6.sin6_family = AF_INET6;
prefix.ipv6.sin6_addr.s6_addr[0] = 0x20;
prefix.ipv6.sin6_addr.s6_addr[1] = 0x02;
}
return &prefix;
}
#endif
/** Masks (i. e. zeroes out) the rightmost bits of the given source
* address, putting the result into the given dest address.
* Returns the number of masked bits. If 0 is returned, *dest remains
* unchanged.
*/
int sock_addr_mask_lower_bits(sock_addr * dest, const sock_addr * source,
unsigned int v4bits, unsigned int v6bits)
{
int len;
unsigned int mask_bits;
unsigned char * what;
switch (sock_addr_get_family(source)) {
case AF_INET:
len = 4;
what = (unsigned char*)&dest->ipv4.sin_addr.s_addr;
mask_bits = v4bits;
break;
#ifdef HAVE_IPV6
case AF_INET6:
len = 16;
what = dest->ipv6.sin6_addr.s6_addr;
if (IN6_IS_ADDR_V4MAPPED(&source->ipv6.sin6_addr)) {
// mapped v4 address -> apply v4 mask
mask_bits = v4bits;
} else if (sock_addr_is_addr_eq_bits(source, SIXTO4_PREFIX(), 16)) {
// 6to4 address - apply v4 mask to 6to4 prefix, unless v4 mask is 0 then use v6 mask
mask_bits = v4bits != 0 ? 80 + v4bits : v6bits;
} else {
// normal v6
mask_bits = v6bits;
}
break;
#endif
default:
return 0;
Copy link
Member

@gstrauss gstrauss Mar 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a reusable function in a generic sock_addr.[ch], this should probably copy the address, too, without a mask. Maybe *dest = *source at the top. (Setting sa_family and other parts of sock_addr appears to be missing, too.) With the removal of the use of IP cache in mod_accesslog in this PR, the calling function in mod_accesslog need not check for sa_family (and should have used inline function sock_addr_get_family() anyway), and the caller and might use sock_addr.c:sock_addr_inet_ntop_append_buffer() on the masked (sock_addr *)dest. That would use the stack and avoid an allocation.

However, given other discussion, this function might be removed from the PR so these comments are not action items.

}
if (0 < mask_bits) {
unsigned int i = mask_bits;
*dest = *source;
while (--len >= 0 && i > 0) {
if (i >= 8) {
what[len] = 0;
i -= 8;
} else {
what[len] &= ~((1 << i) - 1);
i = 0;
}
}
}

return mask_bits;
}

void sock_addr_set_port (sock_addr * const restrict saddr, const unsigned short port)
{
Expand Down
2 changes: 2 additions & 0 deletions src/sock_addr.h
Expand Up @@ -50,6 +50,8 @@ int sock_addr_is_addr_port_eq (const sock_addr *saddr1, const sock_addr *saddr2)
__attribute_pure__
int sock_addr_is_addr_eq_bits(const sock_addr * restrict a, const sock_addr * restrict b, int bits);

int sock_addr_mask_lower_bits(sock_addr * dest, const sock_addr * src, unsigned int v4bits, unsigned int v6bits);

void sock_addr_set_port (sock_addr * restrict saddr, unsigned short port);

int sock_addr_assign (sock_addr * restrict saddr, int family, unsigned short nport, const void * restrict naddr);
Expand Down