Coverage Report

Created: 2026-11-12 07:05

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
0
char *oidc_http_url_encode(const request_rec *r, const char *str) {
73
0
  static const char hex[] = "0123456789ABCDEF";
74
0
  char *rv = NULL;
75
0
  char *p = NULL;
76
0
  size_t len = 0;
77
78
0
  if (str == NULL)
79
0
    return "";
80
81
0
  len = _oidc_strlen(str);
82
83
  /* worst case each input byte expands to a 3-byte %XX sequence */
84
0
  rv = apr_palloc(r->pool, len * 3 + 1);
85
0
  p = rv;
86
0
  for (size_t i = 0; i < len; i++) {
87
0
    const unsigned char c = (unsigned char)str[i];
88
0
    if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9')) ||
89
0
        (c == '-') || (c == '.') || (c == '_') || (c == '~')) {
90
0
      *p++ = (char)c;
91
0
    } else {
92
0
      *p++ = '%';
93
0
      *p++ = hex[c >> 4];
94
0
      *p++ = hex[c & 0x0f];
95
0
    }
96
0
  }
97
0
  *p = '\0';
98
99
0
  return rv;
100
0
}
101
102
0
static int oidc_http_url_decode_hex_digit(const char c) {
103
0
  if ((c >= '0') && (c <= '9'))
104
0
    return c - '0';
105
0
  if ((c >= 'A') && (c <= 'F'))
106
0
    return c - 'A' + 10;
107
0
  if ((c >= 'a') && (c <= 'f'))
108
0
    return c - 'a' + 10;
109
0
  return -1;
110
0
}
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
0
char *oidc_http_url_decode(const request_rec *r, const char *str) {
118
0
  char *rv = NULL;
119
0
  char *p = NULL;
120
0
  size_t i = 0;
121
0
  size_t len = 0;
122
123
0
  if (str == NULL)
124
0
    return "";
125
126
0
  len = _oidc_strlen(str);
127
128
0
  rv = apr_palloc(r->pool, len + 1);
129
0
  p = rv;
130
0
  while (i < len) {
131
0
    const char c = str[i];
132
0
    if (c == '+') {
133
0
      *p++ = ' ';
134
0
      i++;
135
0
    } 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
0
      const int hi = oidc_http_url_decode_hex_digit(str[i + 1]);
139
0
      const int lo = (hi >= 0) ? oidc_http_url_decode_hex_digit(str[i + 2]) : -1;
140
0
      if (lo >= 0) {
141
0
        *p++ = (char)((hi << 4) | lo);
142
0
        i += 3;
143
0
      } else {
144
0
        *p++ = c;
145
0
        i++;
146
0
      }
147
0
    } else {
148
0
      *p++ = c;
149
0
      i++;
150
0
    }
151
0
  }
152
0
  *p = '\0';
153
154
0
  return rv;
155
0
}
156
157
/*
158
 * obtain a HTTP request header value
159
 */
160
0
static const char *oidc_http_hdr_in_get(const request_rec *r, const char *name) {
161
0
  const char *value = apr_table_get(r->headers_in, name);
162
0
  if (value)
163
0
    oidc_debug(r, "%s=%s", name, value);
164
0
  return value;
165
0
}
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
0
              const char postfix_separator, const char *needle) {
183
0
  char *ctx = NULL;
184
0
  const char *elem = NULL;
185
0
  const char *value = oidc_http_hdr_in_get(r, name);
186
0
  apr_byte_t rc = FALSE;
187
0
  if (value) {
188
0
    elem = apr_strtok(apr_pstrdup(r->pool, value), separator, &ctx);
189
0
    while (elem != NULL) {
190
0
      while (*elem == OIDC_CHAR_SPACE)
191
0
        elem++;
192
0
      if ((_oidc_strncmp(elem, needle, _oidc_strlen(needle)) == 0) &&
193
0
          ((elem[_oidc_strlen(needle)] == '\0') ||
194
0
           (elem[_oidc_strlen(needle)] == postfix_separator))) {
195
0
        rc = TRUE;
196
0
        break;
197
0
      }
198
0
      elem = apr_strtok(NULL, separator, &ctx);
199
0
    }
200
0
  }
201
0
  return rc;
202
0
}
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
0
const char *oidc_http_hdr_in_user_agent_get(const request_rec *r) {
301
0
  return oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_USER_AGENT);
302
0
}
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
0
apr_byte_t oidc_http_hdr_in_accept_contains(const request_rec *r, const char *needle) {
357
0
  return oidc_http_hdr_in_contains(r, OIDC_HTTP_HDR_ACCEPT, OIDC_STR_COMMA, OIDC_CHAR_SEMI_COLON, needle);
358
0
}
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
0
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
0
  const char *separators = "()<>@,;:\\\"/[]?={} \t";
459
460
0
  char *ns = apr_pstrdup(r->pool, str);
461
0
  const size_t len = _oidc_strlen(ns);
462
0
  for (size_t i = 0; i < len; i++) {
463
0
    if (ns[i] < 32 || ns[i] == 127)
464
0
      ns[i] = '-';
465
0
    else if (strchr(separators, ns[i]) != NULL)
466
0
      ns[i] = '-';
467
0
  }
468
0
  return ns;
469
0
}
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
  /* grow the buffer geometrically: pool memory is only released when the request ends, so allocating a new
488
   * buffer of the exact size for every chunk would keep all earlier copies around and use memory quadratic
489
   * in the response size */
490
0
  const size_t needed = mem->size + realsize + 1;
491
0
  if (needed > mem->capacity) {
492
0
    size_t capacity = mem->capacity * 2;
493
0
    if (capacity > OIDC_CURL_RESPONSE_DATA_SIZE_MAX + 1)
494
0
      capacity = OIDC_CURL_RESPONSE_DATA_SIZE_MAX + 1;
495
0
    if (capacity < needed)
496
0
      capacity = needed;
497
498
0
    char *newptr = apr_palloc(mem->r->pool, capacity);
499
0
    if (newptr == NULL) {
500
0
      oidc_error(mem->r, "memory allocation for new buffer of %ld bytes failed", (long)capacity);
501
0
      return 0;
502
0
    }
503
504
    /* copy over the data from current memory */
505
0
    if (mem->size > 0)
506
0
      _oidc_memcpy(newptr, mem->memory, mem->size);
507
0
    mem->memory = newptr;
508
0
    mem->capacity = capacity;
509
0
  }
510
511
  /* append the cURL buffer */
512
0
  _oidc_memcpy(&(mem->memory[mem->size]), contents, realsize);
513
0
  mem->size += realsize;
514
0
  mem->memory[mem->size] = 0;
515
516
0
  return realsize;
517
0
}
518
519
/*
520
 * callback for CURL to write response headers that come back from an HTTP call
521
 */
522
0
size_t oidc_http_response_header(const char *buffer, size_t size, size_t nitems, void *userdata) {
523
  /* received header is nitems * size long in 'buffer' NOT ZERO TERMINATED */
524
0
  oidc_curl_resp_hdr_ctx_t *ctx = (oidc_curl_resp_hdr_ctx_t *)userdata;
525
0
  const char *hdr = NULL;
526
0
  char *value = NULL;
527
0
  char *h_name = NULL;
528
0
  apr_ssize_t h_len = 0;
529
0
  int i = 0;
530
531
  /* see if there is a header to search for */
532
0
  if ((ctx->hdrs == NULL) || (apr_hash_count(ctx->hdrs) == 0))
533
0
    goto end;
534
535
  /* make hdr a \0 terminated string for easier processing */
536
0
  hdr = apr_pstrndup(ctx->r->pool, buffer, nitems * size);
537
538
  /* search for a name: value pair */
539
0
  value = _oidc_strstr(hdr, OIDC_STR_COLON);
540
0
  if (value == NULL)
541
0
    goto end;
542
543
  /* split the header name and value */
544
0
  *value = '\0';
545
546
  /* see if there's any header value characters at all after the colon */
547
0
  if (_oidc_strlen(hdr) < nitems * size) {
548
0
    value++;
549
    /* skip spaces after the colon */
550
0
    while (*value == ' ')
551
0
      value++;
552
    /* remove trailing /r/n */
553
0
    i = (int)_oidc_strlen(value) - 1;
554
0
    while ((i >= 0) && ((value[i] == '\r') || (value[i] == '\n')))
555
0
      value[i--] = '\0';
556
0
  }
557
558
  /* check if the caller is interested in the value of the current response header */
559
0
  for (apr_hash_index_t *hi = apr_hash_first(NULL, ctx->hdrs); hi; hi = apr_hash_next(hi)) {
560
0
    apr_hash_this(hi, (const void **)&h_name, &h_len, NULL);
561
0
    if (_oidc_strcasecmp(hdr, h_name) == 0) {
562
0
      oidc_debug(ctx->r, "returning response header: %s: %s", h_name, value);
563
0
      apr_hash_set(ctx->hdrs, h_name, APR_HASH_KEY_STRING, apr_pstrdup(ctx->r->pool, value));
564
0
      break;
565
0
    }
566
0
  }
567
568
0
end:
569
570
0
  return nitems * size;
571
0
}
572
573
/* context structure for encoding parameters */
574
typedef struct oidc_http_encode_t {
575
  request_rec *r;
576
  /* the encoded "key=value" fragments, joined once with "&" by the callers below; accumulating
577
   * them into a growing string per parameter instead made the work quadratic in the number of
578
   * parameters, which a request-controlled parameter count can turn into an OOM (OSS-Fuzz 551746349) */
579
  apr_array_header_t *elems;
580
} oidc_http_encode_t;
581
582
/*
583
 * names of protocol parameters that carry secrets or tokens and must be redacted
584
 * before a request URL/body is written to the debug log; the single source of truth
585
 * for both the per-parameter check and the body redaction below
586
 */
