Coverage Report

Created: 2026-06-09 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bind9/lib/isc/file.c
Line
Count
Source
1
/*
2
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3
 *
4
 * SPDX-License-Identifier: MPL-2.0
5
 *
6
 * This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9
 *
10
 * See the COPYRIGHT file distributed with this work for additional
11
 * information regarding copyright ownership.
12
 */
13
14
/*
15
 * Portions Copyright (c) 1987, 1993
16
 *      The Regents of the University of California.  All rights reserved.
17
 *
18
 * Redistribution and use in source and binary forms, with or without
19
 * modification, are permitted provided that the following conditions
20
 * are met:
21
 * 1. Redistributions of source code must retain the above copyright
22
 *    notice, this list of conditions and the following disclaimer.
23
 * 2. Redistributions in binary form must reproduce the above copyright
24
 *    notice, this list of conditions and the following disclaimer in the
25
 *    documentation and/or other materials provided with the distribution.
26
 * 3. Neither the name of the University nor the names of its contributors
27
 *    may be used to endorse or promote products derived from this software
28
 *    without specific prior written permission.
29
 *
30
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
31
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
34
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40
 * SUCH DAMAGE.
41
 */
42
43
/*! \file */
44
45
#include <errno.h>
46
#include <fcntl.h>
47
#include <inttypes.h>
48
#include <limits.h>
49
#include <stdbool.h>
50
#include <stdlib.h>
51
#include <sys/stat.h>
52
#include <sys/time.h>
53
#include <time.h>   /* Required for utimes on some platforms. */
54
#include <unistd.h> /* Required for mkstemp on NetBSD. */
55
56
#ifdef HAVE_SYS_MMAN_H
57
#include <sys/mman.h>
58
#endif /* ifdef HAVE_SYS_MMAN_H */
59
60
#include <isc/dir.h>
61
#include <isc/file.h>
62
#include <isc/log.h>
63
#include <isc/md.h>
64
#include <isc/mem.h>
65
#include <isc/random.h>
66
#include <isc/string.h>
67
#include <isc/time.h>
68
#include <isc/util.h>
69
70
#include "errno2result.h"
71
72
/*
73
 * XXXDCL As the API for accessing file statistics undoubtedly gets expanded,
74
 * it might be good to provide a mechanism that allows for the results
75
 * of a previous stat() to be used again without having to do another stat,
76
 * such as perl's mechanism of using "_" in place of a file name to indicate
77
 * that the results of the last stat should be used.  But then you get into
78
 * annoying MP issues.   BTW, Win32 has stat().
79
 */
80
static isc_result_t
81
8
file_stats(const char *file, struct stat *stats) {
82
8
  isc_result_t result = ISC_R_SUCCESS;
83
84
8
  REQUIRE(file != NULL);
85
8
  REQUIRE(stats != NULL);
86
87
8
  if (stat(file, stats) != 0) {
88
4
    result = isc__errno2result(errno);
89
4
  }
90
91
8
  return result;
92
8
}
93
94
static isc_result_t
95
0
file_lstats(const char *file, struct stat *stats) {
96
0
  isc_result_t result = ISC_R_SUCCESS;
97
98
0
  REQUIRE(file != NULL);
99
0
  REQUIRE(stats != NULL);
100
101
0
  if (lstat(file, stats) != 0) {
102
0
    result = isc__errno2result(errno);
103
0
  }
104
105
0
  return result;
106
0
}
107
108
static isc_result_t
109
0
fd_stats(int fd, struct stat *stats) {
110
0
  isc_result_t result = ISC_R_SUCCESS;
111
112
0
  REQUIRE(stats != NULL);
113
114
0
  if (fstat(fd, stats) != 0) {
115
0
    result = isc__errno2result(errno);
116
0
  }
117
118
0
  return result;
119
0
}
120
121
isc_result_t
122
0
isc_file_getsizefd(int fd, off_t *size) {
123
0
  isc_result_t result;
124
0
  struct stat stats;
125
126
0
  REQUIRE(size != NULL);
127
128
0
  result = fd_stats(fd, &stats);
129
130
0
  if (result == ISC_R_SUCCESS) {
131
0
    *size = stats.st_size;
132
0
  }
133
134
0
  return result;
135
0
}
136
137
isc_result_t
138
0
isc_file_mode(const char *file, mode_t *modep) {
139
0
  isc_result_t result;
140
0
  struct stat stats;
141
142
0
  REQUIRE(modep != NULL);
143
144
0
  result = file_stats(file, &stats);
145
0
  if (result == ISC_R_SUCCESS) {
146
0
    *modep = (stats.st_mode & 07777);
147
0
  }
148
149
0
  return result;
150
0
}
151
152
isc_result_t
153
2
isc_file_getmodtime(const char *file, isc_time_t *modtime) {
154
2
  isc_result_t result;
155
2
  struct stat stats;
156
157
2
  REQUIRE(file != NULL);
158
2
  REQUIRE(modtime != NULL);
159
160
2
  result = file_stats(file, &stats);
161
162
2
  if (result == ISC_R_SUCCESS) {
163
2
#if defined(HAVE_STAT_NSEC)
164
2
    isc_time_set(modtime, stats.st_mtime, stats.st_mtim.tv_nsec);
165
#else  /* if defined(HAVE_STAT_NSEC) */
166
    isc_time_set(modtime, stats.st_mtime, 0);
167
#endif /* if defined(HAVE_STAT_NSEC) */
168
2
  }
169
170
2
  return result;
171
2
}
172
173
isc_result_t
174
0
isc_file_getsize(const char *file, off_t *size) {
175
0
  isc_result_t result;
176
0
  struct stat stats;
177
178
0
  REQUIRE(file != NULL);
179
0
  REQUIRE(size != NULL);
180
181
0
  result = file_stats(file, &stats);
182
183
0
  if (result == ISC_R_SUCCESS) {
184
0
    *size = stats.st_size;
185
0
  }
186
187
0
  return result;
188
0
}
189
190
isc_result_t
191
0
isc_file_settime(const char *file, isc_time_t *when) {
192
0
  struct timeval times[2];
193
194
0
  REQUIRE(file != NULL && when != NULL);
195
196
  /*
197
   * tv_sec is at least a 32 bit quantity on all platforms we're
198
   * dealing with, but it is signed on most (all?) of them,
199
   * so we need to make sure the high bit isn't set.  This unfortunately
200
   * loses when either:
201
   *   * tv_sec becomes a signed 64 bit integer but long is 32 bits
202
   *  and isc_time_seconds > LONG_MAX, or
203
   *   * isc_time_seconds is changed to be > 32 bits but long is 32 bits
204
   *      and isc_time_seconds has at least 33 significant bits.
205
   */
206
0
  times[0].tv_sec = times[1].tv_sec = (long)isc_time_seconds(when);
207
208
  /*
209
   * Here is the real check for the high bit being set.
210
   */
211
0
  if ((times[0].tv_sec &
212
0
       (1ULL << (sizeof(times[0].tv_sec) * CHAR_BIT - 1))) != 0)
213
0
  {
214
0
    return ISC_R_RANGE;
215
0
  }
216
217
  /*
218
   * isc_time_nanoseconds guarantees a value that divided by 1000 will
219
   * fit into the minimum possible size tv_usec field.
220
   */
221
0
  times[0].tv_usec = times[1].tv_usec =
222
0
    (int32_t)(isc_time_nanoseconds(when) / 1000);
223
224
0
  if (utimes(file, times) < 0) {
225
0
    return isc__errno2result(errno);
226
0
  }
227
228
0
  return ISC_R_SUCCESS;
229
0
}
230
231
#undef TEMPLATE
232
0
#define TEMPLATE "tmp-XXXXXXXXXX" /*%< 14 characters. */
233
234
isc_result_t
235
0
isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
236
0
  return isc_file_template(path, TEMPLATE, buf, buflen);
237
0
}
238
239
isc_result_t
240
isc_file_template(const char *path, const char *templet, char *buf,
241
0
      size_t buflen) {
242
0
  const char *s;
243
244
0
  REQUIRE(templet != NULL);
245
0
  REQUIRE(buf != NULL);
246
247
0
  if (path == NULL) {
248
0
    path = "";
249
0
  }
250
251
0
  s = strrchr(templet, '/');
252
0
  if (s != NULL) {
253
0
    templet = s + 1;
254
0
  }
255
256
0
  s = strrchr(path, '/');
257
258
0
  if (s != NULL) {
259
0
    size_t prefixlen = s - path + 1;
260
0
    if ((prefixlen + strlen(templet) + 1) > buflen) {
261
0
      return ISC_R_NOSPACE;
262
0
    }
263
264
    /* Copy 'prefixlen' bytes and NUL terminate. */
265
0
    strlcpy(buf, path, ISC_MIN(prefixlen + 1, buflen));
266
0
    strlcat(buf, templet, buflen);
267
0
  } else {
268
0
    if ((strlen(templet) + 1) > buflen) {
269
0
      return ISC_R_NOSPACE;
270
0
    }
271
272
0
    strlcpy(buf, templet, buflen);
273
0
  }
274
275
0
  return ISC_R_SUCCESS;
276
0
}
277
278
static const char alphnum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv"
279
            "wxyz0123456789";
280
281
isc_result_t
282
0
isc_file_renameunique(const char *file, char *templet) {
283
0
  char *x;
284
0
  char *cp;
285
286
0
  REQUIRE(file != NULL);
287
0
  REQUIRE(templet != NULL);
288
289
0
  cp = templet;
290
0
  while (*cp != '\0') {
291
0
    cp++;
292
0
  }
293
0
  if (cp == templet) {
294
0
    return ISC_R_FAILURE;
295
0
  }
296
297
0
  x = cp--;
298
0
  while (cp >= templet && *cp == 'X') {
299
0
    *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
300
0
    x = cp--;
301
0
  }
302
0
  while (link(file, templet) == -1) {
303
0
    if (errno != EEXIST) {
304
0
      return isc__errno2result(errno);
305
0
    }
306
0
    for (cp = x;;) {
307
0
      const char *t;
308
0
      if (*cp == '\0') {
309
0
        return ISC_R_FAILURE;
310
0
      }
311
0
      t = strchr(alphnum, *cp);
312
0
      if (t == NULL || *++t == '\0') {
313
0
        *cp++ = alphnum[0];
314
0
      } else {
315
0
        *cp = *t;
316
0
        break;
317
0
      }
318
0
    }
319
0
  }
320
0
  if (unlink(file) < 0) {
321
0
    if (errno != ENOENT) {
322
0
      return isc__errno2result(errno);
323
0
    }
324
0
  }
325
0
  return ISC_R_SUCCESS;
326
0
}
327
328
isc_result_t
329
0
isc_file_openunique(char *templet, FILE **fp) {
330
0
  int mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
331
0
  return isc_file_openuniquemode(templet, mode, fp);
332
0
}
333
334
isc_result_t
335
0
isc_file_openuniqueprivate(char *templet, FILE **fp) {
336
0
  int mode = S_IWUSR | S_IRUSR;
337
0
  return isc_file_openuniquemode(templet, mode, fp);
338
0
}
339
340
isc_result_t
341
0
isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
342
0
  int fd;
343
0
  FILE *f;
344
0
  isc_result_t result = ISC_R_SUCCESS;
345
0
  char *x;
346
0
  char *cp;
347
348
0
  REQUIRE(templet != NULL);
349
0
  REQUIRE(fp != NULL && *fp == NULL);
350
351
0
  cp = templet;
352
0
  while (*cp != '\0') {
353
0
    cp++;
354
0
  }
355
0
  if (cp == templet) {
356
0
    return ISC_R_FAILURE;
357
0
  }
358
359
0
  x = cp--;
360
0
  while (cp >= templet && *cp == 'X') {
361
0
    *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
362
0
    x = cp--;
363
0
  }
364
365
0
  while ((fd = open(templet, O_RDWR | O_CREAT | O_EXCL, mode)) == -1) {
366
0
    if (errno != EEXIST) {
367
0
      return isc__errno2result(errno);
368
0
    }
369
0
    for (cp = x;;) {
370
0
      const char *t;
371
0
      if (*cp == '\0') {
372
0
        return ISC_R_FAILURE;
373
0
      }
374
0
      t = strchr(alphnum, *cp);
375
0
      if (t == NULL || *++t == '\0') {
376
0
        *cp++ = alphnum[0];
377
0
      } else {
378
0
        *cp = *t;
379
0
        break;
380
0
      }
381
0
    }
382
0
  }
383
0
  f = fdopen(fd, "w+");
384
0
  if (f == NULL) {
385
0
    result = isc__errno2result(errno);
386
0
    if (remove(templet) < 0) {
387
0
      isc_log_write(ISC_LOGCATEGORY_GENERAL,
388
0
              ISC_LOGMODULE_FILE, ISC_LOG_ERROR,
389
0
              "remove '%s': failed", templet);
390
0
    }
391
0
    (void)close(fd);
392
0
  } else {
393
0
    *fp = f;
394
0
  }
395
396
0
  return result;
397
0
}
398
399
isc_result_t
400
0
isc_file_remove(const char *filename) {
401
0
  int r;
402
403
0
  REQUIRE(filename != NULL);
404
405
0
  r = unlink(filename);
406
0
  if (r == 0) {
407
0
    return ISC_R_SUCCESS;
408
0
  } else {
409
0
    return isc__errno2result(errno);
410
0
  }
411
0
}
412
413
isc_result_t
414
0
isc_file_rename(const char *oldname, const char *newname) {
415
0
  int r;
416
417
0
  REQUIRE(oldname != NULL);
418
0
  REQUIRE(newname != NULL);
419
420
0
  r = rename(oldname, newname);
421
0
  if (r == 0) {
422
0
    return ISC_R_SUCCESS;
423
0
  } else {
424
0
    return isc__errno2result(errno);
425
0
  }
426
0
}
427
428
bool
429
6
isc_file_exists(const char *pathname) {
430
6
  struct stat stats;
431
432
6
  REQUIRE(pathname != NULL);
433
434
6
  return file_stats(pathname, &stats) == ISC_R_SUCCESS;
435
6
}
436
437
isc_result_t
438
0
isc_file_isplainfile(const char *filename) {
439
  /*
440
   * This function returns success if filename is a plain file.
441
   */
442
0
  struct stat filestat;
443
0
  memset(&filestat, 0, sizeof(struct stat));
444
445
0
  if ((stat(filename, &filestat)) == -1) {
446
0
    return isc__errno2result(errno);
447
0
  }
448
449
0
  if (!S_ISREG(filestat.st_mode)) {
450
0
    return ISC_R_INVALIDFILE;
451
0
  }
452
453
0
  return ISC_R_SUCCESS;
454
0
}
455
456
isc_result_t
457
0
isc_file_isplainfilefd(int fd) {
458
  /*
459
   * This function returns success if filename is a plain file.
460
   */
461
0
  struct stat filestat;
462
0
  memset(&filestat, 0, sizeof(struct stat));
463
464
0
  if ((fstat(fd, &filestat)) == -1) {
465
0
    return isc__errno2result(errno);
466
0
  }
467
468
0
  if (!S_ISREG(filestat.st_mode)) {
469
0
    return ISC_R_INVALIDFILE;
470
0
  }
471
472
0
  return ISC_R_SUCCESS;
473
0
}
474
475
isc_result_t
476
0
isc_file_isdirectory(const char *filename) {
477
  /*
478
   * This function returns success if filename exists and is a
479
   * directory.
480
   */
481
0
  struct stat filestat;
482
0
  memset(&filestat, 0, sizeof(struct stat));
483
484
0
  if ((stat(filename, &filestat)) == -1) {
485
0
    return isc__errno2result(errno);
486
0
  }
487
488
0
  if (!S_ISDIR(filestat.st_mode)) {
489
0
    return ISC_R_INVALIDFILE;
490
0
  }
491
492
0
  return ISC_R_SUCCESS;
493
0
}
494
495
bool
496
0
isc_file_isabsolute(const char *filename) {
497
0
  REQUIRE(filename != NULL);
498
0
  return filename[0] == '/';
499
0
}
500
501
bool
502
0
isc_file_iscurrentdir(const char *filename) {
503
0
  REQUIRE(filename != NULL);
504
0
  return filename[0] == '.' && filename[1] == '\0';
505
0
}
506
507
bool
508
0
isc_file_ischdiridempotent(const char *filename) {
509
0
  REQUIRE(filename != NULL);
510
0
  if (isc_file_isabsolute(filename)) {
511
0
    return true;
512
0
  }
513
0
  if (isc_file_iscurrentdir(filename)) {
514
0
    return true;
515
0
  }
516
0
  return false;
517
0
}
518
519
const char *
520
0
isc_file_basename(const char *filename) {
521
0
  const char *s;
522
523
0
  REQUIRE(filename != NULL);
524
525
0
  s = strrchr(filename, '/');
526
0
  if (s == NULL) {
527
0
    return filename;
528
0
  }
529
530
0
  return s + 1;
531
0
}
532
533
void
534
0
isc_file_progname(const char *filename, char *buf, size_t buflen) {
535
0
  const char *base;
536
0
  size_t len;
537
538
0
  REQUIRE(filename != NULL);
539
0
  REQUIRE(buf != NULL);
540
541
0
  base = isc_file_basename(filename);
542
543
  /*
544
   * Libtool doesn't preserve the program name prior to final
545
   * installation.  Remove the libtool prefix ("lt-").
546
   */
547
0
  if (strncmp(base, "lt-", 3) == 0) {
548
0
    base += 3;
549
0
  }
550
551
0
  len = strlen(base) + 1;
552
553
0
  RUNTIME_CHECK(len <= buflen);
554
555
0
  memmove(buf, base, len);
556
0
}
557
558
/*
559
 * Put the absolute name of the current directory into 'dirname', which is
560
 * a buffer of at least 'length' characters.  End the string with the
561
 * appropriate path separator, such that the final product could be
562
 * concatenated with a relative pathname to make a valid pathname string.
563
 */
564
static isc_result_t
565
0
dir_current(char *dirname, size_t length) {
566
0
  char *cwd;
567
0
  isc_result_t result = ISC_R_SUCCESS;
568
569
0
  REQUIRE(dirname != NULL);
570
0
  REQUIRE(length > 0U);
571
572
0
  cwd = getcwd(dirname, length);
573
574
0
  if (cwd == NULL) {
575
0
    if (errno == ERANGE) {
576
0
      result = ISC_R_NOSPACE;
577
0
    } else {
578
0
      result = isc__errno2result(errno);
579
0
    }
580
0
  } else {
581
0
    if (strlen(dirname) + 1 == length) {
582
0
      result = ISC_R_NOSPACE;
583
0
    } else if (dirname[1] != '\0') {
584
0
      strlcat(dirname, "/", length);
585
0
    }
586
0
  }
587
588
0
  return result;
589
0
}
590
591
isc_result_t
592
0
isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
593
0
  RETERR(dir_current(path, pathlen));
594
595
0
  if (strlen(path) + strlen(filename) + 1 > pathlen) {
596
0
    return ISC_R_NOSPACE;
597
0
  }
598
0
  strlcat(path, filename, pathlen);
599
600
0
  return ISC_R_SUCCESS;
601
0
}
602
603
isc_result_t
604
0
isc_file_truncate(const char *filename, off_t size) {
605
0
  isc_result_t result = ISC_R_SUCCESS;
606
607
0
  if (truncate(filename, size) < 0) {
608
0
    result = isc__errno2result(errno);
609
0
  }
610
0
  return result;
611
0
}
612
613
isc_result_t
614
0
isc_file_safecreate(const char *filename, FILE **fp) {
615
0
  isc_result_t result;
616
0
  int flags;
617
0
  struct stat sb;
618
0
  FILE *f;
619
0
  int fd;
620
621
0
  REQUIRE(filename != NULL);
622
0
  REQUIRE(fp != NULL && *fp == NULL);
623
624
0
  result = file_lstats(filename, &sb);
625
0
  if (result == ISC_R_SUCCESS) {
626
0
    if (!S_ISREG(sb.st_mode)) {
627
0
      return ISC_R_INVALIDFILE;
628
0
    }
629
0
    flags = O_WRONLY | O_TRUNC | O_NOFOLLOW;
630
0
  } else if (result == ISC_R_FILENOTFOUND) {
631
0
    flags = O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW;
632
0
  } else {
633
0
    return result;
634
0
  }
635
636
0
  fd = open(filename, flags, S_IRUSR | S_IWUSR);
637
0
  if (fd == -1) {
638
0
    return isc__errno2result(errno);
639
0
  }
640
641
0
  f = fdopen(fd, "w");
642
0
  if (f == NULL) {
643
0
    result = isc__errno2result(errno);
644
0
    close(fd);
645
0
    return result;
646
0
  }
647
648
0
  *fp = f;
649
0
  return ISC_R_SUCCESS;
650
0
}
651
652
isc_result_t
653
isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname,
654
0
       char const **bname) {
655
0
  char *dir = NULL;
656
0
  const char *file = NULL, *slash = NULL;
657
658
0
  if (path == NULL) {
659
0
    return ISC_R_INVALIDFILE;
660
0
  }
661
662
0
  slash = strrchr(path, '/');
663
664
0
  if (slash == path) {
665
0
    file = ++slash;
666
0
    dir = isc_mem_strdup(mctx, "/");
667
0
  } else if (slash != NULL) {
668
0
    file = ++slash;
669
0
    dir = isc_mem_allocate(mctx, slash - path);
670
0
    strlcpy(dir, path, slash - path);
671
0
  } else {
672
0
    file = path;
673
0
    dir = isc_mem_strdup(mctx, ".");
674
0
  }
675
676
0
  if (*file == '\0') {
677
0
    isc_mem_free(mctx, dir);
678
0
    return ISC_R_INVALIDFILE;
679
0
  }
680
681
0
  *dirname = dir;
682
0
  *bname = file;
683
684
0
  return ISC_R_SUCCESS;
685
0
}
686
687
2
#define DISALLOW "\\/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
688
689
static isc_result_t
690
digest2hex(unsigned char *digest, unsigned int digestlen, char *hash,
691
2
     size_t hashlen) {
692
2
  unsigned int i;
693
2
  int ret;
694
66
  for (i = 0; i < digestlen; i++) {
695
64
    size_t left = hashlen - i * 2;
696
64
    ret = snprintf(hash + i * 2, left, "%02x", digest[i]);
697
64
    if (ret < 0 || (size_t)ret >= left) {
698
0
      return ISC_R_NOSPACE;
699
0
    }
700
64
  }
701
2
  return ISC_R_SUCCESS;
702
2
}
703
704
isc_result_t
705
isc_file_sanitize(const char *dir, const char *base, const char *ext,
706
2
      char *path, size_t length) {
707
2
  char buf[PATH_MAX];
708
2
  unsigned char digest[ISC_MAX_MD_SIZE];
709
2
  unsigned int digestlen;
710
2
  char hash[ISC_MAX_MD_SIZE * 2 + 1];
711
2
  size_t l = 0;
712
713
2
  REQUIRE(base != NULL);
714
2
  REQUIRE(path != NULL);
715
716
2
  l = strlen(base) + 1;
717
718
  /*
719
   * allow room for a full sha256 hash (64 chars
720
   * plus null terminator)
721
   */
722
2
  if (l < 65U) {
723
2
    l = 65;
724
2
  }
725
726
2
  if (dir != NULL) {
727
0
    l += strlen(dir) + 1;
728
0
  }
729
2
  if (ext != NULL) {
730
2
    l += strlen(ext) + 1;
731
2
  }
732
733
2
  if (l > length || l > (unsigned int)PATH_MAX) {
734
0
    return ISC_R_NOSPACE;
735
0
  }
736
737
  /* Check whether the full-length SHA256 hash filename exists */
738
2
  RETERR(isc_md(ISC_MD_SHA256, (const unsigned char *)base, strlen(base),
739
2
          digest, &digestlen));
740
741
2
  RETERR(digest2hex(digest, digestlen, hash, sizeof(hash)));
742
743
2
  snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
744
2
     dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
745
2
     ext != NULL ? ext : "");
746
2
  if (isc_file_exists(buf)) {
747
0
    strlcpy(path, buf, length);
748
0
    return ISC_R_SUCCESS;
749
0
  }
750
751
  /* Check for a truncated SHA256 hash filename */
752
2
  hash[16] = '\0';
753
2
  snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
754
2
     dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
755
2
     ext != NULL ? ext : "");
756
2
  if (isc_file_exists(buf)) {
757
0
    strlcpy(path, buf, length);
758
0
    return ISC_R_SUCCESS;
759
0
  }
760
761
  /*
762
   * If neither hash filename already exists, then we'll use
763
   * the original base name if it has no disallowed characters,
764
   * or the truncated hash name if it does.
765
   */
766
2
  if (strpbrk(base, DISALLOW) != NULL) {
767
0
    strlcpy(path, buf, length);
768
0
    return ISC_R_SUCCESS;
769
0
  }
770
771
2
  snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
772
2
     dir != NULL ? "/" : "", base, ext != NULL ? "." : "",
773
2
     ext != NULL ? ext : "");
774
2
  strlcpy(path, buf, length);
775
2
  return ISC_R_SUCCESS;
776
2
}
777
778
bool
779
0
isc_file_isdirwritable(const char *path) {
780
0
  return access(path, W_OK | X_OK) == 0;
781
0
}