Coverage Report

Created: 2026-08-31 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}