Whitelist function to exclude IPs/networks from JS challenge

This commit is contained in:
matcap 2021-05-10 21:20:11 +02:00
parent 1e0a93a9f1
commit 2a2e4b7f15

View File

@ -27,6 +27,14 @@
#define DEFAULT_TITLE "Verifying your browser..."
#define WHITELIST_MAX_LENGTH 256
typedef struct {
uint32_t addr;
uint32_t mask;
} network_info_t;
typedef struct {
ngx_flag_t enabled;
ngx_uint_t bucket_duration;
@ -34,6 +42,9 @@ typedef struct {
ngx_str_t html_path;
ngx_str_t title;
char *html;
ngx_str_t whitelist_path;
size_t whitelist_len;
network_info_t* whitelist;
} ngx_http_js_challenge_loc_conf_t;
static ngx_int_t ngx_http_js_challenge(ngx_conf_t *cf);
@ -88,6 +99,14 @@ static ngx_command_t ngx_http_js_challenge_commands[] = {
offsetof(ngx_http_js_challenge_loc_conf_t, title),
NULL
},
{
ngx_string("js_challenge_whitelist"),
NGX_HTTP_LOC_CONF | NGX_HTTP_SRV_CONF | NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_js_challenge_loc_conf_t, whitelist_path),
NULL
},
ngx_null_command
};
@ -133,11 +152,83 @@ static void *ngx_http_js_challenge_create_loc_conf(ngx_conf_t *cf) {
conf->secret = (ngx_str_t) {0, NULL};
conf->bucket_duration = NGX_CONF_UNSET_UINT;
conf->enabled = NGX_CONF_UNSET;
conf->whitelist_len = 0;
conf->whitelist = NULL;
return conf;
}
static size_t load_whitelist( const char* file, network_info_t* list, size_t maxlen)
{
FILE* fp = NULL;
char* linebuf = NULL;
char* token;
uint32_t octets[4];
uint32_t maskbits;
size_t len = 0;
if ((fp = fopen(file, "r")) == NULL) {
// Cannot open file
return 0;
}
// Allocate 1KB for line
linebuf = malloc(1024);
if (!linebuf) {
return 0;
}
// Read line
while (fgets(linebuf, 1024, fp) != NULL) {
// If line is empty or begins with # (comment)
// skip altogether
if (strlen(linebuf) == 0 || linebuf[0] == '#') {
continue;
}
// Split by ;
token = strtok(linebuf, ";");
do
{
int res = sscanf(token, "%u.%u.%u.%u/%u",
&octets[0],
&octets[1],
&octets[2],
&octets[3],
&maskbits);
// IP address found
if (res == 4 || res == 5) {
list[len].addr = ((octets[0] << 24) & 0xFF000000)
| ((octets[1] << 16) & 0x00FF0000)
| ((octets[2] << 8) & 0x0000FF00)
| (octets[3] & 0x000000FF);
if (res == 5) {
// IPv4 network address
list[len].mask = ~((1u << (32 - maskbits)) - 1);
}
else {
// IPv4 host address
list[len].mask = 0xFFFFFFFF;
}
++len;
}
} while ((token = strtok(NULL, ";")) != NULL);
}
free(linebuf);
fclose(fp);
return len;
}
static char *ngx_http_js_challenge_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) {
ngx_http_js_challenge_loc_conf_t *prev = parent;
ngx_http_js_challenge_loc_conf_t *conf = child;
@ -147,6 +238,8 @@ static char *ngx_http_js_challenge_merge_loc_conf(ngx_conf_t *cf, void *parent,
ngx_conf_merge_str_value(conf->secret, prev->secret, DEFAULT_SECRET)
ngx_conf_merge_str_value(conf->html_path, prev->html_path, NULL)
ngx_conf_merge_str_value(conf->title, prev->title, DEFAULT_TITLE)
ngx_conf_merge_str_value(conf->whitelist_path, prev->whitelist_path, NULL)
if (conf->bucket_duration < 1) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "bucket_duration must be equal or more than 1");
@ -181,6 +274,32 @@ static char *ngx_http_js_challenge_merge_loc_conf(ngx_conf_t *cf, void *parent,
}
}
if (conf->whitelist_path.data == NULL) {
conf->whitelist_len = 0;
} else if (conf->enabled) {
// Allocate necessary memory
conf->whitelist = ngx_palloc(cf->pool,
WHITELIST_MAX_LENGTH * sizeof(network_info_t));
if (prev->whitelist != NULL) {
// Copy whitelist from previous config
// without need to reload from file
conf->whitelist_len = prev->whitelist_len;
for (size_t i=0; i<conf->whitelist_len; ++i) {
conf->whitelist[i] = prev->whitelist[i];
}
} else {
// Read whitelist file
char path[PATH_MAX];
memcpy(path, conf->whitelist_path.data, conf->whitelist_path.len);
*(path + conf->whitelist_path.len) = '\0';
conf->whitelist_len = load_whitelist(path, conf->whitelist, WHITELIST_MAX_LENGTH);
}
}
return NGX_CONF_OK;
}
@ -324,13 +443,47 @@ int get_cookie(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) {
return -1;
}
static ngx_int_t ngx_http_js_challenge_handler(ngx_http_request_t *r) {
static ngx_int_t ip_whitelist_hit (ngx_http_request_t *r, ngx_http_js_challenge_loc_conf_t* conf) {
char ipstr[16];
uint32_t ip;
//unsigned char* ipa, *ipb;
// Convert ngx_str_t to C string
memset(ipstr, 0, sizeof(ipstr));
ngx_snprintf((u_char*)ipstr, sizeof(ipstr), "%V", &r->connection->addr_text);
// Convert C string to uint32_t
if ( !inet_pton(AF_INET, ipstr, &ip) ) {
return 0;
}
// Network to host order
ip = ntohl(ip);
// Iterate through whitelist
for (size_t i = 0; i < conf->whitelist_len; ++i) {
/*
ipa = (unsigned char*) &ip;
ipb = (unsigned char*) &conf->whitelist[i].addr;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "TESTING %ud.%ud.%ud.%ud <> %ud.%ud.%ud.%ud",
ipa[0],ipa[1],ipa[2],ipa[3],ipb[0],ipb[1],ipb[2],ipb[3]);
*/
if ((ip & conf->whitelist[i].mask) == (conf->whitelist[i].addr & conf->whitelist[i].mask)) {
// Whitelist hit
return 1;
}
}
return 0;
}
static ngx_int_t ngx_http_js_challenge_handler(ngx_http_request_t *r) {
ngx_http_js_challenge_loc_conf_t *conf = ngx_http_get_module_loc_conf(r, ngx_http_js_challenge_module);
if (!conf->enabled) {
if ( !conf->enabled || (conf->enabled && ip_whitelist_hit(r, conf))) {
return NGX_DECLINED;
}
unsigned long bucket = r->start_sec - (r->start_sec % conf->bucket_duration);
ngx_str_t addr = r->connection->addr_text;