|
| 1 | +local _M = {} |
| 2 | +_M.__index = _M |
| 3 | + |
| 4 | +local utils = require "utils" |
| 5 | +local datastore = require "datastore" |
| 6 | +local logger = require "logger" |
| 7 | +local cjson = require "cjson" |
| 8 | +local ipmatcher = require "resty.ipmatcher" |
| 9 | + |
| 10 | +function _M.new() |
| 11 | + local self = setmetatable({}, _M) |
| 12 | + return self, nil |
| 13 | +end |
| 14 | + |
| 15 | +function _M:init() |
| 16 | + -- Check if init is needed |
| 17 | + local init_needed, err = utils.has_variable("USE_GREYLIST", "yes") |
| 18 | + if init_needed == nil then |
| 19 | + return false, err |
| 20 | + end |
| 21 | + if not init_needed then |
| 22 | + return true, "no service uses Greylist, skipping init" |
| 23 | + end |
| 24 | + -- Read greylists |
| 25 | + local greylists = { |
| 26 | + ["IP"] = {}, |
| 27 | + ["RDNS"] = {}, |
| 28 | + ["ASN"] = {}, |
| 29 | + ["USER_AGENT"] = {}, |
| 30 | + ["URI"] = {} |
| 31 | + } |
| 32 | + local i = 0 |
| 33 | + for kind, _ in pairs(greylists) do |
| 34 | + local f, err = io.open("/opt/bunkerweb/cache/greylist/" .. kind .. ".list", "r") |
| 35 | + if f then |
| 36 | + for line in f:lines() do |
| 37 | + table.insert(greylists[kind], line) |
| 38 | + i = i + 1 |
| 39 | + end |
| 40 | + f:close() |
| 41 | + end |
| 42 | + end |
| 43 | + -- Load them into datastore |
| 44 | + local ok, err = datastore:set("plugin_greylist_list", cjson.encode(greylists)) |
| 45 | + if not ok then |
| 46 | + return false, "can't store Greylist list into datastore : " .. err |
| 47 | + end |
| 48 | + return true, "successfully loaded " .. tostring(i) .. " greylisted IP/network/rDNS/ASN/User-Agent/URI" |
| 49 | +end |
| 50 | + |
| 51 | +function _M:access() |
| 52 | + -- Check if access is needed |
| 53 | + local access_needed, err = utils.get_variable("USE_GREYLIST") |
| 54 | + if access_needed == nil then |
| 55 | + return false, err, false, nil |
| 56 | + end |
| 57 | + if access_needed ~= "yes" then |
| 58 | + return true, "Greylist not activated", false, nil |
| 59 | + end |
| 60 | + |
| 61 | + -- Check the cache |
| 62 | + local cached_ip, err = self:is_in_cache("ip" .. ngx.var.remote_addr) |
| 63 | + if cached_ip and cached_ip ~= "ok" then |
| 64 | + return true, "IP is in greylist cache (info = " .. cached_ip .. ")", false, ngx.OK |
| 65 | + end |
| 66 | + local cached_uri, err = self:is_in_cache("uri" .. ngx.var.uri) |
| 67 | + if cached_uri and cached_uri ~= "ok" then |
| 68 | + return true, "URI is in greylist cache (info = " .. cached_uri .. ")", false, ngx.OK |
| 69 | + end |
| 70 | + local cached_ua = true |
| 71 | + if ngx.var.http_user_agent then |
| 72 | + cached_ua, err = self:is_in_cache("ua" .. ngx.var.http_user_agent) |
| 73 | + if cached_ua and cached_ua ~= "ok" then |
| 74 | + return true, "User-Agent is in greylist cache (info = " .. cached_ua .. ")", false, ngx.OK |
| 75 | + end |
| 76 | + end |
| 77 | + if cached_ip and cached_uri and cached_ua then |
| 78 | + return true, "full request is in greylist cache (not greylisted)", false, nil |
| 79 | + end |
| 80 | + |
| 81 | + -- Get list |
| 82 | + local data, err = datastore:get("plugin_greylist_list") |
| 83 | + if not data then |
| 84 | + return false, "can't get Greylist list : " .. err, false, nil |
| 85 | + end |
| 86 | + local ok, greylists = pcall(cjson.decode, data) |
| 87 | + if not ok then |
| 88 | + return false, "error while decoding greylists : " .. greylists, false, nil |
| 89 | + end |
| 90 | + |
| 91 | + -- Return value |
| 92 | + local ret, ret_err = true, "success" |
| 93 | + |
| 94 | + -- Check if IP is in IP/net greylist |
| 95 | + local ip_net, err = utils.get_variable("GREYLIST_IP") |
| 96 | + if ip_net and ip_net ~= "" then |
| 97 | + for element in ip_net:gmatch("%S+") do |
| 98 | + table.insert(greylists["IP"], element) |
| 99 | + end |
| 100 | + end |
| 101 | + if not cached_ip then |
| 102 | + local ipm, err = ipmatcher.new(greylists["IP"]) |
| 103 | + if not ipm then |
| 104 | + ret = false |
| 105 | + ret_err = "can't instantiate ipmatcher " .. err |
| 106 | + else |
| 107 | + if ipm:match(ngx.var.remote_addr) then |
| 108 | + self:add_to_cache("ip" .. ngx.var.remote_addr, "ip/net") |
| 109 | + return ret, "client IP " .. ngx.var.remote_addr .. " is in greylist", false, ngx.OK |
| 110 | + end |
| 111 | + end |
| 112 | + end |
| 113 | + |
| 114 | + -- Check if rDNS is in greylist |
| 115 | + local rdns_global, err = utils.get_variable("GREYLIST_RDNS_GLOBAL") |
| 116 | + local check = true |
| 117 | + if not rdns_global then |
| 118 | + logger.log(ngx.ERR, "GREYLIST", "Error while getting GREYLIST_RDNS_GLOBAL variable : " .. err) |
| 119 | + elseif rdns_global == "yes" then |
| 120 | + check, err = utils.ip_is_global(ngx.var.remote_addr) |
| 121 | + if check == nil then |
| 122 | + logger.log(ngx.ERR, "GREYLIST", "Error while getting checking if IP is global : " .. err) |
| 123 | + end |
| 124 | + end |
| 125 | + if not cached_ip and check then |
| 126 | + local rdns, err = utils.get_rdns(ngx.var.remote_addr) |
| 127 | + if not rdns then |
| 128 | + ret = false |
| 129 | + ret_err = "error while trying to get reverse dns : " .. err |
| 130 | + else |
| 131 | + local rdns_list, err = utils.get_variable("GREYLIST_RDNS") |
| 132 | + if rdns_list and rdns_list ~= "" then |
| 133 | + for element in rdns_list:gmatch("%S+") do |
| 134 | + table.insert(greylists["RDNS"], element) |
| 135 | + end |
| 136 | + end |
| 137 | + for i, suffix in ipairs(greylists["RDNS"]) do |
| 138 | + if rdns:sub(- #suffix) == suffix then |
| 139 | + self:add_to_cache("ip" .. ngx.var.remote_addr, "rDNS " .. suffix) |
| 140 | + return ret, "client IP " .. ngx.var.remote_addr .. " is in greylist (info = rDNS " .. suffix .. ")", false, ngx.OK |
| 141 | + end |
| 142 | + end |
| 143 | + end |
| 144 | + end |
| 145 | + |
| 146 | + -- Check if ASN is in greylist |
| 147 | + if not cached_ip then |
| 148 | + if utils.ip_is_global(ngx.var.remote_addr) then |
| 149 | + local asn, err = utils.get_asn(ngx.var.remote_addr) |
| 150 | + if not asn then |
| 151 | + ret = false |
| 152 | + ret_err = "error while trying to get asn number : " .. err |
| 153 | + else |
| 154 | + local asn_list, err = utils.get_variable("GREYLIST_ASN") |
| 155 | + if asn_list and asn_list ~= "" then |
| 156 | + for element in asn_list:gmatch("%S+") do |
| 157 | + table.insert(greylists["ASN"], element) |
| 158 | + end |
| 159 | + end |
| 160 | + for i, asn_bl in ipairs(greylists["ASN"]) do |
| 161 | + if tostring(asn) == asn_bl then |
| 162 | + self:add_to_cache("ip" .. ngx.var.remote_addr, "ASN " .. tostring(asn)) |
| 163 | + return ret, "client IP " .. ngx.var.remote_addr .. " is in greylist (kind = ASN " .. tostring(asn) .. ")", false, |
| 164 | + ngx.OK |
| 165 | + end |
| 166 | + end |
| 167 | + end |
| 168 | + end |
| 169 | + end |
| 170 | + |
| 171 | + -- IP is not greylisted |
| 172 | + local ok, err = self:add_to_cache("ip" .. ngx.var.remote_addr, "ok") |
| 173 | + if not ok then |
| 174 | + ret = false |
| 175 | + ret_err = err |
| 176 | + end |
| 177 | + |
| 178 | + -- Check if User-Agent is in greylist |
| 179 | + if not cached_ua and ngx.var.http_user_agent then |
| 180 | + local ua_list, err = utils.get_variable("GREYLIST_USER_AGENT") |
| 181 | + if ua_list and ua_list ~= "" then |
| 182 | + for element in ua_list:gmatch("%S+") do |
| 183 | + table.insert(greylists["USER_AGENT"], element) |
| 184 | + end |
| 185 | + end |
| 186 | + for i, ua_bl in ipairs(greylists["USER_AGENT"]) do |
| 187 | + if ngx.var.http_user_agent:match(ua_bl) then |
| 188 | + self:add_to_cache("ua" .. ngx.var.http_user_agent, "UA " .. ua_bl) |
| 189 | + return ret, "client User-Agent " .. ngx.var.http_user_agent .. " is in greylist (matched " .. ua_bl .. ")", false, |
| 190 | + ngx.OK |
| 191 | + end |
| 192 | + end |
| 193 | + -- UA is not greylisted |
| 194 | + local ok, err = self:add_to_cache("ua" .. ngx.var.http_user_agent, "ok") |
| 195 | + if not ok then |
| 196 | + ret = false |
| 197 | + ret_err = err |
| 198 | + end |
| 199 | + end |
| 200 | + |
| 201 | + -- Check if URI is in greylist |
| 202 | + if not cached_uri then |
| 203 | + local uri_list, err = utils.get_variable("GREYLIST_URI") |
| 204 | + if uri_list and uri_list ~= "" then |
| 205 | + for element in uri_list:gmatch("%S+") do |
| 206 | + table.insert(greylists["URI"], element) |
| 207 | + end |
| 208 | + end |
| 209 | + for i, uri_bl in ipairs(greylists["URI"]) do |
| 210 | + if ngx.var.uri:match(uri_bl) then |
| 211 | + self:add_to_cache("uri" .. ngx.var.uri, "URI " .. uri_bl) |
| 212 | + return ret, "client URI " .. ngx.var.uri .. " is in greylist (matched " .. uri_bl .. ")", false, ngx.OK |
| 213 | + end |
| 214 | + end |
| 215 | + end |
| 216 | + |
| 217 | + -- URI is not greylisted |
| 218 | + local ok, err = self:add_to_cache("uri" .. ngx.var.uri, "ok") |
| 219 | + if not ok then |
| 220 | + ret = false |
| 221 | + ret_err = err |
| 222 | + end |
| 223 | + |
| 224 | + return ret, "IP is not in list (error = " .. ret_err .. ")", true, utils.get_deny_status() |
| 225 | +end |
| 226 | + |
| 227 | +function _M:is_in_cache(ele) |
| 228 | + local kind, err = datastore:get("plugin_greylist_cache_" .. ngx.var.server_name .. ele) |
| 229 | + if not kind then |
| 230 | + if err ~= "not found" then |
| 231 | + logger.log(ngx.ERR, "GREYLIST", "Error while accessing cache : " .. err) |
| 232 | + end |
| 233 | + return false, err |
| 234 | + end |
| 235 | + return kind, "success" |
| 236 | +end |
| 237 | + |
| 238 | +function _M:add_to_cache(ele, kind) |
| 239 | + local ok, err = datastore:set("plugin_greylist_cache_" .. ngx.var.server_name .. ele, kind, 3600) |
| 240 | + if not ok then |
| 241 | + logger.log(ngx.ERR, "GREYLIST", "Error while adding element to cache : " .. err) |
| 242 | + return false, err |
| 243 | + end |
| 244 | + return true, "success" |
| 245 | +end |
| 246 | + |
| 247 | +return _M |
0 commit comments