diff --git a/README.md b/README.md index af774a0..ffa6a4a 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ These steps have to be performed on machine with compatible configuration (same ### Known limitations (To Do) -* Users with cookies disabled will be stuck in an infinite refresh loop (TODO: redirect with a known query param, if no cookie is specified but the query arg is set, display an error page) +* None ### Throughput

diff --git a/ngx_http_js_challenge.c b/ngx_http_js_challenge.c index 1572ddb..8e47f66 100644 --- a/ngx_http_js_challenge.c +++ b/ngx_http_js_challenge.c @@ -4,6 +4,8 @@ #include #include +#include + #define DEFAULT_SECRET "changeme" #define SHA1_MD_LEN 20 #define SHA1_STR_LEN 40 @@ -17,15 +19,51 @@ "" \ "" \ "" \ "%s" \ "" \ "" -#define DEFAULT_TITLE "Verifying your browser..." + +#define DEFAULT_TITLE "Browser Verification" + +static int is_private_ip(const char *ip) { + struct in_addr addr; + if (inet_pton(AF_INET, ip, &addr) != 1) { + return 0; // Not a valid IP address + } + + uint32_t host_addr = ntohl(addr.s_addr); + + // 10.0.0.0/8 + if ((host_addr & 0xFF000000) == 0x0A000000) { + return 1; + } + + // 172.16.0.0/12 + if ((host_addr & 0xFFF00000) == 0xAC100000) { + return 1; + } + + // 192.168.0.0/16 + if ((host_addr & 0xFFFF0000) == 0xC0A80000) { + return 1; + } + + return 0; // IP is not within the private ranges +} + typedef struct { ngx_flag_t enabled; @@ -220,7 +258,7 @@ int serve_challenge(ngx_http_request_t *r, const char *challenge, const char *ht static const ngx_str_t content_type = ngx_string("text/html;charset=utf-8"); if (html == NULL) { - html = "

Please wait...

"; + html = "

Your connection is being verified. Please wait...

"; } size_t size = snprintf((char *) buf, sizeof(buf), JS_SOLVER_TEMPLATE, title_c_str, challenge_c_str, html); @@ -347,37 +385,70 @@ static ngx_int_t ngx_http_js_challenge_handler(ngx_http_request_t *r) { return NGX_DECLINED; } + // Check if 'no_cookie' parameter is present in the query string + ngx_uint_t no_cookie_present = 0; + ngx_str_t no_cookie_arg = ngx_string("no_cookie"); + ngx_str_t value; + if (ngx_http_arg(r, no_cookie_arg.data, no_cookie_arg.len, &value) == NGX_OK) { + no_cookie_present = 1; + } + + // Handle the no_cookie case by showing a static error message + if (no_cookie_present) { + ngx_buf_t *b = ngx_create_temp_buf(r->pool, 1024); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + ngx_chain_t out; + out.buf = b; + out.next = NULL; + + b->pos = (u_char *)"

Cookies Required

Please enable cookies in your browser to continue.

"; + b->last = b->pos + strlen((char *)b->pos); + b->memory = 1; // memory of the buffer is readonly + b->last_buf = 1; // this is the last buffer in the buffer chain + + r->headers_out.status = NGX_HTTP_FORBIDDEN; + r->headers_out.content_type_len = sizeof("text/html") - 1; + r->headers_out.content_type.len = sizeof("text/html") - 1; + r->headers_out.content_type.data = (u_char *)"text/html"; + r->headers_out.content_length_n = b->last - b->pos; + + ngx_http_send_header(r); + ngx_http_output_filter(r, &out); + ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN); + return NGX_HTTP_FORBIDDEN; + } + // Check for X-REAL-IP header and fallback to connection IP if not present ngx_str_t addr = r->connection->addr_text; // Default IP ngx_list_part_t *part = &r->headers_in.headers.part; ngx_table_elt_t *header = part->elts; + for (ngx_uint_t i = 0; i < part->nelts; i++) { - if (ngx_strncasecmp(header[i].key.data, (u_char *)"X-REAL-IP", header[i].key.len) == 0 && header[i].value.len > 0) { - addr = header[i].value; // Use X-REAL-IP if available - break; + if ((ngx_strncasecmp(header[i].key.data, (u_char *)"X-REAL-IP", header[i].key.len) == 0 || + ngx_strncasecmp(header[i].key.data, (u_char *)"X-FORWARDED-FOR", header[i].key.len) == 0) && + header[i].value.len > 0 && header[i].value.len <= 39) { + // Convert ngx_str_t to NULL-terminated string for is_private_ip + char ip_str[40]; + ngx_cpystrn((u_char *)ip_str, addr.data, addr.len + 1); + if (is_private_ip(ip_str)) { + addr = header[i].value; + break; + } } if (i == part->nelts - 1 && part->next != NULL) { part = part->next; header = part->elts; - i = 0; + i = -1; } } // Extract User-Agent header - ngx_str_t user_agent = ngx_null_string; - part = &r->headers_in.headers.part; - header = part->elts; - for (ngx_uint_t i = 0; i < part->nelts; i++) { - if (ngx_strncasecmp(header[i].key.data, (u_char *)"User-Agent", header[i].key.len) == 0) { - user_agent = header[i].value; - break; - } - if (i == part->nelts - 1 && part->next != NULL) { - part = part->next; - header = part->elts; - i = 0; - } - } + ngx_str_t user_agent = {0, NULL}; // Initialize ngx_str_t with default values. + if (r && r->headers_in.user_agent) { + user_agent = r->headers_in.user_agent->value; + } unsigned long bucket = r->start_sec - (r->start_sec % conf->bucket_duration); char challenge[SHA1_STR_LEN];