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