587
static const char *_oidc_http_sensitive_params[] = {OIDC_PROTO_CLIENT_SECRET, OIDC_PROTO_CLIENT_ASSERTION,
588
                OIDC_PROTO_CODE, OIDC_PROTO_CODE_VERIFIER, OIDC_PROTO_REFRESH_TOKEN,
589
                OIDC_PROTO_ACCESS_TOKEN,
590
                /* inbound: the front-channel authorization response and the
591
                 * back-channel logout request carry these */
592
                OIDC_PROTO_ID_TOKEN, OIDC_PROTO_LOGOUT_TOKEN, NULL};
593
594
0
apr_byte_t oidc_http_param_is_sensitive(const char *key) {
595
0
  for (int i = 0; _oidc_http_sensitive_params[i] != NULL; i++)
596
0
    if (_oidc_strcmp(key, _oidc_http_sensitive_params[i]) == 0)
597
0
      return TRUE;
598
0
  return FALSE;
599
0
}
600
601
/*
602
 * TRUE when the name_len-byte parameter name is one of _oidc_http_sensitive_params
603
 */
604
0
static apr_byte_t oidc_http_param_name_is_sensitive(const char *name, size_t name_len) {
605
0
  for (int i = 0; _oidc_http_sensitive_params[i] != NULL; i++)
606
0
    if ((_oidc_strlen(_oidc_http_sensitive_params[i]) == name_len) &&
607
0
        (_oidc_strncmp(name, _oidc_http_sensitive_params[i], name_len) == 0))
608
0
      return TRUE;
609
0
  return FALSE;
610
0
}
611
612
/*
613
 * best-effort redaction of the well-known sensitive parameters listed in
614
 * _oidc_http_sensitive_params inside a URL-form-encoded request body, for debug-log
615
 * purposes; JSON request bodies do not carry these parameters in this codebase and
616
 * are therefore left untouched
617
 */
618
0
const char *oidc_http_redact_body_for_log(request_rec *r, const char *data) {
619
0
  size_t n_params = 1;
620
621
0
  if (data == NULL)
622
0
    return NULL;
623
  /* OIDCDebugMaskSecrets Off, or no request pool to build the redacted copy in */
624
0
  if ((r == NULL) || (oidc_util_log_mask_secrets(r) == FALSE))
625
0
    return data;
626
0
  apr_pool_t *pool = r->pool;
627
628
  /*
629
   * one pass over the "&"-separated parameters into a single buffer: rewriting the whole
630
   * string with an apr_psprintf() per redacted value made the work quadratic in the number
631
   * of sensitive parameters, which a request-sized body turned into an OOM (OSS-Fuzz
632
   * 554483423); a redacted value only ever grows to the 3 bytes of "***", so the result fits
633
   * in the input length plus 3 bytes per parameter
634
   */
635
0
  for (const char *c = data; *c != '\0'; c++)
636
0
    if (*c == OIDC_CHAR_AMP)
637
0
      n_params++;
638
0
  char *result = apr_palloc(pool, _oidc_strlen(data) + (3 * n_params) + 1);
639
0
  char *out = result;
640
0
  const char *param = data;
641
642
0
  for (;;) {
643
0
    const char *param_end = strchr(param, OIDC_CHAR_AMP);
644
0
    const size_t param_len = (param_end != NULL) ? (size_t)(param_end - param) : _oidc_strlen(param);
645
0
    const char *eq = memchr(param, OIDC_CHAR_EQUAL, param_len);
646
    /* only a "<name>=" at the parameter boundary is redacted, so that e.g. "xcode=" is not taken for
647
     * "code=" */
648
0
    const apr_byte_t redact =
649
0
        ((eq != NULL) && (oidc_http_param_name_is_sensitive(param, (size_t)(eq - param)) == TRUE)) ? TRUE
650
0
                                 : FALSE;
651
    /* everything up to and including the "<name>=" is kept */
652
0
    const size_t keep = (redact == TRUE) ? (size_t)(eq - param) + 1 : param_len;
653
654
0
    _oidc_memcpy(out, param, keep);
655
0
    out += keep;
656
0
    if (redact == TRUE) {
657
0
      _oidc_memcpy(out, "***", 3);
658
0
      out += 3;
659
0
    }
660
0
    if (param_end == NULL)
661
0
      break;
662
0
    *out = OIDC_CHAR_AMP;
663
0
    out++;
664
0
    param = param_end + 1;
665
0
  }
666
0
  *out = '\0';
667
668
0
  return result;
669
0
}
670
671
/* Sensitive response members differ from the request parameters redacted above. */
672
static const char *_oidc_http_sensitive_json_members[] = {OIDC_PROTO_ACCESS_TOKEN,     OIDC_PROTO_REFRESH_TOKEN,
673
                OIDC_PROTO_ID_TOKEN,         OIDC_PROTO_CLIENT_SECRET,
674
                "registration_access_token", NULL};
675
676
/*
677
 * Redact quoted JSON members without parsing, so malformed or non-JSON responses remain
678
 * loggable and otherwise unchanged. This scanner assumes a sensitive value has no escaped quote:
679
 * the next quote terminates the value.
680
 * The input is copied through into a single buffer with each value replaced as it is found:
681
 * rewriting the whole string with an apr_psprintf() per member made the work quadratic in the
682
 * number of sensitive members, which a response-sized body turned into an OOM (OSS-Fuzz
683
 * 554483423). A match consumes at least the member name, the colon and the opening quote and
684
 * adds at most the 3 bytes of "***", which bounds the size of the result.
685
 */
