├── 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 |
--------------------------------------------------------------------------------