/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "str.h" #include "eacces-error.h" #include "mkdir-parents.h" #include "ipwd.h" #include #include #include static int ATTR_NULL(5) mkdir_chown_full(const char *path, mode_t mode, uid_t uid, gid_t gid, const char *gid_origin) { string_t *str; mode_t old_mask; unsigned int i; int ret, fd = -1, orig_errno; for (i = 0;; i++) { old_mask = umask(0); ret = mkdir(path, mode); umask(old_mask); if (ret < 0) break; fd = open(path, O_RDONLY); if (fd != -1) break; if (errno != ENOENT || i == 3) { i_error("open(%s) failed: %m", path); return -1; } /* it was just rmdir()ed by someone else? retry */ } if (ret < 0) { if (errno == EISDIR || errno == ENOSYS) { /* EISDIR check is for BSD/OS which returns it if path contains '/' at the end and it exists. ENOSYS check is for NFS mount points. */ errno = EEXIST; } i_assert(fd == -1); return -1; } if (fchown(fd, uid, gid) < 0) { i_close_fd(&fd); orig_errno = errno; if (rmdir(path) < 0 && errno != ENOENT) i_error("rmdir(%s) failed: %m", path); errno = orig_errno; if (errno == EPERM && uid == (uid_t)-1) { i_error("%s", eperm_error_get_chgrp("fchown", path, gid, gid_origin)); return -1; } str = t_str_new(256); str_printfa(str, "fchown(%s, %ld", path, uid == (uid_t)-1 ? -1L : (long)uid); if (uid != (uid_t)-1) { struct passwd pw; if (i_getpwuid(uid, &pw) > 0) str_printfa(str, "(%s)", pw.pw_name); } str_printfa(str, ", %ld", gid == (gid_t)-1 ? -1L : (long)gid); if (gid != (gid_t)-1) { struct group gr; if (i_getgrgid(uid, &gr) > 0) str_printfa(str, "(%s)", gr.gr_name); } errno = orig_errno; i_error("%s) failed: %m", str_c(str)); return -1; } if (gid != (gid_t)-1 && (mode & S_ISGID) == 0) { /* make sure the directory doesn't have setgid bit enabled (in case its parent had) */ if (fchmod(fd, mode) < 0) { orig_errno = errno; if (rmdir(path) < 0 && errno != ENOENT) i_error("rmdir(%s) failed: %m", path); errno = orig_errno; i_error("fchmod(%s) failed: %m", path); i_close_fd(&fd); return -1; } } i_close_fd(&fd); return 0; } int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { return mkdir_chown_full(path, mode, uid, gid, NULL); } int mkdir_chgrp(const char *path, mode_t mode, gid_t gid, const char *gid_origin) { return mkdir_chown_full(path, mode, (uid_t)-1, gid, gid_origin); } static int ATTR_NULL(5) mkdir_parents_chown_full(const char *path, mode_t mode, uid_t uid, gid_t gid, const char *gid_origin) { const char *p; int ret; if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) { if (errno != ENOENT) return -1; /* doesn't exist, try recursively creating our parent dir */ p = strrchr(path, '/'); if (p == NULL || p == path) return -1; /* shouldn't happen */ T_BEGIN { ret = mkdir_parents_chown_full(t_strdup_until(path, p), mode, uid, gid, gid_origin); } T_END; if (ret < 0 && errno != EEXIST) return -1; /* should work now */ if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) return -1; } return 0; } int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { return mkdir_parents_chown_full(path, mode, uid, gid, NULL); } int mkdir_parents_chgrp(const char *path, mode_t mode, gid_t gid, const char *gid_origin) { return mkdir_parents_chown_full(path, mode, (uid_t)-1, gid, gid_origin); } int mkdir_parents(const char *path, mode_t mode) { return mkdir_parents_chown(path, mode, (uid_t)-1, (gid_t)-1); } int stat_first_parent(const char *path, const char **root_dir_r, struct stat *st_r) { const char *p; while (stat(path, st_r) < 0) { if (errno != ENOENT || strcmp(path, "/") == 0) { *root_dir_r = path; return -1; } p = strrchr(path, '/'); if (p == NULL) path = "/"; else path = t_strdup_until(path, p); } *root_dir_r = path; return 0; }