686
0
static const char *oidc_http_redact_json_member(apr_pool_t *pool, const char *data, const char *needle) {
687
0
  const apr_size_t needle_len = _oidc_strlen(needle);
688
0
  const apr_size_t data_len = _oidc_strlen(data);
689
0
  char *result = apr_palloc(pool, data_len + (3 * ((data_len / (needle_len + 2)) + 1)) + 1);
690
0
  char *out = result;
691
  /* the input up to here has been copied to the output */
692
0
  const char *copied = data;
693
  /* the scan cursor */
694
0
  const char *pos = data;
695
0
  size_t len = 0;
696
697
0
  for (;;) {
698
0
    const char *match = _oidc_strstr(pos, needle);
699
0
    if (match == NULL)
700
0
      break;
701
    /* step over the member name and expect ": " then a quoted value; anything
702
     * else (a non-string value, a member name appearing inside a value) is
703
     * skipped rather than guessed at */
704
0
    const char *p = match + needle_len;
705
0
    while ((*p == ' ') || (*p == '\t'))
706
0
      p++;
707
0
    if (*p != OIDC_CHAR_COLON) {
708
0
      pos = match + needle_len;
709
0
      continue;
710
0
    }
711
0
    p++;
712
0
    while ((*p == ' ') || (*p == '\t'))
713
0
      p++;
714
0
    if (*p != OIDC_CHAR_DQUOTE) {
715
0
      pos = match + needle_len;
716
0
      continue;
717
0
    }
718
0
    p++;
719
    /* copy everything up to and including the opening quote, then the mask */
720
0
    len = (size_t)(p - copied);
721
0
    _oidc_memcpy(out, copied, len);
722
0
    out += len;
723
0
    _oidc_memcpy(out, "***", 3);
724
0
    out += 3;
725
    /* If the closing quote is missing, mask to the end rather than leak a partial token; the
726
     * closing quote itself is copied with the next chunk and the scan resumes after the mask. */
727
0
    const char *value_end = strchr(p, OIDC_CHAR_DQUOTE);
728
0
    copied = (value_end != NULL) ? value_end : p + _oidc_strlen(p);
729
0
    pos = copied;
730
0
  }
731
732
0
  len = _oidc_strlen(copied);
733
0
  _oidc_memcpy(out, copied, len);
734
0
  out += len;
735
0
  *out = '\0';
736
737
0
  return result;
738
0
}
739
740
0
const char *oidc_http_redact_json_for_log(request_rec *r, const char *data) {
741
0
  const char *result = NULL;
742
743
0
  if (data == NULL)
744
0
    return NULL;
745
  /* OIDCDebugMaskSecrets Off, or no request pool to build the redacted copy in */
746
0
  if ((r == NULL) || (oidc_util_log_mask_secrets(r) == FALSE))
747
0
    return data;
748
0
  apr_pool_t *pool = r->pool;
749
750
0
  result = data;
751
0
  for (int i = 0; _oidc_http_sensitive_json_members[i] != NULL; i++)
752
0
    result = oidc_http_redact_json_member(
753
0
        pool, result, apr_pstrcat(pool, "\"", _oidc_http_sensitive_json_members[i], "\"", NULL));
754
755
0
  return result;
756
0
}
757
758
/*
759
 * add a url-form-encoded name/value pair
760
 */
761
0
static int oidc_http_add_form_url_encoded_param(void *rec, const char *key, const char *value) {
762
0
  oidc_http_encode_t *ctx = (oidc_http_encode_t *)rec;
763
0
  APR_ARRAY_PUSH(ctx->elems, const char *) =
764
0
      apr_psprintf(ctx->r->pool, "%s=%s", oidc_http_url_encode(ctx->r, key), oidc_http_url_encode(ctx->r, value));
765
0
  return 1;
766
0
}
767
768
/*
769
 * add a name/value pair to a form-encoded string used only for debug logging, redacting
770
 * the value when the parameter name is a known secret/token
771
 */
772
0
static int oidc_http_add_form_encoded_param_for_log(void *rec, const char *key, const char *value) {
773
0
  oidc_http_encode_t *ctx = (oidc_http_encode_t *)rec;
774
0
  const char *safe_value = value ? value : "";
775
0
  const char *v = oidc_http_param_is_sensitive(key) ? "***" : safe_value;
776
0
  APR_ARRAY_PUSH(ctx->elems, const char *) = apr_psprintf(ctx->r->pool, "%s=%s", key, v);
777
0
  return 1;
778
0
}
779
780
/*
781
 * run one of the per-parameter encoders above over a table and join the collected "key=value"
782
 * fragments into a single "&"-separated string in one allocation; returns NULL when there are none
783
 */
784
static char *oidc_http_encode_params(request_rec *r, const apr_table_t *params,
785
0
             int (*encoder)(void *, const char *, const char *)) {
786
0
  oidc_http_encode_t ctx = {r, apr_array_make(r->pool, apr_table_elts(params)->nelts, sizeof(const char *))};
787
0
  apr_table_do(encoder, &ctx, params, NULL);
788
0
  return (ctx.elems->nelts > 0) ? apr_array_pstrcat(r->pool, ctx.elems, OIDC_CHAR_AMP) : NULL;
789
0
}
790
791
/*
792
 * construct a URL with query parameters
793
 */
794
0
char *oidc_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params) {
795
0
  char *result = NULL;
796
0
  if (url == NULL) {
797
0
    oidc_error(r, "URL is NULL");
798
0
    return NULL;
799
0
  }
800
0
  if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
801
0
    const char *encoded_params = oidc_http_encode_params(r, params, oidc_http_add_form_url_encoded_param);
802
0
    const char *sep = NULL;
803
0
    if (encoded_params)
804
0
      sep = strchr(url, OIDC_CHAR_QUERY) != NULL ? OIDC_STR_AMP : OIDC_STR_QUERY;
805
0
    result = apr_psprintf(r->pool, "%s%s%s", url, sep ? sep : "", encoded_params ? encoded_params : "");
806
807
0
    const char *log_params = oidc_http_encode_params(r, params, oidc_http_add_form_encoded_param_for_log);
808
0
    oidc_debug(r, "url=%s%s%s", url, sep ? sep : "", log_params ? log_params : "");
809
0
  } else {
810
0
    result = apr_pstrdup(r->pool, url);
811
0
    oidc_debug(r, "url=%s", result);
812
0
  }
813
0
  return result;
814
0
}
815
816
/*
817
 * construct form-encoded POST data
818
 */
819
0
char *oidc_http_form_encoded_data(request_rec *r, const apr_table_t *params) {
820
0
  char *data = NULL;
821
0
  if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
822
0
    data = oidc_http_encode_params(r, params, oidc_http_add_form_url_encoded_param);
823
824
0
    const char *log_params = oidc_http_encode_params(r, params, oidc_http_add_form_encoded_param_for_log);
825
0
    oidc_debug(r, "data=%s", log_params ? log_params : "(null)");
826
0
  } else {
827
0
    oidc_debug(r, "data=(null)");
828
0
  }
829
0
  return data;
830
0
}
831
832
/*
833
 * call curl_easy_setopt with error checking and reporting
834
 */
835
#define OIDC_HTTP_CURL_SETOPT_PARMS(r, curl, code, option, ...)                                                        \
836
0
  code = curl_easy_setopt(curl, option, __VA_ARGS__);                                                            \
837
0
  if (code != CURLE_OK)                                                                                          \
838
0
  oidc_error(r, "curl_easy_setopt(%s) failed with: %s", #option, curl_easy_strerror(code))
839
840
0
#define OIDC_HTTP_CURL_SETOPT(...) OIDC_HTTP_CURL_SETOPT_PARMS(r, curl, code, __VA_ARGS__)
841
842
/*
843
 * set libcurl SSL options
844
 */
845
846
#define OIDC_HTTP_CURL_SETOPT_SSL(option, value)                                                                       \
847
0
  if (_oidc_strstr(env_var_value, #value) != NULL) {                                                             \
848
0
    oidc_debug(r, "curl_easy_setopt(%s): %s (%ld)", #option, #value, (long)value);                         \
849
0
    OIDC_HTTP_CURL_SETOPT(option, (long)value);                                                            \
850
0
  }
851
852
0
static void oidc_http_set_curl_ssl_options(request_rec *r, CURL *curl) {
853
  // NB: the variable names r, curl, code and env_var_value are used in the OIDC_HTTP_CURL_SETOPT_SSL macro
854
0
  const char *env_var_value = NULL;
855
0
  CURLcode code = CURLE_OK;
856
0
  if (r->subprocess_env != NULL)
857
0
    env_var_value = apr_table_get(r->subprocess_env, OIDC_CURLOPT_SSL_OPTIONS_ENV_VAR_NAME);
858
0
  if (env_var_value == NULL)
859
0
    return;
860
0
  oidc_debug(r, "SSL options environment variable %s=%s found", OIDC_CURLOPT_SSL_OPTIONS_ENV_VAR_NAME,
861
0
       env_var_value);
862
0
#if LIBCURL_VERSION_NUM >= 0x071900
863
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST);
864
0
#endif
865
0
#if LIBCURL_VERSION_NUM >= 0x072c00
866
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE)
867
0
#endif
868
0
#if LIBCURL_VERSION_NUM >= 0x074400
869
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_PARTIALCHAIN)
870
0
#endif
871
0
#if LIBCURL_VERSION_NUM >= 0x074600
872
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT)
873
0
#endif
874
0
#if LIBCURL_VERSION_NUM >= 0x074700
875
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA)
876
0
#endif
877
0
#if LIBCURL_VERSION_NUM >= 0x072200
878
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0)
879
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1)
880
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2)
881
0
#endif
882
0
#if LIBCURL_VERSION_NUM >= 0x073400
883
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3)
884
0
#endif
885
0
#if LIBCURL_VERSION_NUM >= 0x073600
886
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_0)
887
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_1)
888
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_2)
889
0
  OIDC_HTTP_CURL_SETOPT_SSL(CURLOPT_SSLVERSION, CURL_SSLVERSION_MAX_TLSv1_3)
