The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1/*
2 * This library is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2.1 of the License, or (at your option) any later version.
6 *
7 * This library is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with this library; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/** Various miscellaneous functions to manipulate files and paths
18 *
19 * @file src/lib/util/file.c
20 *
21 * @copyright 2019 The FreeRADIUS project
22 * @copyright 2019 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
23 */
24RCSID("$Id: 533b26b63b7031f83be763de8ce3f9df0f7a137b $")
25
26#include <sys/param.h>
27#include <fcntl.h>
28#include <stdio.h>
29#include <sys/stat.h>
30
31#include <freeradius-devel/util/file.h>
32#include <freeradius-devel/util/syserror.h>
33#include <freeradius-devel/util/value.h>
34
35/** Callback for the common case of chown() of the directory.
36 *
37 */
38int fr_mkdir_chown(int fd, char const *path, void *uctx)
39{
40 fr_mkdir_chown_t const *ctx = uctx;
41
42 if ((ctx->uid == (uid_t) -1) && (ctx->gid == (gid_t) -1)) return 0;
43
44 if (fchown(fd, ctx->uid, ctx->gid) < 0) {
45 fr_strerror_printf("Failed changing ownership on directory \"%s\": %s",
46 path, fr_syserror(errno));
47 return -1;
48 }
49
50 return 0;
51}
52
53
54static ssize_t _fr_mkdir(int *fd_out, char *start, char *path, mode_t mode, fr_mkdir_func_t func, void *uctx)
55{
56 int ret, fd;
57 char *p;
58
59 /*
60 * Try to make the path. If it exists, chmod it.
61 * If a path doesn't exist, that's OK. Otherwise
62 * return with an error.
63 *
64 * Directories permissions are initially set so
65 * that only we should have access. This prevents
66 * an attacker removing them and swapping them
67 * out for a link to somewhere else.
68 * We change them to the correct permissions later.
69 */
70 ret = mkdir(path, 0700);
71 if (ret >= 0) {
72 fd = open(path, O_DIRECTORY);
73 if (fd < 0) {
74 fr_strerror_printf("Failed opening directory we created: %s",
75 fr_syserror(errno));
76 mkdir_error:
77 p = strrchr(path, FR_DIR_SEP);
78 if (!p) return start - path;
79
80 return start - p;
81 }
82
83 if (fchmod(fd, mode) < 0) {
84 fr_strerror_printf("Failed setting permissions on directory "
85 "we created: %s", fr_syserror(errno));
86 close(fd);
87 goto mkdir_error;
88 }
89 *fd_out = fd;
90 return strlen(start);
91 }
92
93 /*
94 * EEXIST is only OK when we're calling mkdir on the
95 * whole path, and it exists which should have been
96 * caught by fr_mkdir before calling this function.
97 *
98 * Unless we're running in an environment with multiple
99 * processes, in which case EEXIST means that another
100 * process created this directory in between our check
101 * and our creation.
102 */
103 if (errno == EEXIST) {
104 fd = open(path, O_DIRECTORY);
105 if (fd < 0) {
106 fr_strerror_printf("Failed opening existing directory: %s", fr_syserror(errno));
107 goto mkdir_error;
108 }
109 *fd_out = fd;
110 return strlen(start);
111 }
112
113 /*
114 * ENOENT means we're trying to create too much path
115 * at once. Recurse to discover the deepest path
116 * component that already exists.
117 */
118 if (errno != ENOENT) {
119 fr_strerror_printf("Failed creating directory path: %s", fr_syserror(errno));
120 goto mkdir_error;
121 }
122
123 /*
124 * A component in the path doesn't
125 * exist. Look for the LAST path name. Try
126 * to create that. If there's an error, we leave
127 * the path path as the one at which the
128 * error occurred.
129 */
130 p = strrchr(path, FR_DIR_SEP);
131 if (!p || (p == path)) return start - path; /* last path component and we've previously failed */
132
133 *p = '\0';
134 if (_fr_mkdir(fd_out, start, path, mode, func, uctx) <= 0) return start - p;
135
136 if (!fr_cond_assert_msg((*fd_out) >= 0, "Logic error - Bad FD %i", *fd_out)) return start - p;
137
138 /*
139 * At this point *fd_out, should be an FD
140 * for the containing directory.
141 *
142 * Dir may already exist if we're racing
143 * other processes as we do in CI.
144 */
145 if (mkdirat(*fd_out, p + 1, 0700) < 0) {
146 /*
147 * This is usually because of a race with
148 * other processes trying to create the
149 * same directory.
150 */
151 if (errno == EEXIST) {
152 fd = openat(*fd_out, p + 1, O_DIRECTORY);
153 if (fd < 0) {
154 fr_strerror_printf_push("Failed opening existing directory path component: %s",
155 fr_syserror(errno));
156 goto mkdirat_error;
157 }
158 *p = FR_DIR_SEP;
159 goto done;
160 }
161
162 fr_strerror_printf_push("Failed creating directory path component: %s", fr_syserror(errno));
163
164 mkdirat_error:
165 close(*fd_out);
166 *fd_out = -1;
167 return start - p;
168 }
169
170 fd = openat(*fd_out, p + 1, O_DIRECTORY);
171 if (fd < 0) {
172 fr_strerror_printf_push("Failed opening directory we "
173 "created: %s", fr_syserror(errno));
174 goto mkdirat_error;
175 }
176
177 if (fchmod(fd, mode) < 0) {
178 fr_strerror_printf_push("Failed setting permissions on "
179 "directory we created: %s", fr_syserror(errno));
180 goto mkdirat_error;
181 }
182
183 *p = FR_DIR_SEP;
184
185 /*
186 * Call the user function
187 */
188 if (func && (func(fd, path, uctx) < 0)) {
189 fr_strerror_printf_push("Callback failed processing directory \"%s\"", path);
190 goto mkdirat_error;
191 }
192
193 /*
194 * Swap active *fd_out to point to the dir
195 * we just created.
196 */
197done:
198 close(*fd_out);
199 *fd_out = fd;
200
201 return strlen(start);
202}
203
204/** Create directories that are missing in the specified path
205 *
206 * @param[out] fd_out If not NULL, will contain a file descriptor
207 * for the deepest path component created.
208 * @param[in] path to populate with directories.
209 * @param[in] len Length of the path string.
210 * @param[in] mode for new directories.
211 * @param[in] func to call each time a new directory is created.
212 * @param[in] uctx to pass to func.
213 * @return
214 * - >0 on success.
215 * - <= 0 on failure. Negative offset pointing to the
216 * path separator of the path component that caused the error.
217 */
218ssize_t fr_mkdir(int *fd_out, char const *path, ssize_t len, mode_t mode, fr_mkdir_func_t func, void *uctx)
219{
220 char *our_path;
221 int fd = -1;
222 ssize_t slen;
223
224 if (len < 0) len = strlen(path);
225 if (len == 0) return 0;
226
227 /*
228 * Fast path (har har)
229 *
230 * Avoids duping the input for the
231 * common case.
232 */
233 fd = open(path, O_DIRECTORY);
234 if (fd >= 0) goto done;
235
236 /*
237 * Dup the amount of input path
238 * we need.
239 */
240 our_path = talloc_bstrndup(NULL, path, (size_t)len);
241 if (!our_path) {
242 fr_strerror_const("Out of memory");
243 return -1;
244 }
245
246 fr_strerror_clear(); /* We make liberal use of push */
247
248 /*
249 * Call the recursive function to
250 * create any missing dirs in the
251 * specified path.
252 */
253 slen = _fr_mkdir(&fd, our_path, our_path, mode, func, uctx);
254 talloc_free(our_path);
255 if (slen <= 0) return slen;
256
257done:
258 if (fd_out) {
259 *fd_out = fd;
260 } else {
261 close(fd);
262 }
263
264 return len;
265}
266
267/** Convenience wrapper around realpath
268 *
269 * Wraps realpath, but takes a path with an explicit length, and returns
270 * the result in a talloced buffer.
271 *
272 * On error, errno is set, and the string version of the error is
273 * available with fr_strerror().
274 *
275 * @param[in] ctx in which to allocate the result.
276 * @param[in] path To convert to an absolute path.
277 * @param[in] len How much of 'path' to read. If < 0, then
278 * the entire path will be used.
279 * @return
280 * - NULL on error.
281 * - The absolute version of the input path on success.
282 */
283char *fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
284{
285 char *tmp_path = NULL, *abs_path, *talloc_abs_path;
286
287 if (len > 0) path = tmp_path = talloc_bstrndup(NULL, path, (size_t)len);
288
289 abs_path = realpath(path, NULL);
290 if (!abs_path) {
291 fr_strerror_printf("Failed resolving path \"%pV\": %s",
292 fr_box_strvalue_buffer(path), fr_syserror(errno));
293 talloc_free(tmp_path);
294 return NULL;
295 }
296
297 talloc_free(tmp_path);
298
299 talloc_abs_path = talloc_strdup(ctx, abs_path);
300 free(abs_path);
301 if (!talloc_abs_path) {
302 fr_strerror_const("Out of Memory");
303 return NULL;
304 }
305
306 return talloc_abs_path;
307}
308
309/** Create an empty file
310 *
311 * @param[out] fd_out If not NULL, will contain a file descriptor
312 * for the file we just opened.
313 * @param[in] filename path to file.
314 * @param[in] mode Specifies the file mode bits be applied.
315 * @param[in] mkdir Whether we should create directories
316 * for any missing path components.
317 * @param[in] dir_mode Mode of any directories created.
318 * @return
319 * - >0 on success.
320 * - <= 0 on failure. Error available in error stack (use fr_strerror())
321 */
322ssize_t fr_touch(int *fd_out, char const *filename, mode_t mode, bool mkdir, mode_t dir_mode) {
323 int fd;
324
325 fd = open(filename, O_WRONLY | O_CREAT, mode);
326 if (fd < 0) {
327 ssize_t slen = 0;
328 char const *q;
329
330 if (mkdir && (errno == ENOENT) && (q = strrchr(filename, FR_DIR_SEP))) {
331 int dir_fd = -1;
332
333 slen = fr_mkdir(&dir_fd, filename, q - filename, dir_mode, NULL, NULL);
334 if((slen <= 0) || (dir_fd < 0)) return slen;
335
336 fd = openat(dir_fd, q + 1, O_WRONLY | O_CREAT, mode);
337 if (fd >= 0) {
338 close(dir_fd);
339 close(fd);
340 return strlen(filename);
341 }
342 close(dir_fd);
343 slen = -(q - filename);
344 }
345 fr_strerror_printf("Failed creating file: %s", fr_syserror(errno));
346 return slen;
347 }
348
349 if (fd_out) {
350 *fd_out = fd;
351 } else {
352 close(fd);
353 }
354
355 return strlen(filename);
356}
357
358/** Remove a regular file from the filesystem
359 *
360 * @param[in] filename path to file.
361 * @return
362 * - -1 On error.
363 * - 0 if the file was removed.
364 * - 1 if the file didn't exist.
365 */
366int fr_unlink(char const *filename) {
367 if (unlink(filename) == 0) return 0;
368
369 if (errno == ENOENT) return 1;
370
371 fr_strerror_printf("Failed removing regular file \"%s\": %s", filename, fr_syserror(errno));
372
373 return -1;
374}
375
376/** Intended to be used in logging functions to make output more readable
377 *
378 * This function is not performant and should probably not be used at runtime.
379 *
380 * @param[in] filename to strip working directory from.
381 * @return Position in filename after our working directory.
382 */
383char const *fr_cwd_strip(char const *filename)
384{
385 char our_wd[MAXPATHLEN];
386 char const *found;
387
388 if (!getcwd(our_wd, sizeof(our_wd))) return filename;
389
390 found = strstr(filename, our_wd);
391 if (found && (found == our_wd)) {
392 filename += strlen(our_wd);
393 while (*filename == '/') filename++;
394 return filename;
395 }
396
397 return filename;
398}
399
400/** From a pathname, return fd and filename needed for *at() functions
401 *
402 * @param[in] dirfd points to place to store the dirfd
403 * @param[in] filename points to placd to store a pointer into pathname
404 * that points to the filename
405 * @param[in] pathname the full pathname of the file
406 *
407 * @return
408 * - -1 on error
409 * - 0 on success
410 */
411int fr_dirfd(int *dirfd, char const **filename, char const *pathname)
412{
413 char const *last_slash = strrchr(pathname, '/');
414
415 if (last_slash == NULL) {
416 *filename = pathname;
417 *dirfd = AT_FDCWD;
418 return 0;
419 }
420 {
421 char dirpath[PATH_MAX];
422 size_t dirlen = (size_t)(last_slash - pathname);
423
424 if (unlikely(dirlen >= sizeof(dirpath))) {
425 errno = ENAMETOOLONG;
426 return -1;
427 }
428
429 memcpy(dirpath, pathname, dirlen);
430 dirpath[dirlen] = '\0';
431 *filename = last_slash + 1;
432 *dirfd = open(dirpath, O_DIRECTORY);
433 return (*dirfd < 0) ? -1 : 0;
434 }
435}
436
437static bool fr_globdir_file_ok(char const *try, fr_globdir_iter_t *iter)
438{
439 size_t len, room;
440 char const *p;
441 char *filename;
442 struct stat stat_buf;
443
444 /*
445 * Filter the filenames.
446 */
447 if (try[0] == '.') return false;
448
449 /*
450 * Check for valid characters
451 */
452 p = try;
453 while (*p) {
454 /*
455 * Control characters are invalid UTF-8, too.
456 */
457 len = fr_utf8_char((uint8_t const *) p, -1);
458 if (!len) return false;
459
460 /*
461 * The string is valid UTF-8, but is NOT an ASCII
462 * character. We allow it.
463 */
464 if (len > 1) {
465 p += len;
466 continue;
467 }
468
469 /*
470 * Limit the ASCII characters we allow.
471 */
472 if (isalpha((uint8_t)*p) ||
473 isdigit((uint8_t)*p) ||
474 (*p == '-') ||
475 (*p == '_') ||
476 (*p == '.')) {
477 p++;
478 continue;
479 }
480
481 /*
482 * Invalid character in filename, it's not a match.
483 */
484 return false;
485 }
486
487 len = p - try;
488 if (!len) return false;
489
490 /*
491 * Ignore files generated by deb / rpm packaging updates.
492 */
493 if ((len > 10) && (strncmp(&try[len - 10], ".dpkg-dist", 10) == 0)) return false;
494 if ((len > 9) && (strncmp(&try[len - 9], ".dpkg-old", 9) == 0)) return false;
495 if ((len > 7) && (strncmp(&try[len - 7], ".rpmnew", 7) == 0)) return false;
496 if ((len > 8) && (strncmp(&try[len - 8], ".rpmsave", 8) == 0)) return false;
497
498 /*
499 * When reading all files in a directory, iter->filename points to the directory
500 * name which is being read. The file being tested needs to be added after that.
501 */
502 filename = iter->filename + (iter->type == FR_GLOBDIR_DIR ? strlen(iter->filename) : 0);
503
504 /*
505 * strlcpy() returns the length of the input, which can be larger than the available space.
506 */
507 room = (iter->path + PATH_MAX) - filename;
508
509 if (strlcpy(filename, try, room) >= room) return false;
510
511 /*
512 * We only read normal files which are NOT executable, and symlinks.
513 */
514 if (stat(iter->path, &stat_buf) != 0) {
515 return false;
516 }
517
518 if (S_ISREG(stat_buf.st_mode)) {
519 if ((stat_buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0) {
520 return false;
521 }
522 } else if (!S_ISLNK(stat_buf.st_mode)) { /* soft links are executable */
523 return false;
524 }
525
526 return true;
527}
528
529#ifdef HAVE_DIRENT_H
530/*
531 * Filter filenames when reading a directory.
532 */
533static int fr_globdir_dir_next(char const **filename, fr_globdir_iter_t *iter)
534{
535 struct dirent *dp;
536
537 fr_assert(iter->dir != NULL);
538
539 /*
540 * Reading all of the directory entries will result in
541 * reading ones we don't want, so we filter them.
542 */
543 while (true) {
544 errno = 0;
545 dp = readdir(iter->dir);
546 if (!dp) {
547 if (errno != 0) return -1;
548
549 *filename = NULL;
550 return 0;
551 }
552
553 if (!fr_globdir_file_ok(dp->d_name, iter)) continue;
554
555 /*
556 * It's mostly ASCII, and not a leftover file name, stop.
557 */
558 break;
559 }
560
561 *filename = iter->filename;
562 return 0;
563}
564#endif
565
566/** Create a full path from dir + pattern.
567 *
568 */
569static int fr_globdir_get_path(char const *dir, char const *pattern, fr_globdir_iter_t *iter)
570{
571 char const *p;
572 char *q;
573 bool slash;
574
575 /*
576 * Bootstrap the full path.
577 */
578 iter->path = malloc(PATH_MAX);
579 if (!iter->path) {
580 errno = ENOMEM;
581 return -1;
582 }
583
584 /*
585 * Sanity check the directory name.
586 *
587 * We assume that the directory name is otherwise safe.
588 */
589 p = dir;
590 q = iter->path;
591 while (*p) {
592 /*
593 * Suppress duplicate '/', and trailing '/'.
594 */
595 if (*p == '/') {
596 *(q++) = *(p++);
597
598 while (*p == '/') p++;
599
600 if (!*p) break;
601 continue;
602 }
603
604 if ((size_t) (q - iter->path) >= PATH_MAX) {
605 free(iter->path);
606 errno = ENOMEM;
607 return -1;
608 }
609
610 *(q++) = *(p++);
611 }
612
613 /*
614 * Add in a trailing '/' to the directory.
615 */
616 *(q++) = '/';
617 slash = true;
618 iter->filename = q;
619
620 /*
621 * Copy over the pattern name, but sanity check it.
622 */
623 p = pattern;
624 while (*p) {
625 if ((size_t) (q - iter->path) >= PATH_MAX) {
626 free(iter->path);
627 errno = ENOMEM;
628 return -1;
629 }
630
631 /*
632 * @todo - if it's a pattern, then handle [./] as a special case.
633 */
634
635 /*
636 * Handle the case of bad things after the directory name, too.
637 *
638 * But otherwise copy over any '/' which we see in the pattern.
639 */
640 if (!slash) {
641 slash = (*p == '/');
642 *(q++) = *(p++);
643
644 if (!slash) continue;
645 }
646
647 more_slash:
648 /*
649 * ///// --> /
650 */
651 while (*p == '/') p++;
652
653 /*
654 * foo/. may be special
655 */
656 if (*p != '.') {
657 slash = false;
658 continue;
659 }
660
661 /*
662 * foo/./ --> foo/
663 */
664 if (p[1] == '/') {
665 p += 2;
666 goto more_slash;
667 }
668
669 /*
670 * foo/../ --> error
671 */
672 if ((p[1] == '.') && (p[2] == '/')) {
673 free(iter->path);
674 errno = ENOENT;
675 return -1;
676 }
677
678 /*
679 * foo/.bar is OK
680 */
681 slash = false;
682 }
683 *q = '\0';
684
685 return 0;
686}
687
688/** Initialize an iterator over filenames.
689 *
690 * @param[out] filename the _relative_ filename which should be opened
691 * @param[in] dir the directory to read
692 * @param[in] pattern the filename or pattern to read. Cannot be an absolute path.
693 * @param[in,out] iter the iteration structure.
694 * @return
695 * - <0 on error
696 * - 0 on success, but no filename
697 * - 1 for "have file".
698 */
699int fr_globdir_iter_init(char const **filename, char const *dir, char const *pattern, fr_globdir_iter_t *iter)
700{
701 char const *p;
702 char const *to_open;
703
704 /*
705 * Default to files, which is the most common case.
706 */
707 *iter = (fr_globdir_iter_t) {
709 };
710
711 /*
712 * Figure out what kind of thing we're opening.
713 */
714 for (p = pattern; *p != '\0'; p++) {
715 /*
716 * foo/ - read the entire directory.
717 */
718 if (*p == '/') {
719 if (p[1]) continue;
720
721#ifdef HAVE_DIRENT_H
722 iter->type = FR_GLOBDIR_DIR;
723 break;
724#else
725 errno = ENOENT;
726 return -1;
727#endif
728 }
729
730 /*
731 * foo*.txt
732 * foo?.txt
733 * foo.[ch]
734 *
735 * - file globbing.
736 *
737 * File globbing is either full path, or a path relative to CWD. It is most notably NOT
738 * relative to the input "dir". So if there are globs, we need a full path.
739 */
740 if ((*p == '*') || (*p == '?') || (*p == '[')) {
741#ifdef HAVE_GLOB_H
742 /*
743 * @todo - call realpath() to get the canonical filename?
744 */
745 if ((pattern[0] != '/') && (dir[0] != '/')) {
746 errno = ENOENT;
747 return -1;
748 }
749
750 iter->type = FR_GLOBDIR_GLOB;
751 break;
752#else
753 errno = ENOENT;
754 return -1;
755#endif
756 }
757 }
758
759 /*
760 * The pattern is an absolute path, we just use that as-is.
761 */
762 if (pattern[0] == '/') {
763 to_open = pattern;
764
765 } else if (iter->type == FR_GLOBDIR_FILE) {
766 /*
767 * Short-circuit the common case for files. We're just opening a file, and the file is
768 * relative to the directory which was passed in.
769 */
770 *filename = pattern;
771 return 1;
772
773 } else {
774 /*
775 * Either dir is absolute and pattern is relative, or they're both relative. Merge dir +
776 * pattern into a path.
777 *
778 * Note that globs are relative to CWD, so relative globs must be passed in correctly
779 * (that's an @todo), otherwise they won't work.
780 */
781 if (fr_globdir_get_path(dir, pattern, iter) < 0) {
782 return -1;
783 }
784
785 to_open = iter->path;
786 }
787
788 /*
789 * Now that we know what type of thing it is, go do the
790 * right thing.
791 */
792 switch (iter->type) {
794 (void) fr_globdir_iter_free(iter);
795 errno = ENOENT;
796 return -1;
797
798 case FR_GLOBDIR_FILE:
799 *filename = iter->path;
800 break;
801
802#ifdef HAVE_DIRENT_H
803 case FR_GLOBDIR_DIR:
804 /*
805 * No directory means no file. The caller then decides if the file is required.
806 */
807 iter->dir = opendir(to_open);
808 if (!iter->dir) {
809 (void) fr_globdir_iter_free(iter);
810 return 0;
811 }
812
813 if (fr_globdir_dir_next(filename, iter) < 0) {
814 (void) fr_globdir_iter_free(iter);
815 return -1;
816 }
817 break;
818#endif
819
820#ifdef HAVE_GLOB_H
821 case FR_GLOBDIR_GLOB:
822 if (glob(to_open, GLOB_NOESCAPE | GLOB_ERR, NULL, &iter->glob) < 0) {
823 (void) fr_globdir_iter_free(iter);
824 return -1;
825 }
826
827 if (iter->glob.gl_pathc == 0) {
828 *filename = NULL;
829 } else {
830 iter->gl_current = 0;
831
832 /*
833 * @todo - check the filenames using fr_globdir_file_ok()
834 */
835 *filename = iter->glob.gl_pathv[iter->gl_current];
836 }
837 break;
838#endif
839 }
840
841 return 0 + (*filename != NULL);
842}
843
844/** Get the next filename.
845 *
846 * fr_globdir_iter_init()
847 * do {
848 * ... use filename
849 * } while (fr_globdir_iter_next() == 1);
850 * fr_globdir_iter_free()
851 *
852 * @return
853 * - <0 for error
854 * - 0 for done
855 * - 1 for "have filename"
856 */
857int fr_globdir_iter_next(char const **filename, fr_globdir_iter_t *iter)
858{
859 switch (iter->type) {
861 break;
862
863 case FR_GLOBDIR_FILE:
864 *filename = NULL;
865 return 0;
866
867#ifdef HAVE_DIRENT_H
868 case FR_GLOBDIR_DIR:
869 if (fr_globdir_dir_next(filename, iter) < 0) return -1;
870
871 return 0 + (*filename != NULL);
872#endif
873
874#ifdef HAVE_GLOB_H
875 case FR_GLOBDIR_GLOB:
876 iter->gl_current++;
877 if (iter->gl_current >= iter->glob.gl_pathc) {
878 return 0;
879 }
880
881 *filename = iter->glob.gl_pathv[iter->gl_current];
882 fr_assert(*filename != NULL);
883
884 return 1;
885#endif
886 }
887
888 return -1;
889}
890
892{
893 if (iter->path) {
894 free(iter->path);
895 iter->path = NULL;
896 }
897
898 switch (iter->type) {
900 return -1;
901
902 case FR_GLOBDIR_FILE:
903 break;
904
905#ifdef HAVE_DIRENT_H
906 case FR_GLOBDIR_DIR:
907 if (!iter->dir) return 0;
908
909 return closedir(iter->dir);
910#endif
911
912#ifdef HAVE_GLOB_H
913 case FR_GLOBDIR_GLOB:
914 globfree(&iter->glob);
915 break;
916#endif
917 }
918
919 return 0;
920}
921
922/*
923 * This is fine. Don't complain.
924 */
925DIAG_OFF_OPTIONAL(gnu-designator)
926
928 .name = "filename",
929 .chr = '_',
930 .do_utf8 = true,
931 .do_hex = true,
932
933 .esc = {
934 [ 0x00 ... 0x2d ] = true, // special characters, but not '.'
935 [ 0x2f ] = true, // /
936 [ 0x3A ... 0x3f ] = true, // :;<=>?, but not "@"
937 [ 0x5b ... 0x5e ] = true, // [\]^
938 [ 0x60 ] = true, // back-tick
939 [ 0x7b ... 0xff ] = true, // {|}, and all chars which have high bit set, but aren't UTF-8
940 },
941};
942
944 .name = "filename",
945 .chr = '_',
946 .do_utf8 = true,
947 .do_hex = true,
948
949 .esc = {
950 [ 0x00 ... 0x2f ] = true, // special characters, '.', '/', etc.
951 [ 0x3A ... 0x3f ] = true, // :;<=>?, but not "@"
952 [ 0x5b ... 0x5e ] = true, // [\]^
953 [ 0x60 ] = true, // back-tick
954 [ 0x7b ... 0xff ] = true, // {|}, and all chars which have high bit set, but aren't UTF-8
955 },
956};
957
958/** Escape a string-typed value box so its contents are safe to use in a filename.
959 *
960 * The box's safe_for marker is set to this function, so a second
961 * call is a no-op. Boxes with zero length are also a no-op.
962 *
963 * @param[in] ctx talloc_ctx
964 * @param[in,out] vb value box to escape in place.
965 * @return
966 * - 0 on no-op (empty box).
967 * - 1 on success (box was either already safe or has been
968 * rewritten).
969 * - -1 on allocation/escape failure.
970 */
972{
973 fr_sbuff_t sbuff;
975 ssize_t slen;
976
977 fr_assert(vb->type == FR_TYPE_STRING);
978
979 if (vb->vb_length == 0) return 0;
980
982
983 /*
984 * In many cases, the filename is already clean, so we can avoid all kinds of work.
985 */
986 if (!fr_sbuff_in_needs_escaping(vb->vb_strvalue, vb->vb_length, &fr_filename_escape)) {
987 goto done;
988 }
989
990 /*
991 * Escaped output can be up to ~4x the input (each byte
992 * may turn into "_XX"). Let the sbuff grow as needed.
993 * rather than guessing.
994 */
995 if (!fr_sbuff_init_talloc(ctx, &sbuff, &tctx, vb->vb_length + 1, SIZE_MAX)) {
996 return -1;
997 }
998
999 slen = fr_sbuff_in_escape(&sbuff, vb->vb_strvalue, vb->vb_length, &fr_filename_escape);
1000 if (slen < 0) {
1001 error:
1002 talloc_free(sbuff.buff);
1003 return -1;
1004 }
1005
1006 /*
1007 * Trim it to the correct size.
1008 */
1009 if (fr_sbuff_trim_talloc(&sbuff, SIZE_MAX) < 0) goto error;
1010
1011 fr_value_box_strdup_shallow_replace(vb, sbuff.buff, (size_t) slen);
1012
1013done:
1015
1016 return 1;
1017}
1018
1019/** See if a filename is OK.
1020 *
1021 * @param[in] filename to check
1022 * @param[in] end of the filename to check
1023 * @param[in] allow_dir allow trailing '/' on filenames, but not './' or '../'
1024 * @return
1025 * - false - filename is invalid or insecure
1026 * - true - filename is sane and OK
1027 */
1028bool fr_filename_ok(char const *filename, char const *end, bool allow_dir)
1029{
1030 char const *p = filename;
1031 bool isdir;
1032
1033 if (end == filename) {
1034 fr_strerror_const("invalid zero-length (empty) filename");
1035 return false;
1036 }
1037
1038 isdir = true;
1039
1040 /*
1041 * See if the filename is doing anything funky. If so, disallow it.
1042 *
1043 * . - current directory, not allowed
1044 * ./ - ditto
1045 * .foo - allowed
1046 * foo - allowed
1047 * foo/ - disallowed
1048 */
1049 while (p < end) {
1050 if (!isdir) goto next;
1051
1052 /*
1053 * "foo/bar" is OK.
1054 *
1055 * @todo - do we care about repeated "/", such as foo//// ?
1056 * Likely not, but perhaps we want to normalize it.
1057 */
1058 if (*p != '.') goto next;
1059
1060 /*
1061 * "foo/." might be a problem.
1062 */
1063 p++;
1064 if (p == end) {
1065 fr_strerror_const("invalid attempt to access current directory via '/.'");
1066 return false;
1067 }
1068
1069 /*
1070 * "./foo" is allowed. But "foo/./" is not allowed.
1071 */
1072 if (*p == '/') {
1073 p++;
1074
1075 if (p == end) {
1076 fr_strerror_const("invalid attempt to access current directory via './'");
1077 return false;
1078 }
1079
1080 /*
1081 * @todo - do we care about "foo/./././" ?
1082 */
1083 isdir = true;
1084 continue;
1085 }
1086
1087 /*
1088 * "foo/.foo" is allowed.
1089 */
1090 if (*p != '.') goto next;
1091
1092 /*
1093 * "foo/../ is disallowed.
1094 */
1095 p++;
1096 if ((p == end) || (*p == '/')) {
1097 fr_strerror_const("invald attempt to go to parent directory via '/../'");
1098 return false;
1099 }
1100
1101 next:
1102 if (*(uint8_t const *) p < ' ') {
1103 fr_strerror_const("invalid filename contains control character");
1104 return false;
1105 }
1106
1107 isdir = (*p == '/');
1108 p++;
1109 }
1110
1111 /*
1112 * The last character was a '/', we may (or may not) allow it.
1113 */
1114 if (isdir && !allow_dir) {
1115 fr_strerror_const("invalid attempt to access current directory by '/'");
1116 return false;
1117 }
1118
1119 return true;
1120}
1121
1122/** See if a filename is allowed by a list.
1123 *
1124 * @param[in] filename to check
1125 * @param[in] end of the filename to check
1126 * @param[in] array list to check
1127 * @return
1128 * - false - filename is not allowed by the list
1129 * - true - filename is allowed by the list
1130 */
1131bool fr_filename_allowed_by_list(char const *filename, char const *end, char const * const *array)
1132{
1133 size_t i, num_files;
1134 size_t len = end - filename;
1135
1136 num_files = talloc_array_length(array);
1137 if (!num_files) return false;
1138
1139 for (i = 0; i < num_files; i++) {
1140 /*
1141 * Get length of config entry, not including terminating NUL
1142 */
1143 size_t alen = talloc_array_length(array[i]) - 1;
1144
1145 if (!alen) continue;
1146
1147 /*
1148 * Disallow '/' for security reasons.
1149 */
1150 if ((alen == 1) && (array[i][0] == '/')) {
1151 continue;
1152 }
1153
1154 /*
1155 * The allowed directory is longer than the filename, it's not allowed.
1156 */
1157 if (alen > len) continue;
1158
1159 /*
1160 * No leading match, it's not allowed.
1161 */
1162 if (memcmp(filename, array[i], alen) != 0) continue;
1163
1164 /*
1165 * Exact match, it is allowed.
1166 */
1167 if (alen == len) return true;
1168
1169 /*
1170 * "allow = foo/bar/" (trailing slash) is already
1171 * at a directory boundary.
1172 */
1173 if (array[i][alen - 1] == '/') return true;
1174
1175 /*
1176 * Setting "allow = foo/bar" does NOT mean that
1177 * we allow "foo/bard". It MUST be "foo/bar/bad"
1178 */
1179 if (filename[alen] != '/') continue;
1180
1181 return true;
1182 }
1183
1184 return false;
1185}
1186
1187/** Get the filename from a path
1188 *
1189 * @param path to get filename from.
1190 * @return
1191 * - pointer to the filename in the path.
1192 * - pointer to the path if no '/' is found.
1193 */
1194char const *fr_filename(char const *path)
1195{
1196 char const *p = strrchr(path, '/');
1197
1198 if (p) return p + 1;
1199
1200 return path;
1201}
1202
1203/** Trim a common prefix from a filename
1204 *
1205 * @param path to get filename from.
1206 * @param common prefix to trim from the path.
1207 * @return
1208 * - pointer to the position on the path where the common prefix match ended.
1209 */
1210char const *fr_filename_common_trim(char const *path, char const *common)
1211{
1212 char const *p_p, *p_c, *p_pn, *p_cn;
1213
1214 if (!path) return NULL;
1215 if (!common) return NULL;
1216
1217 p_p = path;
1218 p_c = common;
1219
1220 while ((p_pn = strchr(p_p, '/')) != NULL) {
1221 p_cn = strchr(p_c, '/');
1222 if (!p_cn) p_cn = p_c + strlen(p_c);
1223
1224 if ((p_pn - p_p) != (p_cn - p_c)) break; /* path component not the same len */
1225 if (strncmp(p_p, p_c, p_pn - p_p) != 0) break; /* path component not the same */
1226
1227 p_p = p_pn + 1;
1228 p_c = p_cn + 1;
1229 }
1230
1231 return p_p;
1232}
#define RCSID(id)
Definition build.h:603
#define DIAG_OFF_OPTIONAL(_x)
Definition build.h:577
#define unlikely(_x)
Definition build.h:486
#define fr_cond_assert_msg(_x, _fmt,...)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:194
free(array)
talloc_free(hp)
ssize_t fr_mkdir(int *fd_out, char const *path, ssize_t len, mode_t mode, fr_mkdir_func_t func, void *uctx)
Create directories that are missing in the specified path.
Definition file.c:218
bool fr_filename_ok(char const *filename, char const *end, bool allow_dir)
See if a filename is OK.
Definition file.c:1028
int fr_unlink(char const *filename)
Remove a regular file from the filesystem.
Definition file.c:366
int fr_globdir_iter_init(char const **filename, char const *dir, char const *pattern, fr_globdir_iter_t *iter)
Initialize an iterator over filenames.
Definition file.c:699
static int fr_globdir_get_path(char const *dir, char const *pattern, fr_globdir_iter_t *iter)
Create a full path from dir + pattern.
Definition file.c:569
int fr_mkdir_chown(int fd, char const *path, void *uctx)
Callback for the common case of chown() of the directory.
Definition file.c:38
static bool fr_globdir_file_ok(char const *try, fr_globdir_iter_t *iter)
Definition file.c:437
const fr_sbuff_escape_rules_t fr_filename_escape
Definition file.c:927
ssize_t fr_touch(int *fd_out, char const *filename, mode_t mode, bool mkdir, mode_t dir_mode)
Create an empty file.
Definition file.c:322
char const * fr_filename_common_trim(char const *path, char const *common)
Trim a common prefix from a filename.
Definition file.c:1210
const fr_sbuff_escape_rules_t fr_filename_escape_dots
Definition file.c:943
int fr_filename_box_make_safe(TALLOC_CTX *ctx, fr_value_box_t *vb)
Escape a string-typed value box so its contents are safe to use in a filename.
Definition file.c:971
int fr_globdir_iter_next(char const **filename, fr_globdir_iter_t *iter)
Get the next filename.
Definition file.c:857
char const * fr_cwd_strip(char const *filename)
Intended to be used in logging functions to make output more readable.
Definition file.c:383
int fr_dirfd(int *dirfd, char const **filename, char const *pathname)
From a pathname, return fd and filename needed for *at() functions.
Definition file.c:411
char const * fr_filename(char const *path)
Get the filename from a path.
Definition file.c:1194
char * fr_realpath(TALLOC_CTX *ctx, char const *path, ssize_t len)
Convenience wrapper around realpath.
Definition file.c:283
bool fr_filename_allowed_by_list(char const *filename, char const *end, char const *const *array)
See if a filename is allowed by a list.
Definition file.c:1131
int fr_globdir_iter_free(fr_globdir_iter_t *iter)
Definition file.c:891
static ssize_t _fr_mkdir(int *fd_out, char *start, char *path, mode_t mode, fr_mkdir_func_t func, void *uctx)
Definition file.c:54
@ FR_GLOBDIR_FILE
Definition file.h:77
@ FR_GLOBDIR_INVALID
Definition file.h:76
fr_globdir_type_t type
Definition file.h:87
char * path
Definition file.h:89
uid_t uid
Definition file.h:56
gid_t gid
Definition file.h:57
char * filename
Definition file.h:90
int(* fr_mkdir_func_t)(int fd, char const *path, void *uctx)
Callback for allowing additional operations on newly created directories.
Definition file.h:53
@ FR_TYPE_STRING
String of printable characters.
long int ssize_t
unsigned char uint8_t
unsigned long int size_t
size_t fr_utf8_char(uint8_t const *str, ssize_t inlen)
Checks for utf-8, taken from http://www.w3.org/International/questions/qa-forms-utf-8.
Definition print.c:39
#define fr_assert(_expr)
Definition rad_assert.h:37
static bool done
Definition radclient.c:80
int fr_sbuff_trim_talloc(fr_sbuff_t *sbuff, size_t len)
Trim a talloced sbuff to the minimum length required to represent the contained string.
Definition sbuff.c:444
ssize_t fr_sbuff_in_escape(fr_sbuff_t *sbuff, char const *in, size_t inlen, fr_sbuff_escape_rules_t const *e_rules)
Print an escaped string to an sbuff.
Definition sbuff.c:1705
bool fr_sbuff_in_needs_escaping(char const *in, size_t inlen, fr_sbuff_escape_rules_t const *e_rules)
Walk an input string and report whether fr_sbuff_in_escape() would escape any characters in it.
Definition sbuff.c:1785
Talloc sbuff extension structure.
Definition sbuff.h:138
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition strlcpy.c:34
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
char * talloc_bstrndup(TALLOC_CTX *ctx, char const *in, size_t inlen)
Binary safe strndup function.
Definition talloc.c:693
#define talloc_strdup(_ctx, _str)
Definition talloc.h:149
void fr_strerror_clear(void)
Clears all pending messages from the talloc pools.
Definition strerror.c:581
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_printf_push(_fmt,...)
Add a message to an existing stack of messages at the tail.
Definition strerror.h:84
#define fr_strerror_const(_msg)
Definition strerror.h:223
void fr_value_box_strdup_shallow_replace(fr_value_box_t *vb, char const *src, ssize_t len)
Free the existing buffer (if talloced) associated with the valuebox, and replace it with a new one.
Definition value.c:4889
#define fr_value_box_mark_safe_for(_box, _safe_for)
Definition value.h:1124
#define fr_box_strvalue_buffer(_val)
Definition value.h:343
#define fr_value_box_is_safe_for(_box, _safe_for)
Definition value.h:1131
static fr_sbuff_err_t char size_t * len
Definition value.h:1061