/src/mod_auth_openidc/src/http.c
Line | Count | Source |
1 | | /* |
2 | | * Licensed to the Apache Software Foundation (ASF) under one |
3 | | * or more contributor license agreements. See the NOTICE file |
4 | | * distributed with this work for additional information |
5 | | * regarding copyright ownership. The ASF licenses this file |
6 | | * to you under the Apache License, Version 2.0 (the |
7 | | * "License"); you may not use this file except in compliance |
8 | | * with the License. You may obtain a copy of the License at |
9 | | * |
10 | | * http://www.apache.org/licenses/LICENSE-2.0 |
11 | | * |
12 | | * Unless required by applicable law or agreed to in writing, |
13 | | * software distributed under the License is distributed on an |
14 | | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
15 | | * KIND, either express or implied. See the License for the |
16 | | * specific language governing permissions and limitations |
17 | | * under the License. |
18 | | */ |
19 | | |
20 | | /*************************************************************************** |
21 | | * Copyright (C) 2017-2026 ZmartZone Holding BV |
22 | | * Copyright (C) 2013-2017 Ping Identity Corporation |
23 | | * All rights reserved. |
24 | | * |
25 | | * DISCLAIMER OF WARRANTIES: |
26 | | * |
27 | | * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT |
28 | | * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, |
29 | | * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, |
30 | | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY |
31 | | * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE |
32 | | * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET |
33 | | * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE |
34 | | * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR |
35 | | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
36 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF |
37 | | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
38 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
39 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
40 | | * |
41 | | * @Author: Hans Zandbelt - hans.zandbelt@openidc.com |
42 | | */ |
43 | | |
44 | | #include <stddef.h> |
45 | | #ifndef WIN32 |
46 | | #include <unistd.h> |
47 | | #endif |
48 | | |
49 | | #include <apr_strings.h> |
50 | | |
51 | | #include <curl/curl.h> |
52 | | #include <openssl/opensslv.h> |
53 | | |
54 | | #include "cfg/dir.h" |
55 | | #include "const.h" |
56 | | #include "http.h" |
57 | | #include "http_int.h" |
58 | | #include "metrics.h" |
59 | | #include "proto/proto.h" |
60 | | #include "util/util.h" |
61 | | #include "util/util_cfg.h" |
62 | | |
63 | | /* the reusable-handle pool lives at the bottom of this file */ |
64 | | static CURL *oidc_http_curl_acquire(void); |
65 | | static void oidc_http_curl_release(CURL *curl, apr_byte_t reuse); |
66 | | |
67 | | /* |
68 | | * URL-encode a string: percent-encode every byte outside of the RFC 3986 unreserved set, |
69 | | * matching curl_easy_escape which was used here before (but required a throwaway CURL handle |
70 | | * per call since handles must not be shared between threads) |
71 | | */ |
72 | 6.53k | char *oidc_http_url_encode(const request_rec *r, const char *str) { |
73 | 6.53k | static const char hex[] = "0123456789ABCDEF"; |
74 | 6.53k | char *rv = NULL; |
75 | 6.53k | char *p = NULL; |
76 | 6.53k | size_t len = 0; |
77 | | |
78 | 6.53k | if (str == NULL) |
79 | 0 | return ""; |
80 | | |
81 | 6.53k | len = _oidc_strlen(str); |
82 | | |
83 | | /* worst case each input byte expands to a 3-byte %XX sequence */ |
84 | 6.53k | rv = apr_palloc(r->pool, len * 3 + 1); |
85 | 6.53k | p = rv; |
86 | 44.7M | for (size_t i = 0; i < len; i++) { |
87 | 44.7M | const unsigned char c = (unsigned char)str[i]; |
88 | 44.7M | if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9')) || |
89 | 33.3M | (c == '-') || (c == '.') || (c == '_') || (c == '~')) { |
90 | 33.3M | *p++ = (char)c; |
91 | 33.3M | } else { |
92 | 11.3M | *p++ = '%'; |
93 | 11.3M | *p++ = hex[c >> 4]; |
94 | 11.3M | *p++ = hex[c & 0x0f]; |
95 | 11.3M | } |
96 | 44.7M | } |
97 | 6.53k | *p = '\0'; |
98 | | |
99 | 6.53k | return rv; |
100 | 6.53k | } |
101 | | |
102 | 11.3M | static int oidc_http_url_decode_hex_digit(const char c) { |
103 | 11.3M | if ((c >= '0') && (c <= '9')) |
104 | 9.15M | return c - '0'; |
105 | 2.24M | if ((c >= 'A') && (c <= 'F')) |
106 | 2.23M | return c - 'A' + 10; |
107 | 8.32k | if ((c >= 'a') && (c <= 'f')) |
108 | 1.82k | return c - 'a' + 10; |
109 | 6.49k | return -1; |
110 | 8.32k | } |
111 | | |
112 | | /* |
113 | | * URL-decode a string: form-decode "+" to space and percent-decode %XX sequences, copying |
114 | | * malformed/truncated %-sequences through literally, matching the curl_easy_unescape based |
115 | | * implementation that was used here before |
116 | | */ |
117 | 6.53k | char *oidc_http_url_decode(const request_rec *r, const char *str) { |
118 | 6.53k | char *rv = NULL; |
119 | 6.53k | char *p = NULL; |
120 | 6.53k | size_t i = 0; |
121 | 6.53k | size_t len = 0; |
122 | | |
123 | 6.53k | if (str == NULL) |
124 | 0 | return ""; |
125 | | |
126 | 6.53k | len = _oidc_strlen(str); |
127 | | |
128 | 6.53k | rv = apr_palloc(r->pool, len + 1); |
129 | 6.53k | p = rv; |
130 | 45.5M | while (i < len) { |
131 | 45.5M | const char c = str[i]; |
132 | 45.5M | if (c == '+') { |
133 | 5.48k | *p++ = ' '; |
134 | 5.48k | i++; |
135 | 45.5M | } else if (c == '%') { |
136 | | /* str[i + 1] is within bounds (at worst the NUL terminator); str[i + 2] is only |
137 | | * read when str[i + 1] is a hex digit and thus not the terminator */ |
138 | 5.70M | const int hi = oidc_http_url_decode_hex_digit(str[i + 1]); |
139 | 5.70M | const int lo = (hi >= 0) ? oidc_http_url_decode_hex_digit(str[i + 2]) : -1; |
140 | 5.70M | if (lo >= 0) { |
141 | 5.69M | *p++ = (char)((hi << 4) | lo); |
142 | 5.69M | i += 3; |
143 | 5.69M | } else { |
144 | 6.49k | *p++ = c; |
145 | 6.49k | i++; |
146 | 6.49k | } |
147 | 39.8M | } else { |
148 | 39.8M | *p++ = c; |
149 | 39.8M | i++; |
150 | 39.8M | } |
151 | 45.5M | } |
152 | 6.53k | *p = '\0'; |
153 | | |
154 | 6.53k | return rv; |
155 | 6.53k | } |
156 | | |
157 | | /* |
158 | | * obtain a HTTP request header value |
159 | | */ |
160 | 6.53k | static const char *oidc_http_hdr_in_get(const request_rec *r, const char *name) { |
161 | 6.53k | const char *value = apr_table_get(r->headers_in, name); |
162 | 6.53k | if (value) |
163 | 6.53k | oidc_debug(r, "%s=%s", name, value); |
164 | 6.53k | return value; |
165 | 6.53k | } |
166 | | |
167 | | /* |
168 | | * obtain the left-most element of a multi-valued HTTP header value |
169 | | */ |
170 | 0 | static const char *oidc_http_hdr_in_get_left_most_only(const request_rec *r, const char *name, const char *separator) { |
171 | 0 | char *last = NULL; |
172 | 0 | const char *value = oidc_http_hdr_in_get(r, name); |
173 | 0 | if (value) |
174 | 0 | return apr_strtok(apr_pstrdup(r->pool, value), separator, &last); |
175 | 0 | return NULL; |
176 | 0 | } |
177 | | |
178 | | /* |
179 | | * check if a multi-valued HTTP request header contains a specified value |
180 | | */ |
181 | | static apr_byte_t oidc_http_hdr_in_contains(const request_rec *r, const char *name, const char *separator, |
182 | 3.26k | const char postfix_separator, const char *needle) { |
183 | 3.26k | char *ctx = NULL; |
184 | 3.26k | const char *elem = NULL; |
185 | 3.26k | const char *value = oidc_http_hdr_in_get(r, name); |
186 | 3.26k | apr_byte_t rc = FALSE; |
187 | 3.26k | if (value) { |
188 | 3.26k | elem = apr_strtok(apr_pstrdup(r->pool, value), separator, &ctx); |
189 | 1.64M | while (elem != NULL) { |
190 | 1.64M | while (*elem == OIDC_CHAR_SPACE) |
191 | 289 | elem++; |
192 | 1.64M | if ((_oidc_strncmp(elem, needle, _oidc_strlen(needle)) == 0) && |
193 | 1.33k | ((elem[_oidc_strlen(needle)] == '\0') || |
194 | 1.32k | (elem[_oidc_strlen(needle)] == postfix_separator))) { |
195 | 2 | rc = TRUE; |
196 | 2 | break; |
197 | 2 | } |
198 | 1.64M | elem = apr_strtok(NULL, separator, &ctx); |
199 | 1.64M | } |
200 | 3.26k | } |
201 | 3.26k | return rc; |
202 | 3.26k | } |
203 | | |
204 | | /* |
205 | | * copy a header value with CR/LF replaced, to prevent header injection |
206 | | */ |
207 | 0 | static char *oidc_http_hdr_value_sanitize(const request_rec *r, const char *value) { |
208 | 0 | char *s_value = apr_pstrdup(r->pool, value); |
209 | 0 | char *p = NULL; |
210 | 0 | while ((p = strpbrk(s_value, "\r\n"))) |
211 | 0 | *p = OIDC_CHAR_SPACE; |
212 | 0 | return s_value; |
213 | 0 | } |
214 | | |
215 | | /* |
216 | | * set a HTTP response header; table could be headers_out or err_headers_out |
217 | | */ |
218 | 0 | static void oidc_http_hdr_table_set(const request_rec *r, apr_table_t *table, const char *name, const char *value) { |
219 | |
|
220 | 0 | if (value != NULL) { |
221 | |
|
222 | 0 | char *s_value = oidc_http_hdr_value_sanitize(r, value); |
223 | |
|
224 | 0 | oidc_debug(r, "%s: %s", name, s_value); |
225 | 0 | apr_table_set(table, name, s_value); |
226 | |
|
227 | 0 | } else { |
228 | |
|
229 | 0 | oidc_debug(r, "unset %s", name); |
230 | 0 | apr_table_unset(table, name); |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | /* |
235 | | * append a (sanitized) header to a table without the scan for an existing entry that apr_table_set() |
236 | | * does, for callers that collect many headers and merge them into the request's table at once with |
237 | | * apr_table_overlap(); the name and the value must live as long as the table |
238 | | */ |
239 | 0 | void oidc_http_hdr_table_add(const request_rec *r, apr_table_t *table, const char *name, const char *value) { |
240 | 0 | char *s_value = oidc_http_hdr_value_sanitize(r, value); |
241 | 0 | oidc_debug(r, "%s: %s", name, s_value); |
242 | 0 | apr_table_addn(table, name, s_value); |
243 | 0 | } |
244 | | |
245 | | /* |
246 | | * set a (regular) HTTP response header |
247 | | */ |
248 | 0 | static void oidc_http_hdr_out_set(const request_rec *r, const char *name, const char *value) { |
249 | 0 | oidc_http_hdr_table_set(r, r->headers_out, name, value); |
250 | 0 | } |
251 | | |
252 | | /* |
253 | | * obtain a HTTP response header value |
254 | | */ |
255 | 0 | static const char *oidc_http_hdr_out_get(const request_rec *r, const char *name) { |
256 | 0 | return apr_table_get(r->headers_out, name); |
257 | 0 | } |
258 | | |
259 | | /* |
260 | | * set a HTTP response header on all responses, included errors and redirects |
261 | | */ |
262 | 0 | void oidc_http_hdr_err_out_add(const request_rec *r, const char *name, const char *value) { |
263 | 0 | oidc_debug(r, "%s: %s", name, value); |
264 | 0 | apr_table_add(r->err_headers_out, name, value); |
265 | 0 | } |
266 | | |
267 | | /* |
268 | | * mark a response that may carry tokens or other sensitive material as non-cacheable, |
269 | | * on all responses including errors and redirects |
270 | | */ |
271 | 0 | void oidc_http_set_no_cache_headers(const request_rec *r) { |
272 | 0 | oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL, "no-cache, no-store"); |
273 | 0 | oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache"); |
274 | 0 | } |
275 | | |
276 | | /* |
277 | | * set an HTTP request header |
278 | | */ |
279 | 0 | void oidc_http_hdr_in_set(const request_rec *r, const char *name, const char *value) { |
280 | 0 | oidc_http_hdr_table_set(r, r->headers_in, name, value); |
281 | 0 | } |
282 | | |
283 | | /* |
284 | | * obtain a HTTP request cookie value |
285 | | */ |
286 | 0 | const char *oidc_http_hdr_in_cookie_get(const request_rec *r) { |
287 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_COOKIE); |
288 | 0 | } |
289 | | |
290 | | /* |
291 | | * set a HTTP request cookie value |
292 | | */ |
293 | 0 | void oidc_http_hdr_in_cookie_set(const request_rec *r, const char *value) { |
294 | 0 | oidc_http_hdr_in_set(r, OIDC_HTTP_HDR_COOKIE, value); |
295 | 0 | } |
296 | | |
297 | | /* |
298 | | * obtain the User-Agent header value from the HTTP request |
299 | | */ |
300 | 3.26k | const char *oidc_http_hdr_in_user_agent_get(const request_rec *r) { |
301 | 3.26k | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_USER_AGENT); |
302 | 3.26k | } |
303 | | |
304 | | /* |
305 | | * obtain the X-Forwarded-For header value from the HTTP request |
306 | | */ |
307 | 0 | const char *oidc_http_hdr_in_x_forwarded_for_get(const request_rec *r) { |
308 | 0 | return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR, OIDC_STR_COMMA OIDC_STR_SPACE); |
309 | 0 | } |
310 | | |
311 | | /* |
312 | | * obtain the Content-Type header value from the HTTP request |
313 | | */ |
314 | 0 | const char *oidc_http_hdr_in_content_type_get(const request_rec *r) { |
315 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_TYPE); |
316 | 0 | } |
317 | | |
318 | | /* |
319 | | * obtain the Content-Length header value from the HTTP request |
320 | | */ |
321 | 0 | const char *oidc_http_hdr_in_content_length_get(const request_rec *r) { |
322 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_LENGTH); |
323 | 0 | } |
324 | | |
325 | | /* |
326 | | * obtain the X-Requested-With header value from the HTTP request |
327 | | */ |
328 | 0 | const char *oidc_http_hdr_in_x_requested_with_get(const request_rec *r) { |
329 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_X_REQUESTED_WITH); |
330 | 0 | } |
331 | | |
332 | | /* |
333 | | * obtain the Sec-Fetch-Mode header value from the HTTP request |
334 | | */ |
335 | 0 | const char *oidc_http_hdr_in_sec_fetch_mode_get(const request_rec *r) { |
336 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_SEC_FETCH_MODE); |
337 | 0 | } |
338 | | |
339 | | /* |
340 | | * obtain the Sec-Fetch-Dest header value from the HTTP request |
341 | | */ |
342 | 0 | const char *oidc_http_hdr_in_sec_fetch_dest_get(const request_rec *r) { |
343 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_SEC_FETCH_DEST); |
344 | 0 | } |
345 | | |
346 | | /* |
347 | | * obtain the Accept header value from the HTTP request |
348 | | */ |
349 | 0 | const char *oidc_http_hdr_in_accept_get(const request_rec *r) { |
350 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_ACCEPT); |
351 | 0 | } |
352 | | |
353 | | /* |
354 | | * check if a specified value exists in the HTTP Accept request header |
355 | | */ |
356 | 3.26k | apr_byte_t oidc_http_hdr_in_accept_contains(const request_rec *r, const char *needle) { |
357 | 3.26k | return oidc_http_hdr_in_contains(r, OIDC_HTTP_HDR_ACCEPT, OIDC_STR_COMMA, OIDC_CHAR_SEMI_COLON, needle); |
358 | 3.26k | } |
359 | | |
360 | | /* |
361 | | * obtain the Authorization header value from the HTTP request |
362 | | */ |
363 | 0 | const char *oidc_http_hdr_in_authorization_get(const request_rec *r) { |
364 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_AUTHORIZATION); |
365 | 0 | } |
366 | | |
367 | | /* |
368 | | * obtain the X-Forwarded-Proto header value from the HTTP request |
369 | | */ |
370 | 0 | const char *oidc_http_hdr_in_x_forwarded_proto_get(const request_rec *r) { |
371 | 0 | return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA OIDC_STR_SPACE); |
372 | 0 | } |
373 | | |
374 | | /* |
375 | | * obtain the X-Forwarded-Port header value from the HTTP request |
376 | | */ |
377 | 0 | const char *oidc_http_hdr_in_x_forwarded_port_get(const request_rec *r) { |
378 | 0 | return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA OIDC_STR_SPACE); |
379 | 0 | } |
380 | | |
381 | | /* |
382 | | * obtain the X-Forwarded-Host header value from the HTTP request |
383 | | */ |
384 | 0 | const char *oidc_http_hdr_in_x_forwarded_host_get(const request_rec *r) { |
385 | 0 | return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA OIDC_STR_SPACE); |
386 | 0 | } |
387 | | |
388 | | /* |
389 | | * obtain the Forwarded header value from the HTTP request |
390 | | */ |
391 | 0 | const char *oidc_http_hdr_in_forwarded_get(const request_rec *r) { |
392 | 0 | return oidc_http_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_FORWARDED, OIDC_STR_COMMA); |
393 | 0 | } |
394 | | |
395 | | /* |
396 | | * obtain the Host header value from the HTTP request |
397 | | */ |
398 | 0 | const char *oidc_http_hdr_in_host_get(const request_rec *r) { |
399 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_HOST); |
400 | 0 | } |
401 | | |
402 | | /* |
403 | | * obtain the traceparent header value from the HTTP request |
404 | | */ |
405 | 0 | const char *oidc_http_hdr_in_traceparent_get(const request_rec *r) { |
406 | 0 | return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_TRACE_PARENT); |
407 | 0 | } |
408 | | |
409 | | /* |
410 | | * set the Location header value in the HTTP response |
411 | | */ |
412 | 0 | void oidc_http_hdr_out_location_set(const request_rec *r, const char *value) { |
413 | 0 | oidc_http_hdr_out_set(r, OIDC_HTTP_HDR_LOCATION, value); |
414 | 0 | } |
415 | | |
416 | | /* |
417 | | * obtain the Location header value from the HTTP response |
418 | | */ |
419 | 0 | const char *oidc_http_hdr_out_location_get(const request_rec *r) { |
420 | 0 | return oidc_http_hdr_out_get(r, OIDC_HTTP_HDR_LOCATION); |
421 | 0 | } |
422 | | |
423 | | /* |
424 | | * obtain a specified value from the Forwarded header in the HTTP request |
425 | | */ |
426 | 0 | const char *oidc_http_hdr_forwarded_get(const request_rec *r, const char *elem) { |
427 | 0 | const char *value = NULL; |
428 | 0 | char *ptr = NULL; |
429 | 0 | const char *item = apr_psprintf(r->pool, "%s=", elem); |
430 | 0 | value = oidc_http_hdr_in_forwarded_get(r); |
431 | 0 | value = oidc_util_strcasestr(value, item); |
432 | 0 | if (value) { |
433 | 0 | value += _oidc_strlen(item); |
434 | 0 | ptr = _oidc_strstr(value, ";"); |
435 | 0 | if (ptr) |
436 | 0 | *ptr = '\0'; |
437 | 0 | ptr = _oidc_strstr(value, " "); |
438 | 0 | if (ptr) |
439 | 0 | *ptr = '\0'; |
440 | 0 | } |
441 | 0 | return value ? apr_pstrdup(r->pool, value) : NULL; |
442 | 0 | } |
443 | | |
444 | | /* |
445 | | * normalize a string for use as an HTTP Header Name. Any invalid |
446 | | * characters (per http://tools.ietf.org/html/rfc2616#section-4.2 and |
447 | | * http://tools.ietf.org/html/rfc2616#section-2.2) are replaced with |
448 | | * a dash ('-') character. |
449 | | */ |
450 | 3.26k | char *oidc_http_hdr_normalize_name(const request_rec *r, const char *str) { |
451 | | /* token = 1*<any CHAR except CTLs or separators> |
452 | | * CTL = <any US-ASCII control character |
453 | | * (octets 0 - 31) and DEL (127)> |
454 | | * separators = "(" | ")" | "<" | ">" | "@" |
455 | | * | "," | ";" | ":" | "\" | <"> |
456 | | * | "/" | "[" | "]" | "?" | "=" |
457 | | * | "{" | "}" | SP | HT */ |
458 | 3.26k | const char *separators = "()<>@,;:\\\"/[]?={} \t"; |
459 | | |
460 | 3.26k | char *ns = apr_pstrdup(r->pool, str); |
461 | 3.26k | const size_t len = _oidc_strlen(ns); |
462 | 22.7M | for (size_t i = 0; i < len; i++) { |
463 | 22.7M | if (ns[i] < 32 || ns[i] == 127) |
464 | 174k | ns[i] = '-'; |
465 | 22.5M | else if (strchr(separators, ns[i]) != NULL) |
466 | 2.62M | ns[i] = '-'; |
467 | 22.7M | } |
468 | 3.26k | return ns; |
469 | 3.26k | } |
470 | | |
471 | | /* |
472 | | * callback for CURL to write bytes that come back from an HTTP call |
473 | | */ |
474 | 0 | size_t oidc_http_response_data(void *contents, size_t size, size_t nmemb, void *userp) { |
475 | 0 | size_t realsize = size * nmemb; |
476 | 0 | oidc_curl_resp_data_ctx_t *mem = (oidc_curl_resp_data_ctx_t *)userp; |
477 | | |
478 | | /* check if we don't run over the maximum buffer/memory size for HTTP responses */ |
479 | 0 | if (mem->size + realsize > OIDC_CURL_RESPONSE_DATA_SIZE_MAX) { |
480 | 0 | oidc_error( |
481 | 0 | mem->r, |
482 | 0 | "HTTP response larger than maximum allowed size: current size=%ld, additional size=%ld, max=%d", |
483 | 0 | (long)mem->size, (long)realsize, OIDC_CURL_RESPONSE_DATA_SIZE_MAX); |
484 | 0 | return 0; |
485 | 0 | } |
486 | | |
487 | | /* allocate the new buffer for the current + new response bytes */ |
488 | 0 | char *newptr = apr_palloc(mem->r->pool, mem->size + realsize + 1); |
489 | 0 | if (newptr == NULL) { |
490 | 0 | oidc_error(mem->r, "memory allocation for new buffer of %ld bytes failed", |
491 | 0 | (long)(mem->size + realsize + 1)); |
492 | 0 | return 0; |
493 | 0 | } |
494 | | |
495 | | /* copy over the data from current memory plus the cURL buffer */ |
496 | 0 | _oidc_memcpy(newptr, mem->memory, mem->size); |
497 | 0 | _oidc_memcpy(&(newptr[mem->size]), contents, realsize); |
498 | 0 | mem->size += realsize; |
499 | 0 | mem->memory = newptr; |
500 | 0 | mem->memory[mem->size] = 0; |
501 | |
|
502 | 0 | return realsize; |
503 | 0 | } |
504 | | |
505 | | /* |
506 | | * callback for CURL to write response headers that come back from an HTTP call |
507 | | */ |
508 | 0 | size_t oidc_http_response_header(const char *buffer, size_t size, size_t nitems, void *userdata) { |
509 | | /* received header is nitems * size long in 'buffer' NOT ZERO TERMINATED */ |
510 | 0 | oidc_curl_resp_hdr_ctx_t *ctx = (oidc_curl_resp_hdr_ctx_t *)userdata; |
511 | 0 | const char *hdr = NULL; |
512 | 0 | char *value = NULL; |
513 | 0 | char *h_name = NULL; |
514 | 0 | apr_ssize_t h_len = 0; |
515 | 0 | int i = 0; |
516 | | |
517 | | /* see if there is a header to search for */ |
518 | 0 | if ((ctx->hdrs == NULL) || (apr_hash_count(ctx->hdrs) == 0)) |
519 | 0 | goto end; |
520 | | |
521 | | /* make hdr a \0 terminated string for easier processing */ |
522 | 0 | hdr = apr_pstrndup(ctx->r->pool, buffer, nitems * size); |
523 | | |
524 | | /* search for a name: value pair */ |
525 | 0 | value = _oidc_strstr(hdr, OIDC_STR_COLON); |
526 | 0 | if (value == NULL) |
527 | 0 | goto end; |
528 | | |
529 | | /* split the header name and value */ |
530 | 0 | *value = '\0'; |
531 | | |
532 | | /* see if there's any header value characters at all after the colon */ |
533 | 0 | if (_oidc_strlen(hdr) < nitems * size) { |
534 | 0 | value++; |
535 | | /* skip spaces after the colon */ |
536 | 0 | while (*value == ' ') |
537 | 0 | value++; |
538 | | /* remove trailing /r/n */ |
539 | 0 | i = (int)_oidc_strlen(value) - 1; |
540 | 0 | while ((i >= 0) && ((value[i] == '\r') || (value[i] == '\n'))) |
541 | 0 | value[i--] = '\0'; |
542 | 0 | } |
543 | | |
544 | | /* check if the caller is interested in the value of the current response header */ |
545 | 0 | for (apr_hash_index_t *hi = apr_hash_first(NULL, ctx->hdrs); hi; hi = apr_hash_next(hi)) { |
546 | 0 | apr_hash_this(hi, (const void **)&h_name, &h_len, NULL); |
547 | 0 | if (_oidc_strnatcasecmp(hdr, h_name) == 0) { |
548 | 0 | oidc_debug(ctx->r, "returning response header: %s: %s", h_name, value); |
549 | 0 | apr_hash_set(ctx->hdrs, h_name, APR_HASH_KEY_STRING, apr_pstrdup(ctx->r->pool, value)); |
550 | 0 | break; |
551 | 0 | } |
552 | 0 | } |
553 | |
|
554 | 0 | end: |
555 | |
|
556 | 0 | return nitems * size; |
557 | 0 | } |
558 | | |
559 | | /* context structure for encoding parameters */ |
560 | | typedef struct oidc_http_encode_t { |
561 | | request_rec *r; |
562 | | /* the encoded "key=value" fragments, joined once with "&" by the callers below; accumulating |
563 | | * them into a growing string per parameter instead made the work quadratic in the number of |
564 | | * parameters, which a request-controlled parameter count can turn into an OOM (OSS-Fuzz 551746349) */ |
565 | | apr_array_header_t *elems; |
566 | | } oidc_http_encode_t; |
567 | | |
568 | | /* |
569 | | * names of protocol parameters that carry secrets or tokens and must be redacted |
570 | | * before a request URL/body is written to the debug log; the single source of truth |
571 | | * for both the per-parameter check and the body redaction below |
572 | | */ |
573 | | static const char *_oidc_http_sensitive_params[] = {OIDC_PROTO_CLIENT_SECRET, OIDC_PROTO_CLIENT_ASSERTION, |
574 | | OIDC_PROTO_CODE, OIDC_PROTO_CODE_VERIFIER, OIDC_PROTO_REFRESH_TOKEN, |
575 | | OIDC_PROTO_ACCESS_TOKEN, |
576 | | /* inbound: the front-channel authorization response and the |
577 | | * back-channel logout request carry these */ |
578 | | OIDC_PROTO_ID_TOKEN, OIDC_PROTO_LOGOUT_TOKEN, NULL}; |
579 | | |
580 | 3.26k | apr_byte_t oidc_http_param_is_sensitive(const char *key) { |
581 | 29.3k | for (int i = 0; _oidc_http_sensitive_params[i] != NULL; i++) |
582 | 26.1k | if (_oidc_strcmp(key, _oidc_http_sensitive_params[i]) == 0) |
583 | 5 | return TRUE; |
584 | 3.26k | return FALSE; |
585 | 3.26k | } |
586 | | |
587 | | /* |
588 | | * TRUE when the name_len-byte parameter name is one of _oidc_http_sensitive_params |
589 | | */ |
590 | 773 | static apr_byte_t oidc_http_param_name_is_sensitive(const char *name, size_t name_len) { |
591 | 5.41k | for (int i = 0; _oidc_http_sensitive_params[i] != NULL; i++) |
592 | 4.90k | if ((_oidc_strlen(_oidc_http_sensitive_params[i]) == name_len) && |
593 | 284 | (_oidc_strncmp(name, _oidc_http_sensitive_params[i], name_len) == 0)) |
594 | 257 | return TRUE; |
595 | 516 | return FALSE; |
596 | 773 | } |
597 | | |
598 | | /* |
599 | | * best-effort redaction of the well-known sensitive parameters listed in |
600 | | * _oidc_http_sensitive_params inside a URL-form-encoded request body, for debug-log |
601 | | * purposes; JSON request bodies do not carry these parameters in this codebase and |
602 | | * are therefore left untouched |
603 | | */ |
604 | 3.26k | const char *oidc_http_redact_body_for_log(request_rec *r, const char *data) { |
605 | 3.26k | size_t n_params = 1; |
606 | | |
607 | 3.26k | if (data == NULL) |
608 | 0 | return NULL; |
609 | | /* OIDCDebugMaskSecrets Off, or no request pool to build the redacted copy in */ |
610 | 3.26k | if ((r == NULL) || (oidc_util_log_mask_secrets(r) == FALSE)) |
611 | 0 | return data; |
612 | 3.26k | apr_pool_t *pool = r->pool; |
613 | | |
614 | | /* |
615 | | * one pass over the "&"-separated parameters into a single buffer: rewriting the whole |
616 | | * string with an apr_psprintf() per redacted value made the work quadratic in the number |
617 | | * of sensitive parameters, which a request-sized body turned into an OOM (OSS-Fuzz |
618 | | * 554483423); a redacted value only ever grows to the 3 bytes of "***", so the result fits |
619 | | * in the input length plus 3 bytes per parameter |
620 | | */ |
621 | 22.7M | for (const char *c = data; *c != '\0'; c++) |
622 | 22.7M | if (*c == OIDC_CHAR_AMP) |
623 | 2.83M | n_params++; |
624 | 3.26k | char *result = apr_palloc(pool, _oidc_strlen(data) + (3 * n_params) + 1); |
625 | 3.26k | char *out = result; |
626 | 3.26k | const char *param = data; |
627 | | |
628 | 2.84M | for (;;) { |
629 | 2.84M | const char *param_end = strchr(param, OIDC_CHAR_AMP); |
630 | 2.84M | const size_t param_len = (param_end != NULL) ? (size_t)(param_end - param) : _oidc_strlen(param); |
631 | 2.84M | const char *eq = memchr(param, OIDC_CHAR_EQUAL, param_len); |
632 | | /* only a "<name>=" at the parameter boundary is redacted, so that e.g. "xcode=" is not taken for |
633 | | * "code=" */ |
634 | 2.84M | const apr_byte_t redact = |
635 | 2.84M | ((eq != NULL) && (oidc_http_param_name_is_sensitive(param, (size_t)(eq - param)) == TRUE)) ? TRUE |
636 | 2.84M | : FALSE; |
637 | | /* everything up to and including the "<name>=" is kept */ |
638 | 2.84M | const size_t keep = (redact == TRUE) ? (size_t)(eq - param) + 1 : param_len; |
639 | | |
640 | 2.84M | _oidc_memcpy(out, param, keep); |
641 | 2.84M | out += keep; |
642 | 2.84M | if (redact == TRUE) { |
643 | 257 | _oidc_memcpy(out, "***", 3); |
644 | 257 | out += 3; |
645 | 257 | } |
646 | 2.84M | if (param_end == NULL) |
647 | 3.26k | break; |
648 | 2.83M | *out = OIDC_CHAR_AMP; |
649 | 2.83M | out++; |
650 | 2.83M | param = param_end + 1; |
651 | 2.83M | } |
652 | 3.26k | *out = '\0'; |
653 | | |
654 | 3.26k | return result; |
655 | 3.26k | } |
656 | | |
657 | | /* Sensitive response members differ from the request parameters redacted above. */ |
658 | | static const char *_oidc_http_sensitive_json_members[] = {OIDC_PROTO_ACCESS_TOKEN, OIDC_PROTO_REFRESH_TOKEN, |
659 | | OIDC_PROTO_ID_TOKEN, OIDC_PROTO_CLIENT_SECRET, |
660 | | "registration_access_token", NULL}; |
661 | | |
662 | | /* |
663 | | * Redact quoted JSON members without parsing, so malformed or non-JSON responses remain |
664 | | * loggable and otherwise unchanged. This scanner assumes a sensitive value has no escaped quote: |
665 | | * the next quote terminates the value. |
666 | | * The input is copied through into a single buffer with each value replaced as it is found: |
667 | | * rewriting the whole string with an apr_psprintf() per member made the work quadratic in the |
668 | | * number of sensitive members, which a response-sized body turned into an OOM (OSS-Fuzz |
669 | | * 554483423). A match consumes at least the member name, the colon and the opening quote and |
670 | | * adds at most the 3 bytes of "***", which bounds the size of the result. |
671 | | */ |
672 | 16.3k | static const char *oidc_http_redact_json_member(apr_pool_t *pool, const char *data, const char *needle) { |
673 | 16.3k | const apr_size_t needle_len = _oidc_strlen(needle); |
674 | 16.3k | const apr_size_t data_len = _oidc_strlen(data); |
675 | 16.3k | char *result = apr_palloc(pool, data_len + (3 * ((data_len / (needle_len + 2)) + 1)) + 1); |
676 | 16.3k | char *out = result; |
677 | | /* the input up to here has been copied to the output */ |
678 | 16.3k | const char *copied = data; |
679 | | /* the scan cursor */ |
680 | 16.3k | const char *pos = data; |
681 | 16.3k | size_t len = 0; |
682 | | |
683 | 18.4k | for (;;) { |
684 | 18.4k | const char *match = _oidc_strstr(pos, needle); |
685 | 18.4k | if (match == NULL) |
686 | 16.3k | break; |
687 | | /* step over the member name and expect ": " then a quoted value; anything |
688 | | * else (a non-string value, a member name appearing inside a value) is |
689 | | * skipped rather than guessed at */ |
690 | 2.09k | const char *p = match + needle_len; |
691 | 10.7k | while ((*p == ' ') || (*p == '\t')) |
692 | 8.62k | p++; |
693 | 2.09k | if (*p != OIDC_CHAR_COLON) { |
694 | 607 | pos = match + needle_len; |
695 | 607 | continue; |
696 | 607 | } |
697 | 1.48k | p++; |
698 | 2.08k | while ((*p == ' ') || (*p == '\t')) |
699 | 592 | p++; |
700 | 1.48k | if (*p != OIDC_CHAR_DQUOTE) { |
701 | 1.11k | pos = match + needle_len; |
702 | 1.11k | continue; |
703 | 1.11k | } |
704 | 369 | p++; |
705 | | /* copy everything up to and including the opening quote, then the mask */ |
706 | 369 | len = (size_t)(p - copied); |
707 | 369 | _oidc_memcpy(out, copied, len); |
708 | 369 | out += len; |
709 | 369 | _oidc_memcpy(out, "***", 3); |
710 | 369 | out += 3; |
711 | | /* If the closing quote is missing, mask to the end rather than leak a partial token; the |
712 | | * closing quote itself is copied with the next chunk and the scan resumes after the mask. */ |
713 | 369 | const char *value_end = strchr(p, OIDC_CHAR_DQUOTE); |
714 | 369 | copied = (value_end != NULL) ? value_end : p + _oidc_strlen(p); |
715 | 369 | pos = copied; |
716 | 369 | } |
717 | | |
718 | 16.3k | len = _oidc_strlen(copied); |
719 | 16.3k | _oidc_memcpy(out, copied, len); |
720 | 16.3k | out += len; |
721 | 16.3k | *out = '\0'; |
722 | | |
723 | 16.3k | return result; |
724 | 16.3k | } |
725 | | |
726 | 3.26k | const char *oidc_http_redact_json_for_log(request_rec *r, const char *data) { |
727 | 3.26k | const char *result = NULL; |
728 | | |
729 | 3.26k | if (data == NULL) |
730 | 0 | return NULL; |
731 | | /* OIDCDebugMaskSecrets Off, or no request pool to build the redacted copy in */ |
732 | 3.26k | if ((r == NULL) || (oidc_util_log_mask_secrets(r) == FALSE)) |
733 | 0 | return data; |
734 | 3.26k | apr_pool_t *pool = r->pool; |
735 | | |
736 | 3.26k | result = data; |
737 | 19.6k | for (int i = 0; _oidc_http_sensitive_json_members[i] != NULL; i++) |
738 | 16.3k | result = oidc_http_redact_json_member( |
739 | 16.3k | pool, result, apr_pstrcat(pool, "\"", _oidc_http_sensitive_json_members[i], "\"", NULL)); |
740 | | |
741 | 3.26k | return result; |
742 | 3.26k | } |
743 | | |
744 | | /* |
745 | | * add a url-form-encoded name/value pair |
746 | | */ |
747 | 0 | static int oidc_http_add_form_url_encoded_param(void *rec, const char *key, const char *value) { |
748 | 0 | oidc_http_encode_t *ctx = (oidc_http_encode_t *)rec; |
749 | 0 | APR_ARRAY_PUSH(ctx->elems, const char *) = |
750 | 0 | apr_psprintf(ctx->r->pool, "%s=%s", oidc_http_url_encode(ctx->r, key), oidc_http_url_encode(ctx->r, value)); |
751 | 0 | return 1; |
752 | 0 | } |
753 | | |
754 | | /* |
755 | | * add a name/value pair to a form-encoded string used only for debug logging, redacting |
756 | | * the value when the parameter name is a known secret/token |
757 | | */ |
758 | 0 | static int oidc_http_add_form_encoded_param_for_log(void *rec, const char *key, const char *value) { |
759 | 0 | oidc_http_encode_t *ctx = (oidc_http_encode_t *)rec; |
760 | 0 | const char *safe_value = value ? value : ""; |
761 | 0 | const char *v = oidc_http_param_is_sensitive(key) ? "***" : safe_value; |
762 | 0 | APR_ARRAY_PUSH(ctx->elems, const char *) = apr_psprintf(ctx->r->pool, "%s=%s", key, v); |
763 | 0 | return 1; |
764 | 0 | } |
765 | | |
766 | | /* |
767 | | * run one of the per-parameter encoders above over a table and join the collected "key=value" |
768 | | * fragments into a single "&"-separated string in one allocation; returns NULL when there are none |
769 | | */ |
770 | | static char *oidc_http_encode_params(request_rec *r, const apr_table_t *params, |
771 | 0 | int (*encoder)(void *, const char *, const char *)) { |
772 | 0 | oidc_http_encode_t ctx = {r, apr_array_make(r->pool, apr_table_elts(params)->nelts, sizeof(const char *))}; |
773 | 0 | apr_table_do(encoder, &ctx, params, NULL); |
774 | 0 | return (ctx.elems->nelts > 0) ? apr_array_pstrcat(r->pool, ctx.elems, OIDC_CHAR_AMP) : NULL; |
775 | 0 | } |
776 | | |
777 | | /* |
778 | | * construct a URL with query parameters |
779 | | */ |
780 | 0 | char *oidc_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params) { |
781 | 0 | char *result = NULL; |
782 | 0 | if (url == NULL) { |
783 | 0 | oidc_error(r, "URL is NULL"); |
784 | 0 | return NULL; |
785 | 0 | } |
786 | 0 | if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) { |
787 | 0 | const char *encoded_params = oidc_http_encode_params(r, params, oidc_http_add_form_url_encoded_param); |
788 | 0 | const char *sep = NULL; |
789 | 0 | if (encoded_params) |
790 | 0 | sep = strchr(url, OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY; |
791 | 0 | result = apr_psprintf(r->pool, "%s%s%s", url, sep ? sep : "", encoded_params ? encoded_params : ""); |
792 | |
|
793 | 0 | const char *log_params = oidc_http_encode_params(r, params, oidc_http_add_form_encoded_param_for_log); |
794 | 0 | oidc_debug(r, "url=%s%s%s", url, sep ? sep : "", log_params ? log_params : ""); |
795 | 0 | } else { |
796 | 0 | result = apr_pstrdup(r->pool, url); |
797 | 0 | oidc_debug(r, "url=%s", result); |
798 | 0 | } |
799 | 0 | return result; |
800 | 0 | } |
801 | | |
802 | | /* |
803 | | * construct form-encoded POST data |
804 | | */ |
805 | 0 | char *oidc_http_form_encoded_data(request_rec *r, const apr_table_t *params) { |
806 | 0 | char *data = NULL; |
807 | 0 | if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) { |
808 | 0 | data = oidc_http_encode_params(r, params, oidc_http_add_form_url_encoded_param); |
809 | |
|
810 | 0 | const char *log_params = oidc_http_encode_params(r, params, oidc_http_add_form_encoded_param_for_log); |
811 | 0 | oidc_debug(r, "data=%s", log_params ? log_params : "(null)"); |
812 | 0 | } else { |
813 | 0 | oidc_debug(r, "data=(null)"); |
814 | 0 | } |
815 | 0 | return data; |
816 | 0 | } |
817 | | |
818 | | /* |
819 | | * call curl_easy_setopt with error checking and reporting |
820 | | */ |
821 | | #define OIDC_HTTP_CURL_SETOPT_PARMS(r, curl, code, option, ...) \ |
822 | 0 | code = curl_easy_setopt(curl, option, __VA_ARGS__); \ |
823 | 0 | if (code != CURLE_OK) \ |
824 | 0 | oidc_error(r, "curl_easy_setopt(%s) failed with: %s", #option, curl_easy_strerror(code)) |
825 | | |
826 | 0 | #define OIDC_HTTP_CURL_SETOPT(...) OIDC_HTTP_CURL_SETOPT_PARMS(r, curl, code, __VA_ARGS__) |
827 | | |
828 | | /* |
829 | | * set libcurl SSL options |
830 | | */ |
831 | | |
832 | | #define OIDC_HTTP_CURL_SETOPT_SSL(option, value) \ |
833 | 0 | if (_oidc_strstr(env_var_value, #value) != NULL) { \ |
834 | 0 | oidc_debug(r, "curl_easy_setopt(%s): %s (%ld)", #option, #value, (long)value); \ |
835 | 0 | OIDC_HTTP_CURL_SETOPT(option, (long)value); \ |
836 | 0 | } |
837 | | |
838 | 0 | static void oidc_http_set_curl_ssl_options(request_rec *r, CURL *curl) { |
839 | | // NB: the variable names r, curl, code and env_var_value are used in the OIDC_HTTP_CURL_SETOPT_SSL macro |
840 | 0 | const char *env_var_value = NULL; |
841 | 0 | CURLcode code = CURLE_OK; |
842 | 0 | if (r->subprocess_env != NULL) |
843 | 0 | env_var_value = apr_table_get(r->subprocess_env, OIDC_CURLOPT_SSL_OPTIONS_ENV_VAR_NAME); |
844 | 0 | if (env_var_value == NULL) |
845 | 0 | return; |
846 | 0 | oidc_debug(r, "SSL options environment variable %s=%s found", OIDC_CURLOPT_SSL_OPTIONS_ENV_VAR_NAME, |
847 | 0 | env_var_value); |
848 | 0 | #if LIBCURL_VERSION_NUM >= 0x071900 |
849 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST); |
850 | 0 | #endif |
851 | 0 | #if LIBCURL_VERSION_NUM >= 0x072c00 |
852 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE) |
853 | 0 | #endif |
854 | 0 | #if LIBCURL_VERSION_NUM >= 0x074400 |
855 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_PARTIALCHAIN) |
856 | 0 | #endif |
857 | 0 | #if LIBCURL_VERSION_NUM >= 0x074600 |
858 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT) |
859 | 0 | #endif |
860 | 0 | #if LIBCURL_VERSION_NUM >= 0x074700 |
861 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA) |
862 | 0 | #endif |
863 | 0 | #if LIBCURL_VERSION_NUM >= 0x072200 |
864 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0) |
865 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1) |
866 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2) |
867 | 0 | #endif |
868 | 0 | #if LIBCURL_VERSION_NUM >= 0x073400 |
869 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3) |
870 | 0 | #endif |
871 | 0 | #if LIBCURL_VERSION_NUM >= 0x073600 |
872 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_0) |
873 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_1) |
874 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_2) |
875 | 0 | OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_3) |
876 | 0 | #endif |
877 | 0 | } |
878 | | |
879 | | /* |
880 | | * construct our User-Agent header for outgoing requests |
881 | | */ |
882 | 0 | const char *oidc_http_user_agent(request_rec *r) { |
883 | 0 | const char *s_useragent = apr_table_get(r->subprocess_env, OIDC_USER_AGENT_ENV_VAR); |
884 | 0 | if (s_useragent == NULL) { |
885 | 0 | s_useragent = apr_psprintf(r->pool, "[%s:%u:%lu] %s", r->server->server_hostname, |
886 | 0 | r->connection->local_addr->port, (unsigned long)getpid(), NAMEVERSION); |
887 | 0 | s_useragent = apr_psprintf(r->pool, "%s libcurl-%s %s", s_useragent, LIBCURL_VERSION, |
888 | 0 | oidc_util_openssl_version(r->pool)); |
889 | 0 | } |
890 | 0 | return s_useragent; |
891 | 0 | } |
892 | | |
893 | | /* |
894 | | * construct our local address/interface for outgoing requests |
895 | | */ |
896 | 0 | const char *oidc_http_interface(const request_rec *r) { |
897 | 0 | return apr_table_get(r->subprocess_env, OIDC_CURL_INTERFACE_ENV_VAR); |
898 | 0 | } |
899 | | |
900 | | /* |
901 | | * configure the CA bundle for SSL server certificate verification, falling |
902 | | * back to the system curl-ca-bundle.crt on Windows when no explicit bundle |
903 | | * was configured |
904 | | */ |
905 | 0 | static void oidc_http_request_setup_ca_bundle(request_rec *r, CURL *curl, const oidc_cfg_t *c) { |
906 | | // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro |
907 | 0 | CURLcode code = CURLE_OK; |
908 | |
|
909 | 0 | if (oidc_cfg_ca_bundle_path_get(c) != NULL) { |
910 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_CAINFO, oidc_cfg_ca_bundle_path_get(c)); |
911 | 0 | return; |
912 | 0 | } |
913 | |
|
914 | | #ifdef WIN32 |
915 | | DWORD buflen; |
916 | | char *ptr = NULL; |
917 | | char *retval = (char *)malloc(sizeof(TCHAR) * (MAX_PATH + 1)); |
918 | | retval[0] = '\0'; |
919 | | buflen = SearchPath(NULL, "curl-ca-bundle.crt", NULL, MAX_PATH + 1, retval, &ptr); |
920 | | if (buflen > 0) { |
921 | | OIDC_HTTP_CURL_SETOPT(CURLOPT_CAINFO, retval); |
922 | | } else { |
923 | | oidc_warn(r, "no curl-ca-bundle.crt file found in path"); |
924 | | } |
925 | | free(retval); |
926 | | #endif |
927 | 0 | } |
928 | | |
929 | | /* |
930 | | * configure the curl handle to use the optional outgoing proxy, including |
931 | | * its credentials and authentication type when supplied |
932 | | */ |
933 | | static void oidc_http_request_setup_proxy(request_rec *r, CURL *curl, |
934 | 0 | const oidc_http_outgoing_proxy_t *outgoing_proxy) { |
935 | | // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro |
936 | 0 | CURLcode code = CURLE_OK; |
937 | |
|
938 | 0 | if (outgoing_proxy->host_port == NULL) |
939 | 0 | return; |
940 | | |
941 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXY, outgoing_proxy->host_port); |
942 | 0 | if (outgoing_proxy->username_password) { |
943 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXYUSERPWD, outgoing_proxy->username_password); |
944 | 0 | } |
945 | 0 | if (outgoing_proxy->auth_type != (unsigned long)OIDC_CONFIG_POS_INT_UNSET) { |
946 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXYAUTH, outgoing_proxy->auth_type); |
947 | 0 | } |
948 | 0 | } |
949 | | |
950 | | /* |
951 | | * build the list of custom request headers (authorization, content-type, |
952 | | * traceparent, DPoP) to pass on to curl |
953 | | */ |
954 | | struct curl_slist *oidc_http_request_build_header_list(request_rec *r, const oidc_cfg_t *c, const char *content_type, |
955 | 0 | const char *access_token, const char *dpop) { |
956 | 0 | struct curl_slist *h_list = NULL; |
957 | | |
958 | | /* see if we need to add token in the Bearer/DPoP Authorization header */ |
959 | 0 | if (access_token != NULL) |
960 | 0 | h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s %s", OIDC_HTTP_HDR_AUTHORIZATION, |
961 | 0 | dpop ? "DPoP" : "Bearer", access_token)); |
962 | |
|
963 | 0 | if (content_type != NULL) |
964 | 0 | h_list = curl_slist_append(h_list, |
965 | 0 | apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_CONTENT_TYPE, content_type)); |
966 | |
|
967 | 0 | const char *traceparent = oidc_http_hdr_in_traceparent_get(r); |
968 | 0 | if (traceparent && oidc_cfg_trace_parent_get(c) != OIDC_TRACE_PARENT_OFF) { |
969 | 0 | oidc_debug(r, "propagating traceparent header: %s", traceparent); |
970 | 0 | h_list = |
971 | 0 | curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_TRACE_PARENT, traceparent)); |
972 | 0 | } |
973 | |
|
974 | 0 | if (dpop != NULL) { |
975 | 0 | oidc_debug(r, "appending DPoP header (len=%d)", (int)_oidc_strlen(dpop)); |
976 | 0 | h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_DPOP, dpop)); |
977 | 0 | } |
978 | |
|
979 | 0 | return h_list; |
980 | 0 | } |
981 | | |
982 | | /* |
983 | | * pass cookies from the incoming request through to the curl handle by |
984 | | * concatenating the configured cookies into a single Cookie header value |
985 | | */ |
986 | 0 | static void oidc_http_request_pass_cookies(request_rec *r, CURL *curl, const apr_array_header_t *pass_cookies) { |
987 | | // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro |
988 | 0 | CURLcode code = CURLE_OK; |
989 | 0 | char *cookie_string = NULL; |
990 | |
|
991 | 0 | if (pass_cookies == NULL) |
992 | 0 | return; |
993 | | |
994 | 0 | for (int i = 0; i < pass_cookies->nelts; i++) { |
995 | 0 | const char *cookie_name = APR_ARRAY_IDX(pass_cookies, i, const char *); |
996 | 0 | char *cookie_value = oidc_http_get_cookie(r, cookie_name); |
997 | 0 | if (cookie_value == NULL) |
998 | 0 | continue; |
999 | 0 | cookie_string = (cookie_string == NULL) |
1000 | 0 | ? apr_psprintf(r->pool, "%s=%s", cookie_name, cookie_value) |
1001 | 0 | : apr_psprintf(r->pool, "%s; %s=%s", cookie_string, cookie_name, cookie_value); |
1002 | 0 | } |
1003 | |
|
1004 | 0 | if (cookie_string == NULL) |
1005 | 0 | return; |
1006 | | |
1007 | 0 | oidc_debug(r, "passing browser cookies on backend call: %s", cookie_string); |
1008 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_COOKIE, cookie_string); |
1009 | 0 | } |
1010 | | |
1011 | | /* |
1012 | | * execute the curl request honoring the configured retry policy; retries |
1013 | | * are skipped on a request/transfer timeout and short-circuited on success |
1014 | | */ |
1015 | | static apr_byte_t oidc_http_request_perform_with_retries(request_rec *r, const oidc_cfg_t *c, CURL *curl, |
1016 | | const char *url, const char *curl_err, |
1017 | 0 | const oidc_http_timeout_t *http_timeout) { |
1018 | 0 | CURLcode res = CURLE_OK; |
1019 | |
|
1020 | 0 | for (int i = 0; i <= http_timeout->retries; i++) { |
1021 | 0 | res = curl_easy_perform(curl); |
1022 | 0 | if (res == CURLE_OK) |
1023 | 0 | return TRUE; |
1024 | 0 | if (res == CURLE_OPERATION_TIMEDOUT) { |
1025 | | /* in case of a request/transfer timeout (which includes the connect timeout) we'll not retry */ |
1026 | 0 | oidc_error(r, "curl_easy_perform failed with a timeout for %s: [%s]; won't retry", url, |
1027 | 0 | curl_err[0] ? curl_err : "<n/a>"); |
1028 | 0 | OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_CONNECT_ERROR, |
1029 | 0 | curl_err[0] ? curl_err : "timeout"); |
1030 | 0 | return FALSE; |
1031 | 0 | } |
1032 | 0 | oidc_error(r, "curl_easy_perform(%d/%d) failed for %s with: [%s]", i + 1, http_timeout->retries + 1, |
1033 | 0 | url, curl_err[0] ? curl_err : "<n/a>"); |
1034 | 0 | OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_CONNECT_ERROR, curl_err[0] ? curl_err : "undefined"); |
1035 | | /* in case of a connectivity/network glitch we'll back off before retrying */ |
1036 | 0 | if (i < http_timeout->retries) |
1037 | 0 | apr_sleep(apr_time_from_msec(http_timeout->retry_interval)); |
1038 | 0 | } |
1039 | | |
1040 | 0 | return FALSE; |
1041 | 0 | } |
1042 | | |
1043 | | /* |
1044 | | * execute a HTTP (GET or POST) request |
1045 | | */ |
1046 | | static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char *data, const char *content_type, |
1047 | | const char *basic_auth, const char *access_token, const char *dpop, |
1048 | | int ssl_validate_server, char **response, long *response_code, |
1049 | | apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout, |
1050 | | const oidc_http_outgoing_proxy_t *outgoing_proxy, |
1051 | | const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key, |
1052 | 0 | const char *ssl_key_pwd) { |
1053 | | |
1054 | | // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro |
1055 | 0 | CURL *curl = NULL; |
1056 | 0 | CURLcode code = CURLE_OK; |
1057 | 0 | char curl_err[CURL_ERROR_SIZE]; |
1058 | 0 | oidc_curl_resp_data_ctx_t d_buf = {r, NULL, 0}; |
1059 | 0 | oidc_curl_resp_hdr_ctx_t h_buf = {r, response_hdrs}; |
1060 | 0 | struct curl_slist *h_list = NULL; |
1061 | 0 | long http_code = 0; |
1062 | 0 | apr_byte_t rv = FALSE; |
1063 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
1064 | | |
1065 | | /* do some logging about the inputs */ |
1066 | 0 | oidc_debug(r, |
1067 | 0 | "url=%s, data=%s, content_type=%s, basic_auth=%s, access_token=%s, dpop=%s, ssl_validate_server=%d, " |
1068 | 0 | "request_timeout=%d, connect_timeout=%d, retries=%d, retry_interval=%d, outgoing_proxy=%s:%s:%d, " |
1069 | 0 | "pass_cookies=%pp, ssl_cert=%s, ssl_key=%s, ssl_key_pwd=%s", |
1070 | 0 | url, oidc_http_redact_body_for_log(r, data), content_type, basic_auth ? "****" : "null", |
1071 | 0 | oidc_util_mask_value(r, access_token), dpop, ssl_validate_server, http_timeout->request_timeout, |
1072 | 0 | http_timeout->connect_timeout, http_timeout->retries, http_timeout->retry_interval, |
1073 | 0 | outgoing_proxy->host_port, outgoing_proxy->username_password ? "****" : "(null)", |
1074 | 0 | (int)outgoing_proxy->auth_type, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd ? "****" : "(null)"); |
1075 | | |
1076 | | /* an endpoint that is not configured is a caller error, not a transfer failure: fail here rather |
1077 | | * than hand curl a NULL URL and then back off and retry a request that can never be sent */ |
1078 | 0 | if (url == NULL) { |
1079 | 0 | oidc_error(r, "no URL to send the request to: the endpoint is not configured"); |
1080 | 0 | return FALSE; |
1081 | 0 | } |
1082 | | |
1083 | 0 | curl = oidc_http_curl_acquire(); |
1084 | 0 | if (curl == NULL) { |
1085 | 0 | oidc_error(r, "could not obtain a curl handle"); |
1086 | 0 | goto end; |
1087 | 0 | } |
1088 | | |
1089 | | /* set the error buffer as empty before performing a request */ |
1090 | 0 | curl_err[0] = 0; |
1091 | | |
1092 | | /* some of these are not really required */ |
1093 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADER, 0L); |
1094 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_NOPROGRESS, 1L); |
1095 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_NOSIGNAL, 1L); |
1096 | | /* keep reused connections alive across the (potentially long) intervals between calls */ |
1097 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_TCP_KEEPALIVE, 1L); |
1098 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_ERRORBUFFER, curl_err); |
1099 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_FOLLOWLOCATION, 1L); |
1100 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_MAXREDIRS, 5L); |
1101 | | |
1102 | | /* set the timeouts */ |
1103 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_TIMEOUT, (long)http_timeout->request_timeout); |
1104 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_CONNECTTIMEOUT, (long)http_timeout->connect_timeout); |
1105 | | |
1106 | | /* setup the buffer where the response data will be written to */ |
1107 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_WRITEFUNCTION, oidc_http_response_data); |
1108 | | /* coverity[bad_sizeof] */ |
1109 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_WRITEDATA, &d_buf); |
1110 | | |
1111 | | /* setup the buffer where the response headers will be written to */ |
1112 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADERFUNCTION, oidc_http_response_header); |
1113 | | /* coverity[bad_sizeof] */ |
1114 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADERDATA, &h_buf); |
1115 | |
|
1116 | 0 | #ifndef LIBCURL_NO_CURLPROTO |
1117 | 0 | #if LIBCURL_VERSION_NUM >= 0x075500 |
1118 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_REDIR_PROTOCOLS_STR, "http,https"); |
1119 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_PROTOCOLS_STR, "http,https"); |
1120 | | #else |
1121 | | OIDC_HTTP_CURL_SETOPT(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); |
1122 | | OIDC_HTTP_CURL_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); |
1123 | | #endif |
1124 | 0 | #endif |
1125 | | |
1126 | | /* set the options for validating the SSL server certificate that the remote site presents */ |
1127 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_SSL_VERIFYPEER, (ssl_validate_server != FALSE ? 1L : 0L)); |
1128 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_SSL_VERIFYHOST, (ssl_validate_server != FALSE ? 2L : 0L)); |
1129 | |
|
1130 | 0 | oidc_http_set_curl_ssl_options(r, curl); |
1131 | |
|
1132 | 0 | oidc_http_request_setup_ca_bundle(r, curl, c); |
1133 | | |
1134 | | /* identify this HTTP client */ |
1135 | 0 | const char *s_useragent = oidc_http_user_agent(r); |
1136 | 0 | if ((s_useragent != NULL) && (_oidc_strcmp(s_useragent, "") != 0)) { |
1137 | 0 | oidc_debug(r, "set HTTP request header User-Agent to: %s", s_useragent); |
1138 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_USERAGENT, s_useragent); |
1139 | 0 | } |
1140 | | |
1141 | | /* set the local interface if defined */ |
1142 | 0 | const char *s_interface = oidc_http_interface(r); |
1143 | 0 | if ((s_interface != NULL) && (_oidc_strcmp(s_interface, "") != 0)) { |
1144 | 0 | #if LIBCURL_VERSION_NUM >= 0x073000 |
1145 | 0 | oidc_debug(r, "set local interface to: %s", s_interface); |
1146 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_INTERFACE, s_interface); |
1147 | | #else |
1148 | | oidc_warn( |
1149 | | r, "local interface is configured to %s, but the cURL version in use does not support setting this", |
1150 | | s_interface); |
1151 | | #endif |
1152 | 0 | } |
1153 | |
|
1154 | 0 | oidc_http_request_setup_proxy(r, curl, outgoing_proxy); |
1155 | | |
1156 | | /* see if we need to perform HTTP basic authentication to the remote site */ |
1157 | 0 | if (basic_auth != NULL) { |
1158 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); |
1159 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_USERPWD, basic_auth); |
1160 | 0 | } |
1161 | |
|
1162 | 0 | if (ssl_cert != NULL) { |
1163 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_SSLCERT, ssl_cert); |
1164 | 0 | } |
1165 | 0 | if (ssl_key != NULL) { |
1166 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_SSLKEY, ssl_key); |
1167 | 0 | } |
1168 | 0 | if (ssl_key_pwd != NULL) { |
1169 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_KEYPASSWD, ssl_key_pwd); |
1170 | 0 | } |
1171 | |
|
1172 | 0 | if (data != NULL) { |
1173 | | /* set POST data and switch HTTP method to POST */ |
1174 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_POSTFIELDS, data); |
1175 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_POST, 1L); |
1176 | 0 | } |
1177 | |
|
1178 | 0 | h_list = oidc_http_request_build_header_list(r, c, content_type, access_token, dpop); |
1179 | 0 | if (h_list != NULL) { |
1180 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_HTTPHEADER, h_list); |
1181 | 0 | } |
1182 | |
|
1183 | 0 | oidc_http_request_pass_cookies(r, curl, pass_cookies); |
1184 | | |
1185 | | /* set the target URL */ |
1186 | 0 | OIDC_HTTP_CURL_SETOPT(CURLOPT_URL, url); |
1187 | | |
1188 | | /* call it and record the result */ |
1189 | 0 | rv = oidc_http_request_perform_with_retries(r, c, curl, url, curl_err, http_timeout); |
1190 | 0 | if (rv == FALSE) |
1191 | 0 | goto end; |
1192 | | |
1193 | 0 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); |
1194 | 0 | oidc_debug(r, "HTTP response code=%ld", http_code); |
1195 | |
|
1196 | 0 | OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_HTTP_RESPONSE_CODE, apr_psprintf(r->pool, "%ld", http_code)); |
1197 | |
|
1198 | 0 | *response = apr_pstrmemdup(r->pool, d_buf.memory, d_buf.size); |
1199 | 0 | if (response_code) |
1200 | 0 | *response_code = http_code; |
1201 | | |
1202 | | /* set and log the response; the token, introspection and registration responses carry |
1203 | | * credentials, so redact them the way the request body above already is */ |
1204 | 0 | oidc_debug(r, "response=%s", oidc_http_redact_json_for_log(r, *response ? *response : "")); |
1205 | |
|
1206 | 0 | end: |
1207 | | |
1208 | | /* cleanup and return the result; a handle that performed successfully goes back to the |
1209 | | * pool with its connection kept alive for the next request to the same endpoint */ |
1210 | 0 | if (h_list != NULL) |
1211 | 0 | curl_slist_free_all(h_list); |
1212 | 0 | oidc_http_curl_release(curl, rv); |
1213 | |
|
1214 | 0 | return rv; |
1215 | 0 | } |
1216 | | |
1217 | | /* |
1218 | | * execute HTTP GET request |
1219 | | */ |
1220 | | apr_byte_t oidc_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, |
1221 | | const char *access_token, const char *dpop, int ssl_validate_server, char **response, |
1222 | | long *response_code, apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout, |
1223 | | const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, |
1224 | 0 | const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { |
1225 | 0 | const char *query_url = oidc_http_query_encoded_url(r, url, params); |
1226 | 0 | return oidc_http_request(r, query_url, NULL, NULL, basic_auth, access_token, dpop, ssl_validate_server, |
1227 | 0 | response, response_code, response_hdrs, http_timeout, outgoing_proxy, pass_cookies, |
1228 | 0 | ssl_cert, ssl_key, ssl_key_pwd); |
1229 | 0 | } |
1230 | | |
1231 | | /* |
1232 | | * execute HTTP POST request with form-encoded data |
1233 | | */ |
1234 | | apr_byte_t oidc_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, |
1235 | | const char *access_token, const char *dpop, int ssl_validate_server, char **response, |
1236 | | long *response_code, apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout, |
1237 | | const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, |
1238 | 0 | const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { |
1239 | 0 | const char *data = oidc_http_form_encoded_data(r, params); |
1240 | 0 | return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED, basic_auth, access_token, dpop, |
1241 | 0 | ssl_validate_server, response, response_code, response_hdrs, http_timeout, |
1242 | 0 | outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); |
1243 | 0 | } |
1244 | | |
1245 | | /* |
1246 | | * execute HTTP POST request with JSON-encoded data |
1247 | | */ |
1248 | | apr_byte_t oidc_http_post_json(request_rec *r, const char *url, const oidc_json_t *json, const char *basic_auth, |
1249 | | const char *access_token, const char *dpop, int ssl_validate_server, char **response, |
1250 | | long *response_code, apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout, |
1251 | | const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies, |
1252 | 0 | const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) { |
1253 | 0 | const char *data = |
1254 | 0 | json != NULL ? oidc_json_encode(r->pool, json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT) : NULL; |
1255 | 0 | return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_JSON, basic_auth, access_token, dpop, |
1256 | 0 | ssl_validate_server, response, response_code, response_hdrs, http_timeout, |
1257 | 0 | outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd); |
1258 | 0 | } |
1259 | | |
1260 | | /* |
1261 | | * get the current path from the request in a normalized way |
1262 | | */ |
1263 | 0 | static char *oidc_http_get_path(request_rec *r) { |
1264 | 0 | size_t i; |
1265 | 0 | const char *p; |
1266 | 0 | p = r->parsed_uri.path; |
1267 | 0 | if ((p == NULL) || (p[0] == '\0')) |
1268 | 0 | return apr_pstrdup(r->pool, OIDC_STR_FORWARD_SLASH); |
1269 | 0 | for (i = _oidc_strlen(p) - 1; i > 0; i--) |
1270 | 0 | if (p[i] == OIDC_CHAR_FORWARD_SLASH) |
1271 | 0 | break; |
1272 | 0 | return apr_pstrndup(r->pool, p, i + 1); |
1273 | 0 | } |
1274 | | |
1275 | | /* |
1276 | | * get the cookie path setting and check that it matches the request path; cook it up if it is not set |
1277 | | */ |
1278 | 0 | static const char *oidc_http_get_cookie_path(request_rec *r) { |
1279 | 0 | const char *rv = NULL; |
1280 | 0 | char *requestPath = oidc_http_get_path(r); |
1281 | 0 | const char *cookie_path = oidc_cfg_dir_cookie_path_get(r); |
1282 | 0 | if (cookie_path != NULL) { |
1283 | 0 | if (_oidc_strncmp(cookie_path, requestPath, _oidc_strlen(cookie_path)) == 0) |
1284 | 0 | rv = cookie_path; |
1285 | 0 | else { |
1286 | 0 | oidc_warn(r, |
1287 | 0 | "" OIDCCookiePath |
1288 | 0 | " (%s) is not a substring of request path, using request path (%s) for cookie", |
1289 | 0 | cookie_path, requestPath); |
1290 | 0 | rv = requestPath; |
1291 | 0 | } |
1292 | 0 | } else { |
1293 | 0 | rv = requestPath; |
1294 | 0 | } |
1295 | 0 | return rv; |
1296 | 0 | } |
1297 | | |
1298 | 0 | #define OIDC_HTTP_COOKIE_FLAG_DOMAIN "Domain" |
1299 | 0 | #define OIDC_HTTP_COOKIE_FLAG_PATH "Path" |
1300 | 0 | #define OIDC_HTTP_COOKIE_FLAG_EXPIRES "Expires" |
1301 | 0 | #define OIDC_HTTP_COOKIE_FLAG_SECURE "Secure" |
1302 | 0 | #define OIDC_HTTP_COOKIE_FLAG_HTTP_ONLY "HttpOnly" |
1303 | | |
1304 | 0 | #define OIDC_HTTP_COOKIE_MAX_SIZE 4093 |
1305 | | |
1306 | 0 | #define OIDC_SET_COOKIE_APPEND_ENV_VAR "OIDC_SET_COOKIE_APPEND" |
1307 | | |
1308 | | /* |
1309 | | * obtain the value configured in the OIDC_SET_COOKIE_APPEND environment variable |
1310 | | * which is to be added to the HTTP Set-Cookie response header |
1311 | | */ |
1312 | 0 | static const char *oidc_http_set_cookie_append_value(request_rec *r) { |
1313 | 0 | const char *env_var_value = NULL; |
1314 | |
|
1315 | 0 | if (r->subprocess_env != NULL) |
1316 | 0 | env_var_value = apr_table_get(r->subprocess_env, OIDC_SET_COOKIE_APPEND_ENV_VAR); |
1317 | |
|
1318 | 0 | if (env_var_value == NULL) { |
1319 | 0 | oidc_debug(r, "no cookie append environment variable %s found", OIDC_SET_COOKIE_APPEND_ENV_VAR); |
1320 | 0 | return NULL; |
1321 | 0 | } |
1322 | | |
1323 | 0 | oidc_debug(r, "cookie append environment variable %s=%s found", OIDC_SET_COOKIE_APPEND_ENV_VAR, env_var_value); |
1324 | |
|
1325 | 0 | return env_var_value; |
1326 | 0 | } |
1327 | | |
1328 | | /* |
1329 | | * set a cookie in the HTTP response headers |
1330 | | */ |
1331 | | void oidc_http_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, |
1332 | 0 | const char *ext) { |
1333 | |
|
1334 | 0 | const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module); |
1335 | 0 | char *headerString = NULL; |
1336 | 0 | char *expiresString = NULL; |
1337 | 0 | const char *appendString = NULL; |
1338 | | |
1339 | | /* see if we need to clear the cookie */ |
1340 | 0 | if (_oidc_strcmp(cookieValue, "") == 0) |
1341 | 0 | expires = 0; |
1342 | | |
1343 | | /* construct the expire value */ |
1344 | 0 | if (expires != -1) { |
1345 | 0 | expiresString = (char *)apr_pcalloc(r->pool, APR_RFC822_DATE_LEN); |
1346 | 0 | if (apr_rfc822_date(expiresString, expires) != APR_SUCCESS) { |
1347 | 0 | oidc_error(r, "could not set cookie expiry date"); |
1348 | 0 | } |
1349 | 0 | } |
1350 | | |
1351 | | /* construct the cookie value */ |
1352 | 0 | headerString = apr_psprintf(r->pool, "%s=%s", cookieName, cookieValue); |
1353 | |
|
1354 | 0 | headerString = |
1355 | 0 | apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_PATH, oidc_http_get_cookie_path(r)); |
1356 | |
|
1357 | 0 | if (expiresString != NULL) |
1358 | 0 | headerString = |
1359 | 0 | apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_EXPIRES, expiresString); |
1360 | |
|
1361 | 0 | if (oidc_cfg_cookie_domain_get(c) != NULL) |
1362 | 0 | headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_DOMAIN, |
1363 | 0 | oidc_cfg_cookie_domain_get(c)); |
1364 | |
|
1365 | 0 | if (oidc_util_url_cur_is_secure(r, c)) |
1366 | 0 | headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_HTTP_COOKIE_FLAG_SECURE); |
1367 | |
|
1368 | 0 | if (oidc_cfg_cookie_http_only_get(c) != FALSE) |
1369 | 0 | headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_HTTP_COOKIE_FLAG_HTTP_ONLY); |
1370 | |
|
1371 | 0 | appendString = oidc_http_set_cookie_append_value(r); |
1372 | 0 | if (appendString != NULL) |
1373 | 0 | headerString = apr_psprintf(r->pool, "%s; %s", headerString, appendString); |
1374 | 0 | else if (ext != NULL) |
1375 | 0 | headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext); |
1376 | | |
1377 | | /* sanity check on overall cookie value size */ |
1378 | 0 | if (_oidc_strlen(headerString) > OIDC_HTTP_COOKIE_MAX_SIZE) { |
1379 | 0 | oidc_warn(r, |
1380 | 0 | "the length of the cookie value (%d) is greater than %d(!) bytes, this may not work " |
1381 | 0 | "with all browsers/server combinations: consider switching to a server side caching!", |
1382 | 0 | (int)_oidc_strlen(headerString), OIDC_HTTP_COOKIE_MAX_SIZE); |
1383 | 0 | } |
1384 | | |
1385 | | /* use r->err_headers_out so we always print our headers (even on 302 redirect) - headers_out only |
1386 | | * prints on 2xx responses */ |
1387 | 0 | oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_SET_COOKIE, headerString); |
1388 | 0 | } |
1389 | | |
1390 | | /* |
1391 | | * get a cookie from the HTTP request |
1392 | | */ |
1393 | 0 | char *oidc_http_get_cookie(request_rec *r, const char *cookieName) { |
1394 | 0 | const char *cookie = NULL; |
1395 | 0 | char *tokenizerCtx = NULL; |
1396 | 0 | char *rv = NULL; |
1397 | | |
1398 | | /* get the Cookie value */ |
1399 | 0 | char *cookies = apr_pstrdup(r->pool, oidc_http_hdr_in_cookie_get(r)); |
1400 | |
|
1401 | 0 | if (cookies != NULL) { |
1402 | | |
1403 | | /* tokenize on ; to find the cookie we want */ |
1404 | 0 | cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx); |
1405 | |
|
1406 | 0 | while (cookie != NULL) { |
1407 | |
|
1408 | 0 | while (*cookie == OIDC_CHAR_SPACE) |
1409 | 0 | cookie++; |
1410 | | |
1411 | | /* see if we've found the cookie that we're looking for */ |
1412 | 0 | if ((_oidc_strncmp(cookie, cookieName, _oidc_strlen(cookieName)) == 0) && |
1413 | 0 | (cookie[_oidc_strlen(cookieName)] == OIDC_CHAR_EQUAL)) { |
1414 | | |
1415 | | /* skip to the meat of the parameter (the value after the '=') */ |
1416 | 0 | cookie += (_oidc_strlen(cookieName) + 1); |
1417 | 0 | rv = apr_pstrdup(r->pool, cookie); |
1418 | |
|
1419 | 0 | break; |
1420 | 0 | } |
1421 | | |
1422 | | /* go to the next cookie */ |
1423 | 0 | cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx); |
1424 | 0 | } |
1425 | 0 | } |
1426 | | |
1427 | | /* log what we've found */ |
1428 | | /* in client-cookie session mode the value is the whole session credential */ |
1429 | 0 | oidc_debug(r, "returning \"%s\" = %s", cookieName, |
1430 | 0 | rv ? apr_psprintf(r->pool, "\"%s\"", oidc_util_mask_value(r, rv)) : "<null>"); |
1431 | |
|
1432 | 0 | return rv; |
1433 | 0 | } |
1434 | | |
1435 | 0 | #define OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR "_" |
1436 | 0 | #define OIDC_HTTP_COOKIE_CHUNKS_POSTFIX "chunks" |
1437 | | |
1438 | | /* the largest number of chunks a chunked cookie may be split over; the counter cookie holds it as |
1439 | | * a decimal number, so this bounds both what is written and what is accepted on the way back in */ |
1440 | 0 | #define OIDC_HTTP_COOKIE_CHUNKS_MAX 99 |
1441 | | |
1442 | | /* |
1443 | | * get the name of the cookie that contains the number of chunks |
1444 | | */ |
1445 | 0 | static char *oidc_http_get_chunk_count_name(request_rec *r, const char *cookieName) { |
1446 | 0 | return apr_psprintf(r->pool, "%s%s%s", cookieName, OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR, |
1447 | 0 | OIDC_HTTP_COOKIE_CHUNKS_POSTFIX); |
1448 | 0 | } |
1449 | | |
1450 | | /* |
1451 | | * get the number of cookie chunks set by the browser |
1452 | | */ |
1453 | 0 | static int oidc_http_get_chunked_count(request_rec *r, const char *cookieName) { |
1454 | 0 | int chunkCount = 0; |
1455 | 0 | const char *chunkCountValue = oidc_http_get_cookie(r, oidc_http_get_chunk_count_name(r, cookieName)); |
1456 | 0 | chunkCount = _oidc_str_to_int(chunkCountValue, 0); |
1457 | 0 | return chunkCount; |
1458 | 0 | } |
1459 | | |
1460 | | /* |
1461 | | * get the name of a chunk |
1462 | | */ |
1463 | 0 | static char *oidc_http_get_chunk_cookie_name(request_rec *r, const char *cookieName, int i) { |
1464 | 0 | return apr_psprintf(r->pool, "%s%s%d", cookieName, OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR, i); |
1465 | 0 | } |
1466 | | |
1467 | | /* |
1468 | | * get a cookie value that is split over a number of chunked cookies |
1469 | | */ |
1470 | 0 | char *oidc_http_get_chunked_cookie(request_rec *r, const char *cookieName, int chunkSize) { |
1471 | 0 | char *cookieValue = NULL; |
1472 | 0 | char *chunkValue = NULL; |
1473 | 0 | int chunkCount = 0; |
1474 | 0 | if (chunkSize == 0) |
1475 | 0 | return oidc_http_get_cookie(r, cookieName); |
1476 | 0 | chunkCount = oidc_http_get_chunked_count(r, cookieName); |
1477 | 0 | if (chunkCount == 0) |
1478 | 0 | return oidc_http_get_cookie(r, cookieName); |
1479 | 0 | if ((chunkCount < 0) || (chunkCount > OIDC_HTTP_COOKIE_CHUNKS_MAX)) { |
1480 | 0 | oidc_warn(r, "chunk count out of bounds: %d", chunkCount); |
1481 | 0 | return NULL; |
1482 | 0 | } |
1483 | 0 | for (int i = 0; i < chunkCount; i++) { |
1484 | 0 | chunkValue = oidc_http_get_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i)); |
1485 | 0 | if (chunkValue == NULL) { |
1486 | | /* refuse a partial assembly: a truncated value would fail session decode (or worse) |
1487 | | * and leave the browser stuck resubmitting the broken cookie set */ |
1488 | 0 | oidc_warn(r, "could not find chunk %d; aborting", i); |
1489 | 0 | return NULL; |
1490 | 0 | } |
1491 | 0 | cookieValue = apr_psprintf(r->pool, "%s%s", cookieValue ? cookieValue : "", chunkValue); |
1492 | 0 | } |
1493 | 0 | return cookieValue; |
1494 | 0 | } |
1495 | | |
1496 | | /* |
1497 | | * unset all chunked cookies, including the counter cookie, if they exist |
1498 | | */ |
1499 | | static void oidc_http_clear_chunked_cookie(request_rec *r, const char *cookieName, apr_time_t expires, |
1500 | 0 | const char *ext) { |
1501 | 0 | int chunkCount = oidc_http_get_chunked_count(r, cookieName); |
1502 | 0 | if (chunkCount > 0) { |
1503 | 0 | for (int i = 0; i < chunkCount; i++) |
1504 | 0 | oidc_http_set_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i), "", expires, ext); |
1505 | 0 | oidc_http_set_cookie(r, oidc_http_get_chunk_count_name(r, cookieName), "", expires, ext); |
1506 | 0 | } |
1507 | 0 | } |
1508 | | |
1509 | | /* |
1510 | | * set a cookie value that is split over a number of chunked cookies |
1511 | | */ |
1512 | | apr_byte_t oidc_http_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue, |
1513 | 0 | apr_time_t expires, int chunkSize, const char *ext) { |
1514 | 0 | int cookieLength = (int)_oidc_strlen(cookieValue); |
1515 | 0 | const char *chunkValue = NULL; |
1516 | | |
1517 | | /* see if we need to chunk at all */ |
1518 | 0 | if ((chunkSize == 0) || ((cookieLength > 0) && (cookieLength < chunkSize))) { |
1519 | 0 | oidc_http_set_cookie(r, cookieName, cookieValue, expires, ext); |
1520 | 0 | oidc_http_clear_chunked_cookie(r, cookieName, expires, ext); |
1521 | 0 | return TRUE; |
1522 | 0 | } |
1523 | | |
1524 | | /* see if we need to clear a possibly chunked cookie */ |
1525 | 0 | if (cookieLength == 0) { |
1526 | 0 | oidc_http_set_cookie(r, cookieName, "", expires, ext); |
1527 | 0 | oidc_http_clear_chunked_cookie(r, cookieName, expires, ext); |
1528 | 0 | return TRUE; |
1529 | 0 | } |
1530 | | |
1531 | | /* Use ceil division without advertising a trailing empty chunk for exact multiples. */ |
1532 | 0 | int chunkCountValue = (cookieLength + chunkSize - 1) / chunkSize; |
1533 | | |
1534 | | /* refuse to write what oidc_http_get_chunked_cookie would refuse to read back: writing it |
1535 | | * anyway leaves the browser holding a value that is dropped on the next request, which for a |
1536 | | * client-side session means authenticating over and over rather than a visible failure */ |
1537 | 0 | if (chunkCountValue > OIDC_HTTP_COOKIE_CHUNKS_MAX) { |
1538 | 0 | oidc_error(r, |
1539 | 0 | "cookie \"%s\" would have to be split over %d chunks, which is more than the maximum of " |
1540 | 0 | "%d; increase " OIDCSessionCookieChunkSize " (currently %d) or store the session in a " |
1541 | 0 | "server-side cache with " OIDCSessionType " \"server-cache\"", |
1542 | 0 | cookieName, chunkCountValue, OIDC_HTTP_COOKIE_CHUNKS_MAX, chunkSize); |
1543 | 0 | return FALSE; |
1544 | 0 | } |
1545 | | |
1546 | 0 | const char *ptr = cookieValue; |
1547 | 0 | for (int i = 0; i < chunkCountValue; i++) { |
1548 | 0 | chunkValue = apr_pstrndup(r->pool, ptr, chunkSize); |
1549 | 0 | ptr += chunkSize; |
1550 | 0 | oidc_http_set_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i), chunkValue, expires, ext); |
1551 | 0 | } |
1552 | 0 | oidc_http_set_cookie(r, oidc_http_get_chunk_count_name(r, cookieName), |
1553 | 0 | apr_psprintf(r->pool, "%d", chunkCountValue), expires, ext); |
1554 | 0 | oidc_http_set_cookie(r, cookieName, "", expires, ext); |
1555 | |
|
1556 | 0 | return TRUE; |
1557 | 0 | } |
1558 | | |
1559 | | /* |
1560 | | * construct the HTTP outgoing proxy options |
1561 | | */ |
1562 | 0 | const char **oidc_http_proxy_auth_options(void) { |
1563 | 0 | static const char *options[] = {OIDC_HTTP_PROXY_AUTH_BASIC, |
1564 | 0 | OIDC_HTTP_PROXY_AUTH_DIGEST, |
1565 | 0 | OIDC_HTTP_PROXY_AUTH_NTLM, |
1566 | 0 | OIDC_HTTP_PROXY_AUTH_ANY, |
1567 | 0 | #ifdef CURLAUTH_NEGOTIATE |
1568 | 0 | OIDC_HTTP_PROXY_AUTH_NEGOTIATE, |
1569 | 0 | #endif |
1570 | 0 | NULL}; |
1571 | 0 | return options; |
1572 | 0 | } |
1573 | | |
1574 | | /* |
1575 | | * return the CURL enum value for the HTTP outgoing proxy options |
1576 | | */ |
1577 | 0 | unsigned long oidc_http_proxy_s2auth(const char *arg) { |
1578 | 0 | if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_BASIC) == 0) |
1579 | 0 | return CURLAUTH_BASIC; |
1580 | 0 | if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_DIGEST) == 0) |
1581 | 0 | return CURLAUTH_DIGEST; |
1582 | 0 | if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_NTLM) == 0) |
1583 | 0 | return CURLAUTH_NTLM; |
1584 | 0 | if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_ANY) == 0) |
1585 | 0 | return CURLAUTH_ANY; |
1586 | 0 | #ifdef CURLAUTH_NEGOTIATE |
1587 | 0 | if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_NEGOTIATE) == 0) |
1588 | 0 | return CURLAUTH_NEGOTIATE; |
1589 | 0 | #endif |
1590 | 0 | return CURLAUTH_NONE; |
1591 | 0 | } |
1592 | | |
1593 | | /* |
1594 | | * initialize the HTTP/cURL environment |
1595 | | */ |
1596 | 0 | void oidc_http_init(void) { |
1597 | 0 | curl_global_init(CURL_GLOBAL_ALL); |
1598 | 0 | } |
1599 | | |
1600 | | /* |
1601 | | * clean up the HTTP/cURL environment |
1602 | | */ |
1603 | 0 | void oidc_http_cleanup(void) { |
1604 | 0 | curl_global_cleanup(); |
1605 | 0 | } |
1606 | | |
1607 | | /* Pool reset handles to retain their connection, DNS, and TLS caches. */ |
1608 | 0 | #define OIDC_HTTP_CURL_POOL_MAX 16 |
1609 | | |
1610 | | static CURL *_oidc_http_curl_pool[OIDC_HTTP_CURL_POOL_MAX]; |
1611 | | static int _oidc_http_curl_pool_num = 0; |
1612 | | static apr_byte_t _oidc_http_curl_pool_enabled = FALSE; |
1613 | | #if APR_HAS_THREADS |
1614 | | static apr_thread_mutex_t *_oidc_http_curl_pool_mutex = NULL; |
1615 | | #endif |
1616 | | |
1617 | 0 | static void oidc_http_curl_pool_lock(void) { |
1618 | 0 | #if APR_HAS_THREADS |
1619 | 0 | apr_thread_mutex_lock(_oidc_http_curl_pool_mutex); |
1620 | 0 | #endif |
1621 | 0 | } |
1622 | | |
1623 | 0 | static void oidc_http_curl_pool_unlock(void) { |
1624 | 0 | #if APR_HAS_THREADS |
1625 | 0 | apr_thread_mutex_unlock(_oidc_http_curl_pool_mutex); |
1626 | 0 | #endif |
1627 | 0 | } |
1628 | | |
1629 | 0 | static apr_status_t oidc_http_curl_pool_cleanup(void *data) { |
1630 | 0 | for (int i = 0; i < _oidc_http_curl_pool_num; i++) |
1631 | 0 | curl_easy_cleanup(_oidc_http_curl_pool[i]); |
1632 | 0 | _oidc_http_curl_pool_num = 0; |
1633 | 0 | _oidc_http_curl_pool_enabled = FALSE; |
1634 | 0 | #if APR_HAS_THREADS |
1635 | 0 | _oidc_http_curl_pool_mutex = NULL; |
1636 | 0 | #endif |
1637 | 0 | return APR_SUCCESS; |
1638 | 0 | } |
1639 | | |
1640 | 2 | void oidc_http_curl_pool_init(apr_pool_t *pool) { |
1641 | 2 | if (_oidc_http_curl_pool_enabled == TRUE) |
1642 | 0 | return; |
1643 | 2 | #if APR_HAS_THREADS |
1644 | 2 | if (apr_thread_mutex_create(&_oidc_http_curl_pool_mutex, APR_THREAD_MUTEX_DEFAULT, pool) != APR_SUCCESS) |
1645 | 0 | return; |
1646 | 2 | #endif |
1647 | 2 | _oidc_http_curl_pool_num = 0; |
1648 | 2 | _oidc_http_curl_pool_enabled = TRUE; |
1649 | 2 | apr_pool_cleanup_register(pool, NULL, oidc_http_curl_pool_cleanup, apr_pool_cleanup_null); |
1650 | 2 | } |
1651 | | |
1652 | | /* |
1653 | | * forget any handles inherited over fork(): their connections share file descriptors and TLS |
1654 | | * state with the parent, so the child must neither use nor curl_easy_cleanup them (a cleanup |
1655 | | * would write a TLS close-notify into a socket the parent may still be using) |
1656 | | */ |
1657 | 0 | void oidc_http_curl_pool_child_init(void) { |
1658 | 0 | _oidc_http_curl_pool_num = 0; |
1659 | 0 | } |
1660 | | |
1661 | 0 | static CURL *oidc_http_curl_acquire(void) { |
1662 | 0 | CURL *curl = NULL; |
1663 | 0 | if (_oidc_http_curl_pool_enabled == FALSE) |
1664 | 0 | return curl_easy_init(); |
1665 | 0 | oidc_http_curl_pool_lock(); |
1666 | 0 | if (_oidc_http_curl_pool_num > 0) |
1667 | 0 | curl = _oidc_http_curl_pool[--_oidc_http_curl_pool_num]; |
1668 | 0 | oidc_http_curl_pool_unlock(); |
1669 | 0 | return (curl != NULL) ? curl : curl_easy_init(); |
1670 | 0 | } |
1671 | | |
1672 | | /* |
1673 | | * return a handle to the pool after a successful request: the reset clears its options but |
1674 | | * keeps the connection/DNS/TLS-session caches; a handle whose request failed is destroyed |
1675 | | * rather than reused so a poisoned connection state cannot carry over |
1676 | | */ |
1677 | 0 | static void oidc_http_curl_release(CURL *curl, apr_byte_t reuse) { |
1678 | 0 | if (curl == NULL) |
1679 | 0 | return; |
1680 | 0 | if ((reuse == TRUE) && (_oidc_http_curl_pool_enabled == TRUE)) { |
1681 | 0 | curl_easy_reset(curl); |
1682 | 0 | oidc_http_curl_pool_lock(); |
1683 | 0 | if (_oidc_http_curl_pool_num < OIDC_HTTP_CURL_POOL_MAX) { |
1684 | 0 | _oidc_http_curl_pool[_oidc_http_curl_pool_num++] = curl; |
1685 | 0 | curl = NULL; |
1686 | 0 | } |
1687 | 0 | oidc_http_curl_pool_unlock(); |
1688 | 0 | } |
1689 | 0 | if (curl != NULL) |
1690 | 0 | curl_easy_cleanup(curl); |
1691 | 0 | } |