890
0
#endif
891
0
}
892
893
/*
894
 * construct our User-Agent header for outgoing requests
895
 */
896
0
const char *oidc_http_user_agent(request_rec *r) {
897
0
  const char *s_useragent = apr_table_get(r->subprocess_env, OIDC_USER_AGENT_ENV_VAR);
898
0
  if (s_useragent == NULL) {
899
0
    s_useragent = apr_psprintf(r->pool, "[%s:%u:%lu] %s", r->server->server_hostname,
900
0
             r->connection->local_addr->port, (unsigned long)getpid(), NAMEVERSION);
901
0
    s_useragent = apr_psprintf(r->pool, "%s libcurl-%s %s", s_useragent, LIBCURL_VERSION,
902
0
             oidc_util_openssl_version(r->pool));
903
0
  }
904
0
  return s_useragent;
905
0
}
906
907
/*
908
 * construct our local address/interface for outgoing requests
909
 */
910
0
const char *oidc_http_interface(const request_rec *r) {
911
0
  return apr_table_get(r->subprocess_env, OIDC_CURL_INTERFACE_ENV_VAR);
912
0
}
913
914
/*
915
 * configure the CA bundle for SSL server certificate verification, falling
916
 * back to the system curl-ca-bundle.crt on Windows when no explicit bundle
917
 * was configured
918
 */
919
0
static void oidc_http_request_setup_ca_bundle(request_rec *r, CURL *curl, const oidc_cfg_t *c) {
920
  // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro
921
0
  CURLcode code = CURLE_OK;
922
923
0
  if (oidc_cfg_ca_bundle_path_get(c) != NULL) {
924
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_CAINFO, oidc_cfg_ca_bundle_path_get(c));
925
0
    return;
926
0
  }
927
928
#ifdef WIN32
929
  DWORD buflen;
930
  char *ptr = NULL;
931
  char *retval = (char *)malloc(sizeof(TCHAR) * (MAX_PATH + 1));
932
  retval[0] = '\0';
933
  buflen = SearchPath(NULL, "curl-ca-bundle.crt", NULL, MAX_PATH + 1, retval, &ptr);
934
  if (buflen > 0) {
935
    OIDC_HTTP_CURL_SETOPT(CURLOPT_CAINFO, retval);
936
  } else {
937
    oidc_warn(r, "no curl-ca-bundle.crt file found in path");
938
  }
939
  free(retval);
940
#endif
941
0
}
942
943
/*
944
 * configure the curl handle to use the optional outgoing proxy, including
945
 * its credentials and authentication type when supplied
946
 */
947
static void oidc_http_request_setup_proxy(request_rec *r, CURL *curl,
948
0
            const oidc_http_outgoing_proxy_t *outgoing_proxy) {
949
  // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro
950
0
  CURLcode code = CURLE_OK;
951
952
0
  if (outgoing_proxy->host_port == NULL)
953
0
    return;
954
955
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXY, outgoing_proxy->host_port);
956
0
  if (outgoing_proxy->username_password) {
957
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXYUSERPWD, outgoing_proxy->username_password);
958
0
  }
959
0
  if (outgoing_proxy->auth_type != (unsigned long)OIDC_CONFIG_POS_INT_UNSET) {
960
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_PROXYAUTH, outgoing_proxy->auth_type);
961
0
  }
962
0
}
963
964
/*
965
 * build the list of custom request headers (authorization, content-type,
966
 * traceparent, DPoP) to pass on to curl
967
 */
968
struct curl_slist *oidc_http_request_build_header_list(request_rec *r, const oidc_cfg_t *c, const char *content_type,
969
0
                   const char *access_token, const char *dpop) {
970
0
  struct curl_slist *h_list = NULL;
971
972
  /* see if we need to add token in the Bearer/DPoP Authorization header */
973
0
  if (access_token != NULL)
974
0
    h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s %s", OIDC_HTTP_HDR_AUTHORIZATION,
975
0
                dpop ? "DPoP" : "Bearer", access_token));
976
977
0
  if (content_type != NULL)
978
0
    h_list = curl_slist_append(h_list,
979
0
             apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_CONTENT_TYPE, content_type));
980
981
0
  const char *traceparent = oidc_http_hdr_in_traceparent_get(r);
982
0
  if (traceparent && oidc_cfg_trace_parent_get(c) != OIDC_TRACE_PARENT_OFF) {
983
0
    oidc_debug(r, "propagating traceparent header: %s", traceparent);
984
0
    h_list =
985
0
        curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_TRACE_PARENT, traceparent));
986
    /* tracestate accompanies traceparent (W3C Trace Context section 3.3) so propagate it alongside */
987
0
    const char *tracestate = oidc_http_hdr_in_get(r, OIDC_HTTP_HDR_TRACE_STATE);
988
0
    if (tracestate != NULL) {
989
0
      oidc_debug(r, "propagating tracestate header: %s", tracestate);
990
0
      h_list = curl_slist_append(
991
0
          h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_TRACE_STATE, tracestate));
992
0
    }
993
0
  }
994
995
0
  if (dpop != NULL) {
996
0
    oidc_debug(r, "appending DPoP header (len=%d)", (int)_oidc_strlen(dpop));
997
0
    h_list = curl_slist_append(h_list, apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_DPOP, dpop));
998
0
  }
999
1000
0
  return h_list;
1001
0
}
1002
1003
/*
1004
 * pass cookies from the incoming request through to the curl handle by
1005
 * concatenating the configured cookies into a single Cookie header value
1006
 */
1007
0
static void oidc_http_request_pass_cookies(request_rec *r, CURL *curl, const apr_array_header_t *pass_cookies) {
1008
  // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro
1009
0
  CURLcode code = CURLE_OK;
1010
0
  char *cookie_string = NULL;
1011
1012
0
  if (pass_cookies == NULL)
1013
0
    return;
1014
1015
0
  for (int i = 0; i < pass_cookies->nelts; i++) {
1016
0
    const char *cookie_name = APR_ARRAY_IDX(pass_cookies, i, const char *);
1017
0
    char *cookie_value = oidc_http_get_cookie(r, cookie_name);
1018
0
    if (cookie_value == NULL)
1019
0
      continue;
1020
0
    cookie_string = (cookie_string == NULL)
1021
0
            ? apr_psprintf(r->pool, "%s=%s", cookie_name, cookie_value)
1022
0
            : apr_psprintf(r->pool, "%s; %s=%s", cookie_string, cookie_name, cookie_value);
1023
0
  }
1024
1025
0
  if (cookie_string == NULL)
1026
0
    return;
1027
1028
0
  oidc_debug(r, "passing browser cookies on backend call: %s", cookie_string);
1029
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_COOKIE, cookie_string);
1030
0
}
1031
1032
/*
1033
 * execute the curl request honoring the configured retry policy; retries
1034
 * are skipped on a request/transfer timeout and short-circuited on success
1035
 */
1036
static apr_byte_t oidc_http_request_perform_with_retries(request_rec *r, const oidc_cfg_t *c, CURL *curl,
1037
               const char *url, const char *curl_err,
1038
0
               const oidc_http_timeout_t *http_timeout) {
1039
0
  CURLcode res = CURLE_OK;
1040
1041
0
  for (int i = 0; i <= http_timeout->retries; i++) {
1042
0
    res = curl_easy_perform(curl);
1043
0
    if (res == CURLE_OK)
1044
0
      return TRUE;
1045
0
    if (res == CURLE_OPERATION_TIMEDOUT) {
1046
      /* in case of a request/transfer timeout (which includes the connect timeout) we'll not retry */
1047
0
      oidc_error(r, "curl_easy_perform failed with a timeout for %s: [%s]; won't retry", url,
1048
0
           curl_err[0] ? curl_err : "<n/a>");
1049
0
      OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_CONNECT_ERROR,
1050
0
                   curl_err[0] ? curl_err : "timeout");
1051
0
      return FALSE;
1052
0
    }
1053
0
    oidc_error(r, "curl_easy_perform(%d/%d) failed for %s with: [%s]", i + 1, http_timeout->retries + 1,
1054
0
         url, curl_err[0] ? curl_err : "<n/a>");
1055
0
    OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_CONNECT_ERROR, curl_err[0] ? curl_err : "undefined");
