├── Changelog ├── README ├── README.markdown ├── config ├── doc └── usecases.txt ├── src └── ngx_http_testcookie_access_module.c ├── t ├── 00base.t └── 01crypto.t └── util ├── aes.patch ├── tests.sh └── valgrind.sh /Changelog: -------------------------------------------------------------------------------- 1 | v1.24 2 | *) disable caching for module variables 3 | 4 | v1.23 5 | *) testcookie_port_in_redirect config variable added 6 | 7 | v1.22 8 | *) openssl 1.1.0 compatible 9 | 10 | v1.21 11 | *) testcookie_refresh_status directive added (see docs) 12 | 13 | v1.20 14 | *) testcookie_pass directive added (see docs) 15 | *) can be compiled as a dynamic module 16 | *) changed filename to access than filter b/c testcookie technically is access module 17 | 18 | v1.19 19 | *) Set Cache-Control and Expires headers to prevent caching of testcookie responses 20 | 21 | v1.18 22 | *) Secret len now should be more than 31 bytes 23 | 24 | v1.17 25 | *) Secure flag can be operated with variables 26 | 27 | v1.16 28 | *) Optional Secure and HttpOnly flags added for cookies 29 | 30 | v1.15 31 | *) Correct len for $testcookie_ok variable, thanks to GeniusGuard 32 | 33 | v1.14 34 | *) Always set $testcookie_ variables, thanks to GeniusGuard 35 | 36 | v1.13 37 | *) fixed content type on custom refresh, thanks to LoadLow@github 38 | 39 | v1.12 40 | *) fixed uri parsing logic 41 | 42 | v1.11 43 | *) fixed header injection in uri, thanks to glintik@github 44 | 45 | v1.10 46 | *) testcookie_ok changed to 1/0 instead of yes/no - that was done for compatibility 47 | with conditional logging (added to nginx 1.7.0) 48 | 49 | *) Experimental IPv6 whitelisting 50 | 51 | v1.09 52 | *) Secure random changed 53 | 54 | v1.08 55 | *) Bugfix, fixed bypass with HEAD method in redirect_via_refresh mode 56 | 57 | v1.07 58 | *) Default redirect code changed from 302 to 307 for HTTP 1.1+ 59 | 60 | v1.06 61 | *) testcookie directive now can be used in location and server IF 62 | 63 | v1.05 64 | *) New config option testcookie_internal - enable testcookie for internal redirects 65 | *) Padding error patch for SlowAES attached 66 | 67 | v1.04 68 | *) if testcookie_arg is not set - just redirect the client infinitely w/o using fallback_url 69 | 70 | v1.03 71 | *) no check for Internal requests 72 | 73 | v1.02 74 | *) --with-ipv6 compilation is now supported with whitelisting, 75 | but only for IPv6 to IPv4 mapped addresses 76 | *) Whitelisting autotests added 77 | 78 | v1.01 79 | *) --with-ipv6 compilation error fixed 80 | 81 | v1.0 82 | *) Keep-alive block fixed 83 | *) Problem with max attempts fixed 84 | *) var option added 85 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | DESCRIPTION 2 | 3 | testcookie-nginx-module is a simple robot mitigation module using cookie based challenge/response. 4 | Challenge cookies can be set using different methods: 5 | * "Set-Cookie" + 302/307 HTTP Location redirect 6 | * "Set-Cookie" + HTML meta refresh redirect 7 | * Custom template, JavaScript can be used here. 8 | To prevent automatic parsing, challenge cookie value can 9 | be encrypted with AES-128 in CBC mode using custom/random key and iv, 10 | and then decrypted at client side with JavaScript. 11 | 12 | 13 | DIRECTIVES 14 | 15 | testcookie 16 | on - enable module 17 | off - disable module 18 | var - don't intercept requests, only set cookie vars 19 | 20 | testcookie_name 21 | cookie name, default is TCK 22 | 23 | testcookie_domain 24 | cookie domain, default is none, set by browser 25 | 26 | testcookie_expires 27 | cookie expiration value, default 31 Dec 2037 23:55:55 GMT 28 | 29 | testcookie_path 30 | cookie path, useful if you plan to use different keys for locations. default is / 31 | 32 | testcookie_samesite 33 | cookie samesite attribute, allows you to declare if your cookie should be restricted 34 | to a first-party or same-site context. Default is None (Cookies will be sent in all contexts, 35 | i.e sending cross-origin is allowed.) Accepts three values: Lax, Strict, None. 36 | 37 | testcookie_secret 38 | secret string, used in challenge cookie computation, should be 32 bytes or more, 39 | better to be long but static to prevent cookie reset for legitimate users every server restart. 40 | if set to "random" - new secret will be generated every server restart, not recomended(all cookies with previous key will be invalid), 41 | 42 | testcookie_session 43 | sets the challenge generation function input, 44 | $remote_addr - clients IP address will be used as an user unique identifier 45 | $remote_addr$http_user_agent - clients IP + User-Agent 46 | * required configuration directive 47 | 48 | testcookie_arg 49 | GET parameter name, used for cookie setting attempts computation 50 | if not set - server will try to set cookie infinitely(actually, browser will show the error page after 5 attempts). 51 | 52 | testcookie_max_attempts 53 | maximum number of redirects before user will be sent to fallback URL, according to RFC1945 can't be more than 5 54 | if set to 0 or testcookie_arg not set - server will try to set cookie infinitely. 55 | 56 | testcookie_p3p 57 | P3P policy, default is none. 58 | 59 | testcookie_fallback 60 | sets the fallback URL, user will be redirected to after maximum number of attempts, specified by directive 61 | testcookie_max_attempts exceded. nginx scripting variables can be used here. 62 | if not set - client will get 403 after max attempts reached. 63 | 64 | testcookie_whitelist 65 | sets the networks for which the testing will not be used, add search engine networks here 66 | currently IPv4 CIDR only. 67 | 68 | testcookie_pass 69 | variable name, if variable set to 1 cookie check will not be performed, 70 | can be used for more complex whitelisting. 71 | 72 | testcookie_redirect_via_refresh 73 | set cookie and redirect using HTTP meta refresh, required if testcookie_refresh_template used (on|off) 74 | default is off. 75 | 76 | testcookie_refresh_template 77 | custom html instead of simple HTTP meta refresh, you need to set cookie manually from the template 78 | available all the nginx variables and 79 | 80 | $testcookie_nexturl - URL the client should be redirected to 81 | $testcookie_got - cookie value received from client, empty if no cookie or it does not match format 82 | $testcookie_set - correct cookie value we're expecting from client 83 | $testcookie_ok - user passed test (1/0). Note: changed from "yes"/"no" in v1.10 84 | 85 | also, if testcookie_refresh_encrypt_cookie enabled there are three more variables 86 | $testcookie_enc_key - encryption key (32 hex digits) 87 | $testcookie_enc_iv - encryption iv (32 hex digits) 88 | $testcookie_enc_sec - encrypted cookie value (32 hex digits) 89 | 90 | testcookie_refresh_status 91 | custom HTTP response status. 200 by default. 92 | 93 | testcookie_deny_keepalive 94 | close connection just after setting the cookie, no reason to keep connections with bots (on|off) 95 | default is off. 96 | 97 | testcookie_get_only 98 | process only GET requests, POST requests will be bypassed (on|off) 99 | default is off. 100 | 101 | testcookie_https_location 102 | redirect to https protocol after setting the cookie, also affects $testcookie_nexturl 103 | useful with 3dparty SSL offload (on|off) 104 | default is off. 105 | 106 | testcookie_refresh_encrypt_cookie 107 | encrypt cookie variable, used with testcookie_refresh_template to force client-side decryption 108 | AES-128 CBC mode used (on|off) 109 | default is off. 110 | 111 | testcookie_refresh_encrypt_cookie_key 112 | encryption key 113 | possible values: 114 | random - new key generated every nginx restart 115 | 32 hex digits - static key, useful if you plan to obfuscate it deep in client-side javascript 116 | * required directive if encryption enabled 117 | 118 | testcookie_refresh_encrypt_iv 119 | encryption iv 120 | possible values: 121 | random - new iv generated for every client request 122 | random2 - new iv generated for every nginx restart 123 | 32 hex digits - static iv, useful if you plan to obfuscate it deep in client-side javascript 124 | default is random 125 | 126 | testcookie_internal 127 | enable testcookie check for internal redirects (on|off) 128 | useful for this type of configs: 129 | rewrite ^/(.*)$ /index.php?$1 last; 130 | default is off. 131 | 132 | testcookie_httponly_flag 133 | adds HttpOnly flag for cookie (on|off) 134 | default is off. 135 | 136 | testcookie_secure_flag 137 | adds Secure flag for cookie (on|off|$variable) 138 | default is on. 139 | any variable value except "on" interpreted as False. 140 | 141 | testcookie_port_in_redirect 142 | Keep server port in redirect (on|off) 143 | default is off. 144 | 145 | 146 | INSTALLATION 147 | 148 | Grab the nginx source code from nginx.org (), for 149 | example, the version 1.1.15 (see nginx compatibility), and then build 150 | the source with this module: 151 | 152 | wget 'http://nginx.org/download/nginx-1.1.15.tar.gz' 153 | tar -xzvf nginx-1.1.15.tar.gz 154 | cd nginx-1.1.15/ 155 | ./configure --add-module=/path/to/testcookie-nginx-module 156 | 157 | make 158 | make install 159 | 160 | If you use nginx >= 1.9.11 you can compile Dynamic module. 161 | 162 | wget 'http://nginx.org/download/nginx-1.9.11.tar.gz' 163 | tar -xzvf nginx-1.9.11.tar.gz 164 | cd nginx-1.9.11/ 165 | ./configure --add-dynamic-module=/path/to/testcookie-nginx-module 166 | 167 | make 168 | make install 169 | 170 | Then load "ngx_http_testcookie_access_module.so" using "load_module" directive. 171 | 172 | 173 | For using client-side cookie decryption, 174 | you need to manually grab SlowAES () 175 | JavaScript AES implementation, patch it(utils/aes.patch) and put it to document root. 176 | 177 | COMPATIBILITY 178 | 179 | Module was tested with nginx 1.1+, but should work with 1.0+. 180 | 181 | EXAMPLE CONFIGURATION 182 | 183 | http { 184 | #default config, module disabled 185 | testcookie off; 186 | 187 | #setting cookie name 188 | testcookie_name BPC; 189 | 190 | #setting secret 191 | testcookie_secret keepmesecret; 192 | 193 | #setting session key 194 | testcookie_session $remote_addr; 195 | 196 | #setting argument name 197 | testcookie_arg ckattempt; 198 | 199 | #setting maximum number of cookie setting attempts 200 | testcookie_max_attempts 3; 201 | 202 | #setting p3p policy 203 | testcookie_p3p 'CP="CUR ADM OUR NOR STA NID", policyref="/w3c/p3p.xml"'; 204 | 205 | #setting fallback url 206 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 207 | 208 | #configuring whitelist 209 | testcookie_whitelist { 210 | 8.8.8.8/32; 211 | } 212 | 213 | 214 | #setting redirect via html code 215 | testcookie_redirect_via_refresh on; 216 | 217 | #enable encryption 218 | testcookie_refresh_encrypt_cookie on; 219 | 220 | #setting encryption key 221 | testcookie_refresh_encrypt_cookie_key deadbeefdeadbeefdeadbeefdeadbeef; 222 | 223 | #setting encryption iv 224 | testcookie_refresh_encrypt_cookie_iv deadbeefdeadbeefdeadbeefdeadbeef; 225 | 226 | #setting response template 227 | testcookie_refresh_template 'setting cookie...'; 228 | 229 | server { 230 | listen 80; 231 | server_name test.com; 232 | 233 | 234 | location = /aes.min.js { 235 | gzip on; 236 | gzip_min_length 1000; 237 | gzip_types text/plain; 238 | root /var/www/public_html; 239 | } 240 | 241 | location = /w3c/p3p.xml { 242 | root /var/www/public_html; 243 | } 244 | 245 | location / { 246 | #enable module for specific location 247 | testcookie on; 248 | proxy_set_header Host $host; 249 | proxy_set_header X-Real-IP $remote_addr; 250 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 251 | proxy_pass http://127.0.0.1:80; 252 | } 253 | } 254 | } 255 | 256 | TESTS SUITE 257 | 258 | This module comes with a Perl-driven test suite. 259 | Thanks to the Test::Nginx () module in the Perl world. 260 | 261 | SOURCES 262 | 263 | Available on github at kyprizel/testcookie-nginx-module 264 | (). 265 | 266 | TODO 267 | 268 | * Code review 269 | * More encryption algos (-) 270 | * Statistics (-) 271 | 272 | BUGS 273 | 274 | Feel free to report bugs and send patches to kyprizel@gmail.com 275 | or use github's issue tracker(). 276 | 277 | SUPPORT THE PROJECT 278 | 279 | Send your donations to 1FHmPTP6aDBAzVtM7Pe7Y69zqhjPRx847s 280 | 281 | COPYRIGHT & LICENSE 282 | 283 | Copyright (C) 2011-2017 Eldar Zaitov (kyprizel@gmail.com). 284 | 285 | All rights reserved. 286 | 287 | This module is licenced under the terms of BSD license. 288 | 289 | Redistribution and use in source and binary forms, with or without 290 | modification, are permitted provided that the following conditions are 291 | met: 292 | 293 | * Redistributions of source code must retain the above copyright 294 | notice, this list of conditions and the following disclaimer. 295 | 296 | * Redistributions in binary form must reproduce the above copyright 297 | notice, this list of conditions and the following disclaimer in the 298 | documentation and/or other materials provided with the distribution. 299 | 300 | * Neither the name of the authors nor the names of its contributors 301 | may be used to endorse or promote products derived from this 302 | software without specific prior written permission. 303 | 304 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 305 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 306 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 307 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 308 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 309 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 310 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 311 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 312 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 313 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 314 | SUCH DAMAGE. 315 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Description 2 | =========== 3 | 4 | **testcookie-nginx-module** is a simple robot mitigation module using cookie based challenge/response. 5 | 6 | Challenge cookies can be set using different methods: 7 | 8 | * "Set-Cookie" + 302/307 HTTP Location redirect 9 | * "Set-Cookie" + HTML meta refresh redirect 10 | * Custom template, JavaScript can be used here. 11 | 12 | To prevent automatic parsing, challenge cookie value can be encrypted with AES-128 in CBC mode using custom/random key and iv, and then decrypted at client side with JavaScript. 13 | 14 | 15 | Directives 16 | ========== 17 | 18 | testcookie 19 | ---------- 20 | **syntax:** *testcookie (on|off|var);* 21 | 22 | **default:** *off* 23 | 24 | **context:** *http, server, location, if* 25 | 26 | on - Enable module 27 | 28 | off - Disable module 29 | 30 | var - Don't intercept requests, only set module variables. 31 | 32 | 33 | testcookie_name 34 | --------------- 35 | **syntax:** *testcookie_name <string>* 36 | 37 | **default:** *TCK* 38 | 39 | **context:** *http, server, location* 40 | 41 | Sets cookie name. 42 | 43 | testcookie_domain 44 | ----------------- 45 | **syntax:** *testcookie_domain <string>* 46 | 47 | **default:** *none, set by browser* 48 | 49 | **context:** *http, server, location* 50 | 51 | Sets cookie domain. 52 | 53 | 54 | testcookie_expires 55 | ------------------ 56 | **syntax:** *testcookie_expires <string>* 57 | 58 | **default:** *31 Dec 2037 23:55:55 GMT* 59 | 60 | **context:** *http, server, location* 61 | 62 | Sets cookie expiration value. 63 | 64 | testcookie_path 65 | --------------- 66 | **syntax:** *testcookie_path <string>* 67 | 68 | **default:** */* 69 | 70 | **context:** *http, server, location* 71 | 72 | Sets cookie path, useful if you plan to use different keys for locations. 73 | 74 | testcookie_samesite 75 | --------------- 76 | **syntax:** *testcookie_samesite <string>* 77 | 78 | **default:** *None* 79 | 80 | **context:** *http, server, location* 81 | 82 | Sets cookie attribute, allows you to declare if your cookie should be restricted to a first-party or same-site context. 83 | Default is None (Cookies will be sent in all contexts, i.e sending cross-origin is allowed.) 84 | Accepts values: Lax, Strict, None. 85 | 86 | testcookie_secret 87 | ----------------- 88 | **syntax:** *testcookie_secret <string>* 89 | 90 | **default:** *required configuration directive* 91 | 92 | **context:** *http, server, location* 93 | 94 | Secret string, used in challenge cookie computation, should be 32 bytes or more, 95 | better to be long but static to prevent cookie reset for legitimate users every server restart. 96 | If set to *"random"* - new secret will be generated every server restart, not recomended(all cookies with previous key will be invalid), 97 | 98 | testcookie_session 99 | ------------------ 100 | **syntax:** *testcookie_session <variable>* 101 | 102 | **default:** *required configuration directive* 103 | 104 | **context:** *http, server, location* 105 | 106 | Sets the challenge generation function input, 107 | * $remote_addr - clients IP address will be used as an user unique identifier 108 | * $remote_addr$http_user_agent - clients IP + User-Agent 109 | 110 | testcookie_arg 111 | -------------- 112 | **syntax:** *testcookie_arg <string>* 113 | 114 | **default:** *none* 115 | 116 | **context:** *http, server, location* 117 | 118 | Sets GET parameter name, used for cookie setting attempts computation, 119 | 120 | If not set - server will try to set cookie infinitely. 121 | 122 | testcookie_max_attempts 123 | ----------------------- 124 | **syntax:** *testcookie_max_attempts <integer>* 125 | 126 | **default:** *5* 127 | 128 | **context:** *http, server, location* 129 | 130 | Sets maximum number of redirects before user will be sent to fallback URL, according to RFC1945 can't be more than 5. 131 | 132 | If set to 0 - server will try to set cookie infinitely(actually, browser will show the error page). 133 | 134 | 135 | testcookie_p3p 136 | -------------- 137 | **syntax:** *testcookie_p3p <string>* 138 | 139 | **default:** *none* 140 | 141 | **context:** *http, server, location* 142 | 143 | Sets P3P policy. 144 | 145 | testcookie_fallback 146 | ------------------- 147 | **syntax:** *testcookie_fallback <script>* 148 | 149 | **default:** *none* 150 | 151 | **context:** *http, server, location* 152 | 153 | Sets the fallback URL, user will be redirected to after maximum number of attempts, specified by directive *testcookie_max_attempts* exceded. 154 | Nginx scripting variables can be used here. If not set - client will get 403 after max attempts reached. 155 | 156 | testcookie_whitelist 157 | -------------------- 158 | **syntax:** *testcookie_whitelist <network list>* 159 | 160 | **default:** *none* 161 | 162 | **context:** *http, server* 163 | 164 | Sets the networks for which the testing will not be used, add search engine networks here. Currently IPv4 CIDR only. 165 | 166 | testcookie_pass 167 | --------------- 168 | **syntax:** *testcookie_pass $variable;* 169 | 170 | **default:** *none* 171 | 172 | **context:** *http, server* 173 | 174 | Sets the variable name to test if cookie check should be bypassed. 175 | If variable value set to *1* during the request - cookie check will not be performed. 176 | Can be used for more complex whitelisting. 177 | 178 | testcookie_redirect_via_refresh 179 | ------------------------------- 180 | **syntax:** *testcookie_redirect_via_refresh (on|off);* 181 | 182 | **default:** *off* 183 | 184 | **context:** *http, server, location* 185 | 186 | Set cookie and redirect using HTTP meta refresh, required if *testcookie_refresh_template* used. 187 | 188 | testcookie_refresh_template 189 | --------------------------- 190 | **syntax:** *testcookie_refresh_template <string>* 191 | 192 | **default:** *none* 193 | 194 | **context:** *http, server, location* 195 | 196 | Use custom html instead of simple HTTP meta refresh, you need to set cookie manually from the template 197 | Available all the nginx variables and 198 | 199 | $testcookie_nexturl - URL the client should be redirected to, if max_attempts exceeded *testcookie_fallback* value will be here 200 | $testcookie_got - cookie value received from client, empty if no cookie or it does not match format 201 | $testcookie_set - correct cookie value we're expecting from client 202 | $testcookie_ok - user passed test (1 - passed, 0 - not passed) Note: changed from "yes"/"no" in v1.10 203 | 204 | also, if testcookie_refresh_encrypt_cookie enabled there are three more variables: 205 | 206 | $testcookie_enc_key - encryption key (32 hex digits) 207 | $testcookie_enc_iv - encryption iv (32 hex digits) 208 | $testcookie_enc_sec - encrypted cookie value (32 hex digits) 209 | 210 | testcookie_refresh_status 211 | ------------------------- 212 | **syntax:** *testcookie_refresh_status <code>* 213 | 214 | **default:** *200* 215 | 216 | **context:** *http, server, location* 217 | 218 | Use custom HTTP status code when serving html. 219 | 220 | 221 | testcookie_deny_keepalive 222 | ------------------------- 223 | **syntax:** *testcookie_deny_keepalive (on|off);* 224 | 225 | **default:** *off* 226 | 227 | **context:** *http, server, location* 228 | 229 | Close connection just after setting the cookie, no reason to keep connections with bots. 230 | 231 | testcookie_get_only 232 | ------------------- 233 | **syntax:** *testcookie_get_only (on|off);* 234 | 235 | **default:** *off* 236 | 237 | **context:** *http, server, location* 238 | 239 | Process only GET requests, POST requests will be bypassed. 240 | 241 | testcookie_https_location 242 | ------------------------- 243 | **syntax:** *testcookie_https_location (on|off);* 244 | 245 | **default:** *off* 246 | 247 | **context:** *http, server, location* 248 | 249 | Redirect client to https protocol after setting the cookie, also affects *$testcookie_nexturl*, useful with 3dparty SSL offload. 250 | 251 | testcookie_refresh_encrypt_cookie 252 | --------------------------------- 253 | **syntax:** *testcookie_refresh_encrypt_cookie (on|off);* 254 | 255 | **default:** *off* 256 | 257 | **context:** *http, server, location* 258 | 259 | Encrypt cookie variable, used with *testcookie_refresh_template* to force client-side decryption with AES-128 CBC. 260 | 261 | testcookie_refresh_encrypt_cookie_key 262 | ------------------------------------- 263 | **syntax:** *testcookie_refresh_encrypt_cookie_key <32 hex digits|random>* 264 | 265 | **default:** *required directive if encryption enabled* 266 | 267 | **context:** *http, server, location* 268 | 269 | Sets encryption key. 270 | 271 | Possible values: 272 | 273 | random - new key generated every nginx restart 274 | 32 hex digits - static key, useful if you plan to obfuscate it deep in client-side javascript. 275 | 276 | testcookie_refresh_encrypt_iv 277 | ----------------------------- 278 | **syntax:** *testcookie_refresh_encrypt_iv <32 hex digits|random|random2>* 279 | 280 | **default:** *random* 281 | 282 | **context:** *http, server, location* 283 | 284 | Sets encryption iv. 285 | 286 | Possible values: 287 | random - new iv generated for every client request 288 | random2 - new iv generated for every nginx restart 289 | 32 hex digits - static iv, useful if you plan to obfuscate it deep in client-side javascript 290 | 291 | testcookie_internal 292 | ------------------- 293 | **syntax:** *testcookie_internal (on|off);* 294 | 295 | **default:** *off* 296 | 297 | **context:** *http, server, location* 298 | 299 | Enable testcookie check for internal redirects (disabled by default for optimization purposes!), useful for this type of configs: 300 | 301 | rewrite ^/(.*)$ /index.php?$1 last; 302 | 303 | testcookie_httponly_flag 304 | ------------------------ 305 | **syntax:** *testcookie_httponly_flag (on|off);* 306 | 307 | **default:** *off* 308 | 309 | **context:** *http, server, location* 310 | 311 | Enable HttpOnly flag for cookie. 312 | 313 | testcookie_secure_flag 314 | ------------------------ 315 | **syntax:** *testcookie_secure_flag (on|off|$variable);* 316 | 317 | **default:** *on* 318 | 319 | **context:** *http, server, location* 320 | 321 | Enable Secure flag for cookie. 322 | Any variable value except "on" interpreted as False. 323 | 324 | testcookie_port_in_redirect 325 | --------------------------- 326 | **syntax:** *testcookie_port_in_redirect (on|off);* 327 | 328 | **default:** *off* 329 | 330 | **context:** *http, server, location* 331 | 332 | Expose port in redirect. 333 | 334 | 335 | Installation 336 | ============ 337 | 338 | Grab the nginx source code from [nginx.org](http://nginx.org/), for example, the version 1.1.15 (see nginx compatibility), and then build the source with this module: 339 | 340 | wget 'http://nginx.org/download/nginx-1.1.15.tar.gz' 341 | tar -xzvf nginx-1.1.15.tar.gz 342 | cd nginx-1.1.15/ 343 | ./configure --add-module=/path/to/testcookie-nginx-module 344 | 345 | make 346 | make install 347 | 348 | If you use nginx >= 1.9.11 you can compile Dynamic module. 349 | 350 | wget 'http://nginx.org/download/nginx-1.9.11.tar.gz' 351 | tar -xzvf nginx-1.9.11.tar.gz 352 | cd nginx-1.9.11/ 353 | ./configure --add-dynamic-module=/path/to/testcookie-nginx-module 354 | 355 | make 356 | make install 357 | 358 | Then load "ngx_http_testcookie_access_module.so" using "load_module" directive. 359 | 360 | For using client-side cookie decryption, you need to manually grab [SlowAES](http://code.google.com/p/slowaes/) JavaScript AES implementation, 361 | **patch it(utils/aes.patch)** and put it to document root. 362 | 363 | Compatibility 364 | ============= 365 | 366 | Module was tested with nginx 1.1+, but should work with 1.0+. 367 | 368 | Example configuration 369 | ===================== 370 | 371 | http { 372 | #default config, module disabled 373 | testcookie off; 374 | 375 | #setting cookie name 376 | testcookie_name BPC; 377 | 378 | #setting secret 379 | testcookie_secret keepmesecret; 380 | 381 | #setting session key 382 | testcookie_session $remote_addr; 383 | 384 | #setting argument name 385 | testcookie_arg ckattempt; 386 | 387 | #setting maximum number of cookie setting attempts 388 | testcookie_max_attempts 3; 389 | 390 | #setting p3p policy 391 | testcookie_p3p 'CP="CUR ADM OUR NOR STA NID", policyref="/w3c/p3p.xml"'; 392 | 393 | #setting fallback url 394 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 395 | 396 | #configuring whitelist 397 | testcookie_whitelist { 398 | 8.8.8.8/32; 399 | } 400 | 401 | 402 | #setting redirect via html code 403 | testcookie_redirect_via_refresh on; 404 | 405 | #enable encryption 406 | testcookie_refresh_encrypt_cookie on; 407 | 408 | #setting encryption key 409 | testcookie_refresh_encrypt_cookie_key deadbeefdeadbeefdeadbeefdeadbeef; 410 | 411 | #setting encryption iv 412 | testcookie_refresh_encrypt_cookie_iv deadbeefdeadbeefdeadbeefdeadbeef; 413 | 414 | #setting response template 415 | testcookie_refresh_template 'setting cookie...'; 416 | 417 | server { 418 | listen 80; 419 | server_name test.com; 420 | 421 | 422 | location = /aes.min.js { 423 | gzip on; 424 | gzip_min_length 1000; 425 | gzip_types text/plain; 426 | root /var/www/public_html; 427 | } 428 | 429 | location = /w3c/p3p.xml { 430 | root /var/www/public_html; 431 | } 432 | 433 | # required for passing Let's Encrypt ACME challenge, remove if not required 434 | location = /.well-known/acme-challenge/ { 435 | root /var/www/public_html; 436 | } 437 | 438 | 439 | location / { 440 | #enable module for specific location 441 | testcookie on; 442 | proxy_set_header Host $host; 443 | proxy_set_header X-Real-IP $remote_addr; 444 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 445 | proxy_pass http://127.0.0.1:80; 446 | } 447 | } 448 | } 449 | 450 | See more cases in "docs" directory of the project. 451 | 452 | Test suite 453 | =========== 454 | 455 | This module comes with a Perl-driven test suite. Thanks to the [Test::Nginx](http://search.cpan.org/perldoc?Test::Nginx) module in the Perl world. 456 | 457 | Sources 458 | ======= 459 | 460 | Available on github at [kyprizel/testcookie-nginx-module](http://github.com/kyprizel/testcookie-nginx-module). 461 | 462 | TODO 463 | ==== 464 | 465 | * Code review 466 | * Statistics (?) 467 | 468 | Bugs 469 | ==== 470 | 471 | Feel free to report bugs and send patches to kyprizel@gmail.com 472 | or using [github's issue tracker](http://github.com/kyprizel/testcookie-nginx-module/issues). 473 | 474 | Support the project 475 | =================== 476 | 477 | Send your donations to 1FHmPTP6aDBAzVtM7Pe7Y69zqhjPRx847s 478 | 479 | 480 | Copyright & License 481 | =================== 482 | 483 | Copyright (C) 2011-2017 Eldar Zaitov (kyprizel@gmail.com). 484 | 485 | All rights reserved. 486 | 487 | This module is licenced under the terms of BSD license. 488 | 489 | Redistribution and use in source and binary forms, with or without 490 | modification, are permitted provided that the following conditions are 491 | met: 492 | 493 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 494 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 495 | * Neither the name of the authors nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 496 | 497 | THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 498 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 499 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 500 | ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 501 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 502 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 503 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 504 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 505 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 506 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 507 | SUCH DAMAGE. 508 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_testcookie_access_module 2 | 3 | # b/c it's a DDoS prevention module - we set module_type=HTTP_AUX_FILTER to run it ASAP 4 | # if you need some more logic, place the module near the other access phase modules 5 | 6 | if test -n "$ngx_module_link"; then 7 | ngx_module_type=HTTP_AUX_FILTER 8 | ngx_module_name=ngx_http_testcookie_access_module 9 | ngx_module_srcs="$ngx_addon_dir/src/ngx_http_testcookie_access_module.c" 10 | #ngx_module_order="$ngx_module_name ngx_http_access_module" 11 | 12 | . auto/module 13 | else 14 | HTTP_AUX_FILTER_MODULES="ngx_http_testcookie_access_module $HTTP_AUX_FILTER_MODULES" 15 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_testcookie_access_module.c" 16 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS" 17 | #CFLAGS="$CFLAGS" 18 | USE_OPENSSL=YES 19 | fi 20 | -------------------------------------------------------------------------------- /doc/usecases.txt: -------------------------------------------------------------------------------- 1 | 1. HTTP GET flood, bots do not accept HTTP response headers 2 | 3 | server { 4 | listen 80; 5 | server_name domain.com; 6 | 7 | 8 | testcookie off; 9 | testcookie_name BPC; 10 | testcookie_secret keepmescret; 11 | testcookie_session $remote_addr; 12 | testcookie_arg attempt; 13 | testcookie_max_attempts 3; 14 | testcookie_fallback /cookies.html?backurl=http://$host$request_uri; 15 | testcookie_get_only on; 16 | 17 | 18 | location = /cookies.html { 19 | root /var/www/public_html; 20 | } 21 | 22 | location / { 23 | testcookie on; 24 | proxy_set_header Host $host; 25 | proxy_set_header X-Real-IP $remote_addr; 26 | proxy_pass http://127.0.0.1:8080; 27 | } 28 | } 29 | 30 | 31 | 32 | 2. HTTP GET flood, bots accept HTTP response headers, but can't parse HTML 33 | 34 | server { 35 | listen 80; 36 | server_name domain.com; 37 | 38 | testcookie off; 39 | testcookie_name BPC; 40 | testcookie_secret keepmescret; 41 | testcookie_session $remote_addr; 42 | testcookie_arg attempt; 43 | testcookie_max_attempts 3; 44 | testcookie_fallback /cookies.html?backurl=http://$host$uri?$query_string; 45 | testcookie_get_only on; 46 | testcookie_redirect_via_refresh on; 47 | testcookie_refresh_template ''; 48 | 49 | location = /cookies.html { 50 | root /var/www/public_html; 51 | } 52 | 53 | 54 | location / { 55 | testcookie on; 56 | proxy_set_header Host $host; 57 | proxy_set_header X-Real-IP $remote_addr; 58 | proxy_pass http://127.0.0.1:8080; 59 | } 60 | } 61 | 62 | 3. Iframe with our URL was set on some popular site 63 | 64 | server { 65 | listen 80; 66 | server_name domain.com; 67 | 68 | 69 | testcookie off; 70 | testcookie_name BPC; 71 | testcookie_secret keepmescret; 72 | testcookie_session $remote_addr; 73 | testcookie_arg attempt; 74 | testcookie_max_attempts 3; 75 | testcookie_fallback /cookies.html?backurl=http://$host$request_uri; 76 | testcookie_get_only on; 77 | testcookie_redirect_via_refresh on; 78 | testcookie_refresh_template ''; 79 | 80 | location = /cookies.html { 81 | root /var/www/public_html; 82 | } 83 | 84 | location / { 85 | testcookie on; 86 | proxy_set_header Host $host; 87 | proxy_set_header X-Real-IP $remote_addr; 88 | proxy_pass http://127.0.0.1:8080; 89 | } 90 | } 91 | 92 | 93 | 4. HTTP GET flood, bots accept HTTP response headers, and can parse HTML 94 | 95 | server { 96 | listen 80; 97 | server_name domain.com; 98 | 99 | testcookie off; 100 | testcookie_name BPC; 101 | testcookie_secret keepmescret; 102 | testcookie_session $remote_addr; 103 | testcookie_arg attempt; 104 | testcookie_max_attempts 3; 105 | testcookie_fallback /cookies.html?backurl=http://$host$request_uri; 106 | testcookie_get_only on; 107 | testcookie_redirect_via_refresh on; 108 | 109 | testcookie_refresh_encrypt_cookie on; 110 | testcookie_refresh_encrypt_cookie_key random; 111 | testcookie_refresh_encrypt_cookie_iv random; 112 | testcookie_refresh_template 'setting cookie...'; 113 | 114 | location = /aes.min.js { 115 | gzip on; 116 | gzip_min_length 1000; 117 | gzip_types text/plain; 118 | root /var/www/public_html; 119 | } 120 | 121 | location = /cookies.html { 122 | root /var/www/public_html; 123 | } 124 | 125 | location / { 126 | testcookie on; 127 | proxy_set_header Host $host; 128 | proxy_set_header X-Real-IP $remote_addr; 129 | proxy_pass http://127.0.0.1:8080; 130 | } 131 | } 132 | 133 | 5. HTTP GET flood, bots accept HTTP response headers, and can parse HTML, then decrypt cookies client-side, but w/o JS emulation 134 | 135 | server { 136 | listen 80; 137 | server_name domain.com; 138 | 139 | testcookie off; 140 | testcookie_name BPC; 141 | testcookie_secret keepmescret; 142 | testcookie_session $remote_addr; 143 | testcookie_arg attempt; 144 | testcookie_max_attempts 3; 145 | testcookie_fallback /cookies.html?backurl=http://$host$request_uri; 146 | testcookie_get_only on; 147 | testcookie_redirect_via_refresh on; 148 | testcookie_refresh_encrypt_cookie on; 149 | testcookie_refresh_encrypt_cookie_key deadbeefdeadbeefdeadbeefdeadbeef; #change it by cron 150 | testcookie_refresh_encrypt_cookie_iv deadbeefdeadbeefdeadbeefdeadbeef; #change it by cron 151 | 152 | testcookie_refresh_template 'setting cookie...'; 153 | 154 | location = /aes.min.js { 155 | gzip on; 156 | gzip_min_length 1000; 157 | gzip_types text/plain; 158 | root /var/www/public_html; 159 | } 160 | 161 | location = /cookies.html { 162 | root /var/www/public_html; 163 | } 164 | 165 | location / { 166 | testcookie on; 167 | proxy_set_header Host $host; 168 | proxy_set_header X-Real-IP $remote_addr; 169 | proxy_pass http://127.0.0.1:8080; 170 | } 171 | } 172 | 173 | 6. User-Agent whitelisting example (not recomended!) 174 | 175 | server { 176 | listen 80; 177 | server_name domain.com; 178 | 179 | 180 | testcookie on; 181 | testcookie_name BPC; 182 | testcookie_secret keepmescret; 183 | testcookie_session $remote_addr; 184 | testcookie_arg attempt; 185 | testcookie_max_attempts 3; 186 | testcookie_fallback /cookies.html?backurl=http://$host$request_uri; 187 | testcookie_get_only on; 188 | 189 | 190 | location = /cookies.html { 191 | testcookie off; 192 | root /var/www/public_html; 193 | } 194 | 195 | location / { 196 | if ($http_user_agent =~ "Yandex|Google") { 197 | testcookie off; 198 | } 199 | proxy_set_header Host $host; 200 | proxy_set_header X-Real-IP $remote_addr; 201 | proxy_pass http://127.0.0.1:8080; 202 | } 203 | } 204 | 205 | 206 | 7. Whitelisting with "map" 207 | 208 | map $remote_addr $trusted { 209 | default 0; 210 | "127.0.0.1" 1; 211 | } 212 | 213 | server { 214 | listen 80; 215 | server_name domain.com; 216 | 217 | testcookie off; 218 | testcookie_name BPC; 219 | testcookie_secret keepmescret; 220 | testcookie_session $remote_addr; 221 | testcookie_arg attempt; 222 | testcookie_max_attempts 3; 223 | testcookie_fallback /cookies.html?backurl=http://$host$request_uri; 224 | testcookie_get_only on; 225 | 226 | location = /cookies.html { 227 | root /var/www/public_html; 228 | } 229 | 230 | location / { 231 | testcookie on; 232 | testcookie_pass $trusted; 233 | proxy_set_header Host $host; 234 | proxy_set_header X-Real-IP $remote_addr; 235 | proxy_pass http://127.0.0.1:8080; 236 | 237 | } 238 | } 239 | 240 | 241 | 8. Dynamic whitelisting example (need to change modules order first!) 242 | 243 | server { 244 | listen 80; 245 | server_name domain.com; 246 | 247 | testcookie off; 248 | testcookie_name BPC; 249 | testcookie_secret keepmescret; 250 | testcookie_session $remote_addr; 251 | testcookie_arg attempt; 252 | testcookie_max_attempts 3; 253 | testcookie_fallback /cookies.html?backurl=http://$host$request_uri; 254 | testcookie_get_only on; 255 | 256 | location = /cookies.html { 257 | root /var/www/public_html; 258 | } 259 | 260 | location / { 261 | testcookie on; 262 | auth_request /precheck; 263 | testcookie_pass $trusted; 264 | proxy_set_header Host $host; 265 | proxy_set_header X-Real-IP $remote_addr; 266 | proxy_pass http://127.0.0.1:8080; 267 | 268 | } 269 | 270 | location = /precheck { 271 | proxy_pass http://127.0.0.1:9090; 272 | proxy_pass_request_body off; 273 | proxy_set_header Content-Length ""; 274 | proxy_set_header X-Original-URI $request_uri; 275 | auth_request_set $trusted $upstream_http_x_trusted; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/ngx_http_testcookie_access_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | v1.24 3 | 4 | Copyright (C) 2011-2018 Eldar Zaitov (eldar@kyprizel.net). 5 | All rights reserved. 6 | This module is licenced under the terms of BSD license. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define REFRESH_COOKIE_ENCRYPTION 15 | 16 | #ifdef REFRESH_COOKIE_ENCRYPTION 17 | #include 18 | #include 19 | #endif 20 | 21 | #define NGX_HTTP_TESTCOOKIE_OFF 0 22 | #define NGX_HTTP_TESTCOOKIE_ON 1 23 | #define NGX_HTTP_TESTCOOKIE_VAR 2 24 | 25 | /* 31 Dec 2037 23:55:55 GMT */ 26 | #define NGX_HTTP_TESTCOOKIE_MAX_EXPIRES 2145916555 27 | #define DEFAULT_COOKIE_NAME "TCK" 28 | #ifndef MD5_DIGEST_LENGTH 29 | #define MD5_DIGEST_LENGTH 16 30 | #endif 31 | #define RFC1945_ATTEMPTS 4 32 | 33 | typedef struct { 34 | ngx_uint_t enable; 35 | 36 | ngx_str_t name; 37 | ngx_str_t domain; 38 | ngx_str_t path; 39 | ngx_str_t p3p; 40 | ngx_str_t samesite; 41 | 42 | time_t expires; 43 | 44 | ngx_str_t arg; 45 | ngx_str_t secret; 46 | ngx_http_complex_value_t session_key; 47 | 48 | ngx_int_t max_attempts; 49 | 50 | ngx_radix_tree_t *whitelist; 51 | #if (NGX_HAVE_INET6) 52 | ngx_radix_tree_t *whitelist6; 53 | #endif 54 | 55 | ngx_str_t fallback; 56 | ngx_array_t *fallback_lengths; 57 | ngx_array_t *fallback_values; 58 | 59 | ngx_flag_t redirect_via_refresh; 60 | ngx_str_t refresh_template; 61 | ngx_array_t *refresh_template_lengths; 62 | ngx_array_t *refresh_template_values; 63 | ngx_uint_t refresh_status; 64 | 65 | #ifdef REFRESH_COOKIE_ENCRYPTION 66 | ngx_flag_t refresh_encrypt_cookie; 67 | u_char *refresh_encrypt_cookie_key; 68 | u_char *refresh_encrypt_cookie_iv; 69 | #endif 70 | 71 | ngx_flag_t redirect_to_https; 72 | ngx_flag_t get_only; 73 | ngx_flag_t deny_keepalive; 74 | ngx_flag_t internal; 75 | ngx_flag_t httponly_flag; 76 | ngx_flag_t port_in_redirect; 77 | ngx_http_complex_value_t *secure_flag; 78 | ngx_http_complex_value_t *pass_var; 79 | } ngx_http_testcookie_conf_t; 80 | 81 | 82 | typedef struct { 83 | u_char *uid_set; 84 | u_char *uid_got; 85 | #ifdef REFRESH_COOKIE_ENCRYPTION 86 | u_char *encrypt_key; 87 | u_char *encrypt_iv; 88 | #endif 89 | u_short ok; 90 | ngx_str_t cookie; 91 | } ngx_http_testcookie_ctx_t; 92 | 93 | static ngx_conf_enum_t ngx_http_testcookie_access_state[] = { 94 | { ngx_string("off"), NGX_HTTP_TESTCOOKIE_OFF }, 95 | { ngx_string("on"), NGX_HTTP_TESTCOOKIE_ON }, 96 | { ngx_string("var"), NGX_HTTP_TESTCOOKIE_VAR }, 97 | { ngx_null_string, 0 } 98 | }; 99 | 100 | 101 | static ngx_int_t ngx_http_send_refresh(ngx_http_request_t *r, ngx_http_testcookie_conf_t *conf); 102 | static ngx_int_t ngx_http_send_custom_refresh(ngx_http_request_t *r, ngx_http_testcookie_conf_t *conf); 103 | static ngx_int_t ngx_http_testcookie_handler(ngx_http_request_t *r); 104 | 105 | static ngx_http_testcookie_ctx_t *ngx_http_testcookie_get_uid(ngx_http_request_t *r, 106 | ngx_http_testcookie_conf_t *conf); 107 | static ngx_int_t ngx_http_testcookie_set_uid(ngx_http_request_t *r, 108 | ngx_http_testcookie_ctx_t *ctx, ngx_http_testcookie_conf_t *conf); 109 | static ngx_int_t ngx_http_testcookie_timestamp_variable(ngx_http_request_t *r, 110 | ngx_http_variable_value_t *v, uintptr_t data); 111 | 112 | static ngx_int_t ngx_http_testcookie_add_variables(ngx_conf_t *cf); 113 | static ngx_int_t ngx_http_testcookie_init(ngx_conf_t *cf); 114 | static void *ngx_http_testcookie_create_conf(ngx_conf_t *cf); 115 | static char *ngx_http_testcookie_merge_conf(ngx_conf_t *cf, void *parent, void *child); 116 | static char *ngx_http_testcookie_domain(ngx_conf_t *cf, void *post, void *data); 117 | static char *ngx_http_testcookie_path(ngx_conf_t *cf, void *post, void *data); 118 | static char *ngx_http_testcookie_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 119 | static char *ngx_http_testcookie_p3p(ngx_conf_t *cf, void *post, void *data); 120 | static char *ngx_http_testcookie_samesite(ngx_conf_t *cf, void *post, void *data); 121 | static char *ngx_http_testcookie_secret(ngx_conf_t *cf, void *post, void *data); 122 | static char *ngx_http_testcookie_max_attempts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 123 | static char *ngx_http_testcookie_whitelist_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 124 | static char *ngx_http_testcookie_whitelist(ngx_conf_t *cf, ngx_command_t *dummy, void *conf); 125 | static char *ngx_http_testcookie_fallback_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 126 | static char *ngx_http_testcookie_session_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 127 | static char *ngx_http_testcookie_refresh_template_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 128 | u_char *ngx_hextobin(u_char *dst, u_char *src, size_t len); 129 | int ngx_ishex(u_char *src, size_t len); 130 | static char *ngx_http_testcookie_refresh_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 131 | static ngx_int_t ngx_http_testcookie_nocache(ngx_http_request_t *r); 132 | 133 | #ifdef REFRESH_COOKIE_ENCRYPTION 134 | static char *ngx_http_testcookie_set_encryption_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 135 | static char *ngx_http_testcookie_set_encryption_iv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 136 | #endif 137 | 138 | static u_char expires[] = "; expires=Thu, 31-Dec-37 23:55:55 GMT"; 139 | 140 | static u_char ngx_http_msie_refresh_head[] = 141 | "" CRLF; 145 | 146 | 147 | static ngx_conf_post_handler_pt ngx_http_testcookie_domain_p = ngx_http_testcookie_domain; 148 | static ngx_conf_post_handler_pt ngx_http_testcookie_path_p = ngx_http_testcookie_path; 149 | static ngx_conf_post_handler_pt ngx_http_testcookie_p3p_p = ngx_http_testcookie_p3p; 150 | static ngx_conf_post_handler_pt ngx_http_testcookie_samesite_p = ngx_http_testcookie_samesite; 151 | static ngx_conf_post_handler_pt ngx_http_testcookie_secret_p = ngx_http_testcookie_secret; 152 | 153 | static ngx_command_t ngx_http_testcookie_access_commands[] = { 154 | 155 | { ngx_string("testcookie"), 156 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF 157 | |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 158 | ngx_conf_set_enum_slot, 159 | NGX_HTTP_LOC_CONF_OFFSET, 160 | offsetof(ngx_http_testcookie_conf_t, enable), 161 | ngx_http_testcookie_access_state }, 162 | { ngx_string("testcookie_name"), 163 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 164 | ngx_conf_set_str_slot, 165 | NGX_HTTP_LOC_CONF_OFFSET, 166 | offsetof(ngx_http_testcookie_conf_t, name), 167 | NULL }, 168 | 169 | { ngx_string("testcookie_domain"), 170 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 171 | ngx_conf_set_str_slot, 172 | NGX_HTTP_LOC_CONF_OFFSET, 173 | offsetof(ngx_http_testcookie_conf_t, domain), 174 | &ngx_http_testcookie_domain_p }, 175 | 176 | { ngx_string("testcookie_path"), 177 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 178 | ngx_conf_set_str_slot, 179 | NGX_HTTP_LOC_CONF_OFFSET, 180 | offsetof(ngx_http_testcookie_conf_t, path), 181 | &ngx_http_testcookie_path_p }, 182 | 183 | { ngx_string("testcookie_expires"), 184 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 185 | ngx_http_testcookie_expires, 186 | NGX_HTTP_LOC_CONF_OFFSET, 187 | 0, 188 | NULL }, 189 | 190 | { ngx_string("testcookie_p3p"), 191 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 192 | ngx_conf_set_str_slot, 193 | NGX_HTTP_LOC_CONF_OFFSET, 194 | offsetof(ngx_http_testcookie_conf_t, p3p), 195 | &ngx_http_testcookie_p3p_p }, 196 | 197 | { ngx_string("testcookie_samesite"), 198 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 199 | ngx_conf_set_str_slot, 200 | NGX_HTTP_LOC_CONF_OFFSET, 201 | offsetof(ngx_http_testcookie_conf_t, samesite), 202 | &ngx_http_testcookie_samesite_p }, 203 | 204 | { ngx_string("testcookie_arg"), 205 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 206 | ngx_conf_set_str_slot, 207 | NGX_HTTP_LOC_CONF_OFFSET, 208 | offsetof(ngx_http_testcookie_conf_t, arg), 209 | NULL }, 210 | 211 | { ngx_string("testcookie_session"), 212 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 213 | ngx_http_testcookie_session_slot, 214 | NGX_HTTP_LOC_CONF_OFFSET, 215 | 0, 216 | NULL }, 217 | 218 | { ngx_string("testcookie_secret"), 219 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 220 | ngx_conf_set_str_slot, 221 | NGX_HTTP_LOC_CONF_OFFSET, 222 | offsetof(ngx_http_testcookie_conf_t, secret), 223 | &ngx_http_testcookie_secret_p }, 224 | 225 | { ngx_string("testcookie_fallback"), 226 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 227 | ngx_http_testcookie_fallback_slot, 228 | NGX_HTTP_LOC_CONF_OFFSET, 229 | offsetof(ngx_http_testcookie_conf_t, fallback), 230 | NULL }, 231 | 232 | { ngx_string("testcookie_max_attempts"), 233 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 234 | ngx_http_testcookie_max_attempts, 235 | NGX_HTTP_LOC_CONF_OFFSET, 236 | 0, 237 | NULL }, 238 | 239 | { ngx_string("testcookie_whitelist"), 240 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, 241 | ngx_http_testcookie_whitelist_block, 242 | NGX_HTTP_LOC_CONF_OFFSET, 243 | offsetof(ngx_http_testcookie_conf_t, whitelist), 244 | NULL }, 245 | 246 | { ngx_string("testcookie_https_location"), 247 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 248 | ngx_conf_set_flag_slot, 249 | NGX_HTTP_LOC_CONF_OFFSET, 250 | offsetof(ngx_http_testcookie_conf_t, redirect_to_https), 251 | NULL }, 252 | 253 | { ngx_string("testcookie_get_only"), 254 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 255 | ngx_conf_set_flag_slot, 256 | NGX_HTTP_LOC_CONF_OFFSET, 257 | offsetof(ngx_http_testcookie_conf_t, get_only), 258 | NULL }, 259 | 260 | { ngx_string("testcookie_redirect_via_refresh"), 261 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 262 | ngx_conf_set_flag_slot, 263 | NGX_HTTP_LOC_CONF_OFFSET, 264 | offsetof(ngx_http_testcookie_conf_t, redirect_via_refresh), 265 | NULL }, 266 | 267 | { ngx_string("testcookie_refresh_template"), 268 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 269 | ngx_http_testcookie_refresh_template_slot, 270 | NGX_HTTP_LOC_CONF_OFFSET, 271 | 0, 272 | NULL }, 273 | 274 | { ngx_string("testcookie_refresh_status"), 275 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 276 | ngx_http_testcookie_refresh_status, 277 | NGX_HTTP_LOC_CONF_OFFSET, 278 | 0, 279 | NULL }, 280 | 281 | { ngx_string("testcookie_deny_keepalive"), 282 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 283 | ngx_conf_set_flag_slot, 284 | NGX_HTTP_LOC_CONF_OFFSET, 285 | offsetof(ngx_http_testcookie_conf_t, deny_keepalive), 286 | NULL }, 287 | 288 | { ngx_string("testcookie_internal"), 289 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 290 | ngx_conf_set_flag_slot, 291 | NGX_HTTP_LOC_CONF_OFFSET, 292 | offsetof(ngx_http_testcookie_conf_t, internal), 293 | NULL }, 294 | 295 | #ifdef REFRESH_COOKIE_ENCRYPTION 296 | 297 | { ngx_string("testcookie_refresh_encrypt_cookie"), 298 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 299 | ngx_conf_set_flag_slot, 300 | NGX_HTTP_LOC_CONF_OFFSET, 301 | offsetof(ngx_http_testcookie_conf_t, refresh_encrypt_cookie), 302 | NULL }, 303 | 304 | { ngx_string("testcookie_refresh_encrypt_cookie_key"), 305 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 306 | ngx_http_testcookie_set_encryption_key, 307 | NGX_HTTP_LOC_CONF_OFFSET, 308 | 0, 309 | NULL }, 310 | 311 | { ngx_string("testcookie_refresh_encrypt_cookie_iv"), 312 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 313 | ngx_http_testcookie_set_encryption_iv, 314 | NGX_HTTP_LOC_CONF_OFFSET, 315 | 0, 316 | NULL }, 317 | 318 | #endif 319 | 320 | { ngx_string("testcookie_httponly_flag"), 321 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 322 | ngx_conf_set_flag_slot, 323 | NGX_HTTP_LOC_CONF_OFFSET, 324 | offsetof(ngx_http_testcookie_conf_t, httponly_flag), 325 | NULL }, 326 | 327 | { ngx_string("testcookie_secure_flag"), 328 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 329 | ngx_http_set_complex_value_slot, 330 | NGX_HTTP_LOC_CONF_OFFSET, 331 | offsetof(ngx_http_testcookie_conf_t, secure_flag), 332 | NULL }, 333 | 334 | { ngx_string("testcookie_pass"), 335 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 336 | ngx_http_set_complex_value_slot, 337 | NGX_HTTP_LOC_CONF_OFFSET, 338 | offsetof(ngx_http_testcookie_conf_t, pass_var), 339 | NULL }, 340 | 341 | { ngx_string("testcookie_port_in_redirect"), 342 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 343 | ngx_conf_set_flag_slot, 344 | NGX_HTTP_LOC_CONF_OFFSET, 345 | offsetof(ngx_http_testcookie_conf_t, port_in_redirect), 346 | NULL }, 347 | 348 | ngx_null_command 349 | }; 350 | 351 | 352 | static ngx_http_module_t ngx_http_testcookie_access_module_ctx = { 353 | ngx_http_testcookie_add_variables, /* preconfiguration */ 354 | ngx_http_testcookie_init, /* postconfiguration */ 355 | 356 | NULL, /* create main configuration */ 357 | NULL, /* init main configuration */ 358 | 359 | NULL, /* create server configuration */ 360 | NULL, /* merge server configuration */ 361 | 362 | ngx_http_testcookie_create_conf, /* create location configration */ 363 | ngx_http_testcookie_merge_conf /* merge location configration */ 364 | }; 365 | 366 | 367 | ngx_module_t ngx_http_testcookie_access_module = { 368 | NGX_MODULE_V1, 369 | &ngx_http_testcookie_access_module_ctx, /* module context */ 370 | ngx_http_testcookie_access_commands, /* module directives */ 371 | NGX_HTTP_MODULE, /* module type */ 372 | NULL, /* init master */ 373 | NULL, /* init module */ 374 | NULL, /* init process */ 375 | NULL, /* init thread */ 376 | NULL, /* exit thread */ 377 | NULL, /* exit process */ 378 | NULL, /* exit master */ 379 | NGX_MODULE_V1_PADDING 380 | }; 381 | 382 | 383 | static ngx_str_t ngx_http_testcookie_got = ngx_string("testcookie_got"); 384 | static ngx_str_t ngx_http_testcookie_set = ngx_string("testcookie_set"); 385 | static ngx_str_t ngx_http_testcookie_ok = ngx_string("testcookie_ok"); 386 | static ngx_str_t ngx_http_testcookie_nexturl = ngx_string("testcookie_nexturl"); 387 | static ngx_str_t ngx_http_testcookie_timestamp = ngx_string("testcookie_timestamp"); 388 | 389 | #ifdef REFRESH_COOKIE_ENCRYPTION 390 | static ngx_str_t ngx_http_testcookie_enc_set = ngx_string("testcookie_enc_set"); 391 | static ngx_str_t ngx_http_testcookie_enc_iv = ngx_string("testcookie_enc_iv"); 392 | static ngx_str_t ngx_http_testcookie_enc_key = ngx_string("testcookie_enc_key"); 393 | #endif 394 | 395 | static ngx_int_t 396 | ngx_http_send_refresh(ngx_http_request_t *r, ngx_http_testcookie_conf_t *conf) 397 | { 398 | u_char *p, *location; 399 | size_t len, size; 400 | uintptr_t escape; 401 | ngx_int_t rc; 402 | ngx_buf_t *b; 403 | ngx_chain_t out; 404 | 405 | len = r->headers_out.location->value.len; 406 | location = r->headers_out.location->value.data; 407 | 408 | escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH); 409 | 410 | size = sizeof(ngx_http_msie_refresh_head) - 1 411 | + escape + len 412 | + sizeof(ngx_http_msie_refresh_tail) - 1; 413 | 414 | r->err_status = conf->refresh_status; 415 | 416 | r->headers_out.content_type_len = sizeof("text/html") - 1; 417 | r->headers_out.content_type.len = sizeof("text/html") - 1; 418 | r->headers_out.content_type.data = (u_char *) "text/html"; 419 | 420 | r->headers_out.location->hash = 0; 421 | r->headers_out.location = NULL; 422 | 423 | r->headers_out.content_length_n = size; 424 | 425 | if (r->headers_out.content_length) { 426 | r->headers_out.content_length->hash = 0; 427 | r->headers_out.content_length = NULL; 428 | } 429 | 430 | ngx_http_clear_accept_ranges(r); 431 | ngx_http_clear_last_modified(r); 432 | ngx_http_clear_etag(r); 433 | ngx_http_testcookie_nocache(r); 434 | 435 | rc = ngx_http_send_header(r); 436 | 437 | if (rc == NGX_ERROR) { 438 | return rc; 439 | } 440 | 441 | if (r->header_only) { 442 | ngx_http_finalize_request(r, 0); 443 | return NGX_DONE; 444 | } 445 | 446 | b = ngx_create_temp_buf(r->pool, size); 447 | if (b == NULL) { 448 | return NGX_ERROR; 449 | } 450 | 451 | p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head, 452 | sizeof(ngx_http_msie_refresh_head) - 1); 453 | 454 | if (escape == 0) { 455 | p = ngx_cpymem(p, location, len); 456 | } else { 457 | p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH); 458 | } 459 | 460 | b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail, 461 | sizeof(ngx_http_msie_refresh_tail) - 1); 462 | 463 | b->last_buf = 1; 464 | b->last_in_chain = 1; 465 | 466 | out.buf = b; 467 | out.next = NULL; 468 | 469 | ngx_http_output_filter(r, &out); 470 | ngx_http_finalize_request(r, 0); 471 | return NGX_DONE; 472 | } 473 | 474 | static ngx_int_t 475 | ngx_http_send_custom_refresh(ngx_http_request_t *r, ngx_http_testcookie_conf_t *conf) 476 | { 477 | ngx_int_t rc; 478 | ngx_buf_t *b; 479 | ngx_chain_t out; 480 | ngx_str_t compiled_refresh_template; 481 | 482 | r->err_status = conf->refresh_status; 483 | 484 | r->headers_out.content_type_len = sizeof("text/html") - 1; 485 | r->headers_out.content_type.len = sizeof("text/html") - 1; 486 | r->headers_out.content_type.data = (u_char *) "text/html"; 487 | 488 | if (conf->refresh_template_lengths != NULL && conf->refresh_template_values != NULL) { 489 | if (ngx_http_script_run(r, &compiled_refresh_template, conf->refresh_template_lengths->elts, 490 | 0, conf->refresh_template_values->elts) == NULL) { 491 | return NGX_ERROR; 492 | } 493 | } else { 494 | compiled_refresh_template.data = conf->refresh_template.data; 495 | compiled_refresh_template.len = conf->refresh_template.len; 496 | } 497 | 498 | r->headers_out.location->hash = 0; 499 | r->headers_out.location = NULL; 500 | 501 | r->headers_out.content_length_n = compiled_refresh_template.len; 502 | 503 | if (r->headers_out.content_length) { 504 | r->headers_out.content_length->hash = 0; 505 | r->headers_out.content_length = NULL; 506 | } 507 | 508 | ngx_http_clear_accept_ranges(r); 509 | ngx_http_clear_last_modified(r); 510 | ngx_http_clear_etag(r); 511 | ngx_http_testcookie_nocache(r); 512 | 513 | rc = ngx_http_send_header(r); 514 | if (rc == NGX_ERROR) { 515 | return rc; 516 | } 517 | 518 | if (r->header_only) { 519 | ngx_http_finalize_request(r, 0); 520 | return NGX_DONE; 521 | } 522 | 523 | b = ngx_create_temp_buf(r->pool, compiled_refresh_template.len); 524 | if (b == NULL) { 525 | return NGX_ERROR; 526 | } 527 | 528 | b->last = ngx_cpymem(b->start, compiled_refresh_template.data, 529 | compiled_refresh_template.len); 530 | 531 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 532 | "compiled refresh template len: \"%d\"", compiled_refresh_template.len); 533 | 534 | b->last_buf = 1; 535 | b->last_in_chain = 1; 536 | 537 | out.buf = b; 538 | out.next = NULL; 539 | 540 | ngx_http_output_filter(r, &out); 541 | ngx_http_finalize_request(r, 0); 542 | return NGX_DONE; 543 | } 544 | 545 | static ngx_int_t 546 | ngx_http_testcookie_handler(ngx_http_request_t *r) 547 | { 548 | ngx_http_testcookie_ctx_t *ctx; 549 | ngx_http_testcookie_conf_t *conf; 550 | ngx_str_t *args, *look; 551 | ngx_uint_t i, j, k, l, uri_len; 552 | ngx_int_t attempt; 553 | ngx_int_t rc; 554 | u_char *buf, *p; 555 | size_t len; 556 | u_short sc; 557 | ngx_table_elt_t *location; 558 | ngx_str_t compiled_fallback; 559 | ngx_str_t pass_mode; 560 | ngx_uint_t port = 80; /* make gcc happy */ 561 | struct sockaddr_in *sin; 562 | #if (NGX_HAVE_INET6) 563 | struct sockaddr_in6 *sin6; 564 | #endif 565 | 566 | if (r != r->main) { 567 | return NGX_DECLINED; 568 | } 569 | 570 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 571 | "request type: %d", r->internal); 572 | 573 | conf = ngx_http_get_module_loc_conf(r, ngx_http_testcookie_access_module); 574 | if (!conf || conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 575 | return NGX_DECLINED; 576 | } 577 | 578 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_handler"); 579 | 580 | if (r->internal && !conf->internal) { 581 | return NGX_DECLINED; 582 | } 583 | 584 | if (conf->pass_var != NULL 585 | && ngx_http_complex_value(r, conf->pass_var, &pass_mode) == NGX_OK 586 | && pass_mode.len == 1 587 | && pass_mode.data[0] == '1') 588 | { 589 | return NGX_DECLINED; 590 | } 591 | 592 | ctx = ngx_http_testcookie_get_uid(r, conf); 593 | if (ctx == NULL) { 594 | // return NGX_DECLINED; 595 | return NGX_HTTP_FORBIDDEN; 596 | } 597 | 598 | if (conf->enable == NGX_HTTP_TESTCOOKIE_VAR) { 599 | return NGX_DECLINED; 600 | } 601 | 602 | if (conf->get_only 603 | && (r->method != NGX_HTTP_GET 604 | && r->method != NGX_HTTP_HEAD)) { 605 | return NGX_DECLINED; 606 | } 607 | 608 | if (conf->deny_keepalive) { 609 | r->keepalive = 0; 610 | } 611 | 612 | 613 | if (ctx->ok == 1) { 614 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 615 | "user passed test"); 616 | return NGX_DECLINED; 617 | } 618 | 619 | args = &r->args; 620 | look = &conf->arg; 621 | i = j = k = l = 0; 622 | attempt = 0; 623 | sc = 0; 624 | uri_len = 0; 625 | 626 | if (look->len > 0) { 627 | if (args->len > 0) { 628 | for (i = 0; i <= args->len; i++) { 629 | if ((i == args->len) || (args->data[i] == '&')) { 630 | if (j > 1) { 631 | k = j; 632 | l = i; 633 | } 634 | j = 0; 635 | } else if ((j == 0) && (i < args->len-look->len)) { 636 | if ((ngx_strncmp(args->data+i, look->data, look->len) == 0) 637 | && (args->data[i+look->len] == '=')) { 638 | j = i+look->len+1; 639 | i = j-1; 640 | } else { 641 | j = 1; 642 | } 643 | } 644 | } 645 | if (l > k) { 646 | attempt = ngx_atoi(args->data+k, 1); 647 | } 648 | } 649 | 650 | if (conf->max_attempts > 0 && attempt >= conf->max_attempts) { 651 | r->keepalive = 0; 652 | if (conf->fallback.len == 0) { 653 | return NGX_HTTP_FORBIDDEN; 654 | } 655 | if (conf->fallback_lengths != NULL && conf->fallback_values != NULL) { 656 | if (ngx_http_script_run(r, &compiled_fallback, conf->fallback_lengths->elts, 657 | 0, conf->fallback_values->elts) == NULL) { 658 | return NGX_ERROR; 659 | } 660 | buf = compiled_fallback.data; 661 | len = compiled_fallback.len; 662 | } else { 663 | buf = conf->fallback.data; 664 | len = conf->fallback.len; 665 | } 666 | goto redirect; 667 | } 668 | } 669 | 670 | len = 0; 671 | if (r->headers_in.server.len > 0) { 672 | len = sizeof("http://") - 1 + r->headers_in.server.len; 673 | #if (NGX_HTTP_SSL) 674 | if (r->connection->ssl || conf->redirect_to_https) { 675 | /* http:// -> https:// */ 676 | len += 1; 677 | } 678 | #endif 679 | /* XXX: this looks awful :( */ 680 | if (ngx_connection_local_sockaddr(r->connection, NULL, 0) != NGX_OK) { 681 | return NGX_ERROR; 682 | } 683 | switch (r->connection->local_sockaddr->sa_family) { 684 | #if (NGX_HAVE_INET6) 685 | case AF_INET6: 686 | sin6 = (struct sockaddr_in6 *) r->connection->local_sockaddr; 687 | port = ntohs(sin6->sin6_port); 688 | break; 689 | #endif 690 | default: /* AF_INET */ 691 | sin = (struct sockaddr_in *) r->connection->local_sockaddr; 692 | port = ntohs(sin->sin_port); 693 | break; 694 | } 695 | if (port > 0 && port < 65535 && conf->port_in_redirect) { 696 | len += sizeof(":65535") - 1; 697 | } 698 | } 699 | 700 | if (r->unparsed_uri.len == 0) { 701 | len += 1; 702 | } else { 703 | p = r->unparsed_uri.data; 704 | for (uri_len = 0; uri_len < r->unparsed_uri.len; uri_len++) { 705 | if (*p == '?') { 706 | break; 707 | } 708 | p++; 709 | } 710 | len += uri_len; 711 | } 712 | if (look->len > 0) { 713 | if (args->len == 0) { 714 | sc = 1; 715 | len += look->len + sizeof("?=1") - 1; 716 | } else { 717 | if (l == k) { 718 | if (k == l && l == args->len) { 719 | sc = 2; 720 | len += args->len + sizeof("?1") - 1; 721 | } else { 722 | sc = 3; 723 | len += look->len + args->len + sizeof("?=1&") - 1; 724 | } 725 | } else { 726 | sc = 4; 727 | len += args->len + sizeof("?") - 1; 728 | } 729 | } 730 | } else { 731 | if (args->len > 0) { 732 | len += args->len + sizeof("?") - 1; 733 | } 734 | } 735 | 736 | buf = (u_char *) ngx_pcalloc(r->pool, len + 1); 737 | if (buf == NULL) { 738 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 739 | } 740 | 741 | p = (u_char*) buf; 742 | 743 | if (r->headers_in.server.len > 0) { 744 | #if (NGX_HTTP_SSL) 745 | if (r->connection->ssl || conf->redirect_to_https) { 746 | p = ngx_copy(p, "https://", sizeof("https://") - 1); 747 | p = ngx_copy(p, r->headers_in.server.data, r->headers_in.server.len); 748 | } else { 749 | p = ngx_copy(p, "http://", sizeof("http://") - 1); 750 | p = ngx_copy(p, r->headers_in.server.data, r->headers_in.server.len); 751 | } 752 | #else 753 | p = ngx_copy(p, "http://", sizeof("http://") - 1); 754 | p = ngx_copy(p, r->headers_in.server.data, r->headers_in.server.len); 755 | #endif 756 | 757 | if (port > 0 && port < 65535 && conf->port_in_redirect) { 758 | len -= sizeof(":65535") - 1; 759 | len += ngx_sprintf(p, ":%ui", port) - p; 760 | p = ngx_sprintf(p, ":%ui", port); 761 | } 762 | } 763 | 764 | if (r->unparsed_uri.len == 0) { 765 | (*p++) = '/'; 766 | } else { 767 | p = ngx_copy(p, r->unparsed_uri.data, uri_len); 768 | } 769 | 770 | /* 771 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "case%d", sc); 772 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "l: %d", l); 773 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "k: %d", k); 774 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "attempts: %d\n", attempt); 775 | */ 776 | 777 | if (look->len > 0) { 778 | (*p++) = '?'; 779 | switch (sc) { 780 | case 1: 781 | p = ngx_sprintf(p, "%V=1", look); 782 | break; 783 | case 2: 784 | p = ngx_sprintf(p, "%V1", args); 785 | break; 786 | case 3: 787 | p = ngx_sprintf(p, "%V&%V=1", args, look); 788 | break; 789 | case 4: 790 | attempt++; 791 | p = ngx_copy(p, args->data, k); 792 | p = ngx_sprintf(p, "%d", attempt); 793 | p = ngx_copy(p, args->data+l, args->len-l); 794 | break; 795 | default: 796 | break; 797 | } 798 | } else { 799 | if (args->len > 0) { 800 | (*p++) = '?'; 801 | p = ngx_sprintf(p, "%V", args); 802 | } 803 | } 804 | 805 | rc = ngx_http_testcookie_set_uid(r, ctx, conf); 806 | if (rc != NGX_OK) { 807 | return rc; 808 | } 809 | 810 | redirect: 811 | 812 | /* 813 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "buf len: %d", len); 814 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "redirectig user to %s", buf); 815 | */ 816 | 817 | if (r->http_version < NGX_HTTP_VERSION_11) { 818 | r->headers_out.status = NGX_HTTP_MOVED_TEMPORARILY; 819 | rc = NGX_HTTP_MOVED_TEMPORARILY; 820 | } else { 821 | r->headers_out.status = NGX_HTTP_TEMPORARY_REDIRECT; 822 | rc = NGX_HTTP_TEMPORARY_REDIRECT; 823 | } 824 | location = ngx_list_push(&r->headers_out.headers); 825 | if (location == NULL) { 826 | return NGX_ERROR; 827 | } 828 | 829 | location->hash = 1; 830 | location->key.len = sizeof("Location") - 1; 831 | location->key.data = (u_char *) "Location"; 832 | location->value.len = len; 833 | location->value.data = buf; 834 | 835 | r->headers_out.location = location; 836 | 837 | ngx_http_clear_accept_ranges(r); 838 | ngx_http_clear_last_modified(r); 839 | ngx_http_clear_content_length(r); 840 | ngx_http_clear_etag(r); 841 | 842 | if (conf->redirect_via_refresh) { 843 | if (conf->refresh_template.len == 0) { 844 | return ngx_http_send_refresh(r, conf); 845 | } else { 846 | return ngx_http_send_custom_refresh(r, conf); 847 | } 848 | } else { 849 | ngx_http_testcookie_nocache(r); 850 | } 851 | 852 | return rc; 853 | } 854 | 855 | static ngx_int_t 856 | ngx_http_testcookie_got_variable(ngx_http_request_t *r, 857 | ngx_http_variable_value_t *v, uintptr_t data) 858 | { 859 | ngx_http_testcookie_ctx_t *ctx; 860 | ngx_http_testcookie_conf_t *conf; 861 | 862 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_got_variable"); 863 | 864 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_testcookie_access_module); 865 | if (conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 866 | v->not_found = 1; 867 | return NGX_OK; 868 | } 869 | 870 | ctx = ngx_http_get_module_ctx(r, ngx_http_testcookie_access_module); 871 | if (ctx == NULL) { 872 | ctx = ngx_http_testcookie_get_uid(r, conf); 873 | if (ctx == NULL) { 874 | v->not_found = 1; 875 | return NGX_OK; 876 | } 877 | } 878 | 879 | if (ctx->uid_got == NULL) { 880 | v->not_found = 1; 881 | return NGX_OK; 882 | } 883 | 884 | v->data = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH*2); 885 | if (v->data == NULL) { 886 | return NGX_ERROR; 887 | } 888 | 889 | v->valid = 1; 890 | v->no_cacheable = 1; 891 | v->not_found = 0; 892 | 893 | ngx_memcpy(v->data, ctx->uid_got, MD5_DIGEST_LENGTH*2); 894 | v->len = MD5_DIGEST_LENGTH*2; 895 | 896 | return NGX_OK; 897 | } 898 | 899 | 900 | #ifdef REFRESH_COOKIE_ENCRYPTION 901 | static ngx_int_t 902 | ngx_http_testcookie_enc_key_variable(ngx_http_request_t *r, 903 | ngx_http_variable_value_t *v, uintptr_t data) 904 | { 905 | ngx_http_testcookie_ctx_t *ctx; 906 | ngx_http_testcookie_conf_t *conf; 907 | 908 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_enc_key_variable"); 909 | 910 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_testcookie_access_module); 911 | if (conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 912 | v->not_found = 1; 913 | return NGX_OK; 914 | } 915 | 916 | if (!conf->refresh_encrypt_cookie) { 917 | v->not_found = 1; 918 | return NGX_OK; 919 | } 920 | 921 | ctx = ngx_http_get_module_ctx(r, ngx_http_testcookie_access_module); 922 | if (ctx == NULL || ctx->encrypt_key == NULL) { 923 | v->not_found = 1; 924 | return NGX_OK; 925 | } 926 | 927 | v->data = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH*2); 928 | if (v->data == NULL) { 929 | return NGX_ERROR; 930 | } 931 | 932 | v->valid = 1; 933 | v->no_cacheable = 1; 934 | v->not_found = 0; 935 | 936 | ngx_hex_dump(v->data, ctx->encrypt_key, MD5_DIGEST_LENGTH); 937 | v->len = MD5_DIGEST_LENGTH*2; 938 | 939 | return NGX_OK; 940 | } 941 | 942 | static ngx_int_t 943 | ngx_http_testcookie_enc_set_variable(ngx_http_request_t *r, 944 | ngx_http_variable_value_t *v, uintptr_t data) 945 | { 946 | ngx_http_testcookie_ctx_t *ctx; 947 | ngx_http_testcookie_conf_t *conf; 948 | 949 | #if OPENSSL_VERSION_NUMBER >= 0x10100003L 950 | EVP_CIPHER_CTX *evp_ctx; 951 | #else 952 | EVP_CIPHER_CTX evp_ctx; 953 | #endif 954 | 955 | u_char *c; 956 | int len; 957 | 958 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_enc_set_variable"); 959 | 960 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_testcookie_access_module); 961 | if (conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 962 | v->not_found = 1; 963 | return NGX_OK; 964 | } 965 | 966 | if (!conf->refresh_encrypt_cookie) { 967 | v->not_found = 1; 968 | return NGX_OK; 969 | } 970 | 971 | v->data = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH*2); 972 | if (v->data == NULL) { 973 | v->not_found = 1; 974 | return NGX_ERROR; 975 | } 976 | 977 | ctx = ngx_http_get_module_ctx(r, ngx_http_testcookie_access_module); 978 | if (ctx == NULL || ctx->encrypt_key == NULL || ctx->encrypt_iv == NULL || ctx->uid_set == NULL) { 979 | v->not_found = 1; 980 | return NGX_OK; 981 | } 982 | 983 | v->valid = 1; 984 | v->no_cacheable = 1; 985 | v->not_found = 0; 986 | 987 | c = (u_char *) ngx_palloc(r->pool, MD5_DIGEST_LENGTH); 988 | if (c == NULL) { 989 | v->not_found = 1; 990 | return NGX_ERROR; 991 | } 992 | 993 | #if OPENSSL_VERSION_NUMBER >= 0x10100003L 994 | evp_ctx = EVP_CIPHER_CTX_new(); 995 | EVP_CipherInit_ex(evp_ctx, EVP_aes_128_cbc(), NULL, NULL, NULL, 1); 996 | 997 | if (!EVP_CipherInit_ex(evp_ctx, NULL, NULL, ctx->encrypt_key, ctx->encrypt_iv, 1)) { 998 | v->not_found = 1; 999 | EVP_CIPHER_CTX_free(evp_ctx); 1000 | return NGX_ERROR; 1001 | } 1002 | 1003 | if (!EVP_CipherUpdate(evp_ctx, c, &len, ctx->uid_set, MD5_DIGEST_LENGTH)) { 1004 | v->not_found = 1; 1005 | EVP_CIPHER_CTX_free(evp_ctx); 1006 | return NGX_ERROR; 1007 | } 1008 | 1009 | EVP_CIPHER_CTX_free(evp_ctx); 1010 | 1011 | #else 1012 | EVP_CIPHER_CTX_init(&evp_ctx); 1013 | if (!EVP_EncryptInit_ex(&evp_ctx, EVP_aes_128_cbc(), NULL, ctx->encrypt_key, ctx->encrypt_iv)) { 1014 | v->not_found = 1; 1015 | EVP_CIPHER_CTX_cleanup(&evp_ctx); 1016 | return NGX_ERROR; 1017 | } 1018 | 1019 | if (!EVP_EncryptUpdate(&evp_ctx, c, &len, ctx->uid_set, MD5_DIGEST_LENGTH)) { 1020 | v->not_found = 1; 1021 | EVP_CIPHER_CTX_cleanup(&evp_ctx); 1022 | return NGX_ERROR; 1023 | } 1024 | /* 1025 | if (!EVP_EncryptFinal_ex(&evp_ctx, c, &len)) { 1026 | v->not_found = 1; 1027 | EVP_CIPHER_CTX_cleanup(&evp_ctx); 1028 | return NGX_ERROR; 1029 | } 1030 | */ 1031 | EVP_CIPHER_CTX_cleanup(&evp_ctx); 1032 | #endif 1033 | 1034 | ngx_hex_dump(v->data, c, MD5_DIGEST_LENGTH); 1035 | 1036 | v->len = MD5_DIGEST_LENGTH*2; 1037 | 1038 | return NGX_OK; 1039 | } 1040 | 1041 | static ngx_int_t 1042 | ngx_http_testcookie_enc_iv_variable(ngx_http_request_t *r, 1043 | ngx_http_variable_value_t *v, uintptr_t data) 1044 | { 1045 | ngx_http_testcookie_ctx_t *ctx; 1046 | ngx_http_testcookie_conf_t *conf; 1047 | 1048 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_enc_iv_variable"); 1049 | 1050 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_testcookie_access_module); 1051 | if (conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 1052 | v->not_found = 1; 1053 | return NGX_OK; 1054 | } 1055 | 1056 | if (!conf->refresh_encrypt_cookie) { 1057 | v->not_found = 1; 1058 | return NGX_OK; 1059 | } 1060 | 1061 | v->data = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH*2); 1062 | if (v->data == NULL) { 1063 | return NGX_ERROR; 1064 | } 1065 | 1066 | ctx = ngx_http_get_module_ctx(r, ngx_http_testcookie_access_module); 1067 | if (ctx == NULL || ctx->encrypt_iv == NULL) { 1068 | v->not_found = 1; 1069 | return NGX_OK; 1070 | } 1071 | 1072 | v->valid = 1; 1073 | v->no_cacheable = 1; 1074 | v->not_found = 0; 1075 | 1076 | ngx_hex_dump(v->data, ctx->encrypt_iv, MD5_DIGEST_LENGTH); 1077 | v->len = MD5_DIGEST_LENGTH*2; 1078 | 1079 | return NGX_OK; 1080 | } 1081 | #endif 1082 | 1083 | static ngx_int_t 1084 | ngx_http_testcookie_timestamp_variable(ngx_http_request_t *r, 1085 | ngx_http_variable_value_t *v, uintptr_t data) 1086 | { 1087 | u_char *p; 1088 | 1089 | p = ngx_pnalloc(r->pool, NGX_INT64_LEN); 1090 | if (p == NULL) { 1091 | return NGX_ERROR; 1092 | } 1093 | 1094 | v->len = ngx_sprintf(p, "%P", ngx_time()) - p; 1095 | v->valid = 1; 1096 | v->no_cacheable = 1; 1097 | v->not_found = 0; 1098 | v->data = p; 1099 | 1100 | return NGX_OK; 1101 | } 1102 | 1103 | 1104 | static ngx_int_t 1105 | ngx_http_testcookie_set_variable(ngx_http_request_t *r, 1106 | ngx_http_variable_value_t *v, uintptr_t data) 1107 | { 1108 | ngx_http_testcookie_ctx_t *ctx; 1109 | ngx_http_testcookie_conf_t *conf; 1110 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_set_variable"); 1111 | 1112 | 1113 | conf = ngx_http_get_module_loc_conf(r, ngx_http_testcookie_access_module); 1114 | if (conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 1115 | v->not_found = 1; 1116 | return NGX_OK; 1117 | } 1118 | 1119 | ctx = ngx_http_get_module_ctx(r, ngx_http_testcookie_access_module); 1120 | if (ctx == NULL || ctx->uid_set == NULL) { 1121 | ctx = ngx_http_testcookie_get_uid(r, conf); 1122 | if (ctx == NULL) { 1123 | v->not_found = 1; 1124 | return NGX_OK; 1125 | } 1126 | } 1127 | 1128 | v->data = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH*2); 1129 | if (v->data == NULL) { 1130 | return NGX_ERROR; 1131 | } 1132 | 1133 | v->valid = 1; 1134 | v->no_cacheable = 1; 1135 | v->not_found = 0; 1136 | 1137 | ngx_hex_dump(v->data, ctx->uid_set, MD5_DIGEST_LENGTH); 1138 | v->len = MD5_DIGEST_LENGTH*2; 1139 | 1140 | return NGX_OK; 1141 | } 1142 | 1143 | static ngx_int_t 1144 | ngx_http_testcookie_ok_variable(ngx_http_request_t *r, 1145 | ngx_http_variable_value_t *v, uintptr_t data) 1146 | { 1147 | ngx_http_testcookie_ctx_t *ctx; 1148 | ngx_http_testcookie_conf_t *conf; 1149 | 1150 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_ok_variable"); 1151 | 1152 | conf = ngx_http_get_module_loc_conf(r, ngx_http_testcookie_access_module); 1153 | if (conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 1154 | v->not_found = 1; 1155 | return NGX_OK; 1156 | } 1157 | 1158 | ctx = ngx_http_get_module_ctx(r, ngx_http_testcookie_access_module); 1159 | if (ctx == NULL) { 1160 | ctx = ngx_http_testcookie_get_uid(r, conf); 1161 | if (ctx == NULL) { 1162 | v->not_found = 1; 1163 | return NGX_OK; 1164 | } 1165 | } 1166 | 1167 | v->len = 1; 1168 | v->data = (u_char *) ngx_pcalloc(r->pool, v->len); 1169 | if (v->data == NULL) { 1170 | return NGX_ERROR; 1171 | } 1172 | 1173 | v->valid = 1; 1174 | v->no_cacheable = 1; 1175 | v->not_found = 0; 1176 | 1177 | if (ctx->ok == 1) { 1178 | ngx_memcpy(v->data, "1", sizeof("1") - 1); 1179 | } else { 1180 | ngx_memcpy(v->data, "0", sizeof("0") - 1); 1181 | } 1182 | 1183 | return NGX_OK; 1184 | } 1185 | 1186 | 1187 | static ngx_int_t 1188 | ngx_http_testcookie_nexturl_variable(ngx_http_request_t *r, 1189 | ngx_http_variable_value_t *v, uintptr_t data) 1190 | { 1191 | ngx_http_testcookie_conf_t *conf; 1192 | u_char *p, *location; 1193 | size_t len; 1194 | uintptr_t escape; 1195 | 1196 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_testcookie_nexturl_variable"); 1197 | 1198 | if (r->headers_out.location == NULL) { 1199 | v->not_found = 1; 1200 | return NGX_OK; 1201 | } 1202 | 1203 | len = r->headers_out.location->value.len; 1204 | location = r->headers_out.location->value.data; 1205 | 1206 | conf = ngx_http_get_module_loc_conf(r, ngx_http_testcookie_access_module); 1207 | if (conf->enable == NGX_HTTP_TESTCOOKIE_OFF) { 1208 | v->not_found = 1; 1209 | return NGX_OK; 1210 | } 1211 | 1212 | escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH); 1213 | 1214 | v->len = len + escape; 1215 | 1216 | v->data = (u_char *) ngx_pcalloc(r->pool, v->len); 1217 | if (v->data == NULL) { 1218 | return NGX_ERROR; 1219 | } 1220 | 1221 | p = v->data; 1222 | 1223 | if (escape == 0) { 1224 | p = ngx_cpymem(p, location, len); 1225 | } else { 1226 | p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH); 1227 | } 1228 | 1229 | v->valid = 1; 1230 | v->no_cacheable = 0; 1231 | v->not_found = 0; 1232 | 1233 | return NGX_OK; 1234 | } 1235 | 1236 | 1237 | static ngx_http_testcookie_ctx_t * 1238 | ngx_http_testcookie_get_uid(ngx_http_request_t *r, ngx_http_testcookie_conf_t *conf) 1239 | { 1240 | #if defined(nginx_version) && nginx_version < 1023000 1241 | ngx_int_t n; 1242 | #else 1243 | ngx_table_elt_t *cookie; 1244 | #endif 1245 | ngx_http_testcookie_conf_t *ucf = conf; 1246 | ngx_http_testcookie_ctx_t *ctx; 1247 | struct sockaddr_in *sin; 1248 | #if (NGX_HAVE_INET6) 1249 | u_char *p; 1250 | in_addr_t addr; 1251 | struct sockaddr_in6 *sin6; 1252 | #endif 1253 | ngx_md5_t md5; 1254 | ngx_str_t value; 1255 | ngx_str_t *check; 1256 | ngx_http_variable_value_t *vv = NULL; 1257 | u_char complex_hash[MD5_DIGEST_LENGTH]; 1258 | u_char complex_hash_hex[MD5_DIGEST_LENGTH*2]; 1259 | 1260 | ctx = ngx_http_get_module_ctx(r, ngx_http_testcookie_access_module); 1261 | if (ctx == NULL) { 1262 | ctx = (ngx_http_testcookie_ctx_t *) ngx_pcalloc(r->pool, sizeof(ngx_http_testcookie_ctx_t)); 1263 | if (ctx == NULL) { 1264 | return NULL; 1265 | } 1266 | ngx_http_set_ctx(r, ctx, ngx_http_testcookie_access_module); 1267 | } 1268 | 1269 | #ifdef REFRESH_COOKIE_ENCRYPTION 1270 | if (conf->refresh_encrypt_cookie == 1) { 1271 | if (conf->refresh_encrypt_cookie_key == NULL) { 1272 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Encryption key is not defined, skipping to prevent errors"); 1273 | return NULL; 1274 | } 1275 | ctx->encrypt_key = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH); 1276 | if (ctx->encrypt_key == NULL) { 1277 | return NULL; 1278 | } 1279 | ctx->encrypt_iv = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH); 1280 | if (ctx->encrypt_iv == NULL) { 1281 | return NULL; 1282 | } 1283 | ngx_memcpy(ctx->encrypt_key, conf->refresh_encrypt_cookie_key, MD5_DIGEST_LENGTH); 1284 | if (conf->refresh_encrypt_cookie_iv == NULL) { 1285 | /* 1286 | SHA1/SHA2 eats too much CPU 1287 | do we _really_ need cryptographically strong random here in our case ? 1288 | */ 1289 | if (RAND_bytes(ctx->encrypt_iv, MD5_DIGEST_LENGTH) != 1) { 1290 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Openssl random IV generation error"); 1291 | return NULL; 1292 | } 1293 | } else { 1294 | ngx_memcpy(ctx->encrypt_iv, conf->refresh_encrypt_cookie_iv, MD5_DIGEST_LENGTH); 1295 | } 1296 | } 1297 | #endif 1298 | 1299 | switch (r->connection->sockaddr->sa_family) { 1300 | case AF_INET: 1301 | 1302 | /* AF_INET only */ 1303 | sin = (struct sockaddr_in *) r->connection->sockaddr; 1304 | 1305 | if (conf->whitelist != NULL) { 1306 | vv = (ngx_http_variable_value_t *) ngx_radix32tree_find(conf->whitelist, ntohl(sin->sin_addr.s_addr)); 1307 | } 1308 | break; 1309 | 1310 | #if (NGX_HAVE_INET6) 1311 | case AF_INET6: 1312 | sin6 = (struct sockaddr_in6 *) r->connection->sockaddr; 1313 | p = sin6->sin6_addr.s6_addr; 1314 | 1315 | if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { 1316 | addr = p[12] << 24; 1317 | addr += p[13] << 16; 1318 | addr += p[14] << 8; 1319 | addr += p[15]; 1320 | if (conf->whitelist != NULL) { 1321 | vv = (ngx_http_variable_value_t *) ngx_radix32tree_find(conf->whitelist, ntohl(addr)); 1322 | } 1323 | } else { 1324 | if (conf->whitelist6 != NULL) { 1325 | vv = (ngx_http_variable_value_t *) ngx_radix128tree_find(conf->whitelist6, p); 1326 | } 1327 | } 1328 | break; 1329 | 1330 | #endif 1331 | } 1332 | 1333 | if (vv != NULL && vv->len > 0) { 1334 | ctx->ok = 1; 1335 | return ctx; 1336 | } 1337 | 1338 | ctx->ok = 0; 1339 | 1340 | if (ucf->session_key.value.len == 0) { 1341 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Session key is not defined, skipping to prevent leaks"); 1342 | return NULL; 1343 | } 1344 | 1345 | if (ngx_http_complex_value(r, &ucf->session_key, &value) != NGX_OK) { 1346 | return ctx; 1347 | } 1348 | 1349 | check = &value; 1350 | 1351 | ngx_md5_init(&md5); 1352 | ngx_md5_update(&md5, check->data, check->len); 1353 | if (conf->secret.len > 0) { 1354 | ngx_md5_update(&md5, conf->secret.data, conf->secret.len); 1355 | } 1356 | ngx_md5_final(complex_hash, &md5); 1357 | 1358 | ctx->uid_set = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH); 1359 | if (ctx->uid_set == NULL) { 1360 | return NULL; 1361 | } 1362 | 1363 | ngx_memcpy(ctx->uid_set, complex_hash, MD5_DIGEST_LENGTH); 1364 | ngx_hex_dump(complex_hash_hex, complex_hash, MD5_DIGEST_LENGTH); 1365 | 1366 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1367 | "input data: \"%V\"", check); 1368 | 1369 | #if defined(nginx_version) && nginx_version < 1023000 1370 | n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &conf->name, 1371 | &ctx->cookie); 1372 | if (n == NGX_DECLINED) { 1373 | return ctx; 1374 | } 1375 | #else 1376 | cookie = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, &conf->name, 1377 | &ctx->cookie); 1378 | if (cookie == NULL) { 1379 | return ctx; 1380 | } 1381 | #endif 1382 | 1383 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1384 | "ctx uid cookie: \"%V\"", &ctx->cookie); 1385 | 1386 | #if defined(nginx_version) && nginx_version >= 1023000 1387 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1388 | "client sent cookies \"%V\"", 1389 | &cookie->value); 1390 | #endif 1391 | 1392 | if (ctx->cookie.len != MD5_DIGEST_LENGTH*2) { 1393 | return ctx; 1394 | } 1395 | 1396 | if (!ngx_ishex(ctx->cookie.data, ctx->cookie.len)) { 1397 | return ctx; 1398 | } 1399 | 1400 | ctx->uid_got = (u_char *) ngx_pcalloc(r->pool, MD5_DIGEST_LENGTH*2); 1401 | if (ctx->uid_got == NULL) { 1402 | return ctx; 1403 | } 1404 | 1405 | ngx_memcpy(ctx->uid_got, ctx->cookie.data, ctx->cookie.len); 1406 | 1407 | if (ngx_memcmp(ctx->uid_got, complex_hash_hex, MD5_DIGEST_LENGTH*2) == 0) { 1408 | ctx->ok = 1; 1409 | } 1410 | 1411 | return ctx; 1412 | } 1413 | 1414 | 1415 | static ngx_int_t 1416 | ngx_http_testcookie_set_uid(ngx_http_request_t *r, ngx_http_testcookie_ctx_t *ctx, 1417 | ngx_http_testcookie_conf_t *conf) 1418 | { 1419 | #define TESTCOOKIE_SECURE_FLAG_ON 1 1420 | #define TESTCOOKIE_SECURE_FLAG_OFF 0 1421 | 1422 | u_char *cookie, *p; 1423 | size_t len; 1424 | ngx_table_elt_t *set_cookie, *p3p; 1425 | ngx_uint_t secure_flag_set = TESTCOOKIE_SECURE_FLAG_ON; 1426 | ngx_str_t secure_flag; 1427 | 1428 | if (conf->redirect_via_refresh && conf->refresh_template.len > 0) { 1429 | return NGX_OK; 1430 | } 1431 | 1432 | len = conf->name.len + MD5_DIGEST_LENGTH*2 + 2; 1433 | 1434 | if (conf->path.len) { 1435 | len += conf->path.len; 1436 | } 1437 | 1438 | if (conf->samesite.len) { 1439 | len += conf->samesite.len; 1440 | } 1441 | 1442 | if (conf->expires) { 1443 | len += sizeof(expires) - 1; 1444 | } 1445 | 1446 | if (conf->domain.len) { 1447 | len += conf->domain.len; 1448 | } 1449 | 1450 | if (conf->httponly_flag) { 1451 | len += sizeof("; HttpOnly") - 1; 1452 | } 1453 | 1454 | if (conf->secure_flag != NULL 1455 | && ngx_http_complex_value(r, conf->secure_flag, &secure_flag) == NGX_OK 1456 | && secure_flag.len 1457 | && (secure_flag.len != 2 || secure_flag.data[1] != 'n' || secure_flag.data[0] != 'o')) 1458 | { 1459 | secure_flag_set = TESTCOOKIE_SECURE_FLAG_OFF; 1460 | } else { 1461 | len += sizeof("; Secure") - 1; 1462 | } 1463 | 1464 | cookie = ngx_palloc(r->pool, len); 1465 | if (cookie == NULL || ctx->uid_set == NULL) { 1466 | return NGX_ERROR; 1467 | } 1468 | 1469 | p = ngx_sprintf(cookie, "%V=", &conf->name); 1470 | p = ngx_hex_dump(p, ctx->uid_set, MD5_DIGEST_LENGTH); 1471 | 1472 | if (conf->expires == NGX_HTTP_TESTCOOKIE_MAX_EXPIRES) { 1473 | p = ngx_cpymem(p, expires, sizeof(expires) - 1); 1474 | } else if (conf->expires) { 1475 | p = ngx_cpymem(p, expires, sizeof("; expires=") - 1); 1476 | p = ngx_http_cookie_time(p, ngx_time() + conf->expires); 1477 | } 1478 | 1479 | p = ngx_copy(p, conf->path.data, conf->path.len); 1480 | p = ngx_copy(p, conf->samesite.data, conf->samesite.len); 1481 | p = ngx_copy(p, conf->domain.data, conf->domain.len); 1482 | 1483 | if (conf->httponly_flag) { 1484 | p = ngx_cpymem(p, (u_char *) "; HttpOnly", sizeof("; HttpOnly") - 1); 1485 | } 1486 | 1487 | if (secure_flag_set == TESTCOOKIE_SECURE_FLAG_ON) { 1488 | p = ngx_cpymem(p, (u_char *) "; Secure", sizeof("; Secure") - 1); 1489 | } 1490 | 1491 | set_cookie = ngx_list_push(&r->headers_out.headers); 1492 | if (set_cookie == NULL) { 1493 | return NGX_ERROR; 1494 | } 1495 | 1496 | set_cookie->hash = 1; 1497 | set_cookie->key.len = sizeof("Set-Cookie") - 1; 1498 | set_cookie->key.data = (u_char *) "Set-Cookie"; 1499 | set_cookie->value.len = p - cookie; 1500 | set_cookie->value.data = cookie; 1501 | 1502 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 1503 | "testcookie cookie uid: \"%V\"", &set_cookie->value); 1504 | 1505 | if (conf->p3p.len == 0) { 1506 | return NGX_OK; 1507 | } 1508 | 1509 | p3p = ngx_list_push(&r->headers_out.headers); 1510 | if (p3p == NULL) { 1511 | return NGX_ERROR; 1512 | } 1513 | 1514 | p3p->hash = 1; 1515 | p3p->key.len = sizeof("P3P") - 1; 1516 | p3p->key.data = (u_char *) "P3P"; 1517 | p3p->value = conf->p3p; 1518 | 1519 | return NGX_OK; 1520 | } 1521 | 1522 | 1523 | static ngx_int_t 1524 | ngx_http_testcookie_add_variables(ngx_conf_t *cf) 1525 | { 1526 | ngx_http_variable_t *var; 1527 | 1528 | 1529 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_got, NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE); 1530 | if (var == NULL) { 1531 | return NGX_ERROR; 1532 | } 1533 | var->get_handler = ngx_http_testcookie_got_variable; 1534 | 1535 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_set, NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE); 1536 | if (var == NULL) { 1537 | return NGX_ERROR; 1538 | } 1539 | var->get_handler = ngx_http_testcookie_set_variable; 1540 | 1541 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_ok, NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE); 1542 | if (var == NULL) { 1543 | return NGX_ERROR; 1544 | } 1545 | var->get_handler = ngx_http_testcookie_ok_variable; 1546 | 1547 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_nexturl, NGX_HTTP_VAR_NOHASH); 1548 | if (var == NULL) { 1549 | return NGX_ERROR; 1550 | } 1551 | var->get_handler = ngx_http_testcookie_nexturl_variable; 1552 | 1553 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_timestamp, NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE); 1554 | if (var == NULL) { 1555 | return NGX_ERROR; 1556 | } 1557 | var->get_handler = ngx_http_testcookie_timestamp_variable; 1558 | 1559 | #ifdef REFRESH_COOKIE_ENCRYPTION 1560 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_enc_key, NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE); 1561 | if (var == NULL) { 1562 | return NGX_ERROR; 1563 | } 1564 | var->get_handler = ngx_http_testcookie_enc_key_variable; 1565 | 1566 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_enc_iv, NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE); 1567 | if (var == NULL) { 1568 | return NGX_ERROR; 1569 | } 1570 | var->get_handler = ngx_http_testcookie_enc_iv_variable; 1571 | 1572 | var = ngx_http_add_variable(cf, &ngx_http_testcookie_enc_set, NGX_HTTP_VAR_NOHASH|NGX_HTTP_VAR_NOCACHEABLE); 1573 | if (var == NULL) { 1574 | return NGX_ERROR; 1575 | } 1576 | var->get_handler = ngx_http_testcookie_enc_set_variable; 1577 | #endif 1578 | 1579 | return NGX_OK; 1580 | } 1581 | 1582 | static void * 1583 | ngx_http_testcookie_create_conf(ngx_conf_t *cf) 1584 | { 1585 | ngx_http_testcookie_conf_t *conf; 1586 | 1587 | conf = (ngx_http_testcookie_conf_t *) ngx_pcalloc(cf->pool, sizeof(ngx_http_testcookie_conf_t)); 1588 | if (conf == NULL) { 1589 | return NGX_CONF_ERROR; 1590 | } 1591 | 1592 | /* 1593 | * set by ngx_pcalloc(): 1594 | * 1595 | * conf->name.len = 0; 1596 | * conf->name.data = NULL; 1597 | * conf->domain.len = 0; 1598 | * conf->domain.data = NULL; 1599 | * conf->path.len = 0; 1600 | * conf->path.data = NULL; 1601 | * conf->samesite.len = 0; 1602 | * conf->samesite.data = NULL; 1603 | * conf->p3p.len = 0; 1604 | * conf->p3p.data = NULL; 1605 | * conf->arg.len = 0; 1606 | * conf->arg.data = NULL; 1607 | * conf->secret.len = 0; 1608 | * conf->secret.data = NULL; 1609 | * conf->session_key.value.data = NULL; 1610 | * conf->session_key.value.len = 0; 1611 | * conf->fallback.len = 0; 1612 | * conf->fallback.data = NULL; 1613 | * conf->refresh_template.len = 0; 1614 | * conf->refresh_template.data = NULL; 1615 | * conf->secure_flag = NULL; 1616 | * conf->pass_var = NULL; 1617 | */ 1618 | 1619 | 1620 | conf->enable = NGX_CONF_UNSET; 1621 | conf->expires = NGX_CONF_UNSET; 1622 | conf->max_attempts = NGX_CONF_UNSET; 1623 | conf->whitelist = NULL; 1624 | #if (NGX_HAVE_INET6) 1625 | conf->whitelist6 = NULL; 1626 | #endif 1627 | conf->fallback_lengths = NULL; 1628 | conf->fallback_values = NULL; 1629 | conf->redirect_to_https = NGX_CONF_UNSET; 1630 | conf->get_only = NGX_CONF_UNSET; 1631 | conf->deny_keepalive = NGX_CONF_UNSET; 1632 | conf->redirect_via_refresh = NGX_CONF_UNSET; 1633 | conf->refresh_template_lengths = NULL; 1634 | conf->refresh_template_values = NULL; 1635 | conf->refresh_status = NGX_CONF_UNSET_UINT; 1636 | conf->internal = NGX_CONF_UNSET; 1637 | conf->httponly_flag = NGX_CONF_UNSET; 1638 | conf->secure_flag = NULL; 1639 | conf->pass_var = NULL; 1640 | conf->port_in_redirect = NGX_CONF_UNSET; 1641 | 1642 | #ifdef REFRESH_COOKIE_ENCRYPTION 1643 | conf->refresh_encrypt_cookie = NGX_CONF_UNSET; 1644 | conf->refresh_encrypt_cookie_key = NULL; 1645 | conf->refresh_encrypt_cookie_iv = NULL; 1646 | #endif 1647 | 1648 | return conf; 1649 | } 1650 | 1651 | 1652 | static char * 1653 | ngx_http_testcookie_merge_conf(ngx_conf_t *cf, void *parent, void *child) 1654 | { 1655 | ngx_http_testcookie_conf_t *prev = parent; 1656 | ngx_http_testcookie_conf_t *conf = child; 1657 | ngx_uint_t n; 1658 | ngx_http_script_compile_t sc; 1659 | 1660 | ngx_conf_merge_uint_value(conf->enable, prev->enable, NGX_HTTP_TESTCOOKIE_OFF); 1661 | 1662 | ngx_conf_merge_str_value(conf->name, prev->name, DEFAULT_COOKIE_NAME); 1663 | ngx_conf_merge_str_value(conf->domain, prev->domain, ""); 1664 | ngx_conf_merge_str_value(conf->path, prev->path, "; path=/"); 1665 | ngx_conf_merge_str_value(conf->p3p, prev->p3p, ""); 1666 | ngx_conf_merge_str_value(conf->samesite, prev->samesite, "; SameSite=None"); 1667 | ngx_conf_merge_str_value(conf->arg, prev->arg, ""); 1668 | ngx_conf_merge_str_value(conf->secret, prev->secret, ""); 1669 | 1670 | ngx_conf_merge_str_value(conf->fallback, prev->fallback, ""); 1671 | ngx_conf_merge_str_value(conf->refresh_template, prev->refresh_template, ""); 1672 | ngx_conf_merge_uint_value(conf->refresh_status, prev->refresh_status, NGX_HTTP_OK); 1673 | 1674 | ngx_conf_merge_value(conf->max_attempts, prev->max_attempts, RFC1945_ATTEMPTS); 1675 | ngx_conf_merge_sec_value(conf->expires, prev->expires, 0); 1676 | 1677 | if (conf->whitelist == NULL) { 1678 | conf->whitelist = prev->whitelist; 1679 | } 1680 | 1681 | #if (NGX_HAVE_INET6) 1682 | if (conf->whitelist6 == NULL) { 1683 | conf->whitelist6 = prev->whitelist6; 1684 | } 1685 | #endif 1686 | 1687 | if (conf->session_key.value.data == NULL) { 1688 | conf->session_key = prev->session_key; 1689 | } 1690 | 1691 | ngx_conf_merge_value(conf->redirect_to_https, prev->redirect_to_https, 0); 1692 | ngx_conf_merge_value(conf->get_only, prev->get_only, 0); 1693 | ngx_conf_merge_value(conf->deny_keepalive, prev->deny_keepalive, 0); 1694 | ngx_conf_merge_value(conf->redirect_via_refresh, prev->redirect_via_refresh, 0); 1695 | ngx_conf_merge_value(conf->internal, prev->internal, 0); 1696 | ngx_conf_merge_value(conf->httponly_flag, prev->httponly_flag, 0); 1697 | ngx_conf_merge_value(conf->port_in_redirect, prev->port_in_redirect, 0); 1698 | 1699 | #ifdef REFRESH_COOKIE_ENCRYPTION 1700 | ngx_conf_merge_value(conf->refresh_encrypt_cookie, prev->refresh_encrypt_cookie, NGX_CONF_UNSET); 1701 | if (conf->refresh_encrypt_cookie_key == NULL) { 1702 | conf->refresh_encrypt_cookie_key = prev->refresh_encrypt_cookie_key; 1703 | } 1704 | if (conf->refresh_encrypt_cookie_iv == NULL) { 1705 | conf->refresh_encrypt_cookie_iv = prev->refresh_encrypt_cookie_iv; 1706 | } 1707 | #endif 1708 | 1709 | /* initializing variables for fallback url */ 1710 | n = ngx_http_script_variables_count(&conf->fallback); 1711 | if (n > 0) { 1712 | ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); 1713 | 1714 | sc.cf = cf; 1715 | sc.source = &conf->fallback; 1716 | sc.lengths = &conf->fallback_lengths; 1717 | sc.values = &conf->fallback_values; 1718 | sc.variables = n; 1719 | sc.complete_lengths = 1; 1720 | sc.complete_values = 1; 1721 | 1722 | if (ngx_http_script_compile(&sc) != NGX_OK) { 1723 | return NGX_CONF_ERROR; 1724 | } 1725 | } 1726 | 1727 | /* initializing variables for refresh template */ 1728 | n = ngx_http_script_variables_count(&conf->refresh_template); 1729 | if (n > 0) { 1730 | ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); 1731 | 1732 | sc.cf = cf; 1733 | sc.source = &conf->refresh_template; 1734 | sc.lengths = &conf->refresh_template_lengths; 1735 | sc.values = &conf->refresh_template_values; 1736 | sc.variables = n; 1737 | sc.complete_lengths = 1; 1738 | sc.complete_values = 1; 1739 | 1740 | if (ngx_http_script_compile(&sc) != NGX_OK) { 1741 | return NGX_CONF_ERROR; 1742 | } 1743 | } 1744 | 1745 | if (conf->secure_flag == NULL) { 1746 | conf->secure_flag = prev->secure_flag; 1747 | } 1748 | 1749 | if (conf->pass_var == NULL) { 1750 | conf->pass_var = prev->pass_var; 1751 | } 1752 | 1753 | return NGX_CONF_OK; 1754 | } 1755 | 1756 | static ngx_int_t 1757 | ngx_http_testcookie_init(ngx_conf_t *cf) 1758 | { 1759 | ngx_http_handler_pt *h; 1760 | ngx_http_core_main_conf_t *cmcf; 1761 | 1762 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 1763 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); 1764 | if (NULL == h) { 1765 | return NGX_ERROR; 1766 | } 1767 | *h = ngx_http_testcookie_handler; 1768 | 1769 | return NGX_OK; 1770 | } 1771 | 1772 | 1773 | static char * 1774 | ngx_http_testcookie_domain(ngx_conf_t *cf, void *post, void *data) 1775 | { 1776 | ngx_str_t *domain = data; 1777 | 1778 | u_char *p, *new; 1779 | 1780 | if (ngx_strcmp(domain->data, "none") == 0) { 1781 | domain->len = 0; 1782 | domain->data = (u_char *) ""; 1783 | 1784 | return NGX_CONF_OK; 1785 | } 1786 | 1787 | new = ngx_palloc(cf->pool, sizeof("; domain=") - 1 + domain->len); 1788 | if (new == NULL) { 1789 | return NGX_CONF_ERROR; 1790 | } 1791 | 1792 | p = ngx_cpymem(new, "; domain=", sizeof("; domain=") - 1); 1793 | ngx_memcpy(p, domain->data, domain->len); 1794 | 1795 | domain->len += sizeof("; domain=") - 1; 1796 | domain->data = new; 1797 | 1798 | return NGX_CONF_OK; 1799 | } 1800 | 1801 | 1802 | static char * 1803 | ngx_http_testcookie_path(ngx_conf_t *cf, void *post, void *data) 1804 | { 1805 | ngx_str_t *path = data; 1806 | 1807 | u_char *p, *new; 1808 | 1809 | new = ngx_palloc(cf->pool, sizeof("; path=") - 1 + path->len); 1810 | if (new == NULL) { 1811 | return NGX_CONF_ERROR; 1812 | } 1813 | 1814 | p = ngx_cpymem(new, "; path=", sizeof("; path=") - 1); 1815 | ngx_memcpy(p, path->data, path->len); 1816 | 1817 | path->len += sizeof("; path=") - 1; 1818 | path->data = new; 1819 | 1820 | return NGX_CONF_OK; 1821 | } 1822 | 1823 | 1824 | static char * 1825 | ngx_http_testcookie_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 1826 | { 1827 | ngx_http_testcookie_conf_t *ucf = conf; 1828 | 1829 | ngx_str_t *value; 1830 | 1831 | if (ucf->expires != NGX_CONF_UNSET) { 1832 | return "is duplicate"; 1833 | } 1834 | 1835 | value = cf->args->elts; 1836 | 1837 | if (ngx_strcmp(value[1].data, "max") == 0) { 1838 | ucf->expires = NGX_HTTP_TESTCOOKIE_MAX_EXPIRES; 1839 | return NGX_CONF_OK; 1840 | } 1841 | 1842 | if (ngx_strcmp(value[1].data, "off") == 0) { 1843 | ucf->expires = 0; 1844 | return NGX_CONF_OK; 1845 | } 1846 | 1847 | ucf->expires = ngx_parse_time(&value[1], 1); 1848 | if (ucf->expires == NGX_ERROR) { 1849 | return "invalid value"; 1850 | } 1851 | 1852 | return NGX_CONF_OK; 1853 | } 1854 | 1855 | 1856 | static char * 1857 | ngx_http_testcookie_p3p(ngx_conf_t *cf, void *post, void *data) 1858 | { 1859 | ngx_str_t *p3p = data; 1860 | 1861 | if (ngx_strcmp(p3p->data, "none") == 0) { 1862 | p3p->len = 0; 1863 | p3p->data = (u_char *) ""; 1864 | } 1865 | 1866 | return NGX_CONF_OK; 1867 | } 1868 | 1869 | 1870 | static char * 1871 | ngx_http_testcookie_samesite(ngx_conf_t *cf, void *post, void *data) 1872 | { 1873 | ngx_str_t *samesite = data; 1874 | 1875 | u_char *p, *new; 1876 | 1877 | new = ngx_palloc(cf->pool, sizeof("; SameSite=") - 1 + samesite->len); 1878 | if (new == NULL) { 1879 | return NGX_CONF_ERROR; 1880 | } 1881 | 1882 | p = ngx_cpymem(new, "; SameSite=", sizeof("; SameSite=") - 1); 1883 | ngx_memcpy(p, samesite->data, samesite->len); 1884 | 1885 | samesite->len += sizeof("; SameSite=") - 1; 1886 | samesite->data = new; 1887 | 1888 | return NGX_CONF_OK; 1889 | } 1890 | 1891 | 1892 | static char * 1893 | ngx_http_testcookie_fallback_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 1894 | { 1895 | ngx_http_script_compile_t sc; 1896 | ngx_str_t *value; 1897 | ngx_uint_t n; 1898 | ngx_http_testcookie_conf_t *ucf = conf; 1899 | 1900 | if (ucf->fallback.data) { 1901 | return "is duplicate"; 1902 | } 1903 | 1904 | value = cf->args->elts; 1905 | 1906 | if (value[1].len == 0 || ngx_strcmp(value[1].data, "none") == 0) { 1907 | ucf->fallback.len = 0; 1908 | ucf->fallback.data = (u_char *) ""; 1909 | return NGX_CONF_OK; 1910 | } 1911 | 1912 | ucf->fallback = value[1]; 1913 | 1914 | n = ngx_http_script_variables_count(&ucf->fallback); 1915 | 1916 | if (n == 0) { 1917 | return NGX_CONF_OK; 1918 | } 1919 | 1920 | ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); 1921 | 1922 | sc.cf = cf; 1923 | sc.source = &ucf->fallback; 1924 | sc.lengths = &ucf->fallback_lengths; 1925 | sc.values = &ucf->fallback_values; 1926 | sc.variables = n; 1927 | sc.complete_lengths = 1; 1928 | sc.complete_values = 1; 1929 | 1930 | if (ngx_http_script_compile(&sc) != NGX_OK) { 1931 | return NGX_CONF_ERROR; 1932 | } 1933 | 1934 | return NGX_CONF_OK; 1935 | } 1936 | 1937 | static char * 1938 | ngx_http_testcookie_session_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 1939 | { 1940 | ngx_str_t *value; 1941 | ngx_http_compile_complex_value_t ccv; 1942 | ngx_http_testcookie_conf_t *ucf = conf; 1943 | 1944 | value = cf->args->elts; 1945 | 1946 | ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 1947 | 1948 | if (value[1].len == 0) { 1949 | return NGX_CONF_ERROR; 1950 | } 1951 | 1952 | ccv.cf = cf; 1953 | ccv.value = &value[1]; 1954 | ccv.complex_value = &ucf->session_key; 1955 | 1956 | if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { 1957 | return NGX_CONF_ERROR; 1958 | } 1959 | 1960 | return NGX_CONF_OK; 1961 | } 1962 | 1963 | static char * 1964 | ngx_http_testcookie_refresh_template_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 1965 | { 1966 | ngx_http_script_compile_t sc; 1967 | ngx_str_t *value; 1968 | ngx_uint_t n; 1969 | ngx_http_testcookie_conf_t *ucf = conf; 1970 | 1971 | if (ucf->refresh_template.data) { 1972 | return "is duplicate"; 1973 | } 1974 | 1975 | value = cf->args->elts; 1976 | 1977 | if (value[1].len == 0 || ngx_strcmp(value[1].data, "none") == 0) { 1978 | ucf->refresh_template.len = 0; 1979 | ucf->refresh_template.data = (u_char *) ""; 1980 | return NGX_CONF_OK; 1981 | } 1982 | 1983 | ucf->refresh_template = value[1]; 1984 | 1985 | n = ngx_http_script_variables_count(&ucf->refresh_template); 1986 | if (n == 0) { 1987 | return NGX_CONF_OK; 1988 | } 1989 | 1990 | ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); 1991 | 1992 | sc.cf = cf; 1993 | sc.source = &ucf->refresh_template; 1994 | sc.lengths = &ucf->refresh_template_lengths; 1995 | sc.values = &ucf->refresh_template_values; 1996 | sc.variables = n; 1997 | sc.complete_lengths = 1; 1998 | sc.complete_values = 1; 1999 | 2000 | if (ngx_http_script_compile(&sc) != NGX_OK) { 2001 | return NGX_CONF_ERROR; 2002 | } 2003 | 2004 | return NGX_CONF_OK; 2005 | } 2006 | 2007 | 2008 | static char * 2009 | ngx_http_testcookie_secret(ngx_conf_t *cf, void *post, void *data) 2010 | { 2011 | ngx_str_t *secret = data; 2012 | 2013 | 2014 | /* 2015 | if (ngx_strcmp(secret->data, "none") == 0) { 2016 | secret->len = 0; 2017 | secret->data = (u_char *) ""; 2018 | } 2019 | */ 2020 | 2021 | #ifdef REFRESH_COOKIE_ENCRYPTION 2022 | if (ngx_strcmp(secret->data, "random") == 0) { 2023 | secret->len = MD5_DIGEST_LENGTH; 2024 | if (RAND_bytes(secret->data, MD5_DIGEST_LENGTH) != 1) { 2025 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2026 | "Openssl random secret generation error\n"); 2027 | return NGX_CONF_ERROR; 2028 | } 2029 | return NGX_CONF_OK; 2030 | } 2031 | #endif 2032 | 2033 | if (secret->len < MD5_DIGEST_LENGTH*2) { 2034 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2035 | "Secret value is too short, should be 32 bytes or more\n"); 2036 | return NGX_CONF_ERROR; 2037 | } 2038 | 2039 | 2040 | return NGX_CONF_OK; 2041 | } 2042 | 2043 | static char * 2044 | ngx_http_testcookie_max_attempts(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 2045 | { 2046 | ngx_http_testcookie_conf_t *ucf = conf; 2047 | 2048 | ngx_int_t n; 2049 | ngx_str_t *value; 2050 | 2051 | value = cf->args->elts; 2052 | 2053 | n = ngx_atoi(value[1].data, value[1].len); 2054 | if (n < 0) { 2055 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2056 | "invalid max number of attempts \"%V\"", &value[1]); 2057 | return NGX_CONF_ERROR; 2058 | } 2059 | 2060 | /* RFC 1945 for HTTP/1.0 allows up to 5 hops */ 2061 | if (n > RFC1945_ATTEMPTS) { 2062 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2063 | "max attempts should must be less than 5"); 2064 | return NGX_CONF_ERROR; 2065 | } 2066 | 2067 | ucf->max_attempts = n; 2068 | 2069 | return NGX_CONF_OK; 2070 | } 2071 | 2072 | #ifdef REFRESH_COOKIE_ENCRYPTION 2073 | static char * 2074 | ngx_http_testcookie_set_encryption_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 2075 | { 2076 | ngx_http_testcookie_conf_t *ucf = conf; 2077 | ngx_str_t *value; 2078 | 2079 | value = cf->args->elts; 2080 | 2081 | ucf->refresh_encrypt_cookie_key = ngx_palloc(cf->pool, MD5_DIGEST_LENGTH); 2082 | 2083 | if (ngx_strcmp(value[1].data, "random") == 0) { 2084 | if (RAND_bytes(ucf->refresh_encrypt_cookie_key, MD5_DIGEST_LENGTH) != 1) { 2085 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2086 | "Openssl random key generation error \"%V\"", &value[0]); 2087 | return NGX_CONF_ERROR; 2088 | } 2089 | return NGX_CONF_OK; 2090 | } 2091 | 2092 | if (value[1].len != MD5_DIGEST_LENGTH*2) { 2093 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2094 | "invalid parameter len, \"%V\" 16 hex bytes required", &value[0]); 2095 | return NGX_CONF_ERROR; 2096 | } 2097 | 2098 | if(ngx_hextobin(ucf->refresh_encrypt_cookie_key, value[1].data, value[1].len) == NULL) { 2099 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2100 | "invalid parameter len, \"%V\" 16 hex bytes required", &value[0]); 2101 | return NGX_CONF_ERROR; 2102 | } 2103 | 2104 | return NGX_CONF_OK; 2105 | } 2106 | 2107 | static char * 2108 | ngx_http_testcookie_set_encryption_iv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 2109 | { 2110 | ngx_http_testcookie_conf_t *ucf = conf; 2111 | ngx_str_t *value; 2112 | 2113 | value = cf->args->elts; 2114 | 2115 | if (ngx_strcmp(value[1].data, "random") == 0) { 2116 | ucf->refresh_encrypt_cookie_iv = NULL; 2117 | return NGX_CONF_OK; 2118 | } 2119 | 2120 | 2121 | ucf->refresh_encrypt_cookie_iv = ngx_palloc(cf->pool, MD5_DIGEST_LENGTH); 2122 | if (ucf->refresh_encrypt_cookie_iv == NULL) { 2123 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2124 | "IV memory allocation error"); 2125 | return NGX_CONF_ERROR; 2126 | } 2127 | 2128 | if (ngx_strcmp(value[1].data, "random2") == 0) { 2129 | if (RAND_bytes(ucf->refresh_encrypt_cookie_iv, MD5_DIGEST_LENGTH) != 1) { 2130 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2131 | "Openssl random IV generation error"); 2132 | return NGX_CONF_ERROR; 2133 | } 2134 | return NGX_CONF_OK; 2135 | } 2136 | 2137 | if (value[1].len != MD5_DIGEST_LENGTH*2) { 2138 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2139 | "invalid parameter len, \"%V\" 16 hex bytes required", &value[0]); 2140 | return NGX_CONF_ERROR; 2141 | } 2142 | 2143 | if(ngx_hextobin(ucf->refresh_encrypt_cookie_iv, value[1].data, value[1].len) == NULL) { 2144 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2145 | "invalid parameter len, \"%V\" 16 hex bytes required", &value[0]); 2146 | return NGX_CONF_ERROR; 2147 | } 2148 | 2149 | return NGX_CONF_OK; 2150 | } 2151 | #endif 2152 | 2153 | 2154 | static char * 2155 | ngx_http_testcookie_whitelist_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 2156 | { 2157 | char *rv; 2158 | ngx_conf_t save; 2159 | ngx_http_testcookie_conf_t *ucf = conf; 2160 | #if (NGX_HAVE_INET6) 2161 | static struct in6_addr zero; 2162 | #endif 2163 | 2164 | ucf->whitelist = ngx_radix_tree_create(cf->pool, -1); 2165 | if (ucf->whitelist == NULL) { 2166 | return NGX_CONF_ERROR; 2167 | } 2168 | 2169 | #if (NGX_HAVE_INET6) 2170 | ucf->whitelist6 = ngx_radix_tree_create(cf->pool, -1); 2171 | if (ucf->whitelist6 == NULL) { 2172 | return NGX_CONF_ERROR; 2173 | } 2174 | #endif 2175 | 2176 | if (ngx_radix32tree_find(ucf->whitelist, 0) != NGX_RADIX_NO_VALUE) { 2177 | return NGX_CONF_ERROR; 2178 | } 2179 | 2180 | if (ngx_radix32tree_insert(ucf->whitelist, 0, 0, 2181 | (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { 2182 | return NGX_CONF_ERROR; 2183 | } 2184 | 2185 | #if (NGX_HAVE_INET6) 2186 | if (ngx_radix128tree_insert(ucf->whitelist6, zero.s6_addr, zero.s6_addr, 2187 | (uintptr_t) &ngx_http_variable_null_value) == NGX_ERROR) { 2188 | return NGX_CONF_ERROR; 2189 | } 2190 | #endif 2191 | 2192 | save = *cf; 2193 | cf->handler = ngx_http_testcookie_whitelist; 2194 | cf->handler_conf = (char *)ucf; 2195 | 2196 | rv = ngx_conf_parse(cf, NULL); 2197 | 2198 | *cf = save; 2199 | 2200 | return rv; 2201 | } 2202 | 2203 | 2204 | static char * 2205 | ngx_http_testcookie_whitelist(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) 2206 | { 2207 | ngx_http_variable_value_t *old; 2208 | ngx_int_t rc; 2209 | ngx_str_t *value, file; 2210 | ngx_uint_t i; 2211 | ngx_cidr_t cidr; 2212 | ngx_http_testcookie_conf_t *ucf = conf; 2213 | 2214 | value = cf->args->elts; 2215 | 2216 | if (ngx_strcmp(value[0].data, "include") == 0) { 2217 | file = value[1]; 2218 | 2219 | if (ngx_conf_full_name(cf->cycle, &file, 1) == NGX_ERROR) { 2220 | return NGX_CONF_ERROR; 2221 | } 2222 | 2223 | ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data); 2224 | 2225 | return ngx_conf_parse(cf, &file); 2226 | } 2227 | 2228 | rc = ngx_ptocidr(&value[0], &cidr); 2229 | 2230 | if (rc == NGX_ERROR) { 2231 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2232 | "invalid parameter \"%V\"", &value[0]); 2233 | return NGX_CONF_ERROR; 2234 | } 2235 | 2236 | if (rc == NGX_DONE) { 2237 | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, 2238 | "low address bits of %V are meaningless", 2239 | &value[0]); 2240 | } 2241 | 2242 | switch (cidr.family) { 2243 | 2244 | #if (NGX_HAVE_INET6) 2245 | case AF_INET6: 2246 | /* fall through */ 2247 | 2248 | for (i = 2; i; i--) { 2249 | rc = ngx_radix128tree_insert(ucf->whitelist6, cidr.u.in6.addr.s6_addr, 2250 | cidr.u.in6.mask.s6_addr, 2251 | (uintptr_t) &ngx_http_variable_true_value); 2252 | 2253 | if (rc == NGX_OK) { 2254 | return NGX_CONF_OK; 2255 | } 2256 | 2257 | if (rc == NGX_ERROR) { 2258 | return NGX_CONF_ERROR; 2259 | } 2260 | 2261 | /* rc == NGX_BUSY */ 2262 | old = (ngx_http_variable_value_t *) 2263 | ngx_radix128tree_find(ucf->whitelist6, 2264 | cidr.u.in6.addr.s6_addr); 2265 | 2266 | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, 2267 | "duplicate \"%V\", old value: \"%v\"", &value[0], old); 2268 | 2269 | rc = ngx_radix128tree_delete(ucf->whitelist6, 2270 | cidr.u.in6.addr.s6_addr, 2271 | cidr.u.in6.mask.s6_addr); 2272 | 2273 | if (rc == NGX_ERROR) { 2274 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); 2275 | return NGX_CONF_ERROR; 2276 | } 2277 | } 2278 | 2279 | #endif 2280 | 2281 | /* fall through */ 2282 | default: /* AF_INET */ 2283 | 2284 | cidr.u.in.addr = ntohl(cidr.u.in.addr); 2285 | cidr.u.in.mask = ntohl(cidr.u.in.mask); 2286 | 2287 | for (i = 2; i; i--) { 2288 | rc = ngx_radix32tree_insert(ucf->whitelist, cidr.u.in.addr, cidr.u.in.mask, 2289 | (uintptr_t) &ngx_http_variable_true_value); 2290 | if (rc == NGX_OK) { 2291 | return NGX_CONF_OK; 2292 | } 2293 | 2294 | if (rc == NGX_ERROR) { 2295 | return NGX_CONF_ERROR; 2296 | } 2297 | 2298 | /* rc == NGX_BUSY */ 2299 | old = (ngx_http_variable_value_t *) 2300 | ngx_radix32tree_find(ucf->whitelist, cidr.u.in.addr & cidr.u.in.mask); 2301 | 2302 | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, 2303 | "duplicate \"%V\", old value: \"%v\"", &value[0], old); 2304 | 2305 | rc = ngx_radix32tree_delete(ucf->whitelist, cidr.u.in.addr, cidr.u.in.mask); 2306 | 2307 | if (rc == NGX_ERROR) { 2308 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid radix tree"); 2309 | return NGX_CONF_ERROR; 2310 | } 2311 | } 2312 | } 2313 | 2314 | return NGX_CONF_ERROR; 2315 | } 2316 | 2317 | int 2318 | ngx_ishex(u_char *src, size_t len) 2319 | { 2320 | u_char c; 2321 | 2322 | if(len % 2) return 0; 2323 | while(len--) { 2324 | c = (*src++); 2325 | if ((c >= 'A' && c <= 'F') || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) continue; 2326 | return 0; 2327 | } 2328 | 2329 | return 1; 2330 | } 2331 | 2332 | u_char * 2333 | ngx_hextobin(u_char *dst, u_char *src, size_t len) 2334 | { 2335 | #define hextobin(c) ((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0') 2336 | size_t i; 2337 | 2338 | if(len % 2) return NULL; 2339 | for(i = 0; i < len/2; i++) { 2340 | *dst++ = hextobin(src[2*i+1]) + hextobin(src[2*i])*16; 2341 | } 2342 | 2343 | return dst; 2344 | } 2345 | 2346 | static ngx_int_t 2347 | ngx_http_testcookie_nocache(ngx_http_request_t *r) 2348 | { 2349 | ngx_table_elt_t *e, *cc; 2350 | #if defined(nginx_version) && nginx_version < 1023000 2351 | ngx_uint_t i; 2352 | ngx_table_elt_t **ccp; 2353 | #endif 2354 | 2355 | e = r->headers_out.expires; 2356 | if (e == NULL) { 2357 | 2358 | e = ngx_list_push(&r->headers_out.headers); 2359 | if (e == NULL) { 2360 | return NGX_ERROR; 2361 | } 2362 | 2363 | r->headers_out.expires = e; 2364 | 2365 | e->hash = 1; 2366 | ngx_str_set(&e->key, "Expires"); 2367 | } 2368 | 2369 | e->value.len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; 2370 | e->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT"; 2371 | 2372 | #if defined(nginx_version) && nginx_version < 1023000 2373 | ccp = r->headers_out.cache_control.elts; 2374 | if (ccp == NULL) { 2375 | if (ngx_array_init(&r->headers_out.cache_control, r->pool, 2376 | 1, sizeof(ngx_table_elt_t *)) 2377 | != NGX_OK) 2378 | { 2379 | return NGX_ERROR; 2380 | } 2381 | 2382 | ccp = ngx_array_push(&r->headers_out.cache_control); 2383 | if (ccp == NULL) { 2384 | return NGX_ERROR; 2385 | } 2386 | 2387 | cc = ngx_list_push(&r->headers_out.headers); 2388 | if (cc == NULL) { 2389 | return NGX_ERROR; 2390 | } 2391 | 2392 | cc->hash = 1; 2393 | ngx_str_set(&cc->key, "Cache-Control"); 2394 | *ccp = cc; 2395 | 2396 | } else { 2397 | for (i = 1; i < r->headers_out.cache_control.nelts; i++) { 2398 | ccp[i]->hash = 0; 2399 | } 2400 | 2401 | cc = ccp[0]; 2402 | } 2403 | #else 2404 | cc = r->headers_out.cache_control; 2405 | if (cc == NULL) { 2406 | cc = ngx_list_push(&r->headers_out.headers); 2407 | if (cc == NULL) { 2408 | return NGX_ERROR; 2409 | } 2410 | 2411 | r->headers_out.cache_control = cc; 2412 | cc->next = NULL; 2413 | 2414 | cc->hash = 1; 2415 | ngx_str_set(&cc->key, "Cache-Control"); 2416 | } else { 2417 | for (cc = cc->next; cc; cc = cc->next) { 2418 | cc->hash = 0; 2419 | } 2420 | 2421 | cc = r->headers_out.cache_control; 2422 | cc->next = NULL; 2423 | } 2424 | #endif 2425 | 2426 | ngx_str_set(&cc->value, "no-cache"); 2427 | 2428 | return NGX_OK; 2429 | } 2430 | 2431 | static char * 2432 | ngx_http_testcookie_refresh_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 2433 | { 2434 | ngx_http_testcookie_conf_t *ucf = conf; 2435 | 2436 | ngx_int_t n; 2437 | ngx_str_t *value; 2438 | 2439 | value = cf->args->elts; 2440 | 2441 | n = ngx_atoi(value[1].data, value[1].len); 2442 | if (n < 100 || n > 599) { 2443 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 2444 | "invalid response code \"%V\"", &value[1]); 2445 | return NGX_CONF_ERROR; 2446 | } 2447 | 2448 | ucf->refresh_status = n; 2449 | 2450 | return NGX_CONF_OK; 2451 | } 2452 | -------------------------------------------------------------------------------- /t/00base.t: -------------------------------------------------------------------------------- 1 | #vi:filetype=perl 2 | 3 | 4 | use lib 'lib'; 5 | use Test::Nginx::Socket; 6 | 7 | plan tests => repeat_each(1) * 3 * blocks() - 7; 8 | no_long_string(); 9 | no_root_location(); 10 | $ENV{TEST_NGINX_SERVROOT} = server_root(); 11 | run_tests(); 12 | 13 | __DATA__ 14 | === TEST 1: Basic GET request, empty attempt counter 15 | --- http_config 16 | testcookie off; 17 | testcookie_name BPC; 18 | testcookie_secret flagmebla; 19 | testcookie_session $remote_addr$http_user_agent; 20 | testcookie_arg tstc; 21 | testcookie_max_attempts 3; 22 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 23 | --- config 24 | testcookie on; 25 | --- request 26 | GET /?a=test HTTP/1.1 27 | --- response_headers 28 | Location: http://localhost:30001/?a=test&tstc=1 29 | Set-Cookie: BPC=4cfb861a6a81106e7660f6eab1d10e0b; path=/ 30 | --- error_code: 307 31 | 32 | 33 | === TEST 2: Basic GET request, attempt counter 1 34 | --- http_config 35 | testcookie off; 36 | testcookie_name BPC; 37 | testcookie_secret flagmebla; 38 | testcookie_session $remote_addr$http_user_agent; 39 | testcookie_arg tstc; 40 | testcookie_max_attempts 3; 41 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 42 | --- config 43 | testcookie on; 44 | --- request 45 | GET /?a=test&tstc=1 HTTP/1.1 46 | --- response_headers 47 | Location: http://localhost:30001/?a=test&tstc=2 48 | Set-Cookie: BPC=4cfb861a6a81106e7660f6eab1d10e0b; path=/ 49 | --- error_code: 307 50 | 51 | 52 | === TEST 3: Basic GET request, attempt counter 3 53 | --- http_config 54 | testcookie off; 55 | testcookie_name BPC; 56 | testcookie_secret flagmebla; 57 | testcookie_session $remote_addr$http_user_agent; 58 | testcookie_arg tstc; 59 | testcookie_max_attempts 3; 60 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 61 | --- config 62 | testcookie on; 63 | --- request 64 | GET /?a=test&tstc=3 HTTP/1.1 65 | --- response_headers 66 | Location: http://google.com/cookies.html?backurl=http://localhost/?a=test&tstc=3 67 | --- error_code: 307 68 | 69 | === TEST 4: Basic GET request, session key user-agent 70 | --- http_config 71 | testcookie off; 72 | testcookie_name BPC; 73 | testcookie_secret flagmebla; 74 | testcookie_session $remote_addr$http_user_agent; 75 | testcookie_arg tstc; 76 | testcookie_max_attempts 3; 77 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 78 | --- config 79 | testcookie on; 80 | --- request 81 | GET /?a=test HTTP/1.1 82 | --- more_headers 83 | User-Agent: Mozilla 84 | --- response_headers 85 | Location: http://localhost:30001/?a=test&tstc=1 86 | Set-Cookie: BPC=30f59f604967b09bb8f1e21caf869cb3; path=/ 87 | --- error_code: 307 88 | 89 | === TEST 5: Basic GET request, META refresh 90 | --- http_config 91 | testcookie off; 92 | testcookie_name BPC; 93 | testcookie_secret flagmebla; 94 | testcookie_session $remote_addr$http_user_agent; 95 | testcookie_arg tstc; 96 | testcookie_max_attempts 3; 97 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 98 | testcookie_redirect_via_refresh on; 99 | --- config 100 | testcookie on; 101 | --- request 102 | GET /?a=test 103 | --- more_headers 104 | User-Agent: Mozilla 105 | --- response_headers 106 | Set-Cookie: BPC=30f59f604967b09bb8f1e21caf869cb3; path=/ 107 | --- error_code: 200 108 | 109 | === TEST 6: Basic GET request, whitelist 110 | --- http_config 111 | testcookie off; 112 | testcookie_name BPC; 113 | testcookie_secret flagmebla; 114 | testcookie_session $remote_addr$http_user_agent; 115 | testcookie_arg tstc; 116 | testcookie_max_attempts 3; 117 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 118 | 119 | testcookie_whitelist { 120 | 8.8.8.8/32; 121 | 127.0.0.1/32; 122 | } 123 | --- config 124 | testcookie on; 125 | --- request 126 | GET /?a=test 127 | --- error_code: 200 128 | --- response_body_like eval 129 | "It works!It works!" 130 | 131 | === TEST 7: Basic GET request, no config arg name 132 | --- http_config 133 | testcookie off; 134 | testcookie_name BPC; 135 | testcookie_secret flagmebla; 136 | testcookie_session $remote_addr$http_user_agent; 137 | testcookie_max_attempts 3; 138 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 139 | --- config 140 | testcookie on; 141 | --- request 142 | GET /?a=test HTTP/1.1 143 | --- more_headers 144 | User-Agent: Mozilla 145 | --- response_headers 146 | Location: http://localhost:30001/?a=test 147 | Set-Cookie: BPC=30f59f604967b09bb8f1e21caf869cb3; path=/ 148 | --- error_code: 307 149 | 150 | === TEST 8: Basic GET request, secret changed 151 | --- http_config 152 | testcookie off; 153 | testcookie_name BPC; 154 | testcookie_secret anothersecret; 155 | testcookie_arg tstc; 156 | testcookie_session $remote_addr$http_user_agent; 157 | testcookie_max_attempts 3; 158 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 159 | --- config 160 | testcookie on; 161 | --- request 162 | GET /?a=test HTTP/1.1 163 | --- more_headers 164 | User-Agent: Mozilla 165 | Location: http://localhost:30001/?a=test&tstc=1 166 | Set-Cookie: BPC=dfdba774f493bc0605000b22132f745a; path=/ 167 | --- error_code: 307 168 | 169 | === TEST 9: Basic GET request, custom refresh template 170 | --- http_config 171 | testcookie off; 172 | testcookie_name BPC; 173 | testcookie_secret flagmebla; 174 | testcookie_session $remote_addr$http_user_agent; 175 | testcookie_arg tstc; 176 | testcookie_max_attempts 3; 177 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 178 | testcookie_redirect_via_refresh on; 179 | testcookie_refresh_template 'hello world!'; 180 | --- config 181 | testcookie on; 182 | --- request 183 | GET /?a=test 184 | --- error_code: 200 185 | --- response_body_like eval 186 | "hello world!" 187 | 188 | === TEST 10: Basic GET request, whitelisting 189 | --- http_config 190 | testcookie off; 191 | testcookie_name BPC; 192 | testcookie_secret flagmebla; 193 | testcookie_session $remote_addr$http_user_agent; 194 | testcookie_arg tstc; 195 | testcookie_max_attempts 3; 196 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 197 | testcookie_redirect_via_refresh on; 198 | testcookie_refresh_template 'hello world!'; 199 | testcookie_whitelist { 200 | 127.0.0.1/32; 201 | } 202 | --- config 203 | testcookie on; 204 | --- request 205 | GET /?a=test 206 | --- error_code: 200 207 | --- response_body_like eval 208 | "It works!It works!" 209 | 210 | === TEST 11: Basic GET request, complex rewrite, test internal redirects 211 | --- http_config 212 | testcookie off; 213 | testcookie_name BPC; 214 | testcookie_secret flagmebla; 215 | testcookie_session $remote_addr$http_user_agent; 216 | testcookie_arg tstc; 217 | testcookie_max_attempts 3; 218 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 219 | testcookie_internal on; 220 | --- config 221 | testcookie on; 222 | rewrite ^/(.*)$ /index.html?$1 last; 223 | --- request 224 | GET /test HTTP/1.1 225 | --- response_headers 226 | Location: http://localhost:30001/index.html?test&tstc=1 227 | Set-Cookie: BPC=4cfb861a6a81106e7660f6eab1d10e0b; path=/ 228 | --- error_code: 307 229 | 230 | === TEST 12: Basic GET request, test user-agent if condition 231 | --- http_config 232 | testcookie off; 233 | testcookie_name BPC; 234 | testcookie_secret flagmebla; 235 | testcookie_session $remote_addr$http_user_agent; 236 | testcookie_arg tstc; 237 | testcookie_max_attempts 3; 238 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 239 | testcookie_internal on; 240 | --- config 241 | location / { 242 | if ($http_user_agent = "test") { 243 | testcookie on; 244 | } 245 | } 246 | --- request 247 | GET /?xxx HTTP/1.1 248 | --- more_headers 249 | User-Agent: test 250 | --- response_headers 251 | Location: http://localhost:30001/?xxx&tstc=1 252 | Set-Cookie: BPC=c6d90bd3e1bab267f80a4ef605cf61d0; path=/ 253 | --- error_code: 307 254 | 255 | === TEST 13: Basic GET request, empty attempt counter, HTTP version 1.0 256 | --- http_config 257 | testcookie off; 258 | testcookie_name BPC; 259 | testcookie_secret flagmebla; 260 | testcookie_session $remote_addr$http_user_agent; 261 | testcookie_arg tstc; 262 | testcookie_max_attempts 3; 263 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 264 | --- config 265 | testcookie on; 266 | --- request 267 | GET /?a=test HTTP/1.0 268 | --- response_headers 269 | Location: http://localhost:30001/?a=test&tstc=1 270 | Set-Cookie: BPC=4cfb861a6a81106e7660f6eab1d10e0b; path=/ 271 | --- error_code: 302 272 | -------------------------------------------------------------------------------- /t/01crypto.t: -------------------------------------------------------------------------------- 1 | #vi:filetype=perl 2 | 3 | 4 | use lib 'lib'; 5 | use Test::Nginx::Socket; 6 | 7 | plan tests => repeat_each(1) * blocks() * 2; 8 | no_long_string(); 9 | no_root_location(); 10 | $ENV{TEST_NGINX_SERVROOT} = server_root(); 11 | run_tests(); 12 | 13 | __DATA__ 14 | === TEST 1: Basic GET request, custom refresh template, encrypted variables, static key 15 | --- http_config 16 | testcookie off; 17 | testcookie_name BPC; 18 | testcookie_secret flagmebla; 19 | testcookie_session $remote_addr$http_user_agent; 20 | testcookie_arg tstc; 21 | testcookie_max_attempts 3; 22 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 23 | testcookie_redirect_via_refresh on; 24 | testcookie_refresh_template '$testcookie_enc_set $testcookie_enc_iv $testcookie_enc_key'; 25 | 26 | testcookie_refresh_encrypt_cookie on; 27 | testcookie_refresh_encrypt_cookie_key deadbeefdeadbeefdeadbeefdeadbeef; 28 | testcookie_refresh_encrypt_cookie_iv deadbeefdeadbeefdeadbeefdeadbeef; 29 | --- config 30 | testcookie on; 31 | --- request 32 | GET /?a=test 33 | --- error_code: 200 34 | --- response_body_like eval 35 | "cc54797809d466c4dc3a40a83c472ddd deadbeefdeadbeefdeadbeefdeadbeef deadbeefdeadbeefdeadbeefdeadbeef" 36 | 37 | === TEST 2: Basic GET request, custom refresh template, encrypted variables, random key 38 | --- http_config 39 | testcookie off; 40 | testcookie_name BPC; 41 | testcookie_secret flagmebla; 42 | testcookie_session $remote_addr$http_user_agent; 43 | testcookie_arg tstc; 44 | testcookie_max_attempts 3; 45 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 46 | testcookie_redirect_via_refresh on; 47 | testcookie_refresh_template '$testcookie_enc_set $testcookie_enc_iv $testcookie_enc_key'; 48 | 49 | testcookie_refresh_encrypt_cookie on; 50 | testcookie_refresh_encrypt_cookie_key random; 51 | testcookie_refresh_encrypt_cookie_iv deadbeefdeadbeefdeadbeefdeadbeef; 52 | --- config 53 | testcookie on; 54 | --- request 55 | GET /?a=test 56 | --- error_code: 200 57 | --- response_body_like eval 58 | '^(\w){32} deadbeefdeadbeefdeadbeefdeadbeef (\w){32}$' 59 | 60 | === TEST 3: Basic GET request, custom refresh template, encrypted variables, random iv 61 | --- http_config 62 | testcookie off; 63 | testcookie_name BPC; 64 | testcookie_secret flagmebla; 65 | testcookie_session $remote_addr$http_user_agent; 66 | testcookie_arg tstc; 67 | testcookie_max_attempts 3; 68 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 69 | testcookie_redirect_via_refresh on; 70 | testcookie_refresh_template '$testcookie_enc_set $testcookie_enc_iv $testcookie_enc_key'; 71 | 72 | testcookie_refresh_encrypt_cookie on; 73 | testcookie_refresh_encrypt_cookie_key deadbeefdeadbeefdeadbeefdeadbeef; 74 | testcookie_refresh_encrypt_cookie_iv random; 75 | --- config 76 | testcookie on; 77 | --- request 78 | GET /?a=test 79 | --- error_code: 200 80 | --- response_body_like eval 81 | '^(\w){32} (\w){32} deadbeefdeadbeefdeadbeefdeadbeef$' 82 | 83 | === TEST 4: Basic GET request, custom refresh template, encrypted variables, random key and iv 84 | --- http_config 85 | testcookie off; 86 | testcookie_name BPC; 87 | testcookie_secret flagmebla; 88 | testcookie_session $remote_addr$http_user_agent; 89 | testcookie_arg tstc; 90 | testcookie_max_attempts 3; 91 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 92 | testcookie_redirect_via_refresh on; 93 | testcookie_refresh_template '$testcookie_enc_set $testcookie_enc_iv $testcookie_enc_key'; 94 | 95 | testcookie_refresh_encrypt_cookie on; 96 | testcookie_refresh_encrypt_cookie_key random; 97 | testcookie_refresh_encrypt_cookie_iv random; 98 | --- config 99 | testcookie on; 100 | --- request 101 | GET /?a=test 102 | --- error_code: 200 103 | --- response_body_like eval 104 | '^(\w){32} (\w){32} (\w){32}$' 105 | 106 | === TEST 5: Basic GET request, custom refresh template, encrypted variables, random key and iv, generated once, after server restart 107 | --- http_config 108 | testcookie off; 109 | testcookie_name BPC; 110 | testcookie_secret flagmebla; 111 | testcookie_session $remote_addr$http_user_agent; 112 | testcookie_arg tstc; 113 | testcookie_max_attempts 3; 114 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 115 | testcookie_redirect_via_refresh on; 116 | testcookie_refresh_template '$testcookie_enc_set $testcookie_enc_iv $testcookie_enc_key'; 117 | 118 | testcookie_refresh_encrypt_cookie on; 119 | testcookie_refresh_encrypt_cookie_key random; 120 | testcookie_refresh_encrypt_cookie_iv random2; 121 | --- config 122 | testcookie on; 123 | --- request 124 | GET /?a=test 125 | --- error_code: 200 126 | --- response_body_like eval 127 | '^(\w){32} (\w){32} (\w){32}$' 128 | 129 | === TEST 6: HEAD request, custom refresh template, encrypted variables, random key and iv, generated once, after server restart 130 | --- http_config 131 | testcookie off; 132 | testcookie_name BPC; 133 | testcookie_secret flagmebla; 134 | testcookie_session $remote_addr$http_user_agent; 135 | testcookie_arg tstc; 136 | testcookie_max_attempts 3; 137 | testcookie_fallback http://google.com/cookies.html?backurl=http://$host$request_uri; 138 | testcookie_redirect_via_refresh on; 139 | testcookie_refresh_template '$testcookie_enc_set $testcookie_enc_iv $testcookie_enc_key'; 140 | 141 | testcookie_refresh_encrypt_cookie on; 142 | testcookie_refresh_encrypt_cookie_key random; 143 | testcookie_refresh_encrypt_cookie_iv random2; 144 | --- config 145 | testcookie on; 146 | --- request 147 | GET /?a=test 148 | --- response_headers 149 | Content-Length: 98 150 | --- error_code: 200 151 | -------------------------------------------------------------------------------- /util/aes.patch: -------------------------------------------------------------------------------- 1 | --- aes.min.js 2012-05-05 22:03:32.000000000 +0400 2 | +++ aes.min.new.js 2012-05-05 22:15:46.000000000 +0400 3 | @@ -767,6 +767,7 @@ 4 | var padCount = 0; 5 | var padByte = -1; 6 | var blockSize = 16; 7 | + if (data.length > 16) { 8 | for (var i = data.length - 1; i >= data.length-1 - blockSize; i--) { 9 | if (data[i] <= blockSize) { 10 | if (padByte == -1) 11 | @@ -783,6 +784,7 @@ 12 | } 13 | if (padCount > 0) 14 | data.splice(data.length - padCount, padCount); 15 | + } 16 | } 17 | /* 18 | * END MODE OF OPERATION SECTION 19 | -------------------------------------------------------------------------------- /util/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | export TEST_NGINX_PORT=30001 5 | prove -v -r t 6 | -------------------------------------------------------------------------------- /util/valgrind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | export TEST_NGINX_PORT=30001 5 | export TEST_NGINX_USE_VALGRIND=1 6 | prove -v -r t 7 | --------------------------------------------------------------------------------