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/cache/file.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
 * caching using a file storage backend
42
 *
43
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
44
 */
45
46
#include "cfg/cache.h"
47
#include "cfg/cfg_int.h"
48
#include "util/util.h"
49
50
/*
51
 * header structure that holds the metadata info for a cache file entry
52
 */
53
typedef struct {
54
  /* length of the cached data */
55
  apr_size_t len;
56
  /* cache expiry timestamp */
57
  apr_time_t expire;
58
} oidc_cache_file_info_t;
59
60
/*
61
 * prefix that distinguishes mod_auth_openidc cache files from other files in the same directory (/tmp)
62
 */
63
0
#define OIDC_CACHE_FILE_PREFIX "mod-auth-openidc-"
64
65
/* post config routine */
66
0
int oidc_cache_file_post_config(apr_pool_t *pool, server_rec *s) {
67
0
  apr_status_t rv = APR_SUCCESS;
68
0
  oidc_cfg_t *cfg = (oidc_cfg_t *)ap_get_module_config(s->module_config, &auth_openidc_module);
69
0
  if (cfg->cache.file_dir == NULL) {
70
    /* by default we'll use the OS specified /tmp dir for cache files */
71
0
    rv = apr_temp_dir_get((const char **)&cfg->cache.file_dir, pool);
72
0
    if (rv != APR_SUCCESS) {
73
0
      oidc_serror(s, "apr_temp_dir_get failed: could not find a temp dir: %s",
74
0
            oidc_cache_status2str(pool, rv));
75
0
      return HTTP_INTERNAL_SERVER_ERROR;
76
0
    }
77
0
  }
78
0
  return OK;
79
0
}
80
81
/*
82
 * return the cache file name for a specified key
83
 */
84
0
static const char *oidc_cache_file_name(request_rec *r, const char *section, const char *key) {
85
0
  return apr_psprintf(r->pool, "%s%s-%s", OIDC_CACHE_FILE_PREFIX, section, oidc_http_url_encode(r, key));
86
0
}
87
88
/*
89
 * return the fully qualified path name to a cache file for a specified key
90
 */
91
0
static const char *oidc_cache_file_path(request_rec *r, const char *section, const char *key) {
92
0
  oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
93
0
  return apr_psprintf(r->pool, "%s/%s", cfg->cache.file_dir, oidc_cache_file_name(r, section, key));
94
0
}
95
96
/*
97
 * read a specified number of bytes from a cache file in to a preallocated buffer
98
 */
99
static apr_status_t oidc_cache_file_read(request_rec *r, const char *path, apr_file_t *fd, void *buf,
100
0
           const apr_size_t len) {
101
102
0
  apr_status_t rc = APR_SUCCESS;
103
0
  apr_size_t bytes_read = 0;
104
0
  char s_err[128];
105
106
  /* (blocking) read the requested number of bytes */
107
0
  rc = apr_file_read_full(fd, buf, len, &bytes_read);
108
109
  /* test for system errors */
110
0
  if (rc != APR_SUCCESS) {
111
0
    oidc_error(r, "could not read from: %s (%s)", path, apr_strerror(rc, s_err, sizeof(s_err)));
112
0
  }
113
114
  /* ensure that we've got the requested number of bytes */
115
0
  if (bytes_read != len) {
116
0
    oidc_error(r,
117
0
         "could not read enough bytes from: \"%s\", bytes_read (%" APR_SIZE_T_FMT
118
0
         ") != len (%" APR_SIZE_T_FMT ")",
119
0
         path, bytes_read, len);
120
0
    rc = APR_EGENERAL;
121
0
  }
122
123
0
  return rc;
124
0
}
125
126
/*
127
 * write a specified number of bytes from a buffer to a cache file
128
 */
129
static apr_status_t oidc_cache_file_write(request_rec *r, const char *path, apr_file_t *fd, const void *buf,
130
0
            const apr_size_t len) {
131
132
0
  apr_status_t rc = APR_SUCCESS;
133
0
  apr_size_t bytes_written = 0;
134
0
  char s_err[128];
135
136
  /* (blocking) write the number of bytes in the buffer */
137
0
  rc = apr_file_write_full(fd, buf, len, &bytes_written);
138
139
  /* check for a system error */
140
0
  if (rc != APR_SUCCESS) {
141
0
    oidc_error(r, "could not write to: \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err)));
142
0
    return rc;
143
0
  }
144
145
  /* check that all bytes from the header were written */
146
0
  if (bytes_written != len) {
147
0
    oidc_error(r,
148
0
         "could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT
149
0
         ") != len (%" APR_SIZE_T_FMT ")",
150
0
         path, bytes_written, len);
151
0
    return APR_EGENERAL;
152
0
  }
153
154
0
  return rc;
155
0
}
156
157
/*
158
 * get a value for the specified key from the cache
159
 */
160
0
static apr_byte_t oidc_cache_file_get(request_rec *r, const char *section, const char *key, char **value) {
161
0
  apr_file_t *fd = NULL;
162
0
  apr_status_t rc = APR_SUCCESS;
163
0
  apr_finfo_t finfo;
164
0
  char s_err[128];
165
166
  /* get the fully qualified path to the cache file based on the key name */
167
0
  const char *path = oidc_cache_file_path(r, section, key);
168
169
  /* reject links and non-regular files before opening the cache entry (no following here,
170
   * unlike the metadata/template reads: this module writes this directory itself and never
171
   * creates symlinks in it, so one can only have been planted) */
172
0
  if (oidc_util_file_is_regular(r->pool, path, FALSE, &finfo) == FALSE) {
173
0
    if (finfo.filetype == APR_NOFILE) {
174
0
      oidc_debug(r, "cache miss for key \"%s\"", key);
175
0
    } else {
176
0
      oidc_warn(r, "ignoring non-regular cache file \"%s\"", path);
177
0
    }
178
0
    return TRUE;
179
0
  }
180
181
  /* open the cache file if it exists, otherwise we just have a "regular" cache miss */
182
0
  if (apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED, APR_OS_DEFAULT, r->pool) != APR_SUCCESS) {
183
0
    oidc_debug(r, "cache miss for key \"%s\"", key);
184
0
    return TRUE;
185
0
  }
186
187
  /* Shared locks allow concurrent reads while still excluding the expiry cleaner. */
188
0
  apr_file_lock(fd, APR_FLOCK_SHARED);
189
190
  /* move the read pointer to the very start of the cache file */
191
0
  apr_off_t begin = 0;
192
0
  apr_file_seek(fd, APR_SET, &begin);
193
0
  if ((rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, fd)) != APR_SUCCESS)
194
0
    goto error_close;
195
0
  if ((finfo.size < (apr_off_t)sizeof(oidc_cache_file_info_t)) ||
196
0
      (finfo.size - (apr_off_t)sizeof(oidc_cache_file_info_t) > (apr_off_t)OIDC_UTIL_FILE_SIZE_MAX)) {
197
0
    oidc_error(r, "cache file \"%s\" has an invalid size (%" APR_OFF_T_FMT ")", path, finfo.size);
198
0
    rc = APR_EGENERAL;
199
0
    goto error_close;
200
0
  }
201
202
  /* read a header with metadata */
203
0
  oidc_cache_file_info_t info;
204
0
  if ((rc = oidc_cache_file_read(r, path, fd, &info, sizeof(oidc_cache_file_info_t))) != APR_SUCCESS)
205
0
    goto error_close;
206
207
  /* check if this cache entry has already expired */
208
0
  if (apr_time_now() >= info.expire) {
209
210
    /*
211
     * Leave expired files for the cleaner. Unlinking here could race a writer's atomic rename
212
     * and remove the replacement entry.
213
     */
214
0
    apr_file_unlock(fd);
215
0
    apr_file_close(fd);
216
217
0
    oidc_debug(r, "cache entry \"%s\" expired, leaving file \"%s\" to the cleaning cycle", key, path);
218
219
    /* nothing strange happened really */
220
0
    return TRUE;
221
0
  }
222
0
  if ((info.len == 0) || (info.len > OIDC_UTIL_FILE_SIZE_MAX) ||
223
0
      ((apr_off_t)info.len > finfo.size - (apr_off_t)sizeof(oidc_cache_file_info_t))) {
224
0
    oidc_error(r, "cache file \"%s\" has an invalid value length (%" APR_SIZE_T_FMT ")", path, info.len);
225
0
    rc = APR_EGENERAL;
226
0
    goto error_close;
227
0
  }
228
229
  /* allocate space for the actual value based on the data size info in the header (+1 for \0 termination) */
230
0
  *value = apr_palloc(r->pool, info.len + 1);
231
232
  /* (blocking) read the requested data in to the buffer */
233
0
  rc = oidc_cache_file_read(r, path, fd, (void *)(*value), info.len);
234
235
  /* barf on failure */
236
0
  if (rc != APR_SUCCESS) {
237
0
    oidc_error(r, "could not read cache value from \"%s\"", path);
238
0
    goto error_close;
239
0
  }
240
241
  /* NUL-terminate: an entry this module wrote already ends in \0 within info.len, but a corrupt or
242
   * externally-planted cache file may not, and every caller treats the value as a C string */
243
0
  (*value)[info.len] = '\0';
244
245
  /* we're done, unlock and close the file */
246
0
  apr_file_unlock(fd);
247
0
  apr_file_close(fd);
248
249
0
  return TRUE;
250
251
0
error_close:
252
253
0
  apr_file_unlock(fd);
254
0
  apr_file_close(fd);
255
256
0
  oidc_error(r, "return error status %d (%s)", rc, apr_strerror(rc, s_err, sizeof(s_err)));
257
258
0
  return FALSE;
259
0
}
260
261
// NB: the file-cache housekeeping parameters are compile-time constants (not configurable)
262
0
#define OIDC_CACHE_FILE_LAST_CLEANED "last-cleaned"
263
264
/*
265
 * decide whether a cache cleaning cycle should run and bump the "last cleaned"
266
 * timestamp; creates the metadata file if it does not yet exist
267
 * returns TRUE if cleaning should proceed; *rc holds the status to return when FALSE
268
 */
269
static apr_byte_t oidc_cache_file_clean_due(request_rec *r, const oidc_cfg_t *cfg, const char *metadata_path,
270
0
              apr_status_t *rc) {
271
0
  apr_file_t *fd = NULL;
272
0
  apr_finfo_t fi;
273
0
  char s_err[128];
274
275
0
  *rc = APR_SUCCESS;
276
277
  /* no metadata file yet: create it and proceed with a first cleaning cycle */
278
0
  if (apr_stat(&fi, metadata_path, APR_FINFO_MTIME, r->pool) != APR_SUCCESS) {
279
0
    if ((*rc = apr_file_open(&fd, metadata_path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE),
280
0
           (APR_FPROT_UREAD | APR_FPROT_UWRITE), r->pool)) != APR_SUCCESS) {
281
0
      oidc_error(r, "error creating cache timestamp file '%s' (%s)", metadata_path,
282
0
           apr_strerror(*rc, s_err, sizeof(s_err)));
283
0
      return FALSE;
284
0
    }
285
0
    if ((*rc = apr_file_close(fd)) != APR_SUCCESS) {
286
0
      oidc_error(r, "error closing cache timestamp file '%s' (%s)", metadata_path,
287
0
           apr_strerror(*rc, s_err, sizeof(s_err)));
288
0
    }
289
0
    *rc = APR_SUCCESS;
290
0
    return TRUE;
291
0
  }
292
293
  /* really only clean once per so much time, check that we have not recently run */
294
0
  if (apr_time_now() < fi.mtime + apr_time_from_sec(oidc_cfg_cache_file_clean_interval_get(cfg))) {
295
0
    oidc_debug(r,
296
0
         "last cleanup call was less than %d seconds ago (next one as early as in %" APR_TIME_T_FMT
297
0
         " secs)",
298
0
         oidc_cfg_cache_file_clean_interval_get(cfg),
299
0
         apr_time_sec(fi.mtime + apr_time_from_sec(oidc_cfg_cache_file_clean_interval_get(cfg)) -
300
0
          apr_time_now()));
301
0
    return FALSE;
302
0
  }
303
304
  /* time to clean, reset the mtime of the metadata file to reflect this cleaning cycle */
305
0
  apr_file_mtime_set(metadata_path, apr_time_now(), r->pool);
306
307
0
  oidc_debug(r, "start cleaning cycle");
308
309
0
  return TRUE;
310
0
}
311
312
/*
313
 * Recheck the device/inode before unlinking so a concurrent atomic replacement is not removed.
314
 * Platforms without that identity information retain the unconditional cleanup behavior.
315
 */
316
static apr_byte_t oidc_cache_file_is_same(request_rec *r, const char *path, const apr_finfo_t *ident,
317
0
            apr_byte_t have_ident) {
318
0
  apr_finfo_t current;
319
320
0
  if (have_ident == FALSE)
321
0
    return TRUE;
322
323
  /* gone already (another cleaner, or a delete): nothing of ours left to remove */
324
0
  if (apr_stat(&current, path, APR_FINFO_IDENT, r->pool) != APR_SUCCESS)
325
0
    return FALSE;
326
327
0
  if ((current.valid & APR_FINFO_IDENT) != APR_FINFO_IDENT)
328
0
    return TRUE;
329
330
0
  return ((current.device == ident->device) && (current.inode == ident->inode)) ? TRUE : FALSE;
331
0
}
332
333
/*
334
 * process a single cache directory entry: remove the file if it expired or its header is corrupt
335
 */
336
static void oidc_cache_file_clean_entry(request_rec *r, oidc_cfg_t *cfg, const char *metadata_filename,
337
0
          const apr_finfo_t *fi) {
338
0
  apr_file_t *fd = NULL;
339
0
  apr_status_t rc;
340
0
  oidc_cache_file_info_t info;
341
0
  apr_finfo_t ident;
342
0
  apr_byte_t have_ident = FALSE;
343
0
  char s_err[128];
344
345
  /* skip non-cache entries, cq. the ".", ".." and the metadata file */
346
0
  if ((fi->name[0] == OIDC_CHAR_DOT) || (_oidc_strstr(fi->name, OIDC_CACHE_FILE_PREFIX) != fi->name) ||
347
0
      (_oidc_strcmp(fi->name, metadata_filename) == 0))
348
0
    return;
349
350
  /* get the fully qualified path to the cache file and open it */
351
0
  const char *path = apr_psprintf(r->pool, "%s/%s", cfg->cache.file_dir, fi->name);
352
0
  if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
353
0
    oidc_error(r, "unable to open cache entry \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err)));
354
0
    return;
355
0
  }
356
357
  /* note which file this actually is, so that only this one can be removed below */
358
0
  have_ident = ((apr_file_info_get(&ident, APR_FINFO_IDENT, fd) == APR_SUCCESS) &&
359
0
          ((ident.valid & APR_FINFO_IDENT) == APR_FINFO_IDENT))
360
0
       ? TRUE
361
0
       : FALSE;
362
363
  /* read the header with cache metadata info */
364
0
  apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
365
0
  rc = oidc_cache_file_read(r, path, fd, &info, sizeof(oidc_cache_file_info_t));
366
0
  apr_file_unlock(fd);
367
  /* close before removing: Windows will not unlink a file this process still holds open */
368
0
  apr_file_close(fd);
369
370
0
  if (rc != APR_SUCCESS) {
371
0
    oidc_error(r, "cache entry (%s) corrupted (%s), removing file \"%s\"", fi->name,
372
0
         apr_strerror(rc, s_err, sizeof(s_err)), path);
373
0
  } else if (apr_time_now() < info.expire) {
374
    /* entry is still valid, keep it */
375
0
    return;
376
0
  } else {
377
0
    oidc_debug(r, "cache entry (%s) expired, removing file \"%s\")", fi->name, path);
378
0
  }
379
380
  /* a writer may have replaced the entry since it was read; only remove the file inspected above */
381
0
  if (oidc_cache_file_is_same(r, path, &ident, have_ident) == FALSE) {
382
0
    oidc_debug(r, "cache entry (%s) is no longer the file that was inspected, leaving it alone", fi->name);
383
0
    return;
384
0
  }
385
386
  /* delete the cache file */
387
0
  if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) {
388
    /* hrm, this will most probably happen again on the next run... */
389
0
    oidc_error(r, "could not delete cache file \"%s\" (%s)", path, apr_strerror(rc, s_err, sizeof(s_err)));
390
0
  }
391
0
}
392
393
/*
394
 * delete all expired entries from the cache directory
395
 */
396
0
static apr_status_t oidc_cache_file_clean(request_rec *r) {
397
0
  apr_status_t rc = APR_SUCCESS;
398
0
  apr_dir_t *dir = NULL;
399
0
  apr_status_t i;
400
0
  apr_finfo_t fi;
401
0
  char s_err[128];
402
403
0
  oidc_cfg_t *cfg = ap_get_module_config(r->server->module_config, &auth_openidc_module);
404
405
  /* get the path to the metadata file that holds "last cleaned" metadata info */
406
0
  const char *metadata_path = oidc_cache_file_path(r, "cache-file", OIDC_CACHE_FILE_LAST_CLEANED);
407
408
  /* decide whether we should run a cleaning cycle (and bump the timestamp if so) */
409
0
  if (oidc_cache_file_clean_due(r, cfg, metadata_path, &rc) == FALSE)
410
0
    return rc;
411
412
  /* time to clean, open the cache directory */
413
0
  if ((rc = apr_dir_open(&dir, cfg->cache.file_dir, r->pool)) != APR_SUCCESS) {
414
0
    oidc_error(r, "error opening cache directory '%s' for cleaning (%s)", cfg->cache.file_dir,
415
0
         apr_strerror(rc, s_err, sizeof(s_err)));
416
0
    return rc;
417
0
  }
418
419
0
  const char *metadata_filename = oidc_cache_file_name(r, "cache-file", OIDC_CACHE_FILE_LAST_CLEANED);
420
421
  /* loop trough the cache file entries */
422
0
  do {
423
0
    i = apr_dir_read(&fi, APR_FINFO_NAME, dir);
424
0
    if (i == APR_SUCCESS)
425
0
      oidc_cache_file_clean_entry(r, cfg, metadata_filename, &fi);
426
0
  } while (i == APR_SUCCESS);
427
428
0
  apr_dir_close(dir);
429
430
0
  return APR_SUCCESS;
431
0
}
432
433
/*
434
 * write a value for the specified key to the cache
435
 */
436
static apr_byte_t oidc_cache_file_set(request_rec *r, const char *section, const char *key, const char *value,
437
0
              apr_time_t expiry) {
438
0
  apr_file_t *fd = NULL;
439
0
  apr_status_t rc = APR_SUCCESS;
440
0
  char s_err[128];
441
0
  char *rnd = NULL;
442
443
0
  if (oidc_util_rand_str(r, &rnd, 12) == FALSE)
444
0
    return FALSE;
445
446
  /* get the fully qualified path to the cache file based on the key name */
447
0
  const char *target = oidc_cache_file_path(r, section, key);
448
0
  const char *path = apr_psprintf(r->pool, "%s.%s.tmp", target, rnd);
449
450
  /* only on writes (not on reads) we clean the cache first (if not done recently) */
451
0
  oidc_cache_file_clean(r);
452
453
  /* just remove cache file if value is NULL */
454
0
  if (value == NULL) {
455
0
    if ((rc = apr_file_remove(target, r->pool)) != APR_SUCCESS) {
456
0
      oidc_error(r, "could not delete cache file \"%s\" (%s)", path,
457
0
           apr_strerror(rc, s_err, sizeof(s_err)));
458
0
    }
459
0
    return TRUE;
460
0
  }
461
462
  /* try to open the cache file for writing, creating it if it does not exist; cache entries can
463
   * hold session/state secrets, so restrict permissions to the owner only */
464
0
  if ((rc = apr_file_open(&fd, path, (APR_FOPEN_WRITE | APR_FOPEN_CREATE), (APR_FPROT_UREAD | APR_FPROT_UWRITE),
465
0
        r->pool)) != APR_SUCCESS) {
466
0
    oidc_error(r, "cache file \"%s\" could not be opened (%s)", path,
467
0
         apr_strerror(rc, s_err, sizeof(s_err)));
468
0
    return FALSE;
469
0
  }
470
471
  /* lock the file and move the write pointer to the start of it */
472
0
  apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
473
0
  apr_off_t begin = 0;
474
0
  apr_file_trunc(fd, begin);
475
0
  apr_file_seek(fd, APR_SET, &begin);
476
477
  /* construct the metadata for this cache entry in the header info */
478
0
  oidc_cache_file_info_t info;
479
0
  info.expire = expiry;
480
0
  info.len = _oidc_strlen(value) + 1;
481
482
  /* write the header, then the value; both returns must be checked, since a short
483
   * write (a full disk, a quota) would otherwise be reported as a stored entry and
484
   * only surface later as an unreadable one */
485
0
  if (oidc_cache_file_write(r, path, fd, &info, sizeof(oidc_cache_file_info_t)) != APR_SUCCESS)
486
0
    goto error;
487
488
0
  if (oidc_cache_file_write(r, path, fd, (const void *)value, info.len) != APR_SUCCESS)
489
0
    goto error;
490
491
  /* unlock and close the written file */
492
0
  apr_file_unlock(fd);
493
0
  apr_file_close(fd);
494
495
0
  if ((rc = apr_file_rename(path, target, r->pool)) != APR_SUCCESS) {
496
0
    oidc_error(r, "cache file: %s could not be renamed to: %s (%s)", path, target,
497
0
         apr_strerror(rc, s_err, sizeof(s_err)));
498
0
    apr_file_remove(path, r->pool);
499
0
    return FALSE;
500
0
  }
501
502
0
  oidc_debug(r, "successfully stored entry for key \"%s\" in file of %" APR_SIZE_T_FMT " bytes", key, info.len);
503
504
0
  return TRUE;
505
506
0
error:
507
508
  /* drop the partially written temporary file rather than leaving it behind */
509
0
  apr_file_unlock(fd);
510
0
  apr_file_close(fd);
511
0
  apr_file_remove(path, r->pool);
512
513
0
  return FALSE;
514
0
}
515
516
// clang-format off
517
518
oidc_cache_t oidc_cache_file = {
519
  "file",
520
  1,
521
  oidc_cache_file_post_config,
522
  NULL,
523
  oidc_cache_file_get,
524
  oidc_cache_file_set,
525
  NULL
526
};
527
528
// clang-format on