1056
    /* in case of a connectivity/network glitch we'll back off before retrying */
1057
0
    if (i < http_timeout->retries)
1058
0
      apr_sleep(apr_time_from_msec(http_timeout->retry_interval));
1059
0
  }
1060
1061
0
  return FALSE;
1062
0
}
1063
1064
/*
1065
 * execute a HTTP (GET or POST) request
1066
 */
1067
static apr_byte_t oidc_http_request(request_rec *r, const char *url, const char *data, const char *content_type,
1068
            const char *basic_auth, const char *access_token, const char *dpop,
1069
            int ssl_validate_server, char **response, long *response_code,
1070
            apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout,
1071
            const oidc_http_outgoing_proxy_t *outgoing_proxy,
1072
            const apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key,
1073
0
            const char *ssl_key_pwd) {
1074
1075
  // NB: the variable names r, curl, and code are used in the OIDC_HTTP_CURL_SETOPT macro
1076
0
  CURL *curl = NULL;
1077
0
  CURLcode code = CURLE_OK;
1078
0
  char curl_err[CURL_ERROR_SIZE];
1079
0
  oidc_curl_resp_data_ctx_t d_buf = {r, NULL, 0, 0};
1080
0
  oidc_curl_resp_hdr_ctx_t h_buf = {r, response_hdrs};
1081
0
  struct curl_slist *h_list = NULL;
1082
0
  long http_code = 0;
1083
0
  apr_byte_t rv = FALSE;
1084
0
  const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1085
1086
  /* do some logging about the inputs */
1087
0
  oidc_debug(r,
1088
0
       "url=%s, data=%s, content_type=%s, basic_auth=%s, access_token=%s, dpop=%s, ssl_validate_server=%d, "
1089
0
       "request_timeout=%d, connect_timeout=%d, retries=%d, retry_interval=%d, outgoing_proxy=%s:%s:%d, "
1090
0
       "pass_cookies=%pp, ssl_cert=%s, ssl_key=%s, ssl_key_pwd=%s",
1091
0
       url, oidc_http_redact_body_for_log(r, data), content_type, basic_auth ? "****" : "null",
1092
0
       oidc_util_mask_value(r, access_token), dpop, ssl_validate_server, http_timeout->request_timeout,
1093
0
       http_timeout->connect_timeout, http_timeout->retries, http_timeout->retry_interval,
1094
0
       outgoing_proxy->host_port, outgoing_proxy->username_password ? "****" : "(null)",
1095
0
       (int)outgoing_proxy->auth_type, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd ? "****" : "(null)");
1096
1097
  /* an endpoint that is not configured is a caller error, not a transfer failure: fail here rather
1098
   * than hand curl a NULL URL and then back off and retry a request that can never be sent */
1099
0
  if (url == NULL) {
1100
0
    oidc_error(r, "no URL to send the request to: the endpoint is not configured");
1101
0
    return FALSE;
1102
0
  }
1103
1104
0
  curl = oidc_http_curl_acquire();
1105
0
  if (curl == NULL) {
1106
0
    oidc_error(r, "could not obtain a curl handle");
1107
0
    goto end;
1108
0
  }
1109
1110
  /* set the error buffer as empty before performing a request */
1111
0
  curl_err[0] = 0;
1112
1113
  /* some of these are not really required */
1114
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADER, 0L);
1115
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_NOPROGRESS, 1L);
1116
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_NOSIGNAL, 1L);
1117
  /* keep reused connections alive across the (potentially long) intervals between calls */
1118
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_TCP_KEEPALIVE, 1L);
1119
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_ERRORBUFFER, curl_err);
1120
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_FOLLOWLOCATION, 1L);
1121
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_MAXREDIRS, 5L);
1122
1123
  /* set the timeouts */
1124
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_TIMEOUT, (long)http_timeout->request_timeout);
1125
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_CONNECTTIMEOUT, (long)http_timeout->connect_timeout);
1126
1127
  /* setup the buffer where the response data will be written to */
1128
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_WRITEFUNCTION, oidc_http_response_data);
1129
  /* coverity[bad_sizeof] */
1130
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_WRITEDATA, &d_buf);
1131
1132
  /* setup the buffer where the response headers will be written to */
1133
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADERFUNCTION, oidc_http_response_header);
1134
  /* coverity[bad_sizeof] */
1135
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_HEADERDATA, &h_buf);
1136
1137
0
#ifndef LIBCURL_NO_CURLPROTO
1138
0
#if LIBCURL_VERSION_NUM >= 0x075500
1139
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
1140
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_PROTOCOLS_STR, "http,https");
1141
#else
1142
  OIDC_HTTP_CURL_SETOPT(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
1143
  OIDC_HTTP_CURL_SETOPT(CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
1144
#endif
1145
0
#endif
1146
1147
  /* set the options for validating the SSL server certificate that the remote site presents */
1148
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_SSL_VERIFYPEER, (ssl_validate_server != FALSE ? 1L : 0L));
1149
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_SSL_VERIFYHOST, (ssl_validate_server != FALSE ? 2L : 0L));
1150
1151
0
  oidc_http_set_curl_ssl_options(r, curl);
1152
1153
0
  oidc_http_request_setup_ca_bundle(r, curl, c);
1154
1155
  /* identify this HTTP client */
1156
0
  const char *s_useragent = oidc_http_user_agent(r);
1157
0
  if ((s_useragent != NULL) && (_oidc_strcmp(s_useragent, "") != 0)) {
1158
0
    oidc_debug(r, "set HTTP request header User-Agent to: %s", s_useragent);
1159
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_USERAGENT, s_useragent);
1160
0
  }
1161
1162
  /* set the local interface if defined */
1163
0
  const char *s_interface = oidc_http_interface(r);
1164
0
  if ((s_interface != NULL) && (_oidc_strcmp(s_interface, "") != 0)) {
1165
0
#if LIBCURL_VERSION_NUM >= 0x073000
1166
0
    oidc_debug(r, "set local interface to: %s", s_interface);
1167
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_INTERFACE, s_interface);
1168
#else
1169
    oidc_warn(
1170
        r, "local interface is configured to %s, but the cURL version in use does not support setting this",
1171
        s_interface);
1172
#endif
1173
0
  }
1174
1175
0
  oidc_http_request_setup_proxy(r, curl, outgoing_proxy);
1176
1177
  /* see if we need to perform HTTP basic authentication to the remote site */
1178
0
  if (basic_auth != NULL) {
1179
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
1180
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_USERPWD, basic_auth);
1181
0
  }
1182
1183
0
  if (ssl_cert != NULL) {
1184
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_SSLCERT, ssl_cert);
1185
0
  }
1186
0
  if (ssl_key != NULL) {
1187
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_SSLKEY, ssl_key);
1188
0
  }
1189
0
  if (ssl_key_pwd != NULL) {
1190
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_KEYPASSWD, ssl_key_pwd);
1191
0
  }
1192
1193
0
  if (data != NULL) {
1194
    /* set POST data and switch HTTP method to POST */
1195
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_POSTFIELDS, data);
1196
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_POST, 1L);
1197
0
  }
1198
1199
0
  h_list = oidc_http_request_build_header_list(r, c, content_type, access_token, dpop);
1200
0
  if (h_list != NULL) {
1201
0
    OIDC_HTTP_CURL_SETOPT(CURLOPT_HTTPHEADER, h_list);
1202
0
  }
1203
1204
0
  oidc_http_request_pass_cookies(r, curl, pass_cookies);
1205
1206
  /* set the target URL */
1207
0
  OIDC_HTTP_CURL_SETOPT(CURLOPT_URL, url);
1208
1209
  /* call it and record the result */
1210
0
  rv = oidc_http_request_perform_with_retries(r, c, curl, url, curl_err, http_timeout);
1211
0
  if (rv == FALSE)
1212
0
    goto end;
1213
1214
0
  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
1215
0
  oidc_debug(r, "HTTP response code=%ld", http_code);
1216
1217
0
  OIDC_METRICS_COUNTER_INC_VALUE(r, c, OM_PROVIDER_HTTP_RESPONSE_CODE, apr_psprintf(r->pool, "%ld", http_code));
1218
1219
0
  *response = apr_pstrmemdup(r->pool, d_buf.memory, d_buf.size);
1220
0
  if (response_code)
1221
0
    *response_code = http_code;
1222
1223
  /* set and log the response; the token, introspection and registration responses carry
1224
   * credentials, so redact them the way the request body above already is */
1225
0
  oidc_debug(r, "response=%s", oidc_http_redact_json_for_log(r, *response ? *response : ""));
1226
1227
0
end:
1228
1229
  /* cleanup and return the result; a handle that performed successfully goes back to the
1230
   * pool with its connection kept alive for the next request to the same endpoint */
1231
0
  if (h_list != NULL)
1232
0
    curl_slist_free_all(h_list);
1233
0
  oidc_http_curl_release(curl, rv);
1234
1235
0
  return rv;
1236
0
}
1237
1238
/*
1239
 * execute HTTP GET request
1240
 */
1241
apr_byte_t oidc_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth,
1242
       const char *access_token, const char *dpop, int ssl_validate_server, char **response,
1243
       long *response_code, apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout,
1244
       const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies,
1245
0
       const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) {
1246
0
  const char *query_url = oidc_http_query_encoded_url(r, url, params);
1247
0
  return oidc_http_request(r, query_url, NULL, NULL, basic_auth, access_token, dpop, ssl_validate_server,
1248
0
         response, response_code, response_hdrs, http_timeout, outgoing_proxy, pass_cookies,
1249
0
         ssl_cert, ssl_key, ssl_key_pwd);
1250
0
}
1251
1252
/*
1253
 * execute HTTP POST request with form-encoded data
1254
 */
1255
apr_byte_t oidc_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth,
1256
             const char *access_token, const char *dpop, int ssl_validate_server, char **response,
1257
             long *response_code, apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout,
1258
             const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies,
1259
0
             const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) {
1260
0
  const char *data = oidc_http_form_encoded_data(r, params);
1261
0
  return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_FORM_ENCODED, basic_auth, access_token, dpop,
1262
0
         ssl_validate_server, response, response_code, response_hdrs, http_timeout,
1263
0
         outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd);
1264
0
}
1265
1266
/*
1267
 * execute HTTP POST request with JSON-encoded data
1268
 */
1269
apr_byte_t oidc_http_post_json(request_rec *r, const char *url, const oidc_json_t *json, const char *basic_auth,
1270
             const char *access_token, const char *dpop, int ssl_validate_server, char **response,
1271
             long *response_code, apr_hash_t *response_hdrs, const oidc_http_timeout_t *http_timeout,
1272
             const oidc_http_outgoing_proxy_t *outgoing_proxy, const apr_array_header_t *pass_cookies,
1273
0
             const char *ssl_cert, const char *ssl_key, const char *ssl_key_pwd) {
1274
0
  const char *data =
1275
0
      json != NULL ? oidc_json_encode(r->pool, json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT) : NULL;
1276
0
  return oidc_http_request(r, url, data, OIDC_HTTP_CONTENT_TYPE_JSON, basic_auth, access_token, dpop,
1277
0
         ssl_validate_server, response, response_code, response_hdrs, http_timeout,
1278
0
         outgoing_proxy, pass_cookies, ssl_cert, ssl_key, ssl_key_pwd);
1279
0
}
1280
1281
/*
1282
 * get the current path from the request in a normalized way
1283
 */
1284
0
static char *oidc_http_get_path(request_rec *r) {
1285
0
  size_t i;
1286
0
  const char *p;
1287
0
  p = r->parsed_uri.path;
1288
0
  if ((p == NULL) || (p[0] == '\0'))
1289
0
    return apr_pstrdup(r->pool, OIDC_STR_FORWARD_SLASH);
1290
0
  for (i = _oidc_strlen(p) - 1; i > 0; i--)
1291
0
    if (p[i] == OIDC_CHAR_FORWARD_SLASH)
1292
0
      break;
1293
0
  return apr_pstrndup(r->pool, p, i + 1);
1294
0
}
1295
1296
/*
1297
 * get the cookie path setting and check that it matches the request path; cook it up if it is not set
1298
 */
1299
0
static const char *oidc_http_get_cookie_path(request_rec *r) {
1300
0
  const char *rv = NULL;
1301
0
  char *requestPath = oidc_http_get_path(r);
1302
0
  const char *cookie_path = oidc_cfg_dir_cookie_path_get(r);
1303
0
  if (cookie_path != NULL) {
1304
0
    if (_oidc_strncmp(cookie_path, requestPath, _oidc_strlen(cookie_path)) == 0)
1305
0
      rv = cookie_path;
1306
0
    else {
1307
0
      oidc_warn(r,
1308
0
          "" OIDCCookiePath
1309
0
          " (%s) is not a substring of request path, using request path (%s) for cookie",
1310
0
          cookie_path, requestPath);
1311
0
      rv = requestPath;
1312
0
    }
1313
0
  } else {
1314
0
    rv = requestPath;
1315
0
  }
1316
0
  return rv;
1317
0
}
1318
1319
0
#define OIDC_HTTP_COOKIE_FLAG_DOMAIN "Domain"
1320
0
#define OIDC_HTTP_COOKIE_FLAG_PATH "Path"
1321
0
#define OIDC_HTTP_COOKIE_FLAG_EXPIRES "Expires"
1322
0
#define OIDC_HTTP_COOKIE_FLAG_SECURE "Secure"
1323
0
#define OIDC_HTTP_COOKIE_FLAG_HTTP_ONLY "HttpOnly"
1324
1325
0
#define OIDC_HTTP_COOKIE_MAX_SIZE 4093
1326
1327
0
#define OIDC_SET_COOKIE_APPEND_ENV_VAR "OIDC_SET_COOKIE_APPEND"
1328
1329
/*
1330
 * obtain the value configured in the OIDC_SET_COOKIE_APPEND environment variable
1331
 * which is to be added to the HTTP Set-Cookie response header
1332
 */
1333
0
static const char *oidc_http_set_cookie_append_value(request_rec *r) {
1334
0
  const char *env_var_value = NULL;
1335
1336
0
  if (r->subprocess_env != NULL)
1337
0
    env_var_value = apr_table_get(r->subprocess_env, OIDC_SET_COOKIE_APPEND_ENV_VAR);
1338
1339
0
  if (env_var_value == NULL) {
1340
0
    oidc_debug(r, "no cookie append environment variable %s found", OIDC_SET_COOKIE_APPEND_ENV_VAR);
1341
0
    return NULL;
1342
0
  }
1343
1344
0
  oidc_debug(r, "cookie append environment variable %s=%s found", OIDC_SET_COOKIE_APPEND_ENV_VAR, env_var_value);
1345
1346
0
  return env_var_value;
1347
0
}
1348
1349
/*
1350
 * set a cookie in the HTTP response headers
1351
 */
1352
void oidc_http_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires,
1353
0
        const char *ext) {
1354
1355
0
  const oidc_cfg_t *c = ap_get_module_config(r->server->module_config, &auth_openidc_module);
1356
0
  char *headerString = NULL;
1357
0
  char *expiresString = NULL;
1358
0
  const char *appendString = NULL;
1359
1360
  /* see if we need to clear the cookie */
1361
0
  if (_oidc_strcmp(cookieValue, "") == 0)
1362
0
    expires = 0;
1363
1364
  /* construct the expire value */
1365
0
  if (expires != -1) {
1366
0
    expiresString = (char *)apr_pcalloc(r->pool, APR_RFC822_DATE_LEN);
1367
0
    if (apr_rfc822_date(expiresString, expires) != APR_SUCCESS) {
1368
0
      oidc_error(r, "could not set cookie expiry date");
1369
0
    }
1370
0
  }
1371
1372
  /* construct the cookie value */
1373
0
  headerString = apr_psprintf(r->pool, "%s=%s", cookieName, cookieValue);
1374
1375
0
  headerString =
1376
0
      apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_PATH, oidc_http_get_cookie_path(r));
1377
1378
0
  if (expiresString != NULL)
1379
0
    headerString =
1380
0
        apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_EXPIRES, expiresString);
1381
1382
0
  if (oidc_cfg_cookie_domain_get(c) != NULL)
1383
0
    headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString, OIDC_HTTP_COOKIE_FLAG_DOMAIN,
1384
0
              oidc_cfg_cookie_domain_get(c));
1385
1386
0
  if (oidc_util_url_cur_is_secure(r, c))
1387
0
    headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_HTTP_COOKIE_FLAG_SECURE);
1388
1389
0
  if (oidc_cfg_cookie_http_only_get(c) != FALSE)
1390
0
    headerString = apr_psprintf(r->pool, "%s; %s", headerString, OIDC_HTTP_COOKIE_FLAG_HTTP_ONLY);
1391
1392
0
  appendString = oidc_http_set_cookie_append_value(r);
1393
0
  if (appendString != NULL)
1394
0
    headerString = apr_psprintf(r->pool, "%s; %s", headerString, appendString);
1395
0
  else if (ext != NULL)
1396
0
    headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext);
1397
1398
  /* sanity check on overall cookie value size */
1399
0
  if (_oidc_strlen(headerString) > OIDC_HTTP_COOKIE_MAX_SIZE) {
1400
0
    oidc_warn(r,
1401
0
        "the length of the cookie value (%d) is greater than %d(!) bytes, this may not work "
1402
0
        "with all browsers/server combinations: consider switching to a server side caching!",
1403
0
        (int)_oidc_strlen(headerString), OIDC_HTTP_COOKIE_MAX_SIZE);
1404
0
  }
1405
1406
  /* use r->err_headers_out so we always print our headers (even on 302 redirect) - headers_out only
1407
   * prints on 2xx responses */
1408
0
  oidc_http_hdr_err_out_add(r, OIDC_HTTP_HDR_SET_COOKIE, headerString);
1409
0
}
1410
1411
/*
1412
 * get a cookie from the HTTP request
1413
 */
1414
0
char *oidc_http_get_cookie(request_rec *r, const char *cookieName) {
1415
0
  const char *cookie = NULL;
1416
0
  char *tokenizerCtx = NULL;
1417
0
  char *rv = NULL;
1418
1419
  /* get the Cookie value */
1420
0
  char *cookies = apr_pstrdup(r->pool, oidc_http_hdr_in_cookie_get(r));
1421
1422
0
  if (cookies != NULL) {
1423
1424
    /* tokenize on ; to find the cookie we want */
1425
0
    cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx);
1426
1427
0
    while (cookie != NULL) {
1428
1429
0
      while (*cookie == OIDC_CHAR_SPACE)
1430
0
        cookie++;
1431
1432
      /* see if we've found the cookie that we're looking for */
1433
0
      if ((_oidc_strncmp(cookie, cookieName, _oidc_strlen(cookieName)) == 0) &&
1434
0
          (cookie[_oidc_strlen(cookieName)] == OIDC_CHAR_EQUAL)) {
1435
1436
        /* skip to the meat of the parameter (the value after the '=') */
1437
0
        cookie += (_oidc_strlen(cookieName) + 1);
1438
0
        rv = apr_pstrdup(r->pool, cookie);
1439
1440
0
        break;
1441
0
      }
1442
1443
      /* go to the next cookie */
1444
0
      cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
1445
0
    }
1446
0
  }
1447
1448
  /* log what we've found */
1449
  /* in client-cookie session mode the value is the whole session credential */
1450
0
  oidc_debug(r, "returning \"%s\" = %s", cookieName,
1451
0
       rv ? apr_psprintf(r->pool, "\"%s\"", oidc_util_mask_value(r, rv)) : "<null>");
1452
1453
0
  return rv;
1454
0
}
1455
1456
0
#define OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR "_"
1457
0
#define OIDC_HTTP_COOKIE_CHUNKS_POSTFIX "chunks"
1458
1459
/* the largest number of chunks a chunked cookie may be split over; the counter cookie holds it as
1460
 * a decimal number, so this bounds both what is written and what is accepted on the way back in */
1461
0
#define OIDC_HTTP_COOKIE_CHUNKS_MAX 99
1462
1463
/*
1464
 * get the name of the cookie that contains the number of chunks
1465
 */
1466
0
static char *oidc_http_get_chunk_count_name(request_rec *r, const char *cookieName) {
1467
0
  return apr_psprintf(r->pool, "%s%s%s", cookieName, OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR,
1468
0
          OIDC_HTTP_COOKIE_CHUNKS_POSTFIX);
1469
0
}
1470
1471
/*
1472
 * get the number of cookie chunks set by the browser
1473
 */
1474
0
static int oidc_http_get_chunked_count(request_rec *r, const char *cookieName) {
1475
0
  int chunkCount = 0;
1476
0
  const char *chunkCountValue = oidc_http_get_cookie(r, oidc_http_get_chunk_count_name(r, cookieName));
1477
0
  chunkCount = _oidc_str_to_int(chunkCountValue, 0);
1478
0
  return chunkCount;
1479
0
}
1480
1481
/*
1482
 * get the name of a chunk
1483
 */
1484
0
static char *oidc_http_get_chunk_cookie_name(request_rec *r, const char *cookieName, int i) {
1485
0
  return apr_psprintf(r->pool, "%s%s%d", cookieName, OIDC_HTTP_COOKIE_CHUNKS_SEPARATOR, i);
1486
0
}
1487
1488
/*
1489
 * get a cookie value that is split over a number of chunked cookies
1490
 */
1491
0
char *oidc_http_get_chunked_cookie(request_rec *r, const char *cookieName, int chunkSize) {
1492
0
  char *cookieValue = NULL;
1493
0
  char *chunkValue = NULL;
1494
0
  int chunkCount = 0;
1495
0
  if (chunkSize == 0)
1496
0
    return oidc_http_get_cookie(r, cookieName);
1497
0
  chunkCount = oidc_http_get_chunked_count(r, cookieName);
1498
0
  if (chunkCount == 0)
1499
0
    return oidc_http_get_cookie(r, cookieName);
1500
0
  if ((chunkCount < 0) || (chunkCount > OIDC_HTTP_COOKIE_CHUNKS_MAX)) {
1501
0
    oidc_warn(r, "chunk count out of bounds: %d", chunkCount);
1502
0
    return NULL;
1503
0
  }
1504
0
  for (int i = 0; i < chunkCount; i++) {
1505
0
    chunkValue = oidc_http_get_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i));
1506
0
    if (chunkValue == NULL) {
1507
      /* refuse a partial assembly: a truncated value would fail session decode (or worse)
1508
       * and leave the browser stuck resubmitting the broken cookie set */
1509
0
      oidc_warn(r, "could not find chunk %d; aborting", i);
1510
0
      return NULL;
1511
0
    }
1512
0
    cookieValue = apr_psprintf(r->pool, "%s%s", cookieValue ? cookieValue : "", chunkValue);
1513
0
  }
1514
0
  return cookieValue;
1515
0
}
1516
1517
/*
1518
 * unset all chunked cookies, including the counter cookie, if they exist
1519
 */
1520
static void oidc_http_clear_chunked_cookie(request_rec *r, const char *cookieName, apr_time_t expires,
1521
0
             const char *ext) {
1522
0
  int chunkCount = oidc_http_get_chunked_count(r, cookieName);
1523
0
  if (chunkCount > 0) {
1524
0
    for (int i = 0; i < chunkCount; i++)
1525
0
      oidc_http_set_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i), "", expires, ext);
1526
0
    oidc_http_set_cookie(r, oidc_http_get_chunk_count_name(r, cookieName), "", expires, ext);
1527
0
  }
1528
0
}
1529
1530
/*
1531
 * set a cookie value that is split over a number of chunked cookies
1532
 */
1533
apr_byte_t oidc_http_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue,
1534
0
          apr_time_t expires, int chunkSize, const char *ext) {
1535
0
  int cookieLength = (int)_oidc_strlen(cookieValue);
1536
0
  const char *chunkValue = NULL;
1537
1538
  /* see if we need to chunk at all */
1539
0
  if ((chunkSize == 0) || ((cookieLength > 0) && (cookieLength < chunkSize))) {
1540
0
    oidc_http_set_cookie(r, cookieName, cookieValue, expires, ext);
1541
0
    oidc_http_clear_chunked_cookie(r, cookieName, expires, ext);
1542
0
    return TRUE;
1543
0
  }
1544
1545
  /* see if we need to clear a possibly chunked cookie */
1546
0
  if (cookieLength == 0) {
1547
0
    oidc_http_set_cookie(r, cookieName, "", expires, ext);
1548
0
    oidc_http_clear_chunked_cookie(r, cookieName, expires, ext);
1549
0
    return TRUE;
1550
0
  }
1551
1552
  /* Use ceil division without advertising a trailing empty chunk for exact multiples. */
1553
0
  int chunkCountValue = (cookieLength + chunkSize - 1) / chunkSize;
1554
1555
  /* refuse to write what oidc_http_get_chunked_cookie would refuse to read back: writing it
1556
   * anyway leaves the browser holding a value that is dropped on the next request, which for a
1557
   * client-side session means authenticating over and over rather than a visible failure */
1558
0
  if (chunkCountValue > OIDC_HTTP_COOKIE_CHUNKS_MAX) {
1559
0
    oidc_error(r,
1560
0
         "cookie \"%s\" would have to be split over %d chunks, which is more than the maximum of "
1561
0
         "%d; increase " OIDCSessionCookieChunkSize " (currently %d) or store the session in a "
1562
0
         "server-side cache with " OIDCSessionType " \"server-cache\"",
1563
0
         cookieName, chunkCountValue, OIDC_HTTP_COOKIE_CHUNKS_MAX, chunkSize);
1564
0
    return FALSE;
1565
0
  }
1566
1567
0
  const char *ptr = cookieValue;
1568
0
  for (int i = 0; i < chunkCountValue; i++) {
1569
0
    chunkValue = apr_pstrndup(r->pool, ptr, chunkSize);
1570
0
    ptr += chunkSize;
1571
0
    oidc_http_set_cookie(r, oidc_http_get_chunk_cookie_name(r, cookieName, i), chunkValue, expires, ext);
1572
0
  }
1573
0
  oidc_http_set_cookie(r, oidc_http_get_chunk_count_name(r, cookieName),
1574
0
           apr_psprintf(r->pool, "%d", chunkCountValue), expires, ext);
1575
0
  oidc_http_set_cookie(r, cookieName, "", expires, ext);
1576
1577
0
  return TRUE;
1578
0
}
1579
1580
/*
1581
 * construct the HTTP outgoing proxy options
1582
 */
1583
0
const char **oidc_http_proxy_auth_options(void) {
1584
0
  static const char *options[] = {OIDC_HTTP_PROXY_AUTH_BASIC,
1585
0
          OIDC_HTTP_PROXY_AUTH_DIGEST,
1586
0
          OIDC_HTTP_PROXY_AUTH_NTLM,
1587
0
          OIDC_HTTP_PROXY_AUTH_ANY,
1588
0
#ifdef CURLAUTH_NEGOTIATE
1589
0
          OIDC_HTTP_PROXY_AUTH_NEGOTIATE,
1590
0
#endif
1591
0
          NULL};
1592
0
  return options;
1593
0
}
1594
1595
/*
1596
 * return the CURL enum value for the HTTP outgoing proxy options
1597
 */
1598
0
unsigned long oidc_http_proxy_s2auth(const char *arg) {
1599
0
  if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_BASIC) == 0)
1600
0
    return CURLAUTH_BASIC;
1601
0
  if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_DIGEST) == 0)
1602
0
    return CURLAUTH_DIGEST;
1603
0
  if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_NTLM) == 0)
1604
0
    return CURLAUTH_NTLM;
1605
0
  if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_ANY) == 0)
1606
0
    return CURLAUTH_ANY;
1607
0
#ifdef CURLAUTH_NEGOTIATE
1608
0
  if (_oidc_strcmp(arg, OIDC_HTTP_PROXY_AUTH_NEGOTIATE) == 0)
1609
0
    return CURLAUTH_NEGOTIATE;
1610
0
#endif
1611
0
  return CURLAUTH_NONE;
1612
0
}
1613
1614
/*
1615
 * initialize the HTTP/cURL environment
1616
 */
1617
0
void oidc_http_init(void) {
1618
0
  curl_global_init(CURL_GLOBAL_ALL);
1619
0
}
1620
1621
/*
1622
 * clean up the HTTP/cURL environment
1623
 */
1624
0
void oidc_http_cleanup(void) {
1625
0
  curl_global_cleanup();
1626
0
}
1627
1628
/* Pool reset handles to retain their connection, DNS, and TLS caches. */
1629
0
#define OIDC_HTTP_CURL_POOL_MAX 16
1630
1631
static CURL *_oidc_http_curl_pool[OIDC_HTTP_CURL_POOL_MAX];
1632
static int _oidc_http_curl_pool_num = 0;
1633
static apr_byte_t _oidc_http_curl_pool_enabled = FALSE;
1634
#if APR_HAS_THREADS
1635
static apr_thread_mutex_t *_oidc_http_curl_pool_mutex = NULL;
1636
#endif
1637
1638
0
static void oidc_http_curl_pool_lock(void) {
1639
0
#if APR_HAS_THREADS
1640
0
  apr_thread_mutex_lock(_oidc_http_curl_pool_mutex);
1641
0
#endif
1642
0
}
1643
1644
0
static void oidc_http_curl_pool_unlock(void) {
1645
0
#if APR_HAS_THREADS
1646
0
  apr_thread_mutex_unlock(_oidc_http_curl_pool_mutex);
1647
0
#endif
1648
0
}
1649
1650
0
static apr_status_t oidc_http_curl_pool_cleanup(void *data) {
1651
0
  for (int i = 0; i < _oidc_http_curl_pool_num; i++)
1652
0
    curl_easy_cleanup(_oidc_http_curl_pool[i]);
1653
0
  _oidc_http_curl_pool_num = 0;
1654
0
  _oidc_http_curl_pool_enabled = FALSE;
1655
0
#if APR_HAS_THREADS
1656
0
  _oidc_http_curl_pool_mutex = NULL;
1657
0
#endif
1658
0
  return APR_SUCCESS;
1659
0
}
1660
1661
2
void oidc_http_curl_pool_init(apr_pool_t *pool) {
1662
2
  if (_oidc_http_curl_pool_enabled == TRUE)
1663
0
    return;
1664
2
#if APR_HAS_THREADS
1665
2
  if (apr_thread_mutex_create(&_oidc_http_curl_pool_mutex, APR_THREAD_MUTEX_DEFAULT, pool) != APR_SUCCESS)
1666
0
    return;
1667
2
#endif
1668
2
  _oidc_http_curl_pool_num = 0;
1669
2
  _oidc_http_curl_pool_enabled = TRUE;
1670
2
  apr_pool_cleanup_register(pool, NULL, oidc_http_curl_pool_cleanup, apr_pool_cleanup_null);
1671
2
}
1672
1673
/*
1674
 * forget any handles inherited over fork(): their connections share file descriptors and TLS
1675
 * state with the parent, so the child must neither use nor curl_easy_cleanup them (a cleanup
1676
 * would write a TLS close-notify into a socket the parent may still be using)
1677
 */
1678
0
void oidc_http_curl_pool_child_init(void) {
1679
0
  _oidc_http_curl_pool_num = 0;
1680
0
}
1681
1682
0
static CURL *oidc_http_curl_acquire(void) {
1683
0
  CURL *curl = NULL;
1684
0
  if (_oidc_http_curl_pool_enabled == FALSE)
1685
0
    return curl_easy_init();
1686
0
  oidc_http_curl_pool_lock();
1687
0
  if (_oidc_http_curl_pool_num > 0)
1688
0
    curl = _oidc_http_curl_pool[--_oidc_http_curl_pool_num];
1689
0
  oidc_http_curl_pool_unlock();
1690
0
  return (curl != NULL) ? curl : curl_easy_init();
1691
0
}
1692
1693
/*
1694
 * return a handle to the pool after a successful request: the reset clears its options but
1695
 * keeps the connection/DNS/TLS-session caches; a handle whose request failed is destroyed
1696
 * rather than reused so a poisoned connection state cannot carry over
1697
 */
1698
0
static void oidc_http_curl_release(CURL *curl, apr_byte_t reuse) {
1699
0
  if (curl == NULL)
1700
0
    return;
1701
0
  if ((reuse == TRUE) && (_oidc_http_curl_pool_enabled == TRUE)) {
1702
0
    curl_easy_reset(curl);
1703
0
    oidc_http_curl_pool_lock();
1704
0
    if (_oidc_http_curl_pool_num < OIDC_HTTP_CURL_POOL_MAX) {
1705
0
      _oidc_http_curl_pool[_oidc_http_curl_pool_num++] = curl;
1706
0
      curl = NULL;
1707
0
    }
1708
0
    oidc_http_curl_pool_unlock();
1709
0
  }
1710
0
  if (curl != NULL)
1711
0
    curl_easy_cleanup(curl);
1712
0
}