commit 770d6da100d6d52f6abc91e9e4c87c06373ee164 Author: Onion Limited Date: Tue May 26 07:36:40 2020 +0000 Init commit diff --git a/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc b/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc new file mode 100644 index 0000000..0cdd74f --- /dev/null +++ b/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc @@ -0,0 +1,306 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +sGsEejVHxvX7/iOE3rM= +=47bK +-----END PGP PUBLIC KEY BLOCK----- diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..21c78a5 --- /dev/null +++ b/README.MD @@ -0,0 +1,110 @@ +# EndGame - Onion Service DDOS Prevention Front System + +Provided by [Dread](http://dreadytofatroptsdj6io7l3xptbet6onoyno2yv7jicoxknyazubrad.onion/) and [White House Market](http://dreadytofatroptsdj6io7l3xptbet6onoyno2yv7jicoxknyazubrad.onion/d/WhiteHouseMarket). With help from [Big Blue Market](http://dreadytofatroptsdj6io7l3xptbet6onoyno2yv7jicoxknyazubrad.onion/d/BigBlueMarket) and [Empire Market](dreadytofatroptsdj6io7l3xptbet6onoyno2yv7jicoxknyazubrad.onion/d/EmpireMarket). + +EndGame is + + - a front system designed to protect the core application servers on an onion service in a safe and private way. + - locally complied and locally run (no trusted or middle party). + - a combination of multiple different technologies working together in harmony (listed below). + - FREE FOR ALL TO USE! + - *arguably* magic ㄟ( ▔, ▔ )ㄏ + +# Main Features + + - Fully scripted and easily deploy-able (for mass scaling!) on blank Debian 10 systems. + - Full featured NGINX LUA script to filter packets and provide a captcha directly using the NGINX layer. + - Rate limiting via Tor's V3 onion service circuit ID system with secondary rate limiting based on a testcookie like system. + - Easy Configuration for both local and remote (over Tor) front systems. + - Easily configurable and change-able to meet an onion service's needs. + +It can also: + - Cause you to grow a bigger dick than the asshole DDOSER (true *figurally*, lies *probably*) + - Save you millions of dollars do to DDOSER's downing your site for ransom or for their extorting fees. + - Make it look like you know what the fuck you are doing. + +### Tech Overview + +Endgame uses a number of open source projects (and libraries) to work properly. + +Projects: +* [NGINX](https://NGINX.org/) - NGINX! A web server *obviously* to provide the packet handling, threading, and proxying. +* [Tor](https://www.torproject.org/) - Tor is free and open-source software for enabling anonymous communication. It's awesome and makes all this possible. +* [Vanguards](https://github.com/mikeperry-tor/vanguards) - A safer onion service circuit building system (to prevent some traffic analysis attacks) +* [STEM](https://stem.torproject.org/) - A python controller for Tor. +* [NYX](https://nyx.torproject.org/) - A command-line monitor for Tor (to easily check the endgame front's Tor process. +* [V3 OnionBalance](https://github.com/asn-d6/onionbalance) - A distributed DNS round-robin like system on Tor to allow load-balancing and elimiate single points of failure. +* [OpenSSL](https://www.openssl.org/) - A dependency for a lot of this projects and libraries. +* [Python3](https://www.python.org/) - A easy to work with programming language we use for background image generation. + +NGINX Modules: +* [Socks NGINX](https://github.com/yorkane/socks-NGINX-module) - A NGINX module to allow proxying to Tor onion services directly on the NGINX layer. +* [NAXSI](https://github.com/nbs-system/naxsi) - A high performance web application firewall for NGINX. +* [Headers More](https://github.com/openresty/headers-more-NGINX-module) - A module for better control of headers in NGINX. +* [Echo NGINX](https://github.com/openresty/echo-nginx-module) - A NGINX module which allows shell style commands in the NGINX configuration file. +* [LUA NGINX](https://github.com/openresty/lua-nginx-module) - The power of LUA into NGINX via a module. This allows all the scripting, packet filtering, and captcha functionality EndGame does. +* [NGINX Development Kit](https://github.com/vision5/ngx_devel_kit) - Development Kit for NGINX (dependency) + +Libraries: +* [LUAJIT2 NGINX](https://github.com/openresty/luajit2) - Just in time compiler for LUA. +* [LUA Resty String](https://github.com/openresty/lua-resty-string) - String functions for ngx_lua and LUAJIT2 +* [LUA Resty Cookie](https://github.com/cloudflare/lua-resty-cookie) - Provides cookie manipulation +* [LUA Resty Session](https://github.com/bungle/lua-resty-session) - Provides session manipulation +* [LUA Resty AES](https://github.com/c64bob/lua-resty-aes/raw/master/lib/resty/aes_functions.lua) - AES Functions file for LUA. Used for shared session cookies. +* [LUA GD](https://github.com/ittner/lua-gd/) - GD image generation bindings For LUA + +### Configuration + +EndGame requires configuration to work properly. + +The main configuration can be found at the top of the `setup.sh` file. It customizes most of the script + +There are options. Such as: +* MASTERONION - Your V3 Master OnionBalance Address **WITHOUT http://** (example: dreadytofatroptsdj6io7l3xptbet6onoyno2yv7jicoxknyazubrad.onion) +* TORAUTHPASSWORD - Password which is used for your Tor Control Port Authentication with NGINX. Alphanumeric without spaces (example: passwordIcanremembertyping) +* KEY - Alphanumeric Key for the shared front session key. Random between 64-128 would do fine. (example: isthis64charactorsalreadyicantbelieveitwowsocoolwaitnotyetohdarn) +* SALT - 8 character salt used with the key. 8 random alphanumeric characters (example: saltsalt) +* HEXCOLOR - HEX color put into the css file to be not purple but your main site's color. Any CSS hex will work. (example: #9b59b6) + +There is also some editing you need to do in the `caphtml_d.lua`, `naxsi_whitelist.rules`, `site.conf`, and `torrc` files. + +- `resty/caphtml_d.lua` - Two Base64 Images. The favicon (line 143) and main logo (line 162). You can use [this](https://base64.guru/converter/encode/image/ico) for the favicon and [this](https://base64.guru/converter/encode/image) for the main logo. +- `naxsi_whitelist.rules` - NAXSI's Whitelist Rules with some internal rules [see this](https://github.com/nbs-system/naxsi/wiki/internal-rules). To be configured for your specific application's use case. +- `site.conf` - Line 110 and 111 has the two rate limiting EndGame does. One by the circuit ID and one by the cookie. Depending on how your site calls files you may need to change these values. + - Defaulty set to 3 consistent on line 110 and 111. 110 for circuit. 111 for cookie. + - Line 245 has a nodelay burst of 6 for the circuit. Line 251 has a nodelay burst of 6 for the cookie. + - Line 268-272 socks proxy_pass system. If you want EndGame to pass the filtered request over Tor you uncomment the socks_* lines and change the exampleprivatev3onion.onion to your core webserver's private v3 address. + - Line 273 regular proxy_pass. If you have a secure local connection you want to use the regular proxy_pass for reliability and latency improvements. Just change it to your core webserver's private IP. +- `torrc` - Depending on what you set your burst as change the HiddenServiceMaxStreams value to that plus 2. + +### Setup Process + +EndGame is **HIGHLY** scripted. Which means it is important you run it on the system that it is intended for or there could be issues. Endgame is designed for `DEBIAN 10`. + +##### STEP 1: + +You need a v3 onionbalance master onion! There is a script included in the onionbalance folder. Onionbalance signed specific descriptors and publishes them to the Tor network. There is no site traffic that goes through onionbalance. As such you can put it on a low powered server or even on your core server. Recommendation is 2 CPU Cores with 2GB of RAM. + +##### STEP 2: + +After you get your onionbalance master onion you should configure the endgame script for your site with the correct variables. While EndGame is designed to work for most onion services it isn't perfect for everyone. You will need to customize it for your own needs. + +##### STEP 3: + +Transfer the files over to a blank debian 10 system with ideally 4 CPU cores and 4GB of RAM. High clocked cores are important (at least 3GHZ). Tor is single threaded with minimal hardware acceleration; getting higher performance cores will provide more resistance to attacks. + +After the files are transferred make the setup.sh file executable and run it with bash. It will do the full setup process and export an onion URL. Visit that onion and hopefully everything will work. If not look at the error logs (located in /var/logs/nginx/) and see where you messed up. + +##### STEP 4: + +Scale out. Without scaling out you are bring a knife to a gun fight. At minimum you need 3 fronts. Onionbalance v3 doesn't have distinct descriptors which means if you go past about 6-9 fronts there might be some descriptor sizing issues on the default onionbalance setup. Endgame does make it much harder to take you down but you need to scale to keep up. Otherwise your front's Tor will get overloaded and you will go down. It's a dick measuring contest between you and the attacker. By scaling out you are effectively adding more length to your dick. + +##### STEP 5: + +After scaling out with multiple fronts add all their onion addresses to onionbalance's configuration and run it. Now you can publish that onionbalance master address as an EndGame protected address. That is it. Repeat these steps as many times as needed to make as many Endgame protected addresses to outscale any DDOSER. + +### End Notes + +EndGame isn't perfect. It can't protect against introduction cell type attacks (the Tor project will need to add POW at the introduction points to fix that). But it does provide good protection and scaling which makes it much harder to take you down overall for whatever people throw at you. + +This all is a major step forward for the darknet community. Never give in to the extorting DDOSERS. You are only paying to be attacked with more power in the future. Instead stand together and say "NO". As a united front we will reach heights never seen before. \ No newline at end of file diff --git a/cap_d.css b/cap_d.css new file mode 100644 index 0000000..e397206 --- /dev/null +++ b/cap_d.css @@ -0,0 +1,3 @@ +html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}strong{font-weight:bold}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}*:focus{outline:0}input,select,textarea{border:0;box-shadow:0}html{height:100%}body{height:100%;line-height:1;background:#1A1E23;font-family:roboto, helvetica, sans-serif, arial, verdana, tahoma;font-size:16px;color:#fff}.container{width:100%;margin:0 auto;min-height:100%;position:relative;max-height:100vh;overflow:hidden}.container>.inner{position:absolute;top:50%;left:0;right:0;margin:0 auto;text-align:center;-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);transform:translateY(-50%);}.container>.inner>.logo{display:inline-block;vertical-align:middle;text-decoration:none;margin-bottom:10px}.container>.inner>.logo>.square{display:inline-block;vertical-align:middle;width:40px;height:40px;background-color:HEXCOLOR;background-size:24px 24px;background-position:center center;background-repeat:no-repeat;margin-right:10px}.container>.inner>.logo>.text{display:inline-block;vertical-align:middle;font-size:30px;color:#fff;font-weight:700}.container>.inner>.date{display:block;text-align:center;font-size:42px}.container>.inner>.date>.day{color:HEXCOLOR;font-weight:bold}.signed{display:block;width:400px;height:150px;margin-top:20px;margin:20px auto 0 auto;}.signed>textarea{margin:0 auto;width:400px;height:150px;min-width:400px;max-width:400px;display:block;padding:15px;background:#fff;border:1px solid HEXCOLOR;min-height:150px;max-height:150px;}p{margin:0 auto 20px auto;max-width:300px;}form.ddos_form .captcha-input{margin:0 auto 20px auto;display: block;width:300px;font-size:0;}form.ddos_form .captcha-input input{display:inline-block;vertical-align:top;height:50px;width:calc(100% - 150px);outline:0;border:none;font-size:16px;color:#000;padding:0 15px;line-height:50px;}form.ddos_form .captcha-input img{display:inline-block;vertical-align:top;}form.ddos_form button{border-radius:3px;display:block;width:300px;margin:0 auto;background:HEXCOLOR;cursor:pointer;color:#fff;font-size:16px;text-transform:uppercase;text-align:center;height:40px;line-height:40px;outline:0;border:none;} +.captchav2 {text-align: center;width: 100%;}.captchav2 > .imgWrap {display: inline-block;vertical-align: middle;width: 150px;height: 150px;background-size: cover;background-position: center center;}.captchav2 > .inputWrap {display: inline-block;vertical-align: middle;width: 120px;height: 120px;position: relative;margin-left: 15px;}.captchav2 > .inputWrap > div {position: absolute;width: 30px;height: 30px;cursor: pointer;border-radius: 100%;border: 2px solid #fff;} .captchav2 > .inputWrap > div.c1 { top: 0; left: 0; }.captchav2 > .inputWrap > div.c2 { top: 0; left: 0; right: 0; margin: 0 auto; }.captchav2 > .inputWrap > div.c3 { top: 0; right: 0; } .captchav2 > .inputWrap > div.c4 { top: 50%; left: 0; margin-top: -15px; }.captchav2 > .inputWrap > div.c5 { top: 50%; left: 0; right: 0; margin: -15px auto 0 auto; }.captchav2 > .inputWrap > div.c6 { top: 50%; right: 0; margin-top: -15px; } .captchav2 > .inputWrap > div.c7 { bottom: 0; left: 0; }.captchav2 > .inputWrap > div.c8 { bottom: 0; left: 0; right: 0; margin: 0 auto; }.captchav2 > .inputWrap > div.c9 { bottom: 0; right: 0; } .captchav2 > .inputWrap > input[type="checkbox"] {opacity: 0;margin: 0;padding: 0;cursor: pointer;position: absolute;width: 30px;height: 30px;z-index: 5;}.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(1) { top: 0; left: 0; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(1):checked ~ .c1 { background: #fff; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(2) { top: 0; left: 0; right: 0; margin: 0 auto; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(2):checked ~ .c2 { background: #fff; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(3) { top: 0; right: 0; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(3):checked ~ .c3 { background: #fff; } .captchav2 > .inputWrap > input[type="checkbox"]:nth-child(4) { top: 50%; left: 0; margin-top: -15px; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(4):checked ~ .c4 { background: #fff; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(5) { top: 50%; left: 0; right: 0; margin: -15px auto 0 auto; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(5):checked ~ .c5 { background: #fff; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(6) { top: 50%; right: 0; margin-top: -15px; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(6):checked ~ .c6 { background: #fff; } .captchav2 > .inputWrap > input[type="checkbox"]:nth-child(7) { bottom: 0; left: 0; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(7):checked ~ .c7 { background: #fff; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(8) { bottom: 0; left: 0; right: 0; margin: 0 auto; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(8):checked ~ .c8 { background: #fff; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(9) { bottom: 0; right: 0; }.captchav2 > .inputWrap > input[type="checkbox"]:nth-child(9):checked ~ .c9 { background: #fff; } + diff --git a/font.ttf b/font.ttf new file mode 100644 index 0000000..51a18bc Binary files /dev/null and b/font.ttf differ diff --git a/gen_background.py b/gen_background.py new file mode 100644 index 0000000..c79640d --- /dev/null +++ b/gen_background.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 -u + +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +import random +import os + + +def generate_background(): + random.seed() + unicode_chars = ( + "\u2605", + "\u2606", + "\u2663", + "\u2667", + "\u2660", + "\u2664", + "\u2662", + "\u2666", + "\u263a", + "\u263b", + "\u26aa", + "\u26ab", + "\u2b53", + "\u2b54", + "\u2b00", + "\u2b08", + "\u2780", + "\u278a", + "\u267c", + "\u267d", + "\u25b2", + "\u25b3", + ) + + unicode_max = len(unicode_chars) + try: + for i in range(0, 25): + im_cropped = Image.new('RGB', (150, 150), + (random.randrange(120, 255), random.randrange(120, 255), random.randrange(120, 255))) + origwidth, origheight = im_cropped.size + + watermark = Image.new("RGBA", im_cropped.size) + waterdraw = ImageDraw.ImageDraw(watermark, "RGBA") + number_of_shapes = random.randrange(10, 15) + for step in range(0, number_of_shapes): + fillcolor = ( + random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255), + random.randrange(240, 255)) + u_char = unicode_chars[random.randrange(0, unicode_max)] + font = ImageFont.truetype("/etc/nginx/font.ttf", random.randrange(25, 30)) + waterdraw.text((random.randrange(-10, 130), random.randrange(-10, 130)), u_char, fill=fillcolor, font=font) + im_cropped.paste(watermark, None, watermark) + im_cropped.save("/tmp/background-" + str(i) + '.jpg', format="JPEG") + + except Exception as e: + print(str(e)) + +generate_background() \ No newline at end of file diff --git a/lua/cap.lua b/lua/cap.lua new file mode 100644 index 0000000..4bf553d --- /dev/null +++ b/lua/cap.lua @@ -0,0 +1,282 @@ +-- encryption key and salt must be shared across fronts. salt must be 8 chars +local key = "encryption_key" +local salt = "salt1234" +-- for how long the captcha is valid. 120 sec is for testing, 3600 1 hour should be production. +local session_timeout = 3600 + +aes = require "resty.aes" +str = require "resty.string" +cook = require "resty.cookie" + +aes_128_cbc_sha512x1 = aes:new(key, salt, aes.cipher(128,"cbc"), aes.hash.sha512, 1) + +local cookie, err = cook:new() +if not cookie then + ngx.log(ngx.ERR, err) + return +end + +function fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function tohex(str) + return (str:gsub('.', function (c) + return string.format('%02X', string.byte(c)) + end)) +end + +caperror = nil + +-- check proxy_protocol_addr if present kill circuit if needed +local pa = "no_proxy" +if ngx.var.proxy_protocol_addr ~= nil then + pa = ngx.var.proxy_protocol_addr +end + +-- if "Host" header is invalid / missing kill circuit and return nothing +if in_array(allowed_hosts, ngx.var.http_host) == nil then + ngx.log(ngx.ERR, "Wrong host (" .. ngx.var.http_host .. ") " .. ngx.var.remote_addr .. "|" .. pa) + if pa ~= "no_proxy" then + local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + end + ngx.exit(444) +end + +-- only GET and POST requests are allowed the others are not used. HEAD for recon checker +if ngx.var.request_method ~= "POST" and ngx.var.request_method ~= "GET" and ngx.var.request_method ~= "HEAD" then + ngx.log(ngx.ERR, "Wrong request (" .. ngx.var.request_method .. ") " .. ngx.var.remote_addr .. "|" .. pa) + if pa ~= "no_proxy" then + local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + end + ngx.exit(444) +end + +-- requests without user-agent are usually invalid +if ngx.var.http_user_agent == nil then + ngx.log(ngx.ERR, "Missing user agent " .. ngx.var.remote_addr .. "|" .. pa) + if pa ~= "no_proxy" then + local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + end + ngx.exit(444) +end + +-- POST without referer is invalid. some poorly configured clients may complain about this +if ngx.var.request_method == "POST" and ngx.var.http_referer == nil then + ngx.log(ngx.ERR, "Post without referer " .. ngx.var.remote_addr .. "|" .. pa) + if pa ~= "no_proxy" then + local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + end + ngx.exit(444) +end + +-- check cookie support similar to testcookie +if ngx.var.request_method == "GET" then + local args = ngx.req.get_uri_args() + if args['tca'] == "1" then + local field, err = cookie:get("dcap") + if err or not field then + ngx.exit(403) + end +-- if cookie cannot be decrypted most likely it has been messed with + local cookdata = aes_128_cbc_sha512x1:decrypt(fromhex(field)) + if not cookdata then + ngx.header.content_type = 'text/plain' + ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.") + ngx.flush() + ngx.exit(200) + end + cooktest = split(cookdata, "|")[1] + if cooktest ~= "cap_not_solved" and cooktest ~= "captcha_solved" then + ngx.exit(403) + end + end + +-- try to set cookie. max-age is irrelevant as it can be faked and check is done against cookie content anyway. should be set to a large value otherwise it will annoy users + local field, err = cookie:get("dcap") + if err then + local tstamp = ngx.now() + local plaintext = "cap_not_solved|" .. tstamp .. "|1" + local ciphertext = tohex(aes_128_cbc_sha512x1:encrypt(plaintext)) + local ok, err = cookie:set({ + key = "dcap", value = ciphertext, path = "/", + domain = ngx.var.host, httponly = true, + max_age = 21600, + samesite = "Strict" + }) + if not ok then + ngx.log(ngx.ERR, err) + return + end + ngx.header.content_type = 'text/html' + ngx.say(" \ + \ + One moment...

") + ngx.flush() + ngx.exit(200) + end +end + +-- captcha generator functions +require "caphtml_d" + +local field, err = cookie:get("dcap") +if not field or field == nil then + displaycap() + ngx.flush() + ngx.exit(200) +end + +-- check if cookie is blacklisted by rate limiter. if it is show the client a message and exit. can get creative with this. +local blocked_cookies = ngx.shared.blocked_cookies +local bct, btcflags = blocked_cookies:get(field) +if bct then + ngx.log(ngx.ERR, "Cookie " .. field .. " blacklisted.") + ngx.header.content_type = 'text/plain' + ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.") + ngx.flush() + ngx.exit(200) +end + +if ngx.var.request_method == "POST" then + local field, err = cookie:get("dcap") + if err then + ngx.exit(403) + end + + if field then + plaintext = aes_128_cbc_sha512x1:decrypt(fromhex(field)) + if not plaintext then + ngx.header.content_type = 'text/plain' + ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.") + ngx.flush() + ngx.exit(200) + end + cookdata = split(plaintext,"|") + local expired = nil + if (tonumber(cookdata[2]) + session_timeout) < ngx.now() then + expired = true + caperror = "Session expired" + displaycap() + ngx.flush() + ngx.exit(200) + end + if cookdata[1] == "captcha_solved" and not expired then + return + end + end + +-- resty has a library for parsing POST data but it's not really needed + ngx.req.read_body() + local dataraw = ngx.req.get_body_data() + if dataraw == nil then + caperror = "You didn't submit anything. Try again." + displaycap() + ngx.flush() + ngx.exit(200) + end + + local data = ngx.req.get_body_data() + data = split(data, "&") + local sentcap = "" + for index, value in ipairs(data) do + sentcap = sentcap .. split(value,"=")[2] + end + + if field then + plaintext = aes_128_cbc_sha512x1:decrypt(fromhex(field)) + if not plaintext then + ngx.header.content_type = 'text/plain' + ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.") + ngx.flush() + ngx.exit(200) + end + cookdata = split(plaintext,"|") + + if (tonumber(cookdata[2]) + 60) < ngx.now() then + caperror = "Captcha expired" + displaycap() + ngx.flush() + ngx.exit(200) + end + + if sentcap == cookdata[3] then + local newcookdata = "" + cookdata[1] = "captcha_solved" + for k,v in pairs(cookdata) do + newcookdata = newcookdata .. "|" .. v + end + local tstamp = ngx.now() + local ciphertext = tohex(aes_128_cbc_sha512x1:encrypt(newcookdata)) + local ok, err = cookie:set({ + key = "dcap", value = ciphertext, path = "/", + domain = ngx.var.host, httponly = true, + max_age = 21600, + samesite = "Strict" + }) + if not ok then + ngx.say("cookie error") + return + end + local redirect_to = ngx.var.uri + if ngx.var.query_string ~= nil then + redirect_to = redirect_to .. "?" .. ngx.var.query_string + end + return ngx.redirect(redirect_to) + else + caperror = "You Got That Wrong. Try again" + end + + else + caperror = "Session invalid or expired" + displaycap() + ngx.flush() + ngx.exit(200) + end +end + +plaintext = aes_128_cbc_sha512x1:decrypt(fromhex(field)) +if not plaintext then + ngx.header.content_type = 'text/plain' + ngx.say("403 DDOS fliter killed your path. (You probably sent too many requests at once). Not calling you a bot, bot, but grab a new identity and try again.") + ngx.flush() + ngx.exit(200) +end +cookdata = split(plaintext,"|") + +if not cookdata then + displaycap() + ngx.flush() + ngx.exit(200) +end + +local expired = nil +if (tonumber(cookdata[2]) + session_timeout) < ngx.now() then + expired = true + caperror = "Session expired" +end + +if cookdata[1] ~= "captcha_solved" or expired then + displaycap() + ngx.flush() + ngx.exit(200) +end + diff --git a/naxsi_core.rules b/naxsi_core.rules new file mode 100644 index 0000000..6870a54 --- /dev/null +++ b/naxsi_core.rules @@ -0,0 +1,88 @@ +################################## +## INTERNAL RULES IDS:1-999 ## +################################## +#@MainRule "msg:weird request, unable to parse" id:1; +#@MainRule "msg:request too big, stored on disk and not parsed" id:2; +#@MainRule "msg:invalid hex encoding, null bytes" id:10; +#@MainRule "msg:unknown content-type" id:11; +#@MainRule "msg:invalid formatted url" id:12; +#@MainRule "msg:invalid POST format" id:13; +#@MainRule "msg:invalid POST boundary" id:14; +#@MainRule "msg:invalid JSON" id:15; +#@MainRule "msg:empty POST" id:16; +#@MainRule "msg:libinjection_sql" id:17; +#@MainRule "msg:libinjection_xss" id:18; + +################################## +## SQL Injections IDs:1000-1099 ## +################################## +MainRule "rx:select|union|update|delete|insert|table|from|ascii|hex|unhex|drop" "msg:sql keywords" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1000; +MainRule "str:\"" "msg:double quote" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8,$XSS:8" id:1001; +MainRule "str:0x" "msg:0x, possible hex encoding" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:2" id:1002; +## Hardcore rules +MainRule "str:/*" "msg:mysql comment (/*)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1003; +MainRule "str:*/" "msg:mysql comment (*/)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1004; +MainRule "str:|" "msg:mysql keyword (|)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1005; +MainRule "str:&&" "msg:mysql keyword (&&)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:8" id:1006; +## end of hardcore rules +MainRule "str:--" "msg:mysql comment (--)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1007; +MainRule "str:;" "msg:semicolon" "mz:BODY|URL|ARGS" "s:$SQL:4,$XSS:8" id:1008; +MainRule "str:=" "msg:equal sign in var, probable sql/xss" "mz:ARGS|BODY" "s:$SQL:2" id:1009; +MainRule "str:(" "msg:open parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1010; +MainRule "str:)" "msg:close parenthesis, probable sql/xss" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1011; +MainRule "str:'" "msg:simple quote" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$SQL:4,$XSS:8" id:1013; +MainRule "str:," "msg:comma" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1015; +MainRule "str:#" "msg:mysql comment (#)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1016; +MainRule "str:@@" "msg:double arobase (@@)" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$SQL:4" id:1017; + +############################### +## OBVIOUS RFI IDs:1100-1199 ## +############################### +MainRule "str:http://" "msg:http:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1100; +MainRule "str:https://" "msg:https:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1101; +MainRule "str:ftp://" "msg:ftp:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1102; +MainRule "str:php://" "msg:php:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1103; +MainRule "str:sftp://" "msg:sftp:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1104; +MainRule "str:zlib://" "msg:zlib:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1105; +MainRule "str:data://" "msg:data:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1106; +MainRule "str:glob://" "msg:glob:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1107; +MainRule "str:phar://" "msg:phar:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1108; +MainRule "str:file://" "msg:file:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1109; +MainRule "str:gopher://" "msg:gopher:// scheme" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$RFI:8" id:1110; + +####################################### +## Directory traversal IDs:1200-1299 ## +####################################### +MainRule "str:.." "msg:double dot" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1200; +MainRule "str:/etc/passwd" "msg:obvious probe" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1202; +MainRule "str:c:\\" "msg:obvious windows path" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1203; +MainRule "str:cmd.exe" "msg:obvious probe" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1204; +MainRule "str:\\" "msg:backslash" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:4" id:1205; +#MainRule "str:/" "msg:slash in args" "mz:ARGS|BODY|$HEADERS_VAR:Cookie" "s:$TRAVERSAL:2" id:1206; + +######################################## +## Cross Site Scripting IDs:1300-1399 ## +######################################## +MainRule "str:<" "msg:html open tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1302; +MainRule "str:>" "msg:html close tag" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1303; +MainRule "str:[" "msg:open square backet ([), possible js" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1310; +MainRule "str:]" "msg:close square bracket (]), possible js" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1311; +MainRule "str:~" "msg:tilde (~) character" "mz:BODY|URL|ARGS|$HEADERS_VAR:Cookie" "s:$XSS:4" id:1312; +MainRule "str:`" "msg:grave accent (`)" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1314; +MainRule "rx:%[2|3]." "msg:double encoding" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315; + +#################################### +## Evading tricks IDs: 1400-1500 ## +#################################### +MainRule "str:&#" "msg:utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400; +MainRule "str:%U" "msg:M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401; + +############################# +## File uploads: 1500-1600 ## +############################# +MainRule "rx:\.ph|\.asp|\.ht" "msg:asp/php file upload" "mz:FILE_EXT" "s:$UPLOAD:8" id:1500; + +MainRule "str:/public/uploads/" "msg:Access to uploads" "mz:URL" "s:$UWA:8" id:42000400; +MainRule "str:/public/image/" "msg:Access to image folder" "mz:URL" "s:$UWA:8" id:42000401; +MainRule "str:/public/" "msg:Access to public folder" "mz:URL" "s:$UWA:8" id:42000402; +MainRule "str:/system/" "msg:Access to system folder" "mz:URL" "s:$UWA:8" id:42000403; \ No newline at end of file diff --git a/naxsi_whitelist.rules b/naxsi_whitelist.rules new file mode 100644 index 0000000..58aa7bb --- /dev/null +++ b/naxsi_whitelist.rules @@ -0,0 +1,4 @@ +BasicRule wl:10; +BasicRule wl:20; +BasicRule wl:16; +BasicRule wl:12; \ No newline at end of file diff --git a/nginx-update.sh b/nginx-update.sh new file mode 100644 index 0000000..92fc50f --- /dev/null +++ b/nginx-update.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +apt-get update +apt-get -y upgrade + +command="nginx -v" +nginxv=$( ${command} 2>&1 ) +NGINXVERSION=$(echo $nginxv | grep -o '[0-9.]*$') +NGINXOPENSSL="1.1.1d" + +wget https://nginx.org/download/nginx-$NGINXVERSION.tar.gz +tar -xzvf nginx-$NGINXVERSION.tar.gz +cd nginx-$NGINXVERSION + +apt-get install -y build-essential zlib1g-dev libpcre3 libpcre3-dev unzip uuid-dev gcc git wget curl libgd3 libgd-dev + +wget https://github.com/apache/incubator-pagespeed-ngx/archive/latest-beta.tar.gz +tar -xzvf latest-beta.tar.gz +cd incubator-pagespeed-ngx-latest-beta +wget https://dl.google.com/dl/page-speed/psol/1.13.35.2-x64.tar.gz +tar -xzvf 1.13.35.2-x64.tar.gz +cd .. + +git clone https://github.com/yorkane/socks-nginx-module +git clone https://github.com/nbs-system/naxsi.git +git clone https://github.com/kyprizel/testcookie-nginx-module.git +wget https://www.openssl.org/source/openssl-$NGINXOPENSSL.tar.gz +tar -xzvf openssl-$NGINXOPENSSL.tar.gz +git clone https://github.com/openresty/headers-more-nginx-module.git +git clone https://github.com/openresty/echo-nginx-module.git + +#some required stuff for lua/luajit. obviously versions should be ckecked with every install/update +git clone https://github.com/openresty/lua-nginx-module +cd lua-nginx-module +git checkout v0.10.16 +cd .. +git clone https://github.com/openresty/luajit2 +cd luajit2 +git checkout v2.1-20200102 +cd .. +git clone https://github.com/vision5/ngx_devel_kit +cd luajit2 +make -j8 && make install +cd .. + +export LUAJIT_LIB=/usr/local/lib +export LUAJIT_INC=/usr/local/include/luajit-2.1 +./configure --with-cc-opt='-Wno-stringop-overflow -Wno-stringop-truncation -Wno-cast-function-type' \ +--with-ld-opt="-Wl,-rpath,/usr/local/lib" \ +--with-compat --with-openssl=openssl-$NGINXOPENSSL \ +--with-http_ssl_module \ +--add-dynamic-module=incubator-pagespeed-ngx-latest-beta \ +--add-dynamic-module=naxsi/naxsi_src --add-dynamic-module=testcookie-nginx-module \ +--add-dynamic-module=headers-more-nginx-module \ +--add-dynamic-module=socks-nginx-module \ +--add-dynamic-module=echo-nginx-module \ + --add-dynamic-module=ngx_devel_kit \ +--add-dynamic-module=lua-nginx-module + +git clone https://github.com/openresty/lua-resty-string +cd lua-resty-string +make install +cd .. + +git clone https://github.com/cloudflare/lua-resty-cookie +cd lua-resty-cookie +make install +cd .. + +git clone https://github.com/bungle/lua-resty-session +cp -a lua-resty-session/lib/resty/session* /usr/local/lib/lua/resty/ + +git clone https://github.com/ittner/lua-gd/ +cd lua-gd +gcc -o gd.so -DGD_XPM -DGD_JPEG -DGD_FONTCONFIG -DGD_FREETYPE -DGD_PNG -DGD_GIF -O2 -Wall -fPIC -fomit-frame-pointer -I/usr/local/include/luajit-2.1 -DVERSION=\"2.0.33r3\" -shared -lgd luagd.c +mv gd.so /usr/local/lib/lua/5.1/gd.so +cd .. + +wget -O /usr/local/lib/lua/resty/aes_functions.lua https://github.com/c64bob/lua-resty-aes/raw/master/lib/resty/aes_functions.lua + +#include seems to be a bit mssed up with luajit +mkdir /etc/nginx/resty +ln -s /usr/local/lib/lua/resty/ /etc/nginx/resty/ + +make -j16 modules + +cp -r objs modules +rm -R /etc/nginx/modules/modules +mv modules /etc/nginx/modules \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..e5144cb --- /dev/null +++ b/nginx.conf @@ -0,0 +1,85 @@ +user www-data; +worker_processes auto; +worker_priority -5; +worker_rlimit_nofile 1024000; +pid /run/nginx.pid; +load_module modules/modules/ngx_http_headers_more_filter_module.so; +load_module modules/modules/ngx_http_naxsi_module.so; +load_module modules/modules/ngx_http_echo_module.so; +load_module modules/modules/ngx_http_socks_module.so; +load_module modules/modules/ndk_http_module.so; +load_module modules/modules/ngx_http_lua_module.so; + +events { + worker_connections 4096; +} + +http { + + ## + # Basic Settings + ## + server_tokens off; + + # Keep Alive + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + reset_timedout_connection on; + + lua_shared_dict blocked_cookies 100M; + + # Timeouts + client_body_timeout 10s; + client_header_timeout 10s; + keepalive_timeout 240s; + send_timeout 120s; + client_max_body_size 10m; + client_body_buffer_size 10m; + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + + log_format detailed escape=json + '{' + '"timestamp": "$time_iso8601",' + '"remote_addr": "$remote_addr",' + '"upstream_addr": "$upstream_addr",' + '"connection": "$connection",' + '"connection_requests": "$connection_requests",' + '"request_time": "$request_time",' + '"upstream_response_time": "$upstream_response_time",' + '"status": "$status",' + '"upstream_status": "$upstream_status",' + '"body_bytes_sent": "$body_bytes_sent ",' + '"request": "$request",' + '"http_user_agent": "$http_user_agent"' + '}'; + #access_log /var/log/site.access.log detailed; + + proxy_redirect off; + + # Gzipping Content + gzip on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + gzip_types application/x-javascript text/css application/javascript text/javascript text/plain text/xml application/json application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype application/x-font-ttf application/xml font/eot font/opentype font/otf image/svg+xml image/vnd.microsoft.icon; + gzip_disable "MSIE [1-6]\."; + + + include /etc/nginx/mime.types; + default_type application/octet-stream; + include /etc/nginx/naxsi_core.rules; + + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "DENY"; + add_header X-Xss-Protection "1; mode=block"; + + ## + # Virtual Host Configs + ## + include /etc/nginx/sites-enabled/*; +} \ No newline at end of file diff --git a/nginx_signing.key b/nginx_signing.key new file mode 100644 index 0000000..d2258b8 --- /dev/null +++ b/nginx_signing.key @@ -0,0 +1,28 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v2.0.22 (GNU/Linux) + +mQENBE5OMmIBCAD+FPYKGriGGf7NqwKfWC83cBV01gabgVWQmZbMcFzeW+hMsgxH +W6iimD0RsfZ9oEbfJCPG0CRSZ7ppq5pKamYs2+EJ8Q2ysOFHHwpGrA2C8zyNAs4I +QxnZZIbETgcSwFtDun0XiqPwPZgyuXVm9PAbLZRbfBzm8wR/3SWygqZBBLdQk5TE +fDR+Eny/M1RVR4xClECONF9UBB2ejFdI1LD45APbP2hsN/piFByU1t7yK2gpFyRt +97WzGHn9MV5/TL7AmRPM4pcr3JacmtCnxXeCZ8nLqedoSuHFuhwyDnlAbu8I16O5 +XRrfzhrHRJFM1JnIiGmzZi6zBvH0ItfyX6ttABEBAAG0KW5naW54IHNpZ25pbmcg +a2V5IDxzaWduaW5nLWtleUBuZ2lueC5jb20+iQE+BBMBAgAoAhsDBgsJCAcDAgYV +CAIJCgsEFgIDAQIeAQIXgAUCV2K1+AUJGB4fQQAKCRCr9b2Ce9m/YloaB/9XGrol +kocm7l/tsVjaBQCteXKuwsm4XhCuAQ6YAwA1L1UheGOG/aa2xJvrXE8X32tgcTjr +KoYoXWcdxaFjlXGTt6jV85qRguUzvMOxxSEM2Dn115etN9piPl0Zz+4rkx8+2vJG +F+eMlruPXg/zd88NvyLq5gGHEsFRBMVufYmHtNfcp4okC1klWiRIRSdp4QY1wdrN +1O+/oCTl8Bzy6hcHjLIq3aoumcLxMjtBoclc/5OTioLDwSDfVx7rWyfRhcBzVbwD +oe/PD08AoAA6fxXvWjSxy+dGhEaXoTHjkCbz/l6NxrK3JFyauDgU4K4MytsZ1HDi +MgMW8hZXxszoICTTiQEcBBABAgAGBQJOTkelAAoJEKZP1bF62zmo79oH/1XDb29S +YtWp+MTJTPFEwlWRiyRuDXy3wBd/BpwBRIWfWzMs1gnCjNjk0EVBVGa2grvy9Jtx +JKMd6l/PWXVucSt+U/+GO8rBkw14SdhqxaS2l14v6gyMeUrSbY3XfToGfwHC4sa/ +Thn8X4jFaQ2XN5dAIzJGU1s5JA0tjEzUwCnmrKmyMlXZaoQVrmORGjCuH0I0aAFk +RS0UtnB9HPpxhGVbs24xXZQnZDNbUQeulFxS4uP3OLDBAeCHl+v4t/uotIad8v6J +SO93vc1evIje6lguE81HHmJn9noxPItvOvSMb2yPsE8mH4cJHRTFNSEhPW6ghmlf +Wa9ZwiVX5igxcvaIRgQQEQIABgUCTk5b0gAKCRDs8OkLLBcgg1G+AKCnacLb/+W6 +cflirUIExgZdUJqoogCeNPVwXiHEIVqithAM1pdY/gcaQZmIRgQQEQIABgUCTk5f +YQAKCRCpN2E5pSTFPnNWAJ9gUozyiS+9jf2rJvqmJSeWuCgVRwCcCUFhXRCpQO2Y +Va3l3WuB+rgKjsQ= +=EWWI +-----END PGP PUBLIC KEY BLOCK----- diff --git a/onionbalance/onionbalance.sh b/onionbalance/onionbalance.sh new file mode 100644 index 0000000..83444f8 --- /dev/null +++ b/onionbalance/onionbalance.sh @@ -0,0 +1,34 @@ +#/bin/bash + +clear +echo "Welcome To The End Game DDOS OnionBalance Setup." +sleep 0.5 +echo "Starting now!" + +apt-get update +apt-get install -y apt-transport-https lsb-release ca-certificates dirmngr git python3-setuptools python3-dev gcc libyaml-0-2 + +echo "deb https://deb.torproject.org/torproject.org buster main" >> /etc/apt/sources.list.d/tor.list +echo "deb-src https://deb.torproject.org/torproject.org buster main" >> /etc/apt/sources.list.d/tor.list +echo "deb https://deb.torproject.org/torproject.org tor-nightly-master-buster main" >> /etc/apt/sources.list.d/tor.list +echo "deb-src https://deb.torproject.org/torproject.org tor-nightly-master-buster main" >> /etc/apt/sources.list.d/tor.list + +wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --import +gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add - + +apt-get update +apt-get install -y tor nyx +apt-get install -y vanguards + +service tor stop +rm /etc/tor/torrc +mv torrc /etc/tor/torrc + +git clone https://gitlab.torproject.org/asn/onionbalance.git +cd onionbalance +python3 setup.py install + +clear +onionbalance-config --hs-version v3 -n 3 + +echo "Setup Done.You need to do configuration" diff --git a/onionbalance/torrc b/onionbalance/torrc new file mode 100644 index 0000000..293dc0c --- /dev/null +++ b/onionbalance/torrc @@ -0,0 +1,5 @@ +SocksPort 0 +ControlPort 9051 +CookieAuthentication 1 +HardwareAccel 1 +RunAsDaemon 1 \ No newline at end of file diff --git a/rc.local b/rc.local new file mode 100644 index 0000000..0946003 --- /dev/null +++ b/rc.local @@ -0,0 +1,6 @@ +#!/bin/sh -e +# This script is executed at the end of each multiuser runlevel + +/startup.sh + +exit 0 \ No newline at end of file diff --git a/resty/caphtml_d.lua b/resty/caphtml_d.lua new file mode 100644 index 0000000..0be2420 --- /dev/null +++ b/resty/caphtml_d.lua @@ -0,0 +1,203 @@ +local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +function base64_encode(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return b:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +function base64_decode(data) + data = string.gsub(data, '[^'..b..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(b:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +function in_array(tab, val) + for index, value in ipairs(tab) do + if value == val then + return true + end + end +end + +local gd = require("gd") + +function displaycap() + math.randomseed(ngx.now()) + + local img_width = 150; + local img_height = 150; + + local capgrid = {} + + local checkmin = 1 + local checkmax = 6 + local checktotal = 0 + + local sessiondice = ""; + + while checktotal < checkmin do + for i=1,9,1 do + check = math.random(0,1) + if checktotal < checkmax and check == 1 then capgrid[i] = check else capgrid[i] = 0 end + if check == 1 then + checktotal = checktotal + 1 + sessiondice = sessiondice .. tostring(i) + end + end + end + + local cookie, err = cook:new() + if not cookie then + ngx.log(ngx.ERR, err) + ngx.say("cookie error") + ngx.exit(200) + end + + local tstamp = ngx.now() + local newcookdata = "cap_not_solved|" .. tstamp .. "|" + newcookdata = newcookdata .. sessiondice + + local ciphertext = tohex(aes_128_cbc_sha512x1:encrypt(newcookdata)) + local ok, err = cookie:set({ + key = "dcap", value = ciphertext, path = "/", + domain = ngx.var.host, httponly = true, + max_age = 21600, + samesite = "Strict" + }) + if not ok then + ngx.say("cookie error") + ngx.exit(200) + end + + local symbols_zero = {'○','□','♘','♢','▽','△','♖','✧','♔','♘','♕','♗','♙','♧'}; + local symbols_one = {'●','■','♞','♦','▼','▲','♜','✦','♚','♞','♛','♝','♟','♣'}; + + + local img = gd.createFromJpeg("/tmp/background-" .. math.random(0,25) .. ".jpg") + + if img == nil then + img = gd.createTrueColor(150, 150) + local white = img:colorAllocate(255, 255, 255) + img:filledRectangle(0, 0, img_width, img_height, white) + end + + img:setThickness(1) + + -- if 0 each row will be horizontal + local draw_angle = 0 + + local current_row = 1 + local capstring = "" + for i=1,9,1 do + local symbol_id = math.random(1,14) + local fillcolor = img:colorAllocate(math.random(5,255), math.random(5,255), math.random(5,255)) + if capgrid[i] == 1 then + capstring = capstring .. symbols_one[symbol_id] + else + capstring = capstring .. symbols_zero[symbol_id] + end + capstring = capstring .. " " + if i % 3 == 0 then + if draw_angle == 1 then + angle = math.rad(math.random(0,10)) + else + angle = 0 + end + if current_row == 1 then + img:stringFT(fillcolor, "/etc/nginx/font.ttf", math.random(18,22), angle, math.random(10,50), math.random(30,60), capstring) + elseif current_row == 2 then + img:stringFT(fillcolor, "/etc/nginx/font.ttf", math.random(18,22), angle, math.random(10,50), math.random(60,90), capstring) + else + img:stringFT(fillcolor, "/etc/nginx/font.ttf", math.random(18,22), angle, math.random(10,50), math.random(100,130), capstring) + end + current_row = current_row + 1 + capstring = "" + end + end + imgbase64 = base64_encode(img:pngStrEx(6)) + + +ngx.header.content_type = 'text/html'; +ngx.say(" \ + \ +DDOS Protection \ + \ + \ + \ + \ +
\ +
\ +
\ +
\ +
dread
\ +
") +if caperror ~= nil +then +ngx.say("

Error: " .. caperror .. "

") +else +ngx.say("

Due to on-going DDOS attacks against our servers, you must complete a captcha challenge to prove you are human.

") +end + +ngx.say("
\ +
\ +
") +ngx.say("
\ + \ + \ + \ + \ + \ + \ + \ + \ +") +ngx.say("
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
") +ngx.say("
\ +
\ + \ +
\ +
\ +
\ + \ +") + +end diff --git a/resty/core.lua b/resty/core.lua new file mode 100644 index 0000000..54d9756 --- /dev/null +++ b/resty/core.lua @@ -0,0 +1,34 @@ +-- Copyright (C) Yichun Zhang (agentzh) + +local subsystem = ngx.config.subsystem + + +require "resty.core.var" +require "resty.core.worker" +require "resty.core.regex" +require "resty.core.shdict" +require "resty.core.time" +require "resty.core.hash" +require "resty.core.uri" +require "resty.core.exit" +require "resty.core.base64" + + +if subsystem == 'http' then + require "resty.core.request" + require "resty.core.response" + require "resty.core.phase" + require "resty.core.ndk" +end + + +require "resty.core.misc" +require "resty.core.ctx" + + +local base = require "resty.core.base" + + +return { + version = base.version +} diff --git a/resty/core/base.lua b/resty/core/base.lua new file mode 100644 index 0000000..30b6ab9 --- /dev/null +++ b/resty/core/base.lua @@ -0,0 +1,258 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local ffi_new = ffi.new +local error = error +local select = select +local ceil = math.ceil +local subsystem = ngx.config.subsystem + + +local str_buf_size = 4096 +local str_buf +local size_ptr +local FREE_LIST_REF = 0 + + +if subsystem == 'http' then + if not ngx.config + or not ngx.config.ngx_lua_version + or ngx.config.ngx_lua_version ~= 10016 + then + error("ngx_http_lua_module 0.10.16 required") + end + +elseif subsystem == 'stream' then + if not ngx.config + or not ngx.config.ngx_lua_version + or ngx.config.ngx_lua_version ~= 8 + then + error("ngx_stream_lua_module 0.0.8 required") + end + +else + error("ngx_http_lua_module 0.10.16 or " + .. "ngx_stream_lua_module 0.0.8 required") +end + + +if string.find(jit.version, " 2.0", 1, true) then + ngx.log(ngx.ALERT, "use of lua-resty-core with LuaJIT 2.0 is ", + "not recommended; use LuaJIT 2.1+ instead") +end + + +local ok, new_tab = pcall(require, "table.new") +if not ok then + new_tab = function (narr, nrec) return {} end +end + + +local clear_tab +ok, clear_tab = pcall(require, "table.clear") +if not ok then + local pairs = pairs + clear_tab = function (tab) + for k, _ in pairs(tab) do + tab[k] = nil + end + end +end + + +-- XXX for now LuaJIT 2.1 cannot compile require() +-- so we make the fast code path Lua only in our own +-- wrapper so that most of the require() calls in hot +-- Lua code paths can be JIT compiled. +do + local orig_require = require + local pkg_loaded = package.loaded + local function my_require(name) + local mod = pkg_loaded[name] + if mod then + return mod + end + return orig_require(name) + end + getfenv(0).require = my_require +end + + +if not pcall(ffi.typeof, "ngx_str_t") then + ffi.cdef[[ + typedef struct { + size_t len; + const unsigned char *data; + } ngx_str_t; + ]] +end + + +if subsystem == 'http' then + if not pcall(ffi.typeof, "ngx_http_request_t") then + ffi.cdef[[ + typedef struct ngx_http_request_s ngx_http_request_t; + ]] + end + + if not pcall(ffi.typeof, "ngx_http_lua_ffi_str_t") then + ffi.cdef[[ + typedef struct { + int len; + const unsigned char *data; + } ngx_http_lua_ffi_str_t; + ]] + end + +elseif subsystem == 'stream' then + if not pcall(ffi.typeof, "ngx_stream_lua_request_t") then + ffi.cdef[[ + typedef struct ngx_stream_lua_request_s ngx_stream_lua_request_t; + ]] + end + + if not pcall(ffi.typeof, "ngx_stream_lua_ffi_str_t") then + ffi.cdef[[ + typedef struct { + int len; + const unsigned char *data; + } ngx_stream_lua_ffi_str_t; + ]] + end + +else + error("unknown subsystem: " .. subsystem) +end + + +local c_buf_type = ffi.typeof("char[?]") + + +local _M = new_tab(0, 18) + + +_M.version = "0.1.17" +_M.new_tab = new_tab +_M.clear_tab = clear_tab + + +local errmsg + + +function _M.get_errmsg_ptr() + if not errmsg then + errmsg = ffi_new("char *[1]") + end + return errmsg +end + + +if not ngx then + error("no existing ngx. table found") +end + + +function _M.set_string_buf_size(size) + if size <= 0 then + return + end + if str_buf then + str_buf = nil + end + str_buf_size = ceil(size) +end + + +function _M.get_string_buf_size() + return str_buf_size +end + + +function _M.get_size_ptr() + if not size_ptr then + size_ptr = ffi_new("size_t[1]") + end + + return size_ptr +end + + +function _M.get_string_buf(size, must_alloc) + -- ngx.log(ngx.ERR, "str buf size: ", str_buf_size) + if size > str_buf_size or must_alloc then + return ffi_new(c_buf_type, size) + end + + if not str_buf then + str_buf = ffi_new(c_buf_type, str_buf_size) + end + + return str_buf +end + + +function _M.ref_in_table(tb, key) + if key == nil then + return -1 + end + local ref = tb[FREE_LIST_REF] + if ref and ref ~= 0 then + tb[FREE_LIST_REF] = tb[ref] + + else + ref = #tb + 1 + end + tb[ref] = key + + -- print("ref key_id returned ", ref) + return ref +end + + +function _M.allows_subsystem(...) + local total = select("#", ...) + + for i = 1, total do + if select(i, ...) == subsystem then + return + end + end + + error("unsupported subsystem: " .. subsystem, 2) +end + + +_M.FFI_OK = 0 +_M.FFI_NO_REQ_CTX = -100 +_M.FFI_BAD_CONTEXT = -101 +_M.FFI_ERROR = -1 +_M.FFI_AGAIN = -2 +_M.FFI_BUSY = -3 +_M.FFI_DONE = -4 +_M.FFI_DECLINED = -5 + + +do + local exdata + + ok, exdata = pcall(require, "thread.exdata") + if ok and exdata then + function _M.get_request() + local r = exdata() + if r ~= nil then + return r + end + end + + else + local getfenv = getfenv + + function _M.get_request() + return getfenv(0).__ngx_req + end + end +end + + +return _M diff --git a/resty/core/base64.lua b/resty/core/base64.lua new file mode 100644 index 0000000..8a0e463 --- /dev/null +++ b/resty/core/base64.lua @@ -0,0 +1,115 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_string = ffi.string +local ngx = ngx +local type = type +local error = error +local floor = math.floor +local tostring = tostring +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_encode_base64 +local ngx_lua_ffi_decode_base64 + + +if subsystem == "http" then + ffi.cdef[[ + size_t ngx_http_lua_ffi_encode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + int no_padding); + + int ngx_http_lua_ffi_decode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + size_t *dlen); + ]] + + ngx_lua_ffi_encode_base64 = C.ngx_http_lua_ffi_encode_base64 + ngx_lua_ffi_decode_base64 = C.ngx_http_lua_ffi_decode_base64 + +elseif subsystem == "stream" then + ffi.cdef[[ + size_t ngx_stream_lua_ffi_encode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + int no_padding); + + int ngx_stream_lua_ffi_decode_base64(const unsigned char *src, + size_t len, unsigned char *dst, + size_t *dlen); + ]] + + ngx_lua_ffi_encode_base64 = C.ngx_stream_lua_ffi_encode_base64 + ngx_lua_ffi_decode_base64 = C.ngx_stream_lua_ffi_decode_base64 +end + + +local function base64_encoded_length(len, no_padding) + return no_padding and floor((len * 8 + 5) / 6) or + floor((len + 2) / 3) * 4 +end + + +ngx.encode_base64 = function (s, no_padding) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + + local slen = #s + local no_padding_bool = false; + local no_padding_int = 0; + + if no_padding then + if no_padding ~= true then + local typ = type(no_padding) + error("bad no_padding: boolean expected, got " .. typ, 2) + end + + no_padding_bool = true + no_padding_int = 1; + end + + local dlen = base64_encoded_length(slen, no_padding_bool) + local dst = get_string_buf(dlen) + local r_dlen = ngx_lua_ffi_encode_base64(s, slen, dst, no_padding_int) + -- if dlen ~= r_dlen then error("discrepancy in len") end + return ffi_string(dst, r_dlen) +end + + +local function base64_decoded_length(len) + return floor((len + 3) / 4) * 3 +end + + +ngx.decode_base64 = function (s) + if type(s) ~= 'string' then + error("string argument only", 2) + end + local slen = #s + local dlen = base64_decoded_length(slen) + -- print("dlen: ", tonumber(dlen)) + local dst = get_string_buf(dlen) + local pdlen = get_size_ptr() + local ok = ngx_lua_ffi_decode_base64(s, slen, dst, pdlen) + if ok == 0 then + return nil + end + return ffi_string(dst, pdlen[0]) +end + + +return { + version = base.version +} diff --git a/resty/core/ctx.lua b/resty/core/ctx.lua new file mode 100644 index 0000000..0683aaa --- /dev/null +++ b/resty/core/ctx.lua @@ -0,0 +1,101 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local debug = require "debug" +local base = require "resty.core.base" +local misc = require "resty.core.misc" + + +local C = ffi.C +local register_getter = misc.register_ngx_magic_key_getter +local register_setter = misc.register_ngx_magic_key_setter +local registry = debug.getregistry() +local new_tab = base.new_tab +local ref_in_table = base.ref_in_table +local get_request = base.get_request +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_OK = base.FFI_OK +local error = error +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_get_ctx_ref +local ngx_lua_ffi_set_ctx_ref + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r); + int ngx_http_lua_ffi_set_ctx_ref(ngx_http_request_t *r, int ref); + ]] + + ngx_lua_ffi_get_ctx_ref = C.ngx_http_lua_ffi_get_ctx_ref + ngx_lua_ffi_set_ctx_ref = C.ngx_http_lua_ffi_set_ctx_ref + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_get_ctx_ref(ngx_stream_lua_request_t *r); + int ngx_stream_lua_ffi_set_ctx_ref(ngx_stream_lua_request_t *r, int ref); + ]] + + ngx_lua_ffi_get_ctx_ref = C.ngx_stream_lua_ffi_get_ctx_ref + ngx_lua_ffi_set_ctx_ref = C.ngx_stream_lua_ffi_set_ctx_ref +end + + +local _M = { + _VERSION = base.version +} + + +local function get_ctx_table() + local r = get_request() + + if not r then + error("no request found") + end + + local ctx_ref = ngx_lua_ffi_get_ctx_ref(r) + if ctx_ref == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + local ctxs = registry.ngx_lua_ctx_tables + if ctx_ref < 0 then + local ctx = new_tab(0, 4) + ctx_ref = ref_in_table(ctxs, ctx) + if ngx_lua_ffi_set_ctx_ref(r, ctx_ref) ~= FFI_OK then + return nil + end + return ctx + end + return ctxs[ctx_ref] +end +register_getter("ctx", get_ctx_table) + + +local function set_ctx_table(ctx) + local r = get_request() + + if not r then + error("no request found") + end + + local ctx_ref = ngx_lua_ffi_get_ctx_ref(r) + if ctx_ref == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + local ctxs = registry.ngx_lua_ctx_tables + if ctx_ref < 0 then + ctx_ref = ref_in_table(ctxs, ctx) + ngx_lua_ffi_set_ctx_ref(r, ctx_ref) + return + end + ctxs[ctx_ref] = ctx +end +register_setter("ctx", set_ctx_table) + + +return _M diff --git a/resty/core/exit.lua b/resty/core/exit.lua new file mode 100644 index 0000000..30a7b61 --- /dev/null +++ b/resty/core/exit.lua @@ -0,0 +1,66 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_string = ffi.string +local ngx = ngx +local error = error +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local get_request = base.get_request +local co_yield = coroutine._yield +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_exit + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, + unsigned char *err, size_t *errlen); + ]] + + ngx_lua_ffi_exit = C.ngx_http_lua_ffi_exit + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_exit(ngx_stream_lua_request_t *r, int status, + unsigned char *err, size_t *errlen); + ]] + + ngx_lua_ffi_exit = C.ngx_stream_lua_ffi_exit +end + + +local ERR_BUF_SIZE = 128 +local FFI_DONE = base.FFI_DONE + + +ngx.exit = function (rc) + local err = get_string_buf(ERR_BUF_SIZE) + local errlen = get_size_ptr() + local r = get_request() + if r == nil then + error("no request found") + end + errlen[0] = ERR_BUF_SIZE + rc = ngx_lua_ffi_exit(r, rc, err, errlen) + if rc == 0 then + -- print("yielding...") + return co_yield() + end + if rc == FFI_DONE then + return + end + error(ffi_string(err, errlen[0]), 2) +end + + +return { + version = base.version +} diff --git a/resty/core/hash.lua b/resty/core/hash.lua new file mode 100644 index 0000000..4a09b00 --- /dev/null +++ b/resty/core/hash.lua @@ -0,0 +1,110 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_new = ffi.new +local ffi_string = ffi.string +local ngx = ngx +local type = type +local error = error +local tostring = tostring +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_md5 +local ngx_lua_ffi_md5_bin +local ngx_lua_ffi_sha1_bin + + +if subsystem == "http" then + ffi.cdef[[ + void ngx_http_lua_ffi_md5_bin(const unsigned char *src, size_t len, + unsigned char *dst); + + void ngx_http_lua_ffi_md5(const unsigned char *src, size_t len, + unsigned char *dst); + + int ngx_http_lua_ffi_sha1_bin(const unsigned char *src, size_t len, + unsigned char *dst); + ]] + + ngx_lua_ffi_md5 = C.ngx_http_lua_ffi_md5 + ngx_lua_ffi_md5_bin = C.ngx_http_lua_ffi_md5_bin + ngx_lua_ffi_sha1_bin = C.ngx_http_lua_ffi_sha1_bin + +elseif subsystem == "stream" then + ffi.cdef[[ + void ngx_stream_lua_ffi_md5_bin(const unsigned char *src, size_t len, + unsigned char *dst); + + void ngx_stream_lua_ffi_md5(const unsigned char *src, size_t len, + unsigned char *dst); + + int ngx_stream_lua_ffi_sha1_bin(const unsigned char *src, size_t len, + unsigned char *dst); + ]] + + ngx_lua_ffi_md5 = C.ngx_stream_lua_ffi_md5 + ngx_lua_ffi_md5_bin = C.ngx_stream_lua_ffi_md5_bin + ngx_lua_ffi_sha1_bin = C.ngx_stream_lua_ffi_sha1_bin +end + + +local MD5_DIGEST_LEN = 16 +local md5_buf = ffi_new("unsigned char[?]", MD5_DIGEST_LEN) + +ngx.md5_bin = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + ngx_lua_ffi_md5_bin(s, #s, md5_buf) + return ffi_string(md5_buf, MD5_DIGEST_LEN) +end + + +local MD5_HEX_DIGEST_LEN = MD5_DIGEST_LEN * 2 +local md5_hex_buf = ffi_new("unsigned char[?]", MD5_HEX_DIGEST_LEN) + +ngx.md5 = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + ngx_lua_ffi_md5(s, #s, md5_hex_buf) + return ffi_string(md5_hex_buf, MD5_HEX_DIGEST_LEN) +end + + +local SHA_DIGEST_LEN = 20 +local sha_buf = ffi_new("unsigned char[?]", SHA_DIGEST_LEN) + +ngx.sha1_bin = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local ok = ngx_lua_ffi_sha1_bin(s, #s, sha_buf) + if ok == 0 then + error("SHA-1 support missing in Nginx") + end + return ffi_string(sha_buf, SHA_DIGEST_LEN) +end + + +return { + version = base.version +} diff --git a/resty/core/misc.lua b/resty/core/misc.lua new file mode 100644 index 0000000..ff7954a --- /dev/null +++ b/resty/core/misc.lua @@ -0,0 +1,240 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local base = require "resty.core.base" +local ffi = require "ffi" +local os = require "os" + + +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local ngx = ngx +local type = type +local error = error +local rawget = rawget +local rawset = rawset +local tonumber = tonumber +local setmetatable = setmetatable +local FFI_OK = base.FFI_OK +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local new_tab = base.new_tab +local get_request = base.get_request +local get_size_ptr = base.get_size_ptr +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_get_resp_status +local ngx_lua_ffi_get_conf_env +local ngx_magic_key_getters +local ngx_magic_key_setters + + +local _M = new_tab(0, 3) +local ngx_mt = new_tab(0, 2) + + +if subsystem == "http" then + ngx_magic_key_getters = new_tab(0, 4) + ngx_magic_key_setters = new_tab(0, 2) + +elseif subsystem == "stream" then + ngx_magic_key_getters = new_tab(0, 2) + ngx_magic_key_setters = new_tab(0, 1) +end + + +local function register_getter(key, func) + ngx_magic_key_getters[key] = func +end +_M.register_ngx_magic_key_getter = register_getter + + +local function register_setter(key, func) + ngx_magic_key_setters[key] = func +end +_M.register_ngx_magic_key_setter = register_setter + + +ngx_mt.__index = function (tb, key) + local f = ngx_magic_key_getters[key] + if f then + return f() + end + return rawget(tb, key) +end + + +ngx_mt.__newindex = function (tb, key, ctx) + local f = ngx_magic_key_setters[key] + if f then + return f(ctx) + end + return rawset(tb, key, ctx) +end + + +setmetatable(ngx, ngx_mt) + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_get_resp_status(ngx_http_request_t *r); + int ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int r); + int ngx_http_lua_ffi_is_subrequest(ngx_http_request_t *r); + int ngx_http_lua_ffi_headers_sent(ngx_http_request_t *r); + int ngx_http_lua_ffi_get_conf_env(const unsigned char *name, + unsigned char **env_buf, + size_t *name_len); + ]] + + + ngx_lua_ffi_get_resp_status = C.ngx_http_lua_ffi_get_resp_status + ngx_lua_ffi_get_conf_env = C.ngx_http_lua_ffi_get_conf_env + + + -- ngx.status + + + local function set_status(status) + local r = get_request() + + if not r then + error("no request found") + end + + if type(status) ~= 'number' then + status = tonumber(status) + end + + local rc = C.ngx_http_lua_ffi_set_resp_status(r, status) + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + return + end + register_setter("status", set_status) + + + -- ngx.is_subrequest + + + local function is_subreq() + local r = get_request() + + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_is_subrequest(r) + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + return rc == 1 + end + register_getter("is_subrequest", is_subreq) + + + -- ngx.headers_sent + + + local function headers_sent() + local r = get_request() + + if not r then + error("no request found") + end + + local rc = C.ngx_http_lua_ffi_headers_sent(r) + + if rc == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + return rc == 1 + end + register_getter("headers_sent", headers_sent) + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_get_resp_status(ngx_stream_lua_request_t *r); + int ngx_stream_lua_ffi_get_conf_env(const unsigned char *name, + unsigned char **env_buf, + size_t *name_len); + ]] + + ngx_lua_ffi_get_resp_status = C.ngx_stream_lua_ffi_get_resp_status + ngx_lua_ffi_get_conf_env = C.ngx_stream_lua_ffi_get_conf_env +end + + +-- ngx.status + + +local function get_status() + local r = get_request() + + if not r then + error("no request found") + end + + local rc = ngx_lua_ffi_get_resp_status(r) + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + return rc +end +register_getter("status", get_status) + + +do + local _getenv = os.getenv + local env_ptr = ffi_new("unsigned char *[1]") + + os.getenv = function (name) + local r = get_request() + if r then + -- past init_by_lua* phase now + os.getenv = _getenv + env_ptr = nil + return os.getenv(name) + end + + local size = get_string_buf_size() + env_ptr[0] = get_string_buf(size) + local name_len_ptr = get_size_ptr() + + local rc = ngx_lua_ffi_get_conf_env(name, env_ptr, name_len_ptr) + if rc == FFI_OK then + return ffi_str(env_ptr[0] + name_len_ptr[0] + 1) + end + + -- FFI_DECLINED + + local value = _getenv(name) + if value ~= nil then + return value + end + + return nil + end +end + + +_M._VERSION = base.version + + +return _M diff --git a/resty/core/ndk.lua b/resty/core/ndk.lua new file mode 100644 index 0000000..6547fe5 --- /dev/null +++ b/resty/core/ndk.lua @@ -0,0 +1,92 @@ +-- Copyright (C) by OpenResty Inc. + + +local ffi = require 'ffi' +local base = require "resty.core.base" +base.allows_subsystem('http') + + +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_new = ffi.new +local ffi_str = ffi.string +local FFI_OK = base.FFI_OK +local new_tab = base.new_tab +local get_string_buf = base.get_string_buf +local get_request = base.get_request +local setmetatable = setmetatable +local type = type +local tostring = tostring +local error = error + + +local _M = { + version = base.version +} + + +ffi.cdef[[ +typedef void * ndk_set_var_value_pt; + +int ngx_http_lua_ffi_ndk_lookup_directive(const unsigned char *var_data, + size_t var_len, ndk_set_var_value_pt *func); +int ngx_http_lua_ffi_ndk_set_var_get(ngx_http_request_t *r, + ndk_set_var_value_pt func, const unsigned char *arg_data, size_t arg_len, + ngx_http_lua_ffi_str_t *value); +]] + + +local func_p = ffi_new("void*[1]") +local ffi_str_size = ffi.sizeof("ngx_http_lua_ffi_str_t") +local ffi_str_type = ffi.typeof("ngx_http_lua_ffi_str_t*") + + +local function ndk_set_var_get(self, var) + if type(var) ~= "string" then + var = tostring(var) + end + + if C.ngx_http_lua_ffi_ndk_lookup_directive(var, #var, func_p) ~= FFI_OK then + error('ndk.set_var: directive "' .. var + .. '" not found or does not use ndk_set_var_value', 2) + end + + local func = func_p[0] + + return function (arg) + local r = get_request() + if not r then + error("no request found") + end + + if type(arg) ~= "string" then + arg = tostring(arg) + end + + local buf = get_string_buf(ffi_str_size) + local value = ffi_cast(ffi_str_type, buf) + local rc = C.ngx_http_lua_ffi_ndk_set_var_get(r, func, arg, #arg, value) + if rc ~= FFI_OK then + error("calling directive " .. var .. " failed with code " .. rc, 2) + end + + return ffi_str(value.data, value.len) + end +end + + +local function ndk_set_var_set() + error("not allowed", 2) +end + + +if ndk then + local mt = new_tab(0, 2) + mt.__newindex = ndk_set_var_set + mt.__index = ndk_set_var_get + + ndk.set_var = setmetatable(new_tab(0, 0), mt) +end + + +return _M diff --git a/resty/core/phase.lua b/resty/core/phase.lua new file mode 100644 index 0000000..d30a534 --- /dev/null +++ b/resty/core/phase.lua @@ -0,0 +1,58 @@ +local ffi = require 'ffi' +local base = require "resty.core.base" + +local C = ffi.C +local FFI_ERROR = base.FFI_ERROR +local get_request = base.get_request +local error = error +local tostring = tostring + + +ffi.cdef[[ +int ngx_http_lua_ffi_get_phase(ngx_http_request_t *r, char **err) +]] + + +local errmsg = base.get_errmsg_ptr() +local context_names = { + [0x0001] = "set", + [0x0002] = "rewrite", + [0x0004] = "access", + [0x0008] = "content", + [0x0010] = "log", + [0x0020] = "header_filter", + [0x0040] = "body_filter", + [0x0080] = "timer", + [0x0100] = "init_worker", + [0x0200] = "balancer", + [0x0400] = "ssl_cert", + [0x0800] = "ssl_session_store", + [0x1000] = "ssl_session_fetch", +} + + +function ngx.get_phase() + local r = get_request() + + -- if we have no request object, assume we are called from the "init" phase + if not r then + return "init" + end + + local context = C.ngx_http_lua_ffi_get_phase(r, errmsg) + if context == FFI_ERROR then -- NGX_ERROR + error(errmsg, 2) + end + + local phase = context_names[context] + if not phase then + error("unknown phase: " .. tostring(context)) + end + + return phase +end + + +return { + version = base.version +} diff --git a/resty/core/regex.lua b/resty/core/regex.lua new file mode 100644 index 0000000..4385b58 --- /dev/null +++ b/resty/core/regex.lua @@ -0,0 +1,1195 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" +local bit = require "bit" +require "resty.core.time" -- for ngx.now used by resty.lrucache +local lrucache = require "resty.lrucache" + +local lrucache_get = lrucache.get +local lrucache_set = lrucache.set +local ffi_string = ffi.string +local ffi_gc = ffi.gc +local ffi_copy = ffi.copy +local ffi_cast = ffi.cast +local C = ffi.C +local bor = bit.bor +local band = bit.band +local lshift = bit.lshift +local sub = string.sub +local fmt = string.format +local byte = string.byte +local ngx = ngx +local type = type +local tostring = tostring +local error = error +local setmetatable = setmetatable +local tonumber = tonumber +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local new_tab = base.new_tab +local subsystem = ngx.config.subsystem +local ngx_phase = ngx.get_phase +local ngx_log = ngx.log +local ngx_NOTICE = ngx.NOTICE + + +local _M = { + version = base.version +} + + +ngx.re = new_tab(0, 5) + + +ffi.cdef[[ + const char *pcre_version(void); +]] + + +local pcre_ver + +if not pcall(function() pcre_ver = ffi_string(C.pcre_version()) end) then + setmetatable(ngx.re, { + __index = function(_, key) + error("no support for 'ngx.re." .. key .. "': OpenResty was " .. + "compiled without PCRE support", 2) + end + }) + + _M.no_pcre = true + + return _M +end + + +local MAX_ERR_MSG_LEN = 128 + + +local FLAG_COMPILE_ONCE = 0x01 +local FLAG_DFA = 0x02 +local FLAG_JIT = 0x04 +local FLAG_DUPNAMES = 0x08 +local FLAG_NO_UTF8_CHECK = 0x10 + + +local PCRE_CASELESS = 0x0000001 +local PCRE_MULTILINE = 0x0000002 +local PCRE_DOTALL = 0x0000004 +local PCRE_EXTENDED = 0x0000008 +local PCRE_ANCHORED = 0x0000010 +local PCRE_UTF8 = 0x0000800 +local PCRE_DUPNAMES = 0x0080000 +local PCRE_JAVASCRIPT_COMPAT = 0x2000000 + + +local PCRE_ERROR_NOMATCH = -1 + + +local regex_match_cache +local regex_sub_func_cache = new_tab(0, 4) +local regex_sub_str_cache = new_tab(0, 4) +local max_regex_cache_size +local regex_cache_size = 0 +local script_engine +local ngx_lua_ffi_max_regex_cache_size +local ngx_lua_ffi_destroy_regex +local ngx_lua_ffi_compile_regex +local ngx_lua_ffi_exec_regex +local ngx_lua_ffi_create_script_engine +local ngx_lua_ffi_destroy_script_engine +local ngx_lua_ffi_init_script_engine +local ngx_lua_ffi_compile_replace_template +local ngx_lua_ffi_script_eval_len +local ngx_lua_ffi_script_eval_data + +-- PCRE 8.43 on macOS introduced the MAP_JIT option when creating the memory +-- region used to store JIT compiled code, which does not survive across +-- `fork()`, causing further usage of PCRE JIT compiler to segfault in worker +-- processes. +-- +-- This flag prevents any regex used in the init phase to be JIT compiled or +-- cached when running under macOS, even if the user requests so. Caching is +-- thus disabled to prevent further calls of same regex in worker to have poor +-- performance. +-- +-- TODO: improve this workaround when PCRE allows for unspecifying the MAP_JIT +-- option. +local no_jit_in_init + +if jit.os == "OSX" then + local maj, min = string.match(pcre_ver, "^(%d+)%.(%d+)") + if maj and min then + local pcre_ver_num = tonumber(maj .. min) + + if pcre_ver_num >= 843 then + no_jit_in_init = true + end + + else + -- assume this version is faulty as well + no_jit_in_init = true + end +end + + +if subsystem == 'http' then + ffi.cdef[[ + + typedef struct { + ngx_str_t value; + void *lengths; + void *values; + } ngx_http_lua_complex_value_t; + + typedef struct { + void *pool; + unsigned char *name_table; + int name_count; + int name_entry_size; + + int ncaptures; + int *captures; + + void *regex; + void *regex_sd; + + ngx_http_lua_complex_value_t *replace; + + const char *pattern; + } ngx_http_lua_regex_t; + + ngx_http_lua_regex_t * + ngx_http_lua_ffi_compile_regex(const unsigned char *pat, + size_t pat_len, int flags, + int pcre_opts, unsigned char *errstr, + size_t errstr_size); + + int ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, + const unsigned char *s, size_t len, int pos); + + void ngx_http_lua_ffi_destroy_regex(ngx_http_lua_regex_t *re); + + int ngx_http_lua_ffi_compile_replace_template(ngx_http_lua_regex_t *re, + const unsigned char + *replace_data, + size_t replace_len); + + struct ngx_http_lua_script_engine_s; + typedef struct ngx_http_lua_script_engine_s *ngx_http_lua_script_engine_t; + + ngx_http_lua_script_engine_t *ngx_http_lua_ffi_create_script_engine(void); + + void ngx_http_lua_ffi_init_script_engine(ngx_http_lua_script_engine_t *e, + const unsigned char *subj, + ngx_http_lua_regex_t *compiled, + int count); + + void ngx_http_lua_ffi_destroy_script_engine( + ngx_http_lua_script_engine_t *e); + + size_t ngx_http_lua_ffi_script_eval_len(ngx_http_lua_script_engine_t *e, + ngx_http_lua_complex_value_t *cv); + + size_t ngx_http_lua_ffi_script_eval_data(ngx_http_lua_script_engine_t *e, + ngx_http_lua_complex_value_t *cv, + unsigned char *dst); + + uint32_t ngx_http_lua_ffi_max_regex_cache_size(void); + ]] + + ngx_lua_ffi_max_regex_cache_size = C.ngx_http_lua_ffi_max_regex_cache_size + ngx_lua_ffi_destroy_regex = C.ngx_http_lua_ffi_destroy_regex + ngx_lua_ffi_compile_regex = C.ngx_http_lua_ffi_compile_regex + ngx_lua_ffi_exec_regex = C.ngx_http_lua_ffi_exec_regex + ngx_lua_ffi_create_script_engine = C.ngx_http_lua_ffi_create_script_engine + ngx_lua_ffi_init_script_engine = C.ngx_http_lua_ffi_init_script_engine + ngx_lua_ffi_destroy_script_engine = C.ngx_http_lua_ffi_destroy_script_engine + ngx_lua_ffi_compile_replace_template = + C.ngx_http_lua_ffi_compile_replace_template + ngx_lua_ffi_script_eval_len = C.ngx_http_lua_ffi_script_eval_len + ngx_lua_ffi_script_eval_data = C.ngx_http_lua_ffi_script_eval_data + +elseif subsystem == 'stream' then + ffi.cdef[[ + + typedef struct { + ngx_str_t value; + void *lengths; + void *values; + } ngx_stream_lua_complex_value_t; + + typedef struct { + void *pool; + unsigned char *name_table; + int name_count; + int name_entry_size; + + int ncaptures; + int *captures; + + void *regex; + void *regex_sd; + + ngx_stream_lua_complex_value_t *replace; + + const char *pattern; + } ngx_stream_lua_regex_t; + + ngx_stream_lua_regex_t * + ngx_stream_lua_ffi_compile_regex(const unsigned char *pat, + size_t pat_len, int flags, + int pcre_opts, unsigned char *errstr, + size_t errstr_size); + + int ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, + const unsigned char *s, size_t len, int pos); + + void ngx_stream_lua_ffi_destroy_regex(ngx_stream_lua_regex_t *re); + + int ngx_stream_lua_ffi_compile_replace_template(ngx_stream_lua_regex_t *re, + const unsigned char + *replace_data, + size_t replace_len); + + struct ngx_stream_lua_script_engine_s; + typedef struct ngx_stream_lua_script_engine_s + *ngx_stream_lua_script_engine_t; + + ngx_stream_lua_script_engine_t * + ngx_stream_lua_ffi_create_script_engine(void); + + void ngx_stream_lua_ffi_init_script_engine( + ngx_stream_lua_script_engine_t *e, const unsigned char *subj, + ngx_stream_lua_regex_t *compiled, int count); + + void ngx_stream_lua_ffi_destroy_script_engine( + ngx_stream_lua_script_engine_t *e); + + size_t ngx_stream_lua_ffi_script_eval_len( + ngx_stream_lua_script_engine_t *e, ngx_stream_lua_complex_value_t *cv); + + size_t ngx_stream_lua_ffi_script_eval_data( + ngx_stream_lua_script_engine_t *e, ngx_stream_lua_complex_value_t *cv, + unsigned char *dst); + + uint32_t ngx_stream_lua_ffi_max_regex_cache_size(void); + ]] + + ngx_lua_ffi_max_regex_cache_size = C.ngx_stream_lua_ffi_max_regex_cache_size + ngx_lua_ffi_destroy_regex = C.ngx_stream_lua_ffi_destroy_regex + ngx_lua_ffi_compile_regex = C.ngx_stream_lua_ffi_compile_regex + ngx_lua_ffi_exec_regex = C.ngx_stream_lua_ffi_exec_regex + ngx_lua_ffi_create_script_engine = C.ngx_stream_lua_ffi_create_script_engine + ngx_lua_ffi_init_script_engine = C.ngx_stream_lua_ffi_init_script_engine + ngx_lua_ffi_destroy_script_engine = + C.ngx_stream_lua_ffi_destroy_script_engine + ngx_lua_ffi_compile_replace_template = + C.ngx_stream_lua_ffi_compile_replace_template + ngx_lua_ffi_script_eval_len = C.ngx_stream_lua_ffi_script_eval_len + ngx_lua_ffi_script_eval_data = C.ngx_stream_lua_ffi_script_eval_data +end + + +local c_str_type = ffi.typeof("const char *") + +local cached_re_opts = new_tab(0, 4) + +local buf_grow_ratio = 2 + + +function _M.set_buf_grow_ratio(ratio) + buf_grow_ratio = ratio +end + + +local function get_max_regex_cache_size() + if max_regex_cache_size then + return max_regex_cache_size + end + max_regex_cache_size = ngx_lua_ffi_max_regex_cache_size() + return max_regex_cache_size +end + + +local regex_cache_is_empty = true + + +function _M.is_regex_cache_empty() + return regex_cache_is_empty +end + + +local function lrucache_set_wrapper(...) + regex_cache_is_empty = false + lrucache_set(...) +end + + +local parse_regex_opts = function (opts) + local t = cached_re_opts[opts] + if t then + return t[1], t[2] + end + + local flags = 0 + local pcre_opts = 0 + local len = #opts + + for i = 1, len do + local opt = byte(opts, i) + if opt == byte("o") then + flags = bor(flags, FLAG_COMPILE_ONCE) + + elseif opt == byte("j") then + flags = bor(flags, FLAG_JIT) + + elseif opt == byte("i") then + pcre_opts = bor(pcre_opts, PCRE_CASELESS) + + elseif opt == byte("s") then + pcre_opts = bor(pcre_opts, PCRE_DOTALL) + + elseif opt == byte("m") then + pcre_opts = bor(pcre_opts, PCRE_MULTILINE) + + elseif opt == byte("u") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + + elseif opt == byte("U") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + flags = bor(flags, FLAG_NO_UTF8_CHECK) + + elseif opt == byte("x") then + pcre_opts = bor(pcre_opts, PCRE_EXTENDED) + + elseif opt == byte("d") then + flags = bor(flags, FLAG_DFA) + + elseif opt == byte("a") then + pcre_opts = bor(pcre_opts, PCRE_ANCHORED) + + elseif opt == byte("D") then + pcre_opts = bor(pcre_opts, PCRE_DUPNAMES) + flags = bor(flags, FLAG_DUPNAMES) + + elseif opt == byte("J") then + pcre_opts = bor(pcre_opts, PCRE_JAVASCRIPT_COMPAT) + + else + error(fmt('unknown flag "%s" (flags "%s")', sub(opts, i, i), opts), + 3) + end + end + + cached_re_opts[opts] = {flags, pcre_opts} + return flags, pcre_opts +end + + +if no_jit_in_init then + local parse_regex_opts_ = parse_regex_opts + + parse_regex_opts = function (opts) + if ngx_phase() ~= "init" then + -- past init_by_lua* phase now + parse_regex_opts = parse_regex_opts_ + return parse_regex_opts(opts) + end + + local t = cached_re_opts[opts] + if t then + return t[1], t[2] + end + + local flags = 0 + local pcre_opts = 0 + local len = #opts + + for i = 1, len do + local opt = byte(opts, i) + if opt == byte("o") then + ngx_log(ngx_NOTICE, "regex compilation cache disabled in init ", + "phase under macOS") + + elseif opt == byte("j") then + ngx_log(ngx_NOTICE, "regex compilation disabled in init ", + "phase under macOS") + + elseif opt == byte("i") then + pcre_opts = bor(pcre_opts, PCRE_CASELESS) + + elseif opt == byte("s") then + pcre_opts = bor(pcre_opts, PCRE_DOTALL) + + elseif opt == byte("m") then + pcre_opts = bor(pcre_opts, PCRE_MULTILINE) + + elseif opt == byte("u") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + + elseif opt == byte("U") then + pcre_opts = bor(pcre_opts, PCRE_UTF8) + flags = bor(flags, FLAG_NO_UTF8_CHECK) + + elseif opt == byte("x") then + pcre_opts = bor(pcre_opts, PCRE_EXTENDED) + + elseif opt == byte("d") then + flags = bor(flags, FLAG_DFA) + + elseif opt == byte("a") then + pcre_opts = bor(pcre_opts, PCRE_ANCHORED) + + elseif opt == byte("D") then + pcre_opts = bor(pcre_opts, PCRE_DUPNAMES) + flags = bor(flags, FLAG_DUPNAMES) + + elseif opt == byte("J") then + pcre_opts = bor(pcre_opts, PCRE_JAVASCRIPT_COMPAT) + + else + error(fmt('unknown flag "%s" (flags "%s")', sub(opts, i, i), + opts), 3) + end + end + + cached_re_opts[opts] = {flags, pcre_opts} + return flags, pcre_opts + end +end + + +local function collect_named_captures(compiled, flags, res) + local name_count = compiled.name_count + local name_table = compiled.name_table + local entry_size = compiled.name_entry_size + + local ind = 0 + local dup_names = (band(flags, FLAG_DUPNAMES) ~= 0) + for i = 1, name_count do + local n = bor(lshift(name_table[ind], 8), name_table[ind + 1]) + -- ngx.say("n = ", n) + local name = ffi_string(name_table + ind + 2) + local cap = res[n] + if dup_names then + -- unmatched captures (false) are not collected + if cap then + local old = res[name] + if old then + old[#old + 1] = cap + else + res[name] = {cap} + end + end + else + res[name] = cap + end + + ind = ind + entry_size + end +end + + +local function collect_captures(compiled, rc, subj, flags, res) + local cap = compiled.captures + local ncap = compiled.ncaptures + local name_count = compiled.name_count + + if not res then + res = new_tab(ncap, name_count) + end + + local i = 0 + local n = 0 + while i <= ncap do + if i > rc then + res[i] = false + else + local from = cap[n] + if from >= 0 then + local to = cap[n + 1] + res[i] = sub(subj, from + 1, to) + else + res[i] = false + end + end + i = i + 1 + n = n + 2 + end + + if name_count > 0 then + collect_named_captures(compiled, flags, res) + end + + return res +end + + +_M.collect_captures = collect_captures + + +local function destroy_compiled_regex(compiled) + ngx_lua_ffi_destroy_regex(ffi_gc(compiled, nil)) +end + + +_M.destroy_compiled_regex = destroy_compiled_regex + + +local function re_match_compile(regex, opts) + local flags = 0 + local pcre_opts = 0 + + if opts then + flags, pcre_opts = parse_regex_opts(opts) + else + opts = "" + end + + local compiled, key + local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) + + -- FIXME: better put this in the outer scope when fixing the ngx.re API's + -- compatibility in the init_by_lua* context. + if not regex_match_cache then + local sz = get_max_regex_cache_size() + if sz <= 0 then + compile_once = false + else + regex_match_cache = lrucache.new(sz) + end + end + + if compile_once then + key = regex .. '\0' .. opts + compiled = lrucache_get(regex_match_cache, key) + end + + -- compile the regex + + if compiled == nil then + -- print("compiled regex not found, compiling regex...") + local errbuf = get_string_buf(MAX_ERR_MSG_LEN) + + compiled = ngx_lua_ffi_compile_regex(regex, #regex, flags, + pcre_opts, errbuf, + MAX_ERR_MSG_LEN) + + if compiled == nil then + return nil, ffi_string(errbuf) + end + + ffi_gc(compiled, ngx_lua_ffi_destroy_regex) + + -- print("ncaptures: ", compiled.ncaptures) + + if compile_once then + -- print("inserting compiled regex into cache") + lrucache_set_wrapper(regex_match_cache, key, compiled) + end + end + + return compiled, compile_once, flags +end + + +_M.re_match_compile = re_match_compile + + +local function re_match_helper(subj, regex, opts, ctx, want_caps, res, nth) + -- we need to cast this to strings to avoid exceptions when they are + -- something else. + subj = tostring(subj) + + local compiled, compile_once, flags = re_match_compile(regex, opts) + if compiled == nil then + -- compiled_once holds the error string + if not want_caps then + return nil, nil, compile_once + end + return nil, compile_once + end + + -- exec the compiled regex + + local rc + do + local pos + if ctx then + pos = ctx.pos + if not pos or pos <= 0 then + pos = 0 + else + pos = pos - 1 + end + + else + pos = 0 + end + + rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, #subj, pos) + end + + if rc == PCRE_ERROR_NOMATCH then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + if not want_caps then + return nil, nil, "pcre_exec() failed: " .. rc + end + return nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not want_caps then + return nil, nil, "capture size too small" + end + return nil, "capture size too small" + end + + rc = 1 + end + + -- print("cap 0: ", compiled.captures[0]) + -- print("cap 1: ", compiled.captures[1]) + + if ctx then + ctx.pos = compiled.captures[1] + 1 + end + + if not want_caps then + if not nth or nth < 0 then + nth = 0 + end + + if nth > compiled.ncaptures then + return nil, nil, "nth out of bound" + end + + if nth >= rc then + return nil, nil + end + + local from = compiled.captures[nth * 2] + 1 + local to = compiled.captures[nth * 2 + 1] + + if from < 0 or to < 0 then + return nil, nil + end + + return from, to + end + + res = collect_captures(compiled, rc, subj, flags, res) + + if not compile_once then + destroy_compiled_regex(compiled) + end + + return res +end + + +function ngx.re.match(subj, regex, opts, ctx, res) + return re_match_helper(subj, regex, opts, ctx, true, res) +end + + +function ngx.re.find(subj, regex, opts, ctx, nth) + return re_match_helper(subj, regex, opts, ctx, false, nil, nth) +end + + +do + local function destroy_re_gmatch_iterator(iterator) + if not iterator._compile_once then + destroy_compiled_regex(iterator._compiled) + end + iterator._compiled = nil + iterator._pos = nil + iterator._subj = nil + end + + + local function iterate_re_gmatch(self) + local compiled = self._compiled + local subj = self._subj + local subj_len = self._subj_len + local flags = self._flags + local pos = self._pos + + if not pos then + -- The iterator is exhausted. + return nil + end + + local rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, subj_len, pos) + + if rc == PCRE_ERROR_NOMATCH then + destroy_re_gmatch_iterator(self) + return nil + end + + if rc < 0 then + destroy_re_gmatch_iterator(self) + return nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + destroy_re_gmatch_iterator(self) + return nil, "capture size too small" + end + + rc = 1 + end + + local cp_pos = tonumber(compiled.captures[1]) + if cp_pos == compiled.captures[0] then + cp_pos = cp_pos + 1 + if cp_pos > subj_len then + local res = collect_captures(compiled, rc, subj, flags) + destroy_re_gmatch_iterator(self) + return res + end + end + self._pos = cp_pos + return collect_captures(compiled, rc, subj, flags) + end + + + local re_gmatch_iterator_mt = { __call = iterate_re_gmatch } + + function ngx.re.gmatch(subj, regex, opts) + subj = tostring(subj) + + local compiled, compile_once, flags = re_match_compile(regex, opts) + if compiled == nil then + -- compiled_once holds the error string + return nil, compile_once + end + + local re_gmatch_iterator = { + _compiled = compiled, + _compile_once = compile_once, + _subj = subj, + _subj_len = #subj, + _flags = flags, + _pos = 0, + } + + return setmetatable(re_gmatch_iterator, re_gmatch_iterator_mt) + end +end -- do + + +local function new_script_engine(subj, compiled, count) + if not script_engine then + script_engine = ngx_lua_ffi_create_script_engine() + if script_engine == nil then + return nil + end + ffi_gc(script_engine, ngx_lua_ffi_destroy_script_engine) + end + + ngx_lua_ffi_init_script_engine(script_engine, subj, compiled, count) + return script_engine +end + + +local function check_buf_size(buf, buf_size, pos, len, new_len, must_alloc) + if new_len > buf_size then + buf_size = buf_size * buf_grow_ratio + if buf_size < new_len then + buf_size = new_len + end + local new_buf = get_string_buf(buf_size, must_alloc) + ffi_copy(new_buf, buf, len) + buf = new_buf + pos = buf + len + end + return buf, buf_size, pos, new_len +end + + +_M.check_buf_size = check_buf_size + + +local function re_sub_compile(regex, opts, replace, func) + local flags = 0 + local pcre_opts = 0 + + if opts then + flags, pcre_opts = parse_regex_opts(opts) + else + opts = "" + end + + local compiled + local compile_once = (band(flags, FLAG_COMPILE_ONCE) == 1) + if compile_once then + if func then + local subcache = regex_sub_func_cache[opts] + if subcache then + -- print("cache hit!") + compiled = subcache[regex] + end + + else + local subcache = regex_sub_str_cache[opts] + if subcache then + local subsubcache = subcache[regex] + if subsubcache then + -- print("cache hit!") + compiled = subsubcache[replace] + end + end + end + end + + -- compile the regex + + if compiled == nil then + -- print("compiled regex not found, compiling regex...") + local errbuf = get_string_buf(MAX_ERR_MSG_LEN) + + compiled = ngx_lua_ffi_compile_regex(regex, #regex, flags, pcre_opts, + errbuf, MAX_ERR_MSG_LEN) + + if compiled == nil then + return nil, ffi_string(errbuf) + end + + ffi_gc(compiled, ngx_lua_ffi_destroy_regex) + + if func == nil then + local rc = + ngx_lua_ffi_compile_replace_template(compiled, replace, + #replace) + if rc ~= 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, "failed to compile the replacement template" + end + end + + -- print("ncaptures: ", compiled.ncaptures) + + if compile_once then + if regex_cache_size < get_max_regex_cache_size() then + -- print("inserting compiled regex into cache") + if func then + local subcache = regex_sub_func_cache[opts] + if not subcache then + regex_sub_func_cache[opts] = {[regex] = compiled} + + else + subcache[regex] = compiled + end + + else + local subcache = regex_sub_str_cache[opts] + if not subcache then + regex_sub_str_cache[opts] = + {[regex] = {[replace] = compiled}} + + else + local subsubcache = subcache[regex] + if not subsubcache then + subcache[regex] = {[replace] = compiled} + + else + subsubcache[replace] = compiled + end + end + end + + regex_cache_size = regex_cache_size + 1 + else + compile_once = false + end + end + end + + return compiled, compile_once, flags +end + + +_M.re_sub_compile = re_sub_compile + + +local function re_sub_func_helper(subj, regex, replace, opts, global) + local compiled, compile_once, flags = + re_sub_compile(regex, opts, nil, replace) + if not compiled then + -- error string is in compile_once + return nil, nil, compile_once + end + + -- exec the compiled regex + + subj = tostring(subj) + local subj_len = #subj + local count = 0 + local pos = 0 + local cp_pos = 0 + + local dst_buf_size = get_string_buf_size() + -- Note: we have to always allocate the string buffer because + -- the user might call whatever resty.core's API functions recursively + -- in the user callback function. + local dst_buf = get_string_buf(dst_buf_size, true) + local dst_pos = dst_buf + local dst_len = 0 + + while true do + local rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, subj_len, pos) + if rc == PCRE_ERROR_NOMATCH then + break + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "capture size too small" + end + + rc = 1 + end + + count = count + 1 + local prefix_len = compiled.captures[0] - cp_pos + + local res = collect_captures(compiled, rc, subj, flags) + + local piece = tostring(replace(res)) + local piece_len = #piece + + local new_dst_len = dst_len + prefix_len + piece_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len, true) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if piece_len > 0 then + ffi_copy(dst_pos, piece, piece_len) + dst_pos = dst_pos + piece_len + end + + cp_pos = compiled.captures[1] + pos = cp_pos + if pos == compiled.captures[0] then + pos = pos + 1 + if pos > subj_len then + break + end + end + + if not global then + break + end + end + + if not compile_once then + destroy_compiled_regex(compiled) + end + + if count > 0 then + if pos < subj_len then + local suffix_len = subj_len - cp_pos + + local new_dst_len = dst_len + suffix_len + local _ + dst_buf, _, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len, true) + + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + suffix_len) + end + return ffi_string(dst_buf, dst_len), count + end + + return subj, 0 +end + + +local function re_sub_str_helper(subj, regex, replace, opts, global) + local compiled, compile_once, flags = + re_sub_compile(regex, opts, replace, nil) + if not compiled then + -- error string is in compile_once + return nil, nil, compile_once + end + + -- exec the compiled regex + + subj = tostring(subj) + local subj_len = #subj + local count = 0 + local pos = 0 + local cp_pos = 0 + + local dst_buf_size = get_string_buf_size() + local dst_buf = get_string_buf(dst_buf_size) + local dst_pos = dst_buf + local dst_len = 0 + + while true do + local rc = ngx_lua_ffi_exec_regex(compiled, flags, subj, subj_len, pos) + if rc == PCRE_ERROR_NOMATCH then + break + end + + if rc < 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "pcre_exec() failed: " .. rc + end + + if rc == 0 then + if band(flags, FLAG_DFA) == 0 then + if not compile_once then + destroy_compiled_regex(compiled) + end + return nil, nil, "capture size too small" + end + + rc = 1 + end + + count = count + 1 + local prefix_len = compiled.captures[0] - cp_pos + + local cv = compiled.replace + if cv.lengths ~= nil then + local e = new_script_engine(subj, compiled, rc) + if e == nil then + return nil, nil, "failed to create script engine" + end + + local bit_len = ngx_lua_ffi_script_eval_len(e, cv) + local new_dst_len = dst_len + prefix_len + bit_len + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if bit_len > 0 then + ngx_lua_ffi_script_eval_data(e, cv, dst_pos) + dst_pos = dst_pos + bit_len + end + + else + local bit_len = cv.value.len + + dst_buf, dst_buf_size, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + dst_len + prefix_len + bit_len) + + if prefix_len > 0 then + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + prefix_len) + dst_pos = dst_pos + prefix_len + end + + if bit_len > 0 then + ffi_copy(dst_pos, cv.value.data, bit_len) + dst_pos = dst_pos + bit_len + end + end + + cp_pos = compiled.captures[1] + pos = cp_pos + if pos == compiled.captures[0] then + pos = pos + 1 + if pos > subj_len then + break + end + end + + if not global then + break + end + end + + if not compile_once then + destroy_compiled_regex(compiled) + end + + if count > 0 then + if pos < subj_len then + local suffix_len = subj_len - cp_pos + + local new_dst_len = dst_len + suffix_len + local _ + dst_buf, _, dst_pos, dst_len = + check_buf_size(dst_buf, dst_buf_size, dst_pos, dst_len, + new_dst_len) + + ffi_copy(dst_pos, ffi_cast(c_str_type, subj) + cp_pos, + suffix_len) + end + return ffi_string(dst_buf, dst_len), count + end + + return subj, 0 +end + + +local function re_sub_helper(subj, regex, replace, opts, global) + local repl_type = type(replace) + if repl_type == "function" then + return re_sub_func_helper(subj, regex, replace, opts, global) + end + + if repl_type ~= "string" then + replace = tostring(replace) + end + + return re_sub_str_helper(subj, regex, replace, opts, global) +end + + +function ngx.re.sub(subj, regex, replace, opts) + return re_sub_helper(subj, regex, replace, opts, false) +end + + +function ngx.re.gsub(subj, regex, replace, opts) + return re_sub_helper(subj, regex, replace, opts, true) +end + + +return _M diff --git a/resty/core/request.lua b/resty/core/request.lua new file mode 100644 index 0000000..ee00339 --- /dev/null +++ b/resty/core/request.lua @@ -0,0 +1,367 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" +base.allows_subsystem("http") +local utils = require "resty.core.utils" + + +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local FFI_DECLINED = base.FFI_DECLINED +local FFI_OK = base.FFI_OK +local new_tab = base.new_tab +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_new = ffi.new +local ffi_str = ffi.string +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local setmetatable = setmetatable +local lower = string.lower +local rawget = rawget +local ngx = ngx +local get_request = base.get_request +local type = type +local error = error +local tostring = tostring +local tonumber = tonumber +local str_replace_char = utils.str_replace_char + + +ffi.cdef[[ + typedef struct { + ngx_http_lua_ffi_str_t key; + ngx_http_lua_ffi_str_t value; + } ngx_http_lua_ffi_table_elt_t; + + int ngx_http_lua_ffi_req_get_headers_count(ngx_http_request_t *r, + int max, int *truncated); + + int ngx_http_lua_ffi_req_get_headers(ngx_http_request_t *r, + ngx_http_lua_ffi_table_elt_t *out, int count, int raw); + + int ngx_http_lua_ffi_req_get_uri_args_count(ngx_http_request_t *r, + int max, int *truncated); + + size_t ngx_http_lua_ffi_req_get_querystring_len(ngx_http_request_t *r); + + int ngx_http_lua_ffi_req_get_uri_args(ngx_http_request_t *r, + unsigned char *buf, ngx_http_lua_ffi_table_elt_t *out, int count); + + double ngx_http_lua_ffi_req_start_time(ngx_http_request_t *r); + + int ngx_http_lua_ffi_req_get_method(ngx_http_request_t *r); + + int ngx_http_lua_ffi_req_get_method_name(ngx_http_request_t *r, + unsigned char **name, size_t *len); + + int ngx_http_lua_ffi_req_set_method(ngx_http_request_t *r, int method); + + int ngx_http_lua_ffi_req_header_set_single_value(ngx_http_request_t *r, + const unsigned char *key, size_t key_len, const unsigned char *value, + size_t value_len); +]] + + +local table_elt_type = ffi.typeof("ngx_http_lua_ffi_table_elt_t*") +local table_elt_size = ffi.sizeof("ngx_http_lua_ffi_table_elt_t") +local truncated = ffi.new("int[1]") + +local req_headers_mt = { + __index = function (tb, key) + return rawget(tb, (str_replace_char(lower(key), '_', '-'))) + end +} + + +function ngx.req.get_headers(max_headers, raw) + local r = get_request() + if not r then + error("no request found") + end + + if not max_headers then + max_headers = -1 + end + + if not raw then + raw = 0 + else + raw = 1 + end + + local n = C.ngx_http_lua_ffi_req_get_headers_count(r, max_headers, + truncated) + if n == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if n == 0 then + return {} + end + + local raw_buf = get_string_buf(n * table_elt_size) + local buf = ffi_cast(table_elt_type, raw_buf) + + local rc = C.ngx_http_lua_ffi_req_get_headers(r, buf, n, raw) + if rc == 0 then + local headers = new_tab(0, n) + for i = 0, n - 1 do + local h = buf[i] + + local key = h.key + key = ffi_str(key.data, key.len) + + local value = h.value + value = ffi_str(value.data, value.len) + + local existing = headers[key] + if existing then + if type(existing) == "table" then + existing[#existing + 1] = value + else + headers[key] = {existing, value} + end + + else + headers[key] = value + end + end + + if raw == 0 then + headers = setmetatable(headers, req_headers_mt) + end + + if truncated[0] ~= 0 then + return headers, "truncated" + end + + return headers + end + + return nil +end + + +function ngx.req.get_uri_args(max_args) + local r = get_request() + if not r then + error("no request found") + end + + if not max_args then + max_args = -1 + end + + local n = C.ngx_http_lua_ffi_req_get_uri_args_count(r, max_args, truncated) + if n == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if n == 0 then + return {} + end + + local args_len = C.ngx_http_lua_ffi_req_get_querystring_len(r) + + local strbuf = get_string_buf(args_len + n * table_elt_size) + local kvbuf = ffi_cast(table_elt_type, strbuf + args_len) + + local nargs = C.ngx_http_lua_ffi_req_get_uri_args(r, strbuf, kvbuf, n) + + local args = new_tab(0, nargs) + for i = 0, nargs - 1 do + local arg = kvbuf[i] + + local key = arg.key + key = ffi_str(key.data, key.len) + + local value = arg.value + local len = value.len + if len == -1 then + value = true + else + value = ffi_str(value.data, len) + end + + local existing = args[key] + if existing then + if type(existing) == "table" then + existing[#existing + 1] = value + else + args[key] = {existing, value} + end + + else + args[key] = value + end + end + + if truncated[0] ~= 0 then + return args, "truncated" + end + + return args +end + + +function ngx.req.start_time() + local r = get_request() + if not r then + error("no request found") + end + + return tonumber(C.ngx_http_lua_ffi_req_start_time(r)) +end + + +do + local methods = { + [0x0002] = "GET", + [0x0004] = "HEAD", + [0x0008] = "POST", + [0x0010] = "PUT", + [0x0020] = "DELETE", + [0x0040] = "MKCOL", + [0x0080] = "COPY", + [0x0100] = "MOVE", + [0x0200] = "OPTIONS", + [0x0400] = "PROPFIND", + [0x0800] = "PROPPATCH", + [0x1000] = "LOCK", + [0x2000] = "UNLOCK", + [0x4000] = "PATCH", + [0x8000] = "TRACE", + } + + local namep = ffi_new("unsigned char *[1]") + + function ngx.req.get_method() + local r = get_request() + if not r then + error("no request found") + end + + do + local id = C.ngx_http_lua_ffi_req_get_method(r) + if id == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + local method = methods[id] + if method then + return method + end + end + + local sizep = get_size_ptr() + local rc = C.ngx_http_lua_ffi_req_get_method_name(r, namep, sizep) + if rc ~= 0 then + return nil + end + + return ffi_str(namep[0], sizep[0]) + end +end -- do + + +function ngx.req.set_method(method) + local r = get_request() + if not r then + error("no request found") + end + + if type(method) ~= "number" then + error("bad method number", 2) + end + + local rc = C.ngx_http_lua_ffi_req_set_method(r, method) + if rc == FFI_OK then + return + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if rc == FFI_DECLINED then + error("unsupported HTTP method: " .. method, 2) + end + + error("unknown error: " .. rc) +end + + +do + local orig_func = ngx.req.set_header + + function ngx.req.set_header(name, value) + if type(value) == "table" then + return orig_func(name, value) + end + + local r = get_request() + if not r then + error("no request found") + end + + if type(name) ~= "string" then + name = tostring(name) + end + + local rc + if not value then + rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, + #name, nil, 0) + + else + if type(value) ~= "string" then + value = tostring(value) + end + + rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, + #name, value, #value) + end + + if rc == FFI_OK or rc == FFI_DECLINED then + return + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + error("error") + end +end -- do + + +function ngx.req.clear_header(name) + local r = get_request() + if not r then + error("no request found") + end + + if type(name) ~= "string" then + name = tostring(name) + end + + local rc = C.ngx_http_lua_ffi_req_header_set_single_value(r, name, #name, + nil, 0) + + if rc == FFI_OK or rc == FFI_DECLINED then + return + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + error("error") +end + + +return { + version = base.version +} diff --git a/resty/core/response.lua b/resty/core/response.lua new file mode 100644 index 0000000..891a07e --- /dev/null +++ b/resty/core/response.lua @@ -0,0 +1,183 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_str = ffi.string +local new_tab = base.new_tab +local FFI_BAD_CONTEXT = base.FFI_BAD_CONTEXT +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local FFI_DECLINED = base.FFI_DECLINED +local get_string_buf = base.get_string_buf +local setmetatable = setmetatable +local type = type +local tostring = tostring +local get_request = base.get_request +local error = error +local ngx = ngx + + +local _M = { + version = base.version +} + + +local MAX_HEADER_VALUES = 100 +local errmsg = base.get_errmsg_ptr() +local ffi_str_type = ffi.typeof("ngx_http_lua_ffi_str_t*") +local ffi_str_size = ffi.sizeof("ngx_http_lua_ffi_str_t") + + +ffi.cdef[[ + int ngx_http_lua_ffi_set_resp_header(ngx_http_request_t *r, + const char *key_data, size_t key_len, int is_nil, + const char *sval, size_t sval_len, ngx_http_lua_ffi_str_t *mvals, + size_t mvals_len, int override, char **errmsg); + + int ngx_http_lua_ffi_get_resp_header(ngx_http_request_t *r, + const unsigned char *key, size_t key_len, + unsigned char *key_buf, ngx_http_lua_ffi_str_t *values, + int max_nvalues, char **errmsg); +]] + + +local function set_resp_header(tb, key, value, no_override) + local r = get_request() + if not r then + error("no request found") + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local rc + if value == nil then + if no_override then + error("invalid header value", 3) + end + + rc = C.ngx_http_lua_ffi_set_resp_header(r, key, #key, true, nil, 0, nil, + 0, 1, errmsg) + else + local sval, sval_len, mvals, mvals_len, buf + + if type(value) == "table" then + mvals_len = #value + if mvals_len == 0 and no_override then + return + end + + buf = get_string_buf(ffi_str_size * mvals_len) + mvals = ffi_cast(ffi_str_type, buf) + for i = 1, mvals_len do + local s = value[i] + if type(s) ~= "string" then + s = tostring(s) + value[i] = s + end + local str = mvals[i - 1] + str.data = s + str.len = #s + end + + sval_len = 0 + + else + if type(value) ~= "string" then + sval = tostring(value) + else + sval = value + end + sval_len = #sval + + mvals_len = 0 + end + + local override_int = no_override and 0 or 1 + rc = C.ngx_http_lua_ffi_set_resp_header(r, key, #key, false, sval, + sval_len, mvals, mvals_len, + override_int, errmsg) + end + + if rc == 0 or rc == FFI_DECLINED then + return + end + + if rc == FFI_NO_REQ_CTX then + error("no request ctx found") + end + + if rc == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + -- rc == FFI_ERROR + error(ffi_str(errmsg[0]), 2) +end + + +_M.set_resp_header = set_resp_header + + +local function get_resp_header(tb, key) + local r = get_request() + if not r then + error("no request found") + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + + local key_buf = get_string_buf(key_len + ffi_str_size * MAX_HEADER_VALUES) + local values = ffi_cast(ffi_str_type, key_buf + key_len) + local n = C.ngx_http_lua_ffi_get_resp_header(r, key, key_len, key_buf, + values, MAX_HEADER_VALUES, + errmsg) + + -- print("retval: ", n) + + if n == FFI_BAD_CONTEXT then + error("API disabled in the current context", 2) + end + + if n == 0 then + return nil + end + + if n == 1 then + local v = values[0] + return ffi_str(v.data, v.len) + end + + if n > 0 then + local ret = new_tab(n, 0) + for i = 1, n do + local v = values[i - 1] + ret[i] = ffi_str(v.data, v.len) + end + return ret + end + + -- n == FFI_ERROR + error(ffi_str(errmsg[0]), 2) +end + + +do + local mt = new_tab(0, 2) + mt.__newindex = set_resp_header + mt.__index = get_resp_header + + ngx.header = setmetatable(new_tab(0, 0), mt) +end + + +return _M diff --git a/resty/core/shdict.lua b/resty/core/shdict.lua new file mode 100644 index 0000000..e5a2ea2 --- /dev/null +++ b/resty/core/shdict.lua @@ -0,0 +1,638 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local _M = { + version = base.version +} + +local ngx_shared = ngx.shared +if not ngx_shared then + return _M +end + + +local ffi_new = ffi.new +local ffi_str = ffi.string +local C = ffi.C +local get_string_buf = base.get_string_buf +local get_string_buf_size = base.get_string_buf_size +local get_size_ptr = base.get_size_ptr +local tonumber = tonumber +local tostring = tostring +local next = next +local type = type +local error = error +local getmetatable = getmetatable +local FFI_DECLINED = base.FFI_DECLINED +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_shdict_get +local ngx_lua_ffi_shdict_incr +local ngx_lua_ffi_shdict_store +local ngx_lua_ffi_shdict_flush_all +local ngx_lua_ffi_shdict_get_ttl +local ngx_lua_ffi_shdict_set_expire +local ngx_lua_ffi_shdict_capacity +local ngx_lua_ffi_shdict_free_space +local ngx_lua_ffi_shdict_udata_to_zone + + +if subsystem == 'http' then + ffi.cdef[[ +int ngx_http_lua_ffi_shdict_get(void *zone, const unsigned char *key, + size_t key_len, int *value_type, unsigned char **str_value_buf, + size_t *str_value_len, double *num_value, int *user_flags, + int get_stale, int *is_stale, char **errmsg); + +int ngx_http_lua_ffi_shdict_incr(void *zone, const unsigned char *key, + size_t key_len, double *value, char **err, int has_init, + double init, long init_ttl, int *forcible); + +int ngx_http_lua_ffi_shdict_store(void *zone, int op, + const unsigned char *key, size_t key_len, int value_type, + const unsigned char *str_value_buf, size_t str_value_len, + double num_value, long exptime, int user_flags, char **errmsg, + int *forcible); + +int ngx_http_lua_ffi_shdict_flush_all(void *zone); + +long ngx_http_lua_ffi_shdict_get_ttl(void *zone, + const unsigned char *key, size_t key_len); + +int ngx_http_lua_ffi_shdict_set_expire(void *zone, + const unsigned char *key, size_t key_len, long exptime); + +size_t ngx_http_lua_ffi_shdict_capacity(void *zone); + +void *ngx_http_lua_ffi_shdict_udata_to_zone(void *zone_udata); + ]] + + ngx_lua_ffi_shdict_get = C.ngx_http_lua_ffi_shdict_get + ngx_lua_ffi_shdict_incr = C.ngx_http_lua_ffi_shdict_incr + ngx_lua_ffi_shdict_store = C.ngx_http_lua_ffi_shdict_store + ngx_lua_ffi_shdict_flush_all = C.ngx_http_lua_ffi_shdict_flush_all + ngx_lua_ffi_shdict_get_ttl = C.ngx_http_lua_ffi_shdict_get_ttl + ngx_lua_ffi_shdict_set_expire = C.ngx_http_lua_ffi_shdict_set_expire + ngx_lua_ffi_shdict_capacity = C.ngx_http_lua_ffi_shdict_capacity + ngx_lua_ffi_shdict_udata_to_zone = + C.ngx_http_lua_ffi_shdict_udata_to_zone + + if not pcall(function () + return C.ngx_http_lua_ffi_shdict_free_space + end) + then + ffi.cdef[[ +size_t ngx_http_lua_ffi_shdict_free_space(void *zone); + ]] + end + + pcall(function () + ngx_lua_ffi_shdict_free_space = C.ngx_http_lua_ffi_shdict_free_space + end) + +elseif subsystem == 'stream' then + + ffi.cdef[[ +int ngx_stream_lua_ffi_shdict_get(void *zone, const unsigned char *key, + size_t key_len, int *value_type, unsigned char **str_value_buf, + size_t *str_value_len, double *num_value, int *user_flags, + int get_stale, int *is_stale, char **errmsg); + +int ngx_stream_lua_ffi_shdict_incr(void *zone, const unsigned char *key, + size_t key_len, double *value, char **err, int has_init, + double init, long init_ttl, int *forcible); + +int ngx_stream_lua_ffi_shdict_store(void *zone, int op, + const unsigned char *key, size_t key_len, int value_type, + const unsigned char *str_value_buf, size_t str_value_len, + double num_value, long exptime, int user_flags, char **errmsg, + int *forcible); + +int ngx_stream_lua_ffi_shdict_flush_all(void *zone); + +long ngx_stream_lua_ffi_shdict_get_ttl(void *zone, + const unsigned char *key, size_t key_len); + +int ngx_stream_lua_ffi_shdict_set_expire(void *zone, + const unsigned char *key, size_t key_len, long exptime); + +size_t ngx_stream_lua_ffi_shdict_capacity(void *zone); + +void *ngx_stream_lua_ffi_shdict_udata_to_zone(void *zone_udata); + ]] + + ngx_lua_ffi_shdict_get = C.ngx_stream_lua_ffi_shdict_get + ngx_lua_ffi_shdict_incr = C.ngx_stream_lua_ffi_shdict_incr + ngx_lua_ffi_shdict_store = C.ngx_stream_lua_ffi_shdict_store + ngx_lua_ffi_shdict_flush_all = C.ngx_stream_lua_ffi_shdict_flush_all + ngx_lua_ffi_shdict_get_ttl = C.ngx_stream_lua_ffi_shdict_get_ttl + ngx_lua_ffi_shdict_set_expire = C.ngx_stream_lua_ffi_shdict_set_expire + ngx_lua_ffi_shdict_capacity = C.ngx_stream_lua_ffi_shdict_capacity + ngx_lua_ffi_shdict_udata_to_zone = + C.ngx_stream_lua_ffi_shdict_udata_to_zone + + if not pcall(function () + return C.ngx_stream_lua_ffi_shdict_free_space + end) + then + ffi.cdef[[ +size_t ngx_stream_lua_ffi_shdict_free_space(void *zone); + ]] + end + + -- ngx_stream_lua is only compatible with NGINX >= 1.13.6, meaning it + -- cannot lack support for ngx_stream_lua_ffi_shdict_free_space. + ngx_lua_ffi_shdict_free_space = C.ngx_stream_lua_ffi_shdict_free_space + +else + error("unknown subsystem: " .. subsystem) +end + +if not pcall(function () return C.free end) then + ffi.cdef[[ +void free(void *ptr); + ]] +end + + +local value_type = ffi_new("int[1]") +local user_flags = ffi_new("int[1]") +local num_value = ffi_new("double[1]") +local is_stale = ffi_new("int[1]") +local forcible = ffi_new("int[1]") +local str_value_buf = ffi_new("unsigned char *[1]") +local errmsg = base.get_errmsg_ptr() + + +local function check_zone(zone) + if not zone or type(zone) ~= "table" then + error("bad \"zone\" argument", 3) + end + + zone = zone[1] + if type(zone) ~= "userdata" then + error("bad \"zone\" argument", 3) + end + + zone = ngx_lua_ffi_shdict_udata_to_zone(zone) + if zone == nil then + error("bad \"zone\" argument", 3) + end + + return zone +end + + +local function shdict_store(zone, op, key, value, exptime, flags) + zone = check_zone(zone) + + if not exptime then + exptime = 0 + elseif exptime < 0 then + error('bad "exptime" argument', 2) + end + + if not flags then + flags = 0 + end + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local str_val_buf + local str_val_len = 0 + local num_val = 0 + local valtyp = type(value) + + -- print("value type: ", valtyp) + -- print("exptime: ", exptime) + + if valtyp == "string" then + valtyp = 4 -- LUA_TSTRING + str_val_buf = value + str_val_len = #value + + elseif valtyp == "number" then + valtyp = 3 -- LUA_TNUMBER + num_val = value + + elseif value == nil then + valtyp = 0 -- LUA_TNIL + + elseif valtyp == "boolean" then + valtyp = 1 -- LUA_TBOOLEAN + num_val = value and 1 or 0 + + else + return nil, "bad value type" + end + + local rc = ngx_lua_ffi_shdict_store(zone, op, key, key_len, + valtyp, str_val_buf, + str_val_len, num_val, + exptime * 1000, flags, errmsg, + forcible) + + -- print("rc == ", rc) + + if rc == 0 then -- NGX_OK + return true, nil, forcible[0] == 1 + end + + -- NGX_DECLINED or NGX_ERROR + return false, ffi_str(errmsg[0]), forcible[0] == 1 +end + + +local function shdict_set(zone, key, value, exptime, flags) + return shdict_store(zone, 0, key, value, exptime, flags) +end + + +local function shdict_safe_set(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0004, key, value, exptime, flags) +end + + +local function shdict_add(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0001, key, value, exptime, flags) +end + + +local function shdict_safe_add(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0005, key, value, exptime, flags) +end + + +local function shdict_replace(zone, key, value, exptime, flags) + return shdict_store(zone, 0x0002, key, value, exptime, flags) +end + + +local function shdict_delete(zone, key) + return shdict_set(zone, key, nil) +end + + +local function shdict_get(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local size = get_string_buf_size() + local buf = get_string_buf(size) + str_value_buf[0] = buf + local value_len = get_size_ptr() + value_len[0] = size + + local rc = ngx_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, 0, + is_stale, errmsg) + if rc ~= 0 then + if errmsg[0] then + return nil, ffi_str(errmsg[0]) + end + + error("failed to get the key") + end + + local typ = value_type[0] + + if typ == 0 then -- LUA_TNIL + return nil + end + + local flags = tonumber(user_flags[0]) + + local val + + if typ == 4 then -- LUA_TSTRING + if str_value_buf[0] ~= buf then + -- ngx.say("len: ", tonumber(value_len[0])) + buf = str_value_buf[0] + val = ffi_str(buf, value_len[0]) + C.free(buf) + else + val = ffi_str(buf, value_len[0]) + end + + elseif typ == 3 then -- LUA_TNUMBER + val = tonumber(num_value[0]) + + elseif typ == 1 then -- LUA_TBOOLEAN + val = (tonumber(buf[0]) ~= 0) + + else + error("unknown value type: " .. typ) + end + + if flags ~= 0 then + return val, flags + end + + return val +end + + +local function shdict_get_stale(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + local size = get_string_buf_size() + local buf = get_string_buf(size) + str_value_buf[0] = buf + local value_len = get_size_ptr() + value_len[0] = size + + local rc = ngx_lua_ffi_shdict_get(zone, key, key_len, value_type, + str_value_buf, value_len, + num_value, user_flags, 1, + is_stale, errmsg) + if rc ~= 0 then + if errmsg[0] then + return nil, ffi_str(errmsg[0]) + end + + error("failed to get the key") + end + + local typ = value_type[0] + + if typ == 0 then -- LUA_TNIL + return nil + end + + local flags = tonumber(user_flags[0]) + local val + + if typ == 4 then -- LUA_TSTRING + if str_value_buf[0] ~= buf then + -- ngx.say("len: ", tonumber(value_len[0])) + buf = str_value_buf[0] + val = ffi_str(buf, value_len[0]) + C.free(buf) + else + val = ffi_str(buf, value_len[0]) + end + + elseif typ == 3 then -- LUA_TNUMBER + val = tonumber(num_value[0]) + + elseif typ == 1 then -- LUA_TBOOLEAN + val = (tonumber(buf[0]) ~= 0) + + else + error("unknown value type: " .. typ) + end + + if flags ~= 0 then + return val, flags, is_stale[0] == 1 + end + + return val, nil, is_stale[0] == 1 +end + + +local function shdict_incr(zone, key, value, init, init_ttl) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + if key_len > 65535 then + return nil, "key too long" + end + + if type(value) ~= "number" then + value = tonumber(value) + end + num_value[0] = value + + if init then + local typ = type(init) + if typ ~= "number" then + init = tonumber(init) + + if not init then + error("bad init arg: number expected, got " .. typ, 2) + end + end + end + + if init_ttl ~= nil then + local typ = type(init_ttl) + if typ ~= "number" then + init_ttl = tonumber(init_ttl) + + if not init_ttl then + error("bad init_ttl arg: number expected, got " .. typ, 2) + end + end + + if init_ttl < 0 then + error('bad "init_ttl" argument', 2) + end + + if not init then + error('must provide "init" when providing "init_ttl"', 2) + end + + else + init_ttl = 0 + end + + local rc = ngx_lua_ffi_shdict_incr(zone, key, key_len, num_value, + errmsg, init and 1 or 0, + init or 0, init_ttl * 1000, + forcible) + if rc ~= 0 then -- ~= NGX_OK + return nil, ffi_str(errmsg[0]) + end + + if not init then + return tonumber(num_value[0]) + end + + return tonumber(num_value[0]), nil, forcible[0] == 1 +end + + +local function shdict_flush_all(zone) + zone = check_zone(zone) + + ngx_lua_ffi_shdict_flush_all(zone) +end + + +local function shdict_ttl(zone, key) + zone = check_zone(zone) + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + + if key_len > 65535 then + return nil, "key too long" + end + + local rc = ngx_lua_ffi_shdict_get_ttl(zone, key, key_len) + + if rc == FFI_DECLINED then + return nil, "not found" + end + + return tonumber(rc) / 1000 +end + + +local function shdict_expire(zone, key, exptime) + zone = check_zone(zone) + + if not exptime then + error('bad "exptime" argument', 2) + end + + if key == nil then + return nil, "nil key" + end + + if type(key) ~= "string" then + key = tostring(key) + end + + local key_len = #key + if key_len == 0 then + return nil, "empty key" + end + + if key_len > 65535 then + return nil, "key too long" + end + + local rc = ngx_lua_ffi_shdict_set_expire(zone, key, key_len, + exptime * 1000) + + if rc == FFI_DECLINED then + return nil, "not found" + end + + -- NGINX_OK/FFI_OK + + return true +end + + +local function shdict_capacity(zone) + zone = check_zone(zone) + + return tonumber(ngx_lua_ffi_shdict_capacity(zone)) +end + + +local shdict_free_space +if ngx_lua_ffi_shdict_free_space then + shdict_free_space = function (zone) + zone = check_zone(zone) + + return tonumber(ngx_lua_ffi_shdict_free_space(zone)) + end + +else + shdict_free_space = function () + error("'shm:free_space()' not supported in NGINX < 1.11.7", 2) + end +end + + +local _, dict = next(ngx_shared, nil) +if dict then + local mt = getmetatable(dict) + if mt then + mt = mt.__index + if mt then + mt.get = shdict_get + mt.get_stale = shdict_get_stale + mt.incr = shdict_incr + mt.set = shdict_set + mt.safe_set = shdict_safe_set + mt.add = shdict_add + mt.safe_add = shdict_safe_add + mt.replace = shdict_replace + mt.delete = shdict_delete + mt.flush_all = shdict_flush_all + mt.ttl = shdict_ttl + mt.expire = shdict_expire + mt.capacity = shdict_capacity + mt.free_space = shdict_free_space + end + end +end + + +return _M diff --git a/resty/core/time.lua b/resty/core/time.lua new file mode 100644 index 0000000..10ae72e --- /dev/null +++ b/resty/core/time.lua @@ -0,0 +1,159 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require 'ffi' +local base = require "resty.core.base" + + +local error = error +local tonumber = tonumber +local type = type +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local time_val = ffi_new("long[1]") +local get_string_buf = base.get_string_buf +local ngx = ngx +local FFI_ERROR = base.FFI_ERROR +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_now +local ngx_lua_ffi_time +local ngx_lua_ffi_today +local ngx_lua_ffi_localtime +local ngx_lua_ffi_utctime +local ngx_lua_ffi_update_time + + +if subsystem == 'http' then + ffi.cdef[[ +double ngx_http_lua_ffi_now(void); +long ngx_http_lua_ffi_time(void); +void ngx_http_lua_ffi_today(unsigned char *buf); +void ngx_http_lua_ffi_localtime(unsigned char *buf); +void ngx_http_lua_ffi_utctime(unsigned char *buf); +void ngx_http_lua_ffi_update_time(void); +int ngx_http_lua_ffi_cookie_time(unsigned char *buf, long t); +void ngx_http_lua_ffi_http_time(unsigned char *buf, long t); +void ngx_http_lua_ffi_parse_http_time(const unsigned char *str, size_t len, + long *time); + ]] + + ngx_lua_ffi_now = C.ngx_http_lua_ffi_now + ngx_lua_ffi_time = C.ngx_http_lua_ffi_time + ngx_lua_ffi_today = C.ngx_http_lua_ffi_today + ngx_lua_ffi_localtime = C.ngx_http_lua_ffi_localtime + ngx_lua_ffi_utctime = C.ngx_http_lua_ffi_utctime + ngx_lua_ffi_update_time = C.ngx_http_lua_ffi_update_time + +elseif subsystem == 'stream' then + ffi.cdef[[ +double ngx_stream_lua_ffi_now(void); +long ngx_stream_lua_ffi_time(void); +void ngx_stream_lua_ffi_today(unsigned char *buf); +void ngx_stream_lua_ffi_localtime(unsigned char *buf); +void ngx_stream_lua_ffi_utctime(unsigned char *buf); +void ngx_stream_lua_ffi_update_time(void); + ]] + + ngx_lua_ffi_now = C.ngx_stream_lua_ffi_now + ngx_lua_ffi_time = C.ngx_stream_lua_ffi_time + ngx_lua_ffi_today = C.ngx_stream_lua_ffi_today + ngx_lua_ffi_localtime = C.ngx_stream_lua_ffi_localtime + ngx_lua_ffi_utctime = C.ngx_stream_lua_ffi_utctime + ngx_lua_ffi_update_time = C.ngx_stream_lua_ffi_update_time +end + + +function ngx.now() + return tonumber(ngx_lua_ffi_now()) +end + + +function ngx.time() + return tonumber(ngx_lua_ffi_time()) +end + + +function ngx.update_time() + ngx_lua_ffi_update_time() +end + + +function ngx.today() + -- the format of today is 2010-11-19 + local today_buf_size = 10 + local buf = get_string_buf(today_buf_size) + ngx_lua_ffi_today(buf) + return ffi_str(buf, today_buf_size) +end + + +function ngx.localtime() + -- the format of localtime is 2010-11-19 20:56:31 + local localtime_buf_size = 19 + local buf = get_string_buf(localtime_buf_size) + ngx_lua_ffi_localtime(buf) + return ffi_str(buf, localtime_buf_size) +end + + +function ngx.utctime() + -- the format of utctime is 2010-11-19 20:56:31 + local utctime_buf_size = 19 + local buf = get_string_buf(utctime_buf_size) + ngx_lua_ffi_utctime(buf) + return ffi_str(buf, utctime_buf_size) +end + + +if subsystem == 'http' then + +function ngx.cookie_time(sec) + if type(sec) ~= "number" then + error("number argument only", 2) + end + + -- the format of cookie time is Mon, 28-Sep-2038 06:00:00 GMT + -- or Mon, 28-Sep-18 06:00:00 GMT + local cookie_time_buf_size = 29 + local buf = get_string_buf(cookie_time_buf_size) + local used_size = C.ngx_http_lua_ffi_cookie_time(buf, sec) + return ffi_str(buf, used_size) +end + + +function ngx.http_time(sec) + if type(sec) ~= "number" then + error("number argument only", 2) + end + + -- the format of http time is Mon, 28 Sep 1970 06:00:00 GMT + local http_time_buf_size = 29 + local buf = get_string_buf(http_time_buf_size) + C.ngx_http_lua_ffi_http_time(buf, sec) + return ffi_str(buf, http_time_buf_size) +end + + +function ngx.parse_http_time(time_str) + if type(time_str) ~= "string" then + error("string argument only", 2) + end + + C.ngx_http_lua_ffi_parse_http_time(time_str, #time_str, time_val) + + local res = time_val[0] + if res == FFI_ERROR then + return nil + end + + return tonumber(res) +end + +end + +return { + version = base.version +} diff --git a/resty/core/uri.lua b/resty/core/uri.lua new file mode 100644 index 0000000..27522e4 --- /dev/null +++ b/resty/core/uri.lua @@ -0,0 +1,94 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_string = ffi.string +local ngx = ngx +local type = type +local tostring = tostring +local get_string_buf = base.get_string_buf +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_escape_uri +local ngx_lua_ffi_unescape_uri +local ngx_lua_ffi_uri_escaped_length + + +if subsystem == "http" then + ffi.cdef[[ + size_t ngx_http_lua_ffi_uri_escaped_length(const unsigned char *src, + size_t len); + + void ngx_http_lua_ffi_escape_uri(const unsigned char *src, size_t len, + unsigned char *dst); + + size_t ngx_http_lua_ffi_unescape_uri(const unsigned char *src, + size_t len, unsigned char *dst); + ]] + + ngx_lua_ffi_escape_uri = C.ngx_http_lua_ffi_escape_uri + ngx_lua_ffi_unescape_uri = C.ngx_http_lua_ffi_unescape_uri + ngx_lua_ffi_uri_escaped_length = C.ngx_http_lua_ffi_uri_escaped_length + +elseif subsystem == "stream" then + ffi.cdef[[ + size_t ngx_stream_lua_ffi_uri_escaped_length(const unsigned char *src, + size_t len); + + void ngx_stream_lua_ffi_escape_uri(const unsigned char *src, size_t len, + unsigned char *dst); + + size_t ngx_stream_lua_ffi_unescape_uri(const unsigned char *src, + size_t len, unsigned char *dst); + ]] + + ngx_lua_ffi_escape_uri = C.ngx_stream_lua_ffi_escape_uri + ngx_lua_ffi_unescape_uri = C.ngx_stream_lua_ffi_unescape_uri + ngx_lua_ffi_uri_escaped_length = C.ngx_stream_lua_ffi_uri_escaped_length +end + + +ngx.escape_uri = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local slen = #s + local dlen = ngx_lua_ffi_uri_escaped_length(s, slen) + -- print("dlen: ", tonumber(dlen)) + if dlen == slen then + return s + end + local dst = get_string_buf(dlen) + ngx_lua_ffi_escape_uri(s, slen, dst) + return ffi_string(dst, dlen) +end + + +ngx.unescape_uri = function (s) + if type(s) ~= 'string' then + if not s then + s = '' + else + s = tostring(s) + end + end + local slen = #s + local dlen = slen + local dst = get_string_buf(dlen) + dlen = ngx_lua_ffi_unescape_uri(s, slen, dst) + return ffi_string(dst, dlen) +end + + +return { + version = base.version, +} diff --git a/resty/core/utils.lua b/resty/core/utils.lua new file mode 100644 index 0000000..398d7d5 --- /dev/null +++ b/resty/core/utils.lua @@ -0,0 +1,44 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" +base.allows_subsystem("http") + + +local C = ffi.C +local ffi_str = ffi.string +local ffi_copy = ffi.copy +local byte = string.byte +local str_find = string.find +local get_string_buf = base.get_string_buf + + +ffi.cdef[[ + void ngx_http_lua_ffi_str_replace_char(unsigned char *buf, size_t len, + const unsigned char find, const unsigned char replace); +]] + + +local _M = { + version = base.version +} + + +function _M.str_replace_char(str, find, replace) + if not str_find(str, find, nil, true) then + return str + end + + local len = #str + local buf = get_string_buf(len) + ffi_copy(buf, str) + + C.ngx_http_lua_ffi_str_replace_char(buf, len, byte(find), + byte(replace)) + + return ffi_str(buf, len) +end + + +return _M diff --git a/resty/core/var.lua b/resty/core/var.lua new file mode 100644 index 0000000..ea9c763 --- /dev/null +++ b/resty/core/var.lua @@ -0,0 +1,160 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local ffi_new = ffi.new +local ffi_str = ffi.string +local type = type +local error = error +local tostring = tostring +local setmetatable = setmetatable +local get_request = base.get_request +local get_string_buf = base.get_string_buf +local get_size_ptr = base.get_size_ptr +local new_tab = base.new_tab +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_var_get +local ngx_lua_ffi_var_set + + +local ERR_BUF_SIZE = 256 + + +ngx.var = new_tab(0, 0) + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_var_get(ngx_http_request_t *r, + const char *name_data, size_t name_len, char *lowcase_buf, + int capture_id, char **value, size_t *value_len, char **err); + + int ngx_http_lua_ffi_var_set(ngx_http_request_t *r, + const unsigned char *name_data, size_t name_len, + unsigned char *lowcase_buf, const unsigned char *value, + size_t value_len, unsigned char *errbuf, size_t *errlen); + ]] + + ngx_lua_ffi_var_get = C.ngx_http_lua_ffi_var_get + ngx_lua_ffi_var_set = C.ngx_http_lua_ffi_var_set + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_var_get(ngx_stream_lua_request_t *r, + const char *name_data, size_t name_len, char *lowcase_buf, + int capture_id, char **value, size_t *value_len, char **err); + + int ngx_stream_lua_ffi_var_set(ngx_stream_lua_request_t *r, + const unsigned char *name_data, size_t name_len, + unsigned char *lowcase_buf, const unsigned char *value, + size_t value_len, unsigned char *errbuf, size_t *errlen); + ]] + + ngx_lua_ffi_var_get = C.ngx_stream_lua_ffi_var_get + ngx_lua_ffi_var_set = C.ngx_stream_lua_ffi_var_set +end + + +local value_ptr = ffi_new("unsigned char *[1]") +local errmsg = base.get_errmsg_ptr() + + +local function var_get(self, name) + local r = get_request() + if not r then + error("no request found") + end + + local value_len = get_size_ptr() + local rc + if type(name) == "number" then + rc = ngx_lua_ffi_var_get(r, nil, 0, nil, name, value_ptr, value_len, + errmsg) + + else + if type(name) ~= "string" then + error("bad variable name", 2) + end + + local name_len = #name + local lowcase_buf = get_string_buf(name_len) + + rc = ngx_lua_ffi_var_get(r, name, name_len, lowcase_buf, 0, value_ptr, + value_len, errmsg) + end + + -- ngx.log(ngx.WARN, "rc = ", rc) + + if rc == 0 then -- NGX_OK + return ffi_str(value_ptr[0], value_len[0]) + end + + if rc == -5 then -- NGX_DECLINED + return nil + end + + if rc == -1 then -- NGX_ERROR + error(ffi_str(errmsg[0]), 2) + end +end + + +local function var_set(self, name, value) + local r = get_request() + if not r then + error("no request found") + end + + if type(name) ~= "string" then + error("bad variable name", 2) + end + local name_len = #name + + local errlen = get_size_ptr() + errlen[0] = ERR_BUF_SIZE + local lowcase_buf = get_string_buf(name_len + ERR_BUF_SIZE) + + local value_len + if value == nil then + value_len = 0 + else + if type(value) ~= 'string' then + value = tostring(value) + end + value_len = #value + end + + local errbuf = lowcase_buf + name_len + local rc = ngx_lua_ffi_var_set(r, name, name_len, lowcase_buf, value, + value_len, errbuf, errlen) + + -- ngx.log(ngx.WARN, "rc = ", rc) + + if rc == 0 then -- NGX_OK + return + end + + if rc == -1 then -- NGX_ERROR + error(ffi_str(errbuf, errlen[0]), 2) + end +end + + +do + local mt = new_tab(0, 2) + mt.__index = var_get + mt.__newindex = var_set + + setmetatable(ngx.var, mt) +end + + +return { + version = base.version +} diff --git a/resty/core/worker.lua b/resty/core/worker.lua new file mode 100644 index 0000000..c336deb --- /dev/null +++ b/resty/core/worker.lua @@ -0,0 +1,77 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local base = require "resty.core.base" + + +local C = ffi.C +local new_tab = base.new_tab +local subsystem = ngx.config.subsystem + + +local ngx_lua_ffi_worker_id +local ngx_lua_ffi_worker_pid +local ngx_lua_ffi_worker_count +local ngx_lua_ffi_worker_exiting + + +ngx.worker = new_tab(0, 4) + + +if subsystem == "http" then + ffi.cdef[[ + int ngx_http_lua_ffi_worker_id(void); + int ngx_http_lua_ffi_worker_pid(void); + int ngx_http_lua_ffi_worker_count(void); + int ngx_http_lua_ffi_worker_exiting(void); + ]] + + ngx_lua_ffi_worker_id = C.ngx_http_lua_ffi_worker_id + ngx_lua_ffi_worker_pid = C.ngx_http_lua_ffi_worker_pid + ngx_lua_ffi_worker_count = C.ngx_http_lua_ffi_worker_count + ngx_lua_ffi_worker_exiting = C.ngx_http_lua_ffi_worker_exiting + +elseif subsystem == "stream" then + ffi.cdef[[ + int ngx_stream_lua_ffi_worker_id(void); + int ngx_stream_lua_ffi_worker_pid(void); + int ngx_stream_lua_ffi_worker_count(void); + int ngx_stream_lua_ffi_worker_exiting(void); + ]] + + ngx_lua_ffi_worker_id = C.ngx_stream_lua_ffi_worker_id + ngx_lua_ffi_worker_pid = C.ngx_stream_lua_ffi_worker_pid + ngx_lua_ffi_worker_count = C.ngx_stream_lua_ffi_worker_count + ngx_lua_ffi_worker_exiting = C.ngx_stream_lua_ffi_worker_exiting +end + + +function ngx.worker.exiting() + return ngx_lua_ffi_worker_exiting() ~= 0 +end + + +function ngx.worker.pid() + return ngx_lua_ffi_worker_pid() +end + + +function ngx.worker.id() + local id = ngx_lua_ffi_worker_id() + if id < 0 then + return nil + end + + return id +end + + +function ngx.worker.count() + return ngx_lua_ffi_worker_count() +end + + +return { + _VERSION = base.version +} diff --git a/resty/lrucache.lua b/resty/lrucache.lua new file mode 100644 index 0000000..147842a --- /dev/null +++ b/resty/lrucache.lua @@ -0,0 +1,338 @@ +-- Copyright (C) Yichun Zhang (agentzh) + + +local ffi = require "ffi" +local ffi_new = ffi.new +local ffi_sizeof = ffi.sizeof +local ffi_cast = ffi.cast +local ffi_fill = ffi.fill +local ngx_now = ngx.now +local uintptr_t = ffi.typeof("uintptr_t") +local setmetatable = setmetatable +local tonumber = tonumber +local type = type +local new_tab +do + local ok + ok, new_tab = pcall(require, "table.new") + if not ok then + new_tab = function(narr, nrec) return {} end + end +end + + +if string.find(jit.version, " 2.0", 1, true) then + ngx.log(ngx.ALERT, "use of lua-resty-lrucache with LuaJIT 2.0 is ", + "not recommended; use LuaJIT 2.1+ instead") +end + + +local ok, tb_clear = pcall(require, "table.clear") +if not ok then + local pairs = pairs + tb_clear = function (tab) + for k, _ in pairs(tab) do + tab[k] = nil + end + end +end + + +-- queue data types +-- +-- this queue is a double-ended queue and the first node +-- is reserved for the queue itself. +-- the implementation is mostly borrowed from nginx's ngx_queue_t data +-- structure. + +ffi.cdef[[ + typedef struct lrucache_queue_s lrucache_queue_t; + struct lrucache_queue_s { + double expire; /* in seconds */ + lrucache_queue_t *prev; + lrucache_queue_t *next; + uint32_t user_flags; + }; +]] + +local queue_arr_type = ffi.typeof("lrucache_queue_t[?]") +local queue_type = ffi.typeof("lrucache_queue_t") +local NULL = ffi.null + + +-- queue utility functions + +local function queue_insert_tail(h, x) + local last = h[0].prev + x.prev = last + last.next = x + x.next = h + h[0].prev = x +end + + +local function queue_init(size) + if not size then + size = 0 + end + local q = ffi_new(queue_arr_type, size + 1) + ffi_fill(q, ffi_sizeof(queue_type, size + 1), 0) + + if size == 0 then + q[0].prev = q + q[0].next = q + + else + local prev = q[0] + for i = 1, size do + local e = q + i + e.user_flags = 0 + prev.next = e + e.prev = prev + prev = e + end + + local last = q[size] + last.next = q + q[0].prev = last + end + + return q +end + + +local function queue_is_empty(q) + -- print("q: ", tostring(q), "q.prev: ", tostring(q), ": ", q == q.prev) + return q == q[0].prev +end + + +local function queue_remove(x) + local prev = x.prev + local next = x.next + + next.prev = prev + prev.next = next + + -- for debugging purpose only: + x.prev = NULL + x.next = NULL +end + + +local function queue_insert_head(h, x) + x.next = h[0].next + x.next.prev = x + x.prev = h + h[0].next = x +end + + +local function queue_last(h) + return h[0].prev +end + + +local function queue_head(h) + return h[0].next +end + + +-- true module stuffs + +local _M = { + _VERSION = '0.10' +} +local mt = { __index = _M } + + +local function ptr2num(ptr) + return tonumber(ffi_cast(uintptr_t, ptr)) +end + + +function _M.new(size) + if size < 1 then + return nil, "size too small" + end + + local self = { + hasht = {}, + free_queue = queue_init(size), + cache_queue = queue_init(), + key2node = {}, + node2key = {}, + num_items = 0, + max_items = size, + } + return setmetatable(self, mt) +end + + +function _M.count(self) + return self.num_items +end + + +function _M.capacity(self) + return self.max_items +end + + +function _M.get(self, key) + local hasht = self.hasht + local val = hasht[key] + if val == nil then + return nil + end + + local node = self.key2node[key] + + -- print(key, ": moving node ", tostring(node), " to cache queue head") + local cache_queue = self.cache_queue + queue_remove(node) + queue_insert_head(cache_queue, node) + + if node.expire >= 0 and node.expire < ngx_now() then + -- print("expired: ", node.expire, " > ", ngx_now()) + return nil, val, node.user_flags + end + + return val, nil, node.user_flags +end + + +function _M.delete(self, key) + self.hasht[key] = nil + + local key2node = self.key2node + local node = key2node[key] + + if not node then + return false + end + + key2node[key] = nil + self.node2key[ptr2num(node)] = nil + + queue_remove(node) + queue_insert_tail(self.free_queue, node) + self.num_items = self.num_items - 1 + return true +end + + +function _M.set(self, key, value, ttl, flags) + local hasht = self.hasht + hasht[key] = value + + local key2node = self.key2node + local node = key2node[key] + if not node then + local free_queue = self.free_queue + local node2key = self.node2key + + if queue_is_empty(free_queue) then + -- evict the least recently used key + -- assert(not queue_is_empty(self.cache_queue)) + node = queue_last(self.cache_queue) + + local oldkey = node2key[ptr2num(node)] + -- print(key, ": evicting oldkey: ", oldkey, ", oldnode: ", + -- tostring(node)) + if oldkey then + hasht[oldkey] = nil + key2node[oldkey] = nil + end + + else + -- take a free queue node + node = queue_head(free_queue) + -- only add count if we are not evicting + self.num_items = self.num_items + 1 + -- print(key, ": get a new free node: ", tostring(node)) + end + + node2key[ptr2num(node)] = key + key2node[key] = node + end + + queue_remove(node) + queue_insert_head(self.cache_queue, node) + + if ttl then + node.expire = ngx_now() + ttl + else + node.expire = -1 + end + + if type(flags) == "number" and flags >= 0 then + node.user_flags = flags + + else + node.user_flags = 0 + end +end + + +function _M.get_keys(self, max_count, res) + if not max_count or max_count == 0 then + max_count = self.num_items + end + + if not res then + res = new_tab(max_count + 1, 0) -- + 1 for trailing hole + end + + local cache_queue = self.cache_queue + local node2key = self.node2key + + local i = 0 + local node = queue_head(cache_queue) + + while node ~= cache_queue do + if i >= max_count then + break + end + + i = i + 1 + res[i] = node2key[ptr2num(node)] + node = node.next + end + + res[i + 1] = nil + + return res +end + + +function _M.flush_all(self) + tb_clear(self.hasht) + tb_clear(self.node2key) + tb_clear(self.key2node) + + local cache_queue = self.cache_queue + local free_queue = self.free_queue + + -- splice the cache_queue into free_queue + if not queue_is_empty(cache_queue) then + local free_head = free_queue[0] + local free_last = free_head.prev + + local cache_head = cache_queue[0] + local cache_first = cache_head.next + local cache_last = cache_head.prev + + free_last.next = cache_first + cache_first.prev = free_last + + cache_last.next = free_head + free_head.prev = cache_last + + cache_head.next = cache_queue + cache_head.prev = cache_queue + end +end + + +return _M diff --git a/resty/lrucache/pureffi.lua b/resty/lrucache/pureffi.lua new file mode 100644 index 0000000..fc1103e --- /dev/null +++ b/resty/lrucache/pureffi.lua @@ -0,0 +1,606 @@ +-- Copyright (C) Yichun Zhang (agentzh) +-- Copyright (C) Shuxin Yang + +--[[ + This module implements a key/value cache store. We adopt LRU as our +replace/evict policy. Each key/value pair is tagged with a Time-to-Live (TTL); +from user's perspective, stale pairs are automatically removed from the cache. + +Why FFI +------- + In Lua, expression "table[key] = nil" does not *PHYSICALLY* remove the value +associated with the key; it just set the value to be nil! So the table will +keep growing with large number of the key/nil pairs which will be purged until +resize() operator is called. + + This "feature" is terribly ill-suited to what we need. Therefore we have to +rely on FFI to build a hash-table where any entry can be physically deleted +immediately. + +Under the hood: +-------------- + In concept, we introduce three data structures to implement the cache store: + 1. key/value vector for storing keys and values. + 2. a queue to mimic the LRU. + 3. hash-table for looking up the value for a given key. + + Unfortunately, efficiency and clarity usually come at each other cost. The +data strucutres we are using are slightly more complicated than what we +described above. + + o. Lua does not have efficient way to store a vector of pair. So, we use + two vectors for key/value pair: one for keys and the other for values + (_M.key_v and _M.val_v, respectively), and i-th key corresponds to + i-th value. + + A key/value pair is identified by the "id" field in a "node" (we shall + discuss node later) + + o. The queue is nothing more than a doubly-linked list of "node" linked via + lrucache_pureffi_queue_s::{next|prev} fields. + + o. The hash-table has two parts: + - the _M.bucket_v[] a vector of bucket, indiced by hash-value, and + - a bucket is a singly-linked list of "node" via the + lrucache_pureffi_queue_s::conflict field. + + A key must be a string, and the hash value of a key is evaluated by: + crc32(key-cast-to-pointer) % size(_M.bucket_v). + We mandate size(_M.bucket_v) being a power-of-two in order to avoid + expensive modulo operation. + + At the heart of the module is an array of "node" (of type + lrucache_pureffi_queue_s). A node: + - keeps the meta-data of its corresponding key/value pair + (embodied by the "id", and "expire" field); + - is a part of LRU queue (embodied by "prev" and "next" fields); + - is a part of hash-table (embodied by the "conflict" field). +]] + +local ffi = require "ffi" +local bit = require "bit" + + +local ffi_new = ffi.new +local ffi_sizeof = ffi.sizeof +local ffi_cast = ffi.cast +local ffi_fill = ffi.fill +local ngx_now = ngx.now +local uintptr_t = ffi.typeof("uintptr_t") +local c_str_t = ffi.typeof("const char*") +local int_t = ffi.typeof("int") +local int_array_t = ffi.typeof("int[?]") + + +local crc_tab = ffi.new("const unsigned int[256]", { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }); + +local setmetatable = setmetatable +local tonumber = tonumber +local tostring = tostring +local type = type + +local brshift = bit.rshift +local bxor = bit.bxor +local band = bit.band + +local new_tab +do + local ok + ok, new_tab = pcall(require, "table.new") + if not ok then + new_tab = function(narr, nrec) return {} end + end +end + +-- queue data types +-- +-- this queue is a double-ended queue and the first node +-- is reserved for the queue itself. +-- the implementation is mostly borrowed from nginx's ngx_queue_t data +-- structure. + +ffi.cdef[[ + /* A lrucache_pureffi_queue_s node hook together three data structures: + * o. the key/value store as embodied by the "id" (which is in essence the + * indentifier of key/pair pair) and the "expire" (which is a metadata + * of the corresponding key/pair pair). + * o. The LRU queue via the prev/next fields. + * o. The hash-tabble as embodied by the "conflict" field. + */ + typedef struct lrucache_pureffi_queue_s lrucache_pureffi_queue_t; + struct lrucache_pureffi_queue_s { + /* Each node is assigned a unique ID at construction time, and the + * ID remain immutatble, regardless the node is in active-list or + * free-list. The queue header is assigned ID 0. Since queue-header + * is a sentinel node, 0 denodes "invalid ID". + * + * Intuitively, we can view the "id" as the identifier of key/value + * pair. + */ + int id; + + /* The bucket of the hash-table is implemented as a singly-linked list. + * The "conflict" refers to the ID of the next node in the bucket. + */ + int conflict; + + uint32_t user_flags; + + double expire; /* in seconds */ + + lrucache_pureffi_queue_t *prev; + lrucache_pureffi_queue_t *next; + }; +]] + +local queue_arr_type = ffi.typeof("lrucache_pureffi_queue_t[?]") +--local queue_ptr_type = ffi.typeof("lrucache_pureffi_queue_t*") +local queue_type = ffi.typeof("lrucache_pureffi_queue_t") +local NULL = ffi.null + + +--======================================================================== +-- +-- Queue utility functions +-- +--======================================================================== + +-- Append the element "x" to the given queue "h". +local function queue_insert_tail(h, x) + local last = h[0].prev + x.prev = last + last.next = x + x.next = h + h[0].prev = x +end + + +--[[ +Allocate a queue with size + 1 elements. Elements are linked together in a +circular way, i.e. the last element's "next" points to the first element, +while the first element's "prev" element points to the last element. +]] +local function queue_init(size) + if not size then + size = 0 + end + local q = ffi_new(queue_arr_type, size + 1) + ffi_fill(q, ffi_sizeof(queue_type, size + 1), 0) + + if size == 0 then + q[0].prev = q + q[0].next = q + + else + local prev = q[0] + for i = 1, size do + local e = q[i] + e.id = i + e.user_flags = 0 + prev.next = e + e.prev = prev + prev = e + end + + local last = q[size] + last.next = q + q[0].prev = last + end + + return q +end + + +local function queue_is_empty(q) + -- print("q: ", tostring(q), "q.prev: ", tostring(q), ": ", q == q.prev) + return q == q[0].prev +end + + +local function queue_remove(x) + local prev = x.prev + local next = x.next + + next.prev = prev + prev.next = next + + -- for debugging purpose only: + x.prev = NULL + x.next = NULL +end + + +-- Insert the element "x" the to the given queue "h" +local function queue_insert_head(h, x) + x.next = h[0].next + x.next.prev = x + x.prev = h + h[0].next = x +end + + +local function queue_last(h) + return h[0].prev +end + + +local function queue_head(h) + return h[0].next +end + + +--======================================================================== +-- +-- Miscellaneous Utility Functions +-- +--======================================================================== + +local function ptr2num(ptr) + return tonumber(ffi_cast(uintptr_t, ptr)) +end + + +local function crc32_ptr(ptr) + local p = brshift(ptr2num(ptr), 3) + local b = band(p, 255) + local crc32 = crc_tab[b] + + b = band(brshift(p, 8), 255) + crc32 = bxor(brshift(crc32, 8), crc_tab[band(bxor(crc32, b), 255)]) + + b = band(brshift(p, 16), 255) + crc32 = bxor(brshift(crc32, 8), crc_tab[band(bxor(crc32, b), 255)]) + + --b = band(brshift(p, 24), 255) + --crc32 = bxor(brshift(crc32, 8), crc_tab[band(bxor(crc32, b), 255)]) + return crc32 +end + + +--======================================================================== +-- +-- Implementation of "export" functions +-- +--======================================================================== + +local _M = { + _VERSION = '0.10' +} +local mt = { __index = _M } + + +-- "size" specifies the maximum number of entries in the LRU queue, and the +-- "load_factor" designates the 'load factor' of the hash-table we are using +-- internally. The default value of load-factor is 0.5 (i.e. 50%); if the +-- load-factor is specified, it will be clamped to the range of [0.1, 1](i.e. +-- if load-factor is greater than 1, it will be saturated to 1, likewise, +-- if load-factor is smaller than 0.1, it will be clamped to 0.1). +function _M.new(size, load_factor) + if size < 1 then + return nil, "size too small" + end + + -- Determine bucket size, which must be power of two. + local load_f = load_factor + if not load_factor then + load_f = 0.5 + elseif load_factor > 1 then + load_f = 1 + elseif load_factor < 0.1 then + load_f = 0.1 + end + + local bs_min = size / load_f + -- The bucket_sz *MUST* be a power-of-two. See the hash_string(). + local bucket_sz = 1 + repeat + bucket_sz = bucket_sz * 2 + until bucket_sz >= bs_min + + local self = { + size = size, + bucket_sz = bucket_sz, + free_queue = queue_init(size), + cache_queue = queue_init(0), + node_v = nil, + key_v = new_tab(size, 0), + val_v = new_tab(size, 0), + bucket_v = ffi_new(int_array_t, bucket_sz), + num_items = 0, + } + -- "note_v" is an array of all the nodes used in the LRU queue. Exprpession + -- node_v[i] evaluates to the element of ID "i". + self.node_v = self.free_queue + + -- Allocate the array-part of the key_v, val_v, bucket_v. + --local key_v = self.key_v + --local val_v = self.val_v + --local bucket_v = self.bucket_v + ffi_fill(self.bucket_v, ffi_sizeof(int_t, bucket_sz), 0) + + return setmetatable(self, mt) +end + + +function _M.count(self) + return self.num_items +end + + +function _M.capacity(self) + return self.size +end + + +local function hash_string(self, str) + local c_str = ffi_cast(c_str_t, str) + + local hv = crc32_ptr(c_str) + hv = band(hv, self.bucket_sz - 1) + -- Hint: bucket is 0-based + return hv +end + + +-- Search the node associated with the key in the bucket, if found returns +-- the the id of the node, and the id of its previous node in the conflict list. +-- The "bucket_hdr_id" is the ID of the first node in the bucket +local function _find_node_in_bucket(key, key_v, node_v, bucket_hdr_id) + if bucket_hdr_id ~= 0 then + local prev = 0 + local cur = bucket_hdr_id + + while cur ~= 0 and key_v[cur] ~= key do + prev = cur + cur = node_v[cur].conflict + end + + if cur ~= 0 then + return cur, prev + end + end +end + + +-- Return the node corresponding to the key/val. +local function find_key(self, key) + local key_hash = hash_string(self, key) + return _find_node_in_bucket(key, self.key_v, self.node_v, + self.bucket_v[key_hash]) +end + + +--[[ This function tries to + 1. Remove the given key and the associated value from the key/value store, + 2. Remove the entry associated with the key from the hash-table. + + NOTE: all queues remain intact. + + If there was a node bound to the key/val, return that node; otherwise, + nil is returned. +]] +local function remove_key(self, key) + local key_v = self.key_v + local val_v = self.val_v + local node_v = self.node_v + local bucket_v = self.bucket_v + + local key_hash = hash_string(self, key) + local cur, prev = + _find_node_in_bucket(key, key_v, node_v, bucket_v[key_hash]) + + if cur then + -- In an attempt to make key and val dead. + key_v[cur] = nil + val_v[cur] = nil + self.num_items = self.num_items - 1 + + -- Remove the node from the hash table + local next_node = node_v[cur].conflict + if prev ~= 0 then + node_v[prev].conflict = next_node + else + bucket_v[key_hash] = next_node + end + node_v[cur].conflict = 0 + + return cur + end +end + + +--[[ Bind the key/val with the given node, and insert the node into the Hashtab. + NOTE: this function does not touch any queue +]] +local function insert_key(self, key, val, node) + -- Bind the key/val with the node + local node_id = node.id + self.key_v[node_id] = key + self.val_v[node_id] = val + + -- Insert the node into the hash-table + local key_hash = hash_string(self, key) + local bucket_v = self.bucket_v + node.conflict = bucket_v[key_hash] + bucket_v[key_hash] = node_id + self.num_items = self.num_items + 1 +end + + +function _M.get(self, key) + if type(key) ~= "string" then + key = tostring(key) + end + + local node_id = find_key(self, key) + if not node_id then + return nil + end + + -- print(key, ": moving node ", tostring(node), " to cache queue head") + local cache_queue = self.cache_queue + local node = self.node_v + node_id + queue_remove(node) + queue_insert_head(cache_queue, node) + + local expire = node.expire + if expire >= 0 and expire < ngx_now() then + -- print("expired: ", node.expire, " > ", ngx_now()) + return nil, self.val_v[node_id], node.user_flags + end + + return self.val_v[node_id], nil, node.user_flags +end + + +function _M.delete(self, key) + if type(key) ~= "string" then + key = tostring(key) + end + + local node_id = remove_key(self, key); + if not node_id then + return false + end + + local node = self.node_v + node_id + queue_remove(node) + queue_insert_tail(self.free_queue, node) + return true +end + + +function _M.set(self, key, value, ttl, flags) + if type(key) ~= "string" then + key = tostring(key) + end + + local node_id = find_key(self, key) + local node + if not node_id then + local free_queue = self.free_queue + if queue_is_empty(free_queue) then + -- evict the least recently used key + -- assert(not queue_is_empty(self.cache_queue)) + node = queue_last(self.cache_queue) + remove_key(self, self.key_v[node.id]) + else + -- take a free queue node + node = queue_head(free_queue) + -- print(key, ": get a new free node: ", tostring(node)) + end + + -- insert the key + insert_key(self, key, value, node) + else + node = self.node_v + node_id + self.val_v[node_id] = value + end + + queue_remove(node) + queue_insert_head(self.cache_queue, node) + + if ttl then + node.expire = ngx_now() + ttl + else + node.expire = -1 + end + + if type(flags) == "number" and flags >= 0 then + node.user_flags = flags + + else + node.user_flags = 0 + end +end + + +function _M.get_keys(self, max_count, res) + if not max_count or max_count == 0 then + max_count = self.num_items + end + + if not res then + res = new_tab(max_count + 1, 0) -- + 1 for trailing hole + end + + local cache_queue = self.cache_queue + local key_v = self.key_v + + local i = 0 + local node = queue_head(cache_queue) + + while node ~= cache_queue do + if i >= max_count then + break + end + + i = i + 1 + res[i] = key_v[node.id] + node = node.next + end + + res[i + 1] = nil + + return res +end + + +function _M.flush_all(self) + local cache_queue = self.cache_queue + local key_v = self.key_v + + local node = queue_head(cache_queue) + + while node ~= cache_queue do + local key = key_v[node.id] + node = node.next + _M.delete(self, key) + end +end + + +return _M diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..5251680 --- /dev/null +++ b/setup.sh @@ -0,0 +1,233 @@ +#!/bin/bash + +#OPTIONS! + +MASTERONION="dreadytofatroptsdj6io7l3xptbet6onoyno2yv7jicoxknyazubrad.onion" +TORAUTHPASSWORD="password" + +#Shared Front Captcha Key. Key alphanumeric between 64-128. Salt needs to be exactly 8 chars. +KEY="encryption_key" +SALT="salt1234" + +#CSS Branding + +HEXCOLOR="#9b59b6" + +#There is more branding you need to do in the resty/caphtml_d.lua file near the end. + +clear + +echo "Welcome To The End Game DDOS Prevention Setup..." +sleep 1 +BLUE='\033[1;34m' +RED='\033[0;31m' +NC='\033[0m' # No Color +printf "\r\nProvided by your lovely ${BLUE}/u/Paris${NC} from dread. \r\n" +printf "with help from ${BLUE}/u/mr_white${NC} from whitehousemarket.\n" +echo "For the full effects of the DDOS prevention you will need to make sure to setup v3 onionbalance." +echo "Max 6-9 backend instances for each onion. This script will help make the backend instances." + +if [ ${#MASTERONION} -lt 62 ]; then + echo "MASTEWRONION doesn't have the correct length. The url needs to include the .onion at the end." + exit 0 +fi + +if [ -z "$TORAUTHPASSWORD" ] +then + echo "you didn't enter your tor authpassword" + exit 0 +fi + +sleep 5 +echo "Proceeding to do the configuration and setup. This will take awhile." + + +### Configuration +string="s/masterbalanceonion/" +string+="$MASTERONION" +string+="/g" +sed -i $string site.conf + +string="s/torauthpassword/" +string+="$torinput" +string+="/g" +sed -i $string site.conf + +string="s/encryption_key/" +string+="$KEY" +string+="/g" +sed -i $string lua/cap.lua + +string="s/salt1234/" +string+="$SALT" +string+="/g" +sed -i $string lua/cap.lua + +string="s/HEXCOLOR/" +string+="$HEXCOLOR" +string+="/g" +sed -i $string cap_d.css + +string="s/SITENAME/" +string+="$SITENAME" +string+="/g" +sed -i $string resty/caphtml_d.lua + +apt-get update +apt-get install -y apt-transport-https lsb-release ca-certificates dirmngr + +echo "deb https://deb.torproject.org/torproject.org buster main" > /etc/apt/sources.list.d/tor.list +echo "deb-src https://deb.torproject.org/torproject.org buster main" >> /etc/apt/sources.list.d/tor.list +echo "deb https://deb.torproject.org/torproject.org tor-nightly-master-buster main" >> /etc/apt/sources.list.d/tor.list +echo "deb-src https://deb.torproject.org/torproject.org tor-nightly-master-buster main" >> /etc/apt/sources.list.d/tor.list +echo "deb https://nginx.org/packages/debian/ buster nginx" > /etc/apt/sources.list.d/nginx.list + +gpg --import A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc +gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | apt-key add - +apt-key add nginx_signing.key + +apt-get update +apt-get install -y tor nyx nginx +apt-get install -y vanguards + +command="nginx -v" +nginxv=$( ${command} 2>&1 ) +NGINXVERSION=$(echo $nginxv | grep -o '[0-9.]*$') +NGINXOPENSSL="1.1.1d" + +wget https://nginx.org/download/nginx-$NGINXVERSION.tar.gz +tar -xzvf nginx-$NGINXVERSION.tar.gz +cd nginx-$NGINXVERSION + +apt-get install -y build-essential zlib1g-dev libpcre3 libpcre3-dev uuid-dev gcc git wget curl libgd3 libgd-dev + +git clone https://github.com/yorkane/socks-nginx-module.git +git clone https://github.com/nbs-system/naxsi.git +wget https://www.openssl.org/source/openssl-$NGINXOPENSSL.tar.gz +tar -xzvf openssl-$NGINXOPENSSL.tar.gz +git clone https://github.com/openresty/headers-more-nginx-module.git +git clone https://github.com/openresty/echo-nginx-module.git + +#some required stuff for lua/luajit. obviously versions should be ckecked with every install/update +git clone https://github.com/openresty/lua-nginx-module +cd lua-nginx-module +git checkout v0.10.16 +cd .. +git clone https://github.com/openresty/luajit2 +cd luajit2 +git checkout v2.1-20200102 +cd .. +git clone https://github.com/vision5/ngx_devel_kit +cd luajit2 +make -j8 && make install +cd .. + +export LUAJIT_LIB=/usr/local/lib +export LUAJIT_INC=/usr/local/include/luajit-2.1 +./configure --with-cc-opt='-Wno-stringop-overflow -Wno-stringop-truncation -Wno-cast-function-type' \ +--with-ld-opt="-Wl,-rpath,/usr/local/lib" \ +--with-compat --with-openssl=openssl-$NGINXOPENSSL \ +--with-http_ssl_module \ +--add-dynamic-module=naxsi/naxsi_src \ +--add-dynamic-module=headers-more-nginx-module \ +--add-dynamic-module=socks-nginx-module \ +--add-dynamic-module=echo-nginx-module \ + --add-dynamic-module=ngx_devel_kit \ +--add-dynamic-module=lua-nginx-module + +git clone https://github.com/openresty/lua-resty-string +cd lua-resty-string +make install +cd .. + +git clone https://github.com/cloudflare/lua-resty-cookie +cd lua-resty-cookie +make install +cd .. + +git clone https://github.com/bungle/lua-resty-session +cp -a lua-resty-session/lib/resty/session* /usr/local/lib/lua/resty/ + +git clone https://github.com/ittner/lua-gd/ +cd lua-gd +gcc -o gd.so -DGD_XPM -DGD_JPEG -DGD_FONTCONFIG -DGD_FREETYPE -DGD_PNG -DGD_GIF -O2 -Wall -fPIC -fomit-frame-pointer -I/usr/local/include/luajit-2.1 -DVERSION=\"2.0.33r3\" -shared -lgd luagd.c +mv gd.so /usr/local/lib/lua/5.1/gd.so +cd .. + +wget -O /usr/local/lib/lua/resty/aes_functions.lua https://github.com/c64bob/lua-resty-aes/raw/master/lib/resty/aes_functions.lua + +#include seems to be a bit mssed up with luajit +mkdir /etc/nginx/resty +ln -s /usr/local/lib/lua/resty/ /etc/nginx/resty/ + +make -j16 modules + +cp -r objs modules +mv modules /etc/nginx/modules +cd .. + +mv nginx.conf /etc/nginx/nginx.conf +mv naxsi_core.rules /etc/nginx/naxsi_core.rules +mv naxsi_whitelist.rules /etc/nginx/naxsi_whitelist.rules +mv lua /etc/nginx/ +mv resty/* /etc/nginx/resty/resty/ +mv /etc/nginx/resty/resty/caphtml_d.lua /etc/nginx/resty/caphtml_d.lua +mkdir /etc/nginx/sites-enabled/ +mv site.conf /etc/nginx/sites-enabled/site.conf + +#background generation +apt-get install -y python3-pil +mv gen_background.py /etc/nginx/gen_background.py +echo "* * * * * root python3 /etc/nginx/gen_background.py" > /etc/cron.d/background-generate +mv font.ttf /etc/nginx/font.ttf +mv cap_d.css /etc/nginx/cap_d.css + +chown -R www-data:www-data /etc/nginx/ +chown -R www-data:www-data /usr/local/lib/lua + +chmod 755 startup.sh +mv startup.sh /startup.sh +chmod 755 rc.local +mv rc.local /etc/rc.local + +mv sysctl.conf /etc/sysctl.conf + +pkill tor + +mv torrc /etc/tor/torrc + +torhash=$(tor --hash-password $TORAUTHPASSWORD| tail -c 62) +string="s/hashedpassword/" +string+="$torhash" +string+="/g" +sed -i $string /etc/tor/torrc + +sleep 10 + +tor + +sleep 20 + +HOSTNAME="$(cat /etc/tor/hidden_service/hostname)" + +string="s/mainonion/" +string+="$HOSTNAME" +string+="/g" +sed -i $string /etc/nginx/sites-enabled/site.conf + +echo "MasterOnionAddress $MASTERONION" >> /etc/tor/hidden_service/ob_config + +pkill tor +sleep 10 + +sed -i "s/#HiddenServiceOnionBalanceInstance/HiddenServiceOnionBalanceInstance/g" /etc/tor/torrc + +tor +nginx +service vanguards start + +clear + +echo "ALL SETUP! Your new front address is" +echo $HOSTNAME +echo "Add it to your onionbalance configuration!" diff --git a/site.conf b/site.conf new file mode 100644 index 0000000..4801ed5 --- /dev/null +++ b/site.conf @@ -0,0 +1,309 @@ +#right now there is a lot of logging to error_log so during an attack those logs will fill the disk eventually. +#a good idea would be to use a syslog server and log to a socket instead of a file for IO optimization +#logging could also be disabled in production + +#depending on cluster setup some things can be changed here. +#keepalive 128; or proxy_bind on multiple local ips can be used to mitigate local port exhaustion +#most likely with this setup it's not the case +#if this runs on the same machine as the application server UNIX sockets should be used instead of TCP + +access_by_lua_no_postpone on; +lua_package_path "/etc/nginx/resty/?.lua;;"; + +init_by_lua_block { + allowed_hosts = { "mainonion", + "masterbalanceonion" + } + + function in_array(tab, val) + for index, value in ipairs(tab) do + if value == val then + return true + end + end + return nil + end + + function split(str, sep) + local result = {} + local regex = ("([^%s]+)"):format(sep) + for each in str:gmatch(regex) do + table.insert(result, each) + end + return result + end + + local function calc_circuit(proxyheaderip) + local cg = split(proxyheaderip, ":") + local g1 = cg[5] + local g2 = cg[6] + + local glen = string.len(g1) + if (glen < 4) then + for i = (4 - glen),1,-1 do + g1 = "0" .. g1 + ::loop_label_1:: + end + end + glen = string.len(g2) + if (glen < 4) then + for i = (4 - glen),1,-1 do + g2 = "0" .. g2 + ::loop_label_2:: + end + end + + local d1 = (string.sub(g1,1,1) .. string.sub(g1,2,2)) + local d2 = (string.sub(g1,3,3) .. string.sub(g1,4,4)) + local d3 = (string.sub(g2,1,1) .. string.sub(g2,2,2)) + local d4 = (string.sub(g2,3,3) .. string.sub(g2,4,4)) + local circuit_id = ((((bit.lshift(tonumber(d1, 16), 24)) + (bit.lshift(tonumber(d2, 16), 16))) + (bit.lshift(tonumber(d3, 16), 8))) + tonumber(d4, 16)) + return circuit_id + end + + function kill_circuit(premature, clientip, headerip) + local circuitid = calc_circuit(headerip) + local response = "Closing circuit " .. circuitid .. " " + local sock = ngx.socket.tcp() + sock:settimeout(1000) + local ok, err = sock:connect(clientip, 9051) + if not ok then + ngx.log(ngx.ERR, "failed to connect to tor: " .. err) + return + end + ngx.log(ngx.ERR, "connected to tor") + + local bytes, err = sock:send("authenticate \"torauthpassword\"\n") + if not bytes then + ngx.log(ngx.ERR, "failed authenticate to tor: " .. err) + return + end + local data, err, partial = sock:receive() + if not data then + ngx.log(ngx.ERR, "failed receive data from tor: " .. err) + return + end + local response = response .. " " .. data + + local bytes, err = sock:send("closecircuit " .. circuitid .. "\n") + if not bytes then + ngx.log(ngx.ERR, "failed send data to tor: " .. err) + return + end + local data, err, partial = sock:receive() + if not data then + ngx.log(ngx.ERR, "failed receive data from tor: " .. err) + return + end + local response = response .. " " .. data + + ngx.log(ngx.ERR, response) + sock:close() + return + end +} + +#rate limits should be set to the maximum number of resources (css/images/iframes) a page will load. those should be kept to a minimum for optimization reasons +#limiting by proxy_protocol_addr won't work with V2 onions and maybe should be disabled. +#limiting by cookie_ works regarless and must be used, otherwise an attacker can solve a captcha by hand and add it to a script/bot + +limit_req_zone $proxy_protocol_addr zone=circuits:50m rate=3r/s; +limit_req_zone $cookie_dcap zone=capcookie:50m rate=3r/s; + +#proxy_protocol only makes sense with V3 onions (exportcircuitid) otherwise it will break things. +#kill_circuit won't be used without it +server { + listen 88 proxy_protocol backlog=16384; + allow 127.0.0.1; + deny all; + + #access_log /var/log/nginx/front_access.log; + + if ($http_x_tor2web) { + return 401; + } + + error_page 401 @tor2web; + + location @tor2web { + echo_status 401; + default_type text/html; + echo

It seems you are connecting over a Tor2Web Proxy.

This is unsafe being that you are giving the proxy a privileged position where it can modify and/or inject content into the webpages you visit as well as track what you do.

; + echo

When visiting please use the Tor Browser and go to the offical onion address. This keeps you private and safe.; + } + + more_clear_headers 'Server:*'; + more_clear_headers 'X-Page-Speed:*'; + more_clear_headers 'Vary*'; + more_clear_headers 'captcha-fails*'; + + #what do do when rate limit is triggered, blacklist the cookie (if exists) and kill circuit + location @ratelimit { + error_log /var/log/nginx/front_error.log; + access_by_lua_block { + local pa = "no_proxy" + if ngx.var.proxy_protocol_addr ~= nil then + pa = ngx.var.proxy_protocol_addr + end + local cook = require "resty.cookie" + local cookie, err = cook:new() + if not cookie then + ngx.log(ngx.ERR, err) + return + end + local field, err = cookie:get("dcap") + if field then + + local blocked_cookies = ngx.shared.blocked_cookies + blocked_cookies:set(field, 1, 3600) + end + + ngx.log(ngx.ERR, "Rate limited " .. ngx.var.remote_addr .. "|" .. pa) + + if pa ~= "no_proxy" then + local ok, err = ngx.timer.at(0, kill_circuit, ngx.var.remote_addr, ngx.var.proxy_protocol_addr) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + end + ngx.exit(444) + } + } + + #what do do when waf is triggered, just show the error page and kill circuit for now. + #naxsi seems to kick in before everything else except rate limiter but if it does trash traffic won't make it to the application servers anyway + #doesn't make sense to blacklist cookie as it will annoy users + + location = @waf { + error_log /var/log/nginx/front_error.log; + default_type text/html; + content_by_lua_block { + ngx.say("Error") + ngx.say("") + ngx.say("

Error

") + ngx.say("

Your browser sent a request that this server could not understand.

") + ngx.say("

Most likely your input contains invalid characters (\" , `, etc.) that except for passwords should not be used.

") + ngx.say("

This may also happen if you are trying to send contact information or external links.

") + ngx.say("

Please go back, check your input and try again.

") + + proxyip = "no_proxy" + torip = ngx.var.remote_addr + if ngx.var.proxy_protocol_addr ~= nil then + proxyip = ngx.var.proxy_protocol_addr + end + + ngx.log(ngx.ERR, "WAF triggered " .. torip .. "|" .. proxyip) + if proxyip ~= "no_proxy" then + local ok, err = ngx.timer.at(0, kill_circuit, torip, proxyip) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + end + } + } + + location /kill { + access_by_lua_block { + proxyip = "no_proxy" + torip = ngx.var.remote_addr + if ngx.var.proxy_protocol_addr ~= nil then + proxyip = ngx.var.proxy_protocol_addr + end + + ngx.log(ngx.ERR, "Kill area visited" .. torip .. "|" .. proxyip) + + local cook = require "resty.cookie" + local cookie, err = cook:new() + if not cookie then + ngx.log(ngx.ERR, err) + return + end + + local field, err = cookie:get("dcap") + if field then + local blocked_cookies = ngx.shared.blocked_cookies + blocked_cookies:set(field, 1, 3600) + end + + if proxyip ~= "no_proxy" then + local ok, err = ngx.timer.at(0, kill_circuit, torip, proxyip) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + end + ngx.exit(444) + } + } + location / { + #access_log /var/log/nginx/front_access.log; + error_log /var/log/nginx/front_error.log; + + #rate limits per circuit ID (won't work with V2 and maybe should be disabled) + limit_req zone=circuits burst=6 nodelay; + error_page 503 =503 @ratelimit; + + #rate limits based on captcha cookie. if an attacker or bot solves the capcha by hand and inputs the cookie in a script + #the cookie will be blacklisted by all fronts (eventually) and subsequent requests dropped. + + limit_req zone=capcookie burst=6 nodelay; + error_page 503 =503 @ratelimit; + + #check if access captca is solved and other things + access_by_lua_file lua/cap.lua; + + SecRulesEnabled; + #LearningMode; + DeniedUrl "@waf"; + CheckRule "$SQL >= 8" BLOCK; + CheckRule "$RFI >= 8" BLOCK; + CheckRule "$TRAVERSAL >= 4" BLOCK; + CheckRule "$EVADE >= 4" BLOCK; + CheckRule "$XSS >= 8" BLOCK; + include "/etc/nginx/naxsi_whitelist.rules"; + error_log /etc/nginx/naxsi.log; + proxy_set_header Host $host; + #socks_pass socks5://127.0.0.1:9050; + #socks_set_host exampleprivatev3onion.onion; + #socks_set_header Host $host; + #socks_redirect off; + #socks_http_version 1.1; + proxy_pass http://10.10.10.10; + header_filter_by_lua_block { + local cookie, err = cook:new() + if not cookie then + ngx.log(ngx.ERR, err) + return + end + local block_cookie = 0 + + if ngx.resp.get_headers()['captcha-fails'] ~= nil then + local field, err = cookie:get("dcap") + if field then + local failed = ngx.shared.failed + local fl = failed:get(field) + if fl ~= nil then + fl = fl + 1 + else + fl = 1 + end + failed:set(field, fl, 3600) + if fl > 3 then + block_cookie = 1 + failed:delete(field) + end + end + end + + if block_cookie > 0 then + local field, err = cookie:get("dcap") + if field then + local blocked_cookies = ngx.shared.blocked_cookies + blocked_cookies:set(field, 1, 3600) + end + end + } + } +} \ No newline at end of file diff --git a/startup.sh b/startup.sh new file mode 100644 index 0000000..3811d47 --- /dev/null +++ b/startup.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +tor +service vanguards start +exit 0 \ No newline at end of file diff --git a/sysctl.conf b/sysctl.conf new file mode 100644 index 0000000..78c0046 --- /dev/null +++ b/sysctl.conf @@ -0,0 +1,65 @@ +# +# /etc/sysctl.conf - Configuration file for setting system variables +# See /etc/sysctl.d/ for additional system variables. +# See sysctl.conf (5) for information. +# + +#kernel.domainname = example.com + +# Uncomment the following to stop low-level messages on console +#kernel.printk = 3 4 1 3 + +##############################################################3 +# Functions previously found in netbase +# + +# Uncomment the next two lines to enable Spoof protection (reverse-path filter) +# Turn on Source Address Verification in all interfaces to +# prevent some spoofing attacks +#net.ipv4.conf.default.rp_filter=1 +#net.ipv4.conf.all.rp_filter=1 + +# Uncomment the next line to enable TCP/IP SYN cookies +# See http://lwn.net/Articles/277146/ +# Note: This may impact IPv6 TCP sessions too +#net.ipv4.tcp_syncookies=1 + +# Uncomment the next line to enable packet forwarding for IPv4 +#net.ipv4.ip_forward=1 + +# Uncomment the next line to enable packet forwarding for IPv6 +# Enabling this option disables Stateless Address Autoconfiguration +# based on Router Advertisements for this host +#net.ipv6.conf.all.forwarding=1 + + +################################################################### +# Additional settings - these settings can improve the network +# security of the host and prevent against some network attacks +# including spoofing attacks and man in the middle attacks through +# redirection. Some network environments, however, require that these +# settings are disabled so review and enable them as needed. +# +# Do not accept ICMP redirects (prevent MITM attacks) +#net.ipv4.conf.all.accept_redirects = 0 +#net.ipv6.conf.all.accept_redirects = 0 +# _or_ +# Accept ICMP redirects only for gateways listed in our default +# gateway list (enabled by default) +# net.ipv4.conf.all.secure_redirects = 1 +# +# Do not send ICMP redirects (we are not a router) +#net.ipv4.conf.all.send_redirects = 0 +# +# Do not accept IP source route packets (we are not a router) +#net.ipv4.conf.all.accept_source_route = 0 +#net.ipv6.conf.all.accept_source_route = 0 +# +# Log Martian Packets +#net.ipv4.conf.all.log_martians = 1 +# +net.ipv6.conf.all.disable_ipv6 = 1 +net.ipv6.conf.default.disable_ipv6 = 1 +net.ipv6.conf.lo.disable_ipv6 = 1 +net.core.somaxconn=16384 +net.core.netdev_max_backlog=100000 \ No newline at end of file diff --git a/torrc b/torrc new file mode 100644 index 0000000..b28a29f --- /dev/null +++ b/torrc @@ -0,0 +1,25 @@ +# Tor config for the onion service instance servers +# --- +# The instance servers run standard onion services. In Basic mode the +# control port does not need to be enabled. +#User debian-tor +#DataDirectory /etc/tor/tor-data + +# ControlPort 9051 +# CookieAuthentication 1 +#SocksPort 0 + +RunAsDaemon 1 + +HiddenServiceDir /etc/tor/hidden_service +HiddenServicePort 80 127.0.0.1:88 +HiddenServiceMaxStreams 8 +HiddenServiceMaxStreamsCloseCircuit 1 +HiddenServiceNumIntroductionPoints 3 +HiddenServiceExportCircuitID haproxy +#HiddenServiceOnionBalanceInstance 1 + +CookieAuthentication 1 +ControlPort 9051 +HashedControlPassword hashedpassword +HardwareAccel 1 \ No newline at end of file