Added a whitelist capability
This commit is contained in:
parent
b64f38d52a
commit
f240382b16
4 changed files with 162 additions and 12 deletions
135
captcha.lua
135
captcha.lua
|
|
@ -34,6 +34,140 @@ do
|
|||
end
|
||||
end
|
||||
|
||||
-- ---------- whitelist ----------
|
||||
local WHITELIST_FILE = "/etc/nginx/captcha-whitelist.txt"
|
||||
local wl4, wl6 = {}, {} -- {{net=..., mask=..., bits=...}, ...}
|
||||
|
||||
local function ip4_to_n(ip)
|
||||
local a, b, c, d = ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
|
||||
if not a then return nil end
|
||||
a, b, c, d = tonumber(a), tonumber(b), tonumber(c), tonumber(d)
|
||||
for _, n in ipairs({a, b, c, d}) do
|
||||
if not n or n < 0 or n > 255 then return nil end
|
||||
end
|
||||
-- Build via arithmetic so this works on plain Lua 5.1 (no bit lib)
|
||||
return ((a * 256 + b) * 256 + c) * 256 + d
|
||||
end
|
||||
|
||||
-- Expand an IPv6 string to 8 groups of 16-bit numbers
|
||||
local function ip6_to_groups(ip)
|
||||
if not ip:find(":") then return nil end
|
||||
local head, tail = ip:match("^(.-)::(.*)$")
|
||||
local h_parts, t_parts = {}, {}
|
||||
if head then
|
||||
for g in (head .. ":"):gmatch("([^:]*):") do h_parts[#h_parts+1] = g end
|
||||
for g in (tail .. ":"):gmatch("([^:]*):") do t_parts[#t_parts+1] = g end
|
||||
if #h_parts == 1 and h_parts[1] == "" then h_parts = {} end
|
||||
if #t_parts == 1 and t_parts[1] == "" then t_parts = {} end
|
||||
else
|
||||
for g in (ip .. ":"):gmatch("([^:]*):") do h_parts[#h_parts+1] = g end
|
||||
end
|
||||
local groups = {}
|
||||
for _, g in ipairs(h_parts) do groups[#groups+1] = tonumber(g, 16) or -1 end
|
||||
local fill = 8 - #h_parts - #t_parts
|
||||
if head then
|
||||
for _ = 1, fill do groups[#groups+1] = 0 end
|
||||
end
|
||||
for _, g in ipairs(t_parts) do groups[#groups+1] = tonumber(g, 16) or -1 end
|
||||
if #groups ~= 8 then return nil end
|
||||
for _, g in ipairs(groups) do
|
||||
if g < 0 or g > 0xFFFF then return nil end
|
||||
end
|
||||
return groups
|
||||
end
|
||||
|
||||
local function load_whitelist()
|
||||
local new4, new6, count = {}, {}, 0
|
||||
local f = io.open(WHITELIST_FILE, "r")
|
||||
if not f then
|
||||
ngx.log(ngx.ERR, "captcha: cannot open whitelist ", WHITELIST_FILE)
|
||||
wl4, wl6 = {}, {}
|
||||
return
|
||||
end
|
||||
for raw in f:lines() do
|
||||
local line = raw:gsub("#.*$", ""):match("^%s*(.-)%s*$") or ""
|
||||
if line ~= "" then
|
||||
local addr, bits = line:match("^([^/]+)/(%d+)$")
|
||||
if not addr then addr = line end
|
||||
|
||||
if addr:find(":", 1, true) then
|
||||
local g = ip6_to_groups(addr)
|
||||
if g then
|
||||
bits = tonumber(bits or 128)
|
||||
if bits >= 0 and bits <= 128 then
|
||||
new6[#new6+1] = { groups = g, bits = bits }
|
||||
count = count + 1
|
||||
else
|
||||
ngx.log(ngx.ERR, "captcha: bad whitelist line: ", raw)
|
||||
end
|
||||
else
|
||||
ngx.log(ngx.ERR, "captcha: bad whitelist line: ", raw)
|
||||
end
|
||||
else
|
||||
local n = ip4_to_n(addr)
|
||||
if n then
|
||||
bits = tonumber(bits or 32)
|
||||
if bits >= 0 and bits <= 32 then
|
||||
-- mask = 2^32 - 2^(32-bits)
|
||||
local mask = (bits == 0) and 0
|
||||
or (4294967296 - 2 ^ (32 - bits))
|
||||
new4[#new4+1] = { net = n - (n % (2 ^ (32 - bits))),
|
||||
mask = mask }
|
||||
count = count + 1
|
||||
else
|
||||
ngx.log(ngx.ERR, "captcha: bad whitelist line: ", raw)
|
||||
end
|
||||
else
|
||||
ngx.log(ngx.ERR, "captcha: bad whitelist line: ", raw)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
wl4, wl6 = new4, new6
|
||||
ngx.log(ngx.ERR, "captcha: whitelist loaded, ", count, " entries")
|
||||
end
|
||||
|
||||
local function ip_whitelisted(ip)
|
||||
if not ip or ip == "" then return false end
|
||||
if ip:find(":", 1, true) then
|
||||
local g = ip6_to_groups(ip); if not g then return false end
|
||||
for _, e in ipairs(wl6) do
|
||||
local bits, ok = e.bits, true
|
||||
for i = 1, 8 do
|
||||
if bits >= 16 then
|
||||
if g[i] ~= e.groups[i] then ok = false; break end
|
||||
bits = bits - 16
|
||||
elseif bits > 0 then
|
||||
local shift = 16 - bits
|
||||
local m = 0xFFFF - (2 ^ shift - 1)
|
||||
if (g[i] - g[i] % (2 ^ shift)) ~= e.groups[i] - e.groups[i] % (2 ^ shift) then
|
||||
ok = false
|
||||
end
|
||||
break
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
if ok then return true end
|
||||
end
|
||||
return false
|
||||
else
|
||||
local n = ip4_to_n(ip); if not n then return false end
|
||||
for _, e in ipairs(wl4) do
|
||||
-- n AND mask == net → network match
|
||||
if (n - n % (4294967296 - e.mask + 0.5 - 0.5)) - (n % (2 ^ 0)) then
|
||||
-- placeholder; see real check below
|
||||
end
|
||||
-- real check using arithmetic AND via subtraction of remainder:
|
||||
local block = 4294967296 - e.mask
|
||||
if (n - n % block) == e.net then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
load_whitelist()
|
||||
|
||||
local function provider()
|
||||
if cfg.cap_site_key ~= "" and cfg.cap_secret_key ~= "" and cfg.cap_url ~= "" then
|
||||
|
|
@ -293,6 +427,7 @@ end
|
|||
-- ---------- public: gate ----------
|
||||
function M.guard()
|
||||
if ngx.var.uri == VERIFY_PATH then return end
|
||||
if ip_whitelisted(ngx.var.remote_addr) then return end
|
||||
if cookie_is_valid(ngx.var["cookie_" .. COOKIE_NAME]) then return end
|
||||
|
||||
-- (re-add any monitoring bypasses you set up earlier here)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue