/* * checkpath.c * Checks for the existence of a file or directory and creates it * if necessary. It can also correct its ownership. */ /* * Copyright (c) 2007-2015 The OpenRC Authors. * See the Authors file at the top-level directory of this distribution and * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS * * This file is part of OpenRC. It is subject to the license terms in * the LICENSE file found in the top-level directory of this * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE * This file may not be copied, modified, propagated, or distributed * except according to the terms contained in the LICENSE file. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include "einfo.h" #include "rc.h" #include "misc.h" #include "selinux.h" #include "_usage.h" typedef enum { inode_unknown = 0, inode_file = 1, inode_dir = 2, inode_fifo = 3, } inode_t; const char *applet = NULL; const char *extraopts ="path1 [path2] [...]"; const char getoptstring[] = "dDfFpm:o:sW" getoptstring_COMMON; const struct option longopts[] = { { "directory", 0, NULL, 'd'}, { "directory-truncate", 0, NULL, 'D'}, { "file", 0, NULL, 'f'}, { "file-truncate", 0, NULL, 'F'}, { "pipe", 0, NULL, 'p'}, { "mode", 1, NULL, 'm'}, { "owner", 1, NULL, 'o'}, { "symlinks", 0, NULL, 's'}, { "writable", 0, NULL, 'W'}, longopts_COMMON }; const char * const longopts_help[] = { "Create a directory if not exists", "Create/empty directory", "Create a file if not exists", "Truncate file", "Create a named pipe (FIFO) if not exists", "Mode to check", "Owner to check (user:group)", "follow symbolic links (irrelevant on linux)", "Check whether the path is writable or not", longopts_help_COMMON }; const char *usagestring = NULL; static int get_dirfd(char *path, bool symlinks) { char *ch; char *item; char *linkpath = NULL; char *path_dupe; char *str; int components = 0; int dirfd; int flags = 0; int new_dirfd; struct stat st; ssize_t linksize; if (!path || *path != '/') eerrorx("%s: empty or relative path", applet); dirfd = openat(AT_FDCWD, "/", O_RDONLY); if (dirfd == -1) eerrorx("%s: unable to open the root directory: %s", applet, strerror(errno)); ch = path; while (*ch) { if (*ch == '/') components++; ch++; } path_dupe = xstrdup(path); item = strtok(path_dupe, "/"); #ifdef O_PATH flags |= O_PATH; #endif if (!symlinks) flags |= O_NOFOLLOW; flags |= O_RDONLY; while (dirfd > 0 && item && components > 1) { str = xstrdup(linkpath ? linkpath : item); new_dirfd = openat(dirfd, str, flags); if (new_dirfd == -1) eerrorx("%s: %s: could not open %s: %s", applet, path, str, strerror(errno)); if (fstat(new_dirfd, &st) == -1) eerrorx("%s: %s: unable to stat %s: %s", applet, path, item, strerror(errno)); if (S_ISLNK(st.st_mode) ) { if (st.st_uid != 0) eerrorx("%s: %s: symbolic link %s not owned by root", applet, path, str); linksize = st.st_size+1; if (linkpath) free(linkpath); linkpath = xmalloc(linksize); memset(linkpath, 0, linksize); if (readlinkat(new_dirfd, "", linkpath, linksize) != st.st_size) eerrorx("%s: symbolic link destination changed", applet); /* * now follow the symlink. */ close(new_dirfd); } else { /* now walk down the directory path */ close(dirfd); dirfd = new_dirfd; free(linkpath); linkpath = NULL; item = strtok(NULL, "/"); components--; } free(str); } free(path_dupe); free(linkpath); return dirfd; } static char *clean_path(char *path) { char *ch; char *ch2; char *str; str = xmalloc(strlen(path) + 1); ch = path; ch2 = str; while (true) { *ch2 = *ch; ch++; ch2++; if (!*(ch-1)) break; while (*(ch - 1) == '/' && *ch == '/') ch++; } /* get rid of trailing / characters */ while ((ch = strrchr(str, '/'))) { if (ch == str) break; if (!*(ch+1)) *ch = 0; else break; } return str; } static int do_check(char *path, uid_t uid, gid_t gid, mode_t mode, inode_t type, bool trunc, bool chowner, bool symlinks, bool selinux_on) { struct stat st; char *name = NULL; int dirfd; int fd; int flags; int r; int readfd; int readflags; int u; memset(&st, 0, sizeof(st)); flags = O_CREAT|O_NDELAY|O_WRONLY|O_NOCTTY; readflags = O_NDELAY|O_NOCTTY|O_RDONLY; #ifdef O_CLOEXEC flags |= O_CLOEXEC; readflags |= O_CLOEXEC; #endif #ifdef O_NOFOLLOW flags |= O_NOFOLLOW; readflags |= O_NOFOLLOW; #endif if (trunc) flags |= O_TRUNC; xasprintf(&name, "%s", basename_c(path)); dirfd = get_dirfd(path, symlinks); readfd = openat(dirfd, name, readflags); if (readfd == -1 || (type == inode_file && trunc)) { if (type == inode_file) { einfo("%s: creating file", path); if (!mode) /* 664 */ mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; u = umask(0); fd = openat(dirfd, name, flags, mode); umask(u); if (fd == -1) { free(name); eerror("%s: open: %s", applet, strerror(errno)); return -1; } if (readfd != -1 && trunc) close(readfd); readfd = fd; } else if (type == inode_dir) { einfo("%s: creating directory", path); if (!mode) /* 775 */ mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH; u = umask(0); /* We do not recursively create parents */ r = mkdirat(dirfd, name, mode); umask(u); if (r == -1 && errno != EEXIST) { free(name); eerror("%s: mkdirat: %s", applet, strerror (errno)); return -1; } readfd = openat(dirfd, name, readflags); if (readfd == -1) { free(name); eerror("%s: unable to open directory: %s", applet, strerror(errno)); return -1; } } else if (type == inode_fifo) { einfo("%s: creating fifo", path); if (!mode) /* 600 */ mode = S_IRUSR | S_IWUSR; u = umask(0); r = mkfifo(path, mode); umask(u); if (r == -1 && errno != EEXIST) { free(name); eerror("%s: mkfifo: %s", applet, strerror (errno)); return -1; } readfd = openat(dirfd, name, readflags); if (readfd == -1) { free(name); eerror("%s: unable to open fifo: %s", applet, strerror(errno)); return -1; } } } free(name); if (fstat(readfd, &st) != -1) { if (type != inode_dir && S_ISDIR(st.st_mode)) { eerror("%s: is a directory", path); close(readfd); return 1; } if (type != inode_file && S_ISREG(st.st_mode)) { eerror("%s: is a file", path); close(readfd); return 1; } if (type != inode_fifo && S_ISFIFO(st.st_mode)) { eerror("%s: is a fifo", path); close(readfd); return -1; } if (mode && (st.st_mode & 07777) != mode) { if ((type != inode_dir) && (st.st_nlink > 1)) { eerror("%s: chmod: Too many hard links to %s", applet, path); close(readfd); return -1; } if (S_ISLNK(st.st_mode)) { eerror("%s: chmod: %s %s", applet, path, " is a symbolic link"); close(readfd); return -1; } einfo("%s: correcting mode", path); if (fchmod(readfd, mode)) { eerror("%s: chmod: %s", applet, strerror(errno)); close(readfd); return -1; } } if (chowner && (st.st_uid != uid || st.st_gid != gid)) { if ((type != inode_dir) && (st.st_nlink > 1)) { eerror("%s: chown: %s %s", applet, "Too many hard links to", path); close(readfd); return -1; } if (S_ISLNK(st.st_mode)) { eerror("%s: chown: %s %s", applet, path, " is a symbolic link"); close(readfd); return -1; } einfo("%s: correcting owner", path); if (fchown(readfd, uid, gid)) { eerror("%s: chown: %s", applet, strerror(errno)); close(readfd); return -1; } } if (selinux_on) selinux_util_label(path); } else { eerror("fstat: %s: %s", path, strerror(errno)); close(readfd); return -1; } close(readfd); return 0; } static int parse_owner(struct passwd **user, struct group **group, const char *owner) { char *u = xstrdup (owner); char *g = strchr (u, ':'); int id = 0; int retval = 0; if (g) *g++ = '\0'; if (user && *u) { if (sscanf(u, "%d", &id) == 1) *user = getpwuid((uid_t) id); else *user = getpwnam(u); if (*user == NULL) retval = -1; } if (group && g && *g) { if (sscanf(g, "%d", &id) == 1) *group = getgrgid((gid_t) id); else *group = getgrnam(g); if (*group == NULL) retval = -1; } free(u); return retval; } int main(int argc, char **argv) { int opt; uid_t uid = geteuid(); gid_t gid = getgid(); mode_t mode = 0; struct passwd *pw = NULL; struct group *gr = NULL; inode_t type = inode_unknown; int retval = EXIT_SUCCESS; bool trunc = false; bool chowner = false; bool symlinks = false; bool writable = false; bool selinux_on = false; char *path = NULL; applet = basename_c(argv[0]); while ((opt = getopt_long(argc, argv, getoptstring, longopts, (int *) 0)) != -1) { switch (opt) { case 'D': trunc = true; /* falls through */ case 'd': type = inode_dir; break; case 'F': trunc = true; /* falls through */ case 'f': type = inode_file; break; case 'p': type = inode_fifo; break; case 'm': if (parse_mode(&mode, optarg) != 0) eerrorx("%s: invalid mode `%s'", applet, optarg); break; case 'o': chowner = true; if (parse_owner(&pw, &gr, optarg) != 0) eerrorx("%s: owner `%s' not found", applet, optarg); break; case 's': #ifndef O_PATH symlinks = true; #endif break; case 'W': writable = true; break; case_RC_COMMON_GETOPT } } if (optind >= argc) usage(EXIT_FAILURE); if (writable && type != inode_unknown) eerrorx("%s: -W cannot be specified along with -d, -f or -p", applet); if (pw) { uid = pw->pw_uid; gid = pw->pw_gid; } if (gr) gid = gr->gr_gid; if (selinux_util_open() == 1) selinux_on = true; while (optind < argc) { path = clean_path(argv[optind]); if (writable) exit(!is_writable(path)); if (do_check(path, uid, gid, mode, type, trunc, chowner, symlinks, selinux_on)) retval = EXIT_FAILURE; optind++; free(path); } if (selinux_on) selinux_util_close(); return retval; }