From 9d56215d973676130fede91e39ee34d56ca19fb6 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Thu, 5 Nov 2009 08:06:02 -0500 Subject: Build files.c only for tools Move files.c into tools directory --- server/Makefile.am | 6 +- server/tools/files.c | 736 ++++++++++++++++++++++++++++++++++++++++++++++ server/tools/tools_util.h | 10 + server/util/files.c | 735 --------------------------------------------- server/util/util.h | 10 - 5 files changed, 749 insertions(+), 748 deletions(-) create mode 100644 server/tools/files.c delete mode 100644 server/util/files.c (limited to 'server') diff --git a/server/Makefile.am b/server/Makefile.am index 2173b171c..483684519 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -183,7 +183,6 @@ SSSD_UTIL_OBJ = \ util/backup_file.c \ util/strtonum.c \ util/check_and_open.c \ - util/files.c \ $(SSSD_DEBUG_OBJ) SSSD_RESPONDER_OBJ = \ @@ -194,7 +193,8 @@ SSSD_RESPONDER_OBJ = \ SSSD_TOOLS_OBJ = \ tools/sss_sync_ops.c \ - tools/tools_util.c + tools/tools_util.c \ + tools/files.c SSSD_RESOLV_OBJ = \ resolv/async_resolv.c @@ -433,7 +433,7 @@ files_tests_SOURCES = \ $(SSSD_DEBUG_OBJ) \ tests/files-tests.c \ util/check_and_open.c \ - util/files.c + tools/files.c files_tests_CFLAGS = \ $(AM_CFLAGS) \ $(CHECK_CFLAGS) diff --git a/server/tools/files.c b/server/tools/files.c new file mode 100644 index 000000000..6c6447059 --- /dev/null +++ b/server/tools/files.c @@ -0,0 +1,736 @@ +/* + Authors: + Jakub Hrozek + + Copyright (C) 2009 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright (c) 1991 - 1994, Julianne Frances Haugh + * Copyright (c) 1996 - 2001, Marek Michałkiewicz + * Copyright (c) 2003 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2008, Nicolas François + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the copyright holders or contributors may not be used to + * endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "util/util.h" +#include "tools/tools_util.h" + +#ifdef HAVE_SELINUX +#include +#endif + +int copy_tree(const char *src_root, const char *dst_root, + uid_t uid, gid_t gid); + +struct copy_ctx { + const char *src_orig; + const char *dst_orig; + dev_t src_dev; +}; + +#ifdef HAVE_SELINUX +/* + * selinux_file_context - Set the security context before any file or + * directory creation. + * + * selinux_file_context () should be called before any creation of file, + * symlink, directory, ... + * + * Callers may have to Reset SELinux to create files with default + * contexts: + * reset_selinux_file_context(); + */ +int selinux_file_context(const char *dst_name) +{ + security_context_t scontext = NULL; + + if (is_selinux_enabled() == 1) { + /* Get the default security context for this file */ + if (matchpathcon(dst_name, 0, &scontext) < 0) { + if (security_getenforce () != 0) { + return 1; + } + } + /* Set the security context for the next created file */ + if (setfscreatecon(scontext) < 0) { + if (security_getenforce() != 0) { + return 1; + } + } + freecon(scontext); + } + + return 0; +} + +int reset_selinux_file_context(void) +{ + setfscreatecon(NULL); + return EOK; +} + +#else /* HAVE_SELINUX */ +int selinux_file_context(const char *dst_name) +{ + return EOK; +} + +int reset_selinux_file_context(void) +{ + return EOK; +} +#endif /* HAVE_SELINUX */ + +/* wrapper in order not to create a temporary context in + * every iteration */ +static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx, + dev_t parent_dev, + const char *root); + +int remove_tree(const char *root) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret; + + tmp_ctx = talloc_new(NULL); + if (!tmp_ctx) { + return ENOMEM; + } + + ret = remove_tree_with_ctx(tmp_ctx, 0, root); + talloc_free(tmp_ctx); + return ret; +} + +/* + * The context is not freed in case of error + * because this is a recursive function, will be freed when we + * reach the top level remove_tree() again + */ +static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx, + dev_t parent_dev, + const char *root) +{ + char *fullpath = NULL; + struct dirent *result; + struct dirent direntp; + struct stat statres; + DIR *rootdir = NULL; + int ret; + + rootdir = opendir(root); + if (rootdir == NULL) { + ret = errno; + DEBUG(1, ("Cannot open directory %s [%d][%s]", + root, ret, strerror(ret))); + goto fail; + } + + while (readdir_r(rootdir, &direntp, &result) == 0) { + if (result == NULL) { + /* End of directory */ + break; + } + + if (strcmp (direntp.d_name, ".") == 0 || + strcmp (direntp.d_name, "..") == 0) { + continue; + } + + fullpath = talloc_asprintf(mem_ctx, "%s/%s", root, direntp.d_name); + if (fullpath == NULL) { + ret = ENOMEM; + goto fail; + } + + ret = lstat(fullpath, &statres); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot stat %s: [%d][%s]\n", + fullpath, ret, strerror(ret))); + goto fail; + } + + if (S_ISDIR(statres.st_mode)) { + /* if directory, recursively descend, but check if on the same FS */ + if (parent_dev && parent_dev != statres.st_dev) { + DEBUG(1, ("Directory %s is on different filesystem, " + "will not follow\n", fullpath)); + ret = EFAULT; + goto fail; + } + + ret = remove_tree_with_ctx(mem_ctx, statres.st_dev, fullpath); + if (ret != EOK) { + DEBUG(1, ("Removing subdirectory %s failed: [%d][%s]\n", + fullpath, ret, strerror(ret))); + goto fail; + } + } else { + ret = unlink(fullpath); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Removing file %s failed: [%d][%s]\n", + fullpath, ret, strerror(ret))); + goto fail; + } + } + + talloc_free(fullpath); + } + + ret = closedir(rootdir); + if (ret != 0) { + ret = errno; + goto fail; + } + + ret = rmdir(root); + if (ret != 0) { + ret = errno; + goto fail; + } + +fail: + return ret; +} + +static int copy_dir(const char *src, const char *dst, + const struct stat *statp, const struct timeval mt[2], + uid_t uid, gid_t gid) +{ + int ret = 0; + + /* + * Create a new target directory, make it owned by + * the user and then recursively copy that directory. + */ + selinux_file_context(dst); + + ret = mkdir(dst, statp->st_mode); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot mkdir directory '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + ret = chown(dst, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chown directory '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + ret = chmod(dst, statp->st_mode); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chmod directory '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + ret = copy_tree(src, dst, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot copy directory from '%s' to '%s': [%d][%s].\n", + src, dst, ret, strerror(ret))); + return ret; + } + + ret = utimes(dst, mt); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot set utimes on a directory '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + return EOK; +} + +static char *talloc_readlink(TALLOC_CTX *mem_ctx, const char *filename) +{ + size_t size = 1024; + ssize_t nchars; + char *buffer; + + buffer = talloc_array(mem_ctx, char, size); + if (!buffer) { + return NULL; + } + + while (1) { + nchars = readlink(filename, buffer, size); + if (nchars < 0) { + return NULL; + } + + if ((size_t) nchars < size) { + /* The buffer was large enough */ + break; + } + + /* Try again with a bigger buffer */ + size *= 2; + buffer = talloc_realloc(mem_ctx, buffer, char, size); + if (!buffer) { + return NULL; + } + } + + /* readlink does not nul-terminate */ + buffer[nchars] = '\0'; + return buffer; +} + +static int copy_symlink(struct copy_ctx *cctx, + const char *src, + const char *dst, + const struct stat *statp, + const struct timeval mt[], + uid_t uid, gid_t gid) +{ + int ret; + char *oldlink; + char *tmp; + TALLOC_CTX *tmp_ctx = NULL; + + tmp_ctx = talloc_new(cctx); + if (!tmp_ctx) { + return ENOMEM; + } + + /* + * Get the name of the file which the link points + * to. If that name begins with the original + * source directory name, that part of the link + * name will be replaced with the original + * destination directory name. + */ + oldlink = talloc_readlink(tmp_ctx, src); + if (oldlink == NULL) { + ret = ENOMEM; + goto done; + } + + /* If src was a link to an entry of the src_orig directory itself, + * create a link to the corresponding entry in the dst_orig + * directory. + * FIXME: This may change a relative link to an absolute link + */ + if (strncmp(oldlink, cctx->src_orig, strlen(cctx->src_orig)) == 0) { + tmp = talloc_asprintf(tmp_ctx, "%s%s", cctx->dst_orig, oldlink + strlen(cctx->src_orig)); + if (tmp == NULL) { + ret = ENOMEM; + goto done; + } + + talloc_free(oldlink); + oldlink = tmp; + } + + selinux_file_context(dst); + + ret = symlink(oldlink, dst); + if (ret != 0) { + ret = errno; + DEBUG(1, ("symlink() failed on file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto done; + } + + ret = lchown(dst, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("lchown() failed on file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto done; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +static int copy_special(const char *dst, + const struct stat *statp, + const struct timeval mt[], + uid_t uid, gid_t gid) +{ + int ret = 0; + + selinux_file_context(dst); + + ret = mknod(dst, statp->st_mode & ~07777, statp->st_rdev); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot mknod special file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + ret = chown(dst, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chown special file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + ret = chmod(dst, statp->st_mode & 07777); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chmod special file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + ret = utimes(dst, mt); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot call utimes on special file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + return ret; + } + + return EOK; +} + +static int copy_file(const char *src, + const char *dst, + const struct stat *statp, + const struct timeval mt[], + uid_t uid, gid_t gid) +{ + int ret; + int ifd = -1; + int ofd = -1; + char buf[1024]; + ssize_t cnt, written, offset; + struct stat fstatbuf; + + ifd = open(src, O_RDONLY); + if (ifd < 0) { + ret = errno; + DEBUG(1, ("Cannot open() source file '%s': [%d][%s].\n", + src, ret, strerror(ret))); + goto fail; + } + + ret = fstat(ifd, &fstatbuf); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fstat() source file '%s': [%d][%s].\n", + src, ret, strerror(ret))); + goto fail; + } + + if (statp->st_dev != fstatbuf.st_dev || + statp->st_ino != fstatbuf.st_ino) { + DEBUG(1, ("File %s was modified between lstat and open.\n", src)); + ret = EIO; + goto fail; + } + + selinux_file_context(dst); + + ofd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, statp->st_mode & 07777); + if (ofd < 0) { + ret = errno; + DEBUG(1, ("Cannot open() destination file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + + ret = fchown(ofd, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fchown() destination file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + + ret = fchmod(ofd, statp->st_mode & 07777); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fchmod() destination file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + + while ((cnt = read(ifd, buf, sizeof(buf))) > 0) { + offset = 0; + while (cnt > 0) { + written = write(ofd, buf+offset, (size_t)cnt); + if (written == -1) { + ret = errno; + DEBUG(1, ("Cannot write() to source file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + offset += written; + cnt -= written; + } + } + if (cnt == -1) { + ret = errno; + DEBUG(1, ("Cannot read() from source file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + + + ret = close(ifd); + ifd = -1; + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot close() source file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + + ret = close(ofd); + ifd = -1; + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot close() destination file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + + ret = utimes(dst, mt); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot call utimes() on destination file '%s': [%d][%s].\n", + dst, ret, strerror(ret))); + goto fail; + } + + return EOK; + + /* Reachable by jump only */ +fail: + if (ifd != -1) close(ifd); + if (ofd != -1) close(ofd); + return ret; +} + +/* + * The context is not freed in case of error + * because this is a recursive function, will be freed when we + * reach the top level copy_tree() again + */ +static int copy_entry(struct copy_ctx *cctx, + const char *src, + const char *dst, + uid_t uid, + gid_t gid) +{ + int ret = EOK; + struct stat sb; + struct timeval mt[2]; + + ret = lstat(src, &sb); + if (ret == -1) { + ret = errno; + DEBUG(1, ("Cannot lstat() the source file '%s': [%d][%s].\n", + src, ret, strerror(ret))); + return ret; + } + + mt[0].tv_sec = sb.st_atime; + mt[0].tv_usec = 0; + + mt[1].tv_sec = sb.st_mtime; + mt[1].tv_usec = 0; + + if (S_ISLNK (sb.st_mode)) { + ret = copy_symlink(cctx, src, dst, &sb, mt, uid, gid); + if (ret != EOK) { + DEBUG(1, ("Cannot copy symlink '%s' to '%s': [%d][%s]\n", + src, dst, ret, strerror(ret))); + } + return ret; + } + + if (S_ISDIR(sb.st_mode)) { + /* Check if we're still on the same FS */ + if (sb.st_dev != cctx->src_dev) { + DEBUG(2, ("Will not descend to other FS\n")); + /* Skip this without error */ + return EOK; + } + return copy_dir(src, dst, &sb, mt, uid, gid); + } else if (!S_ISREG(sb.st_mode)) { + /* + * Deal with FIFOs and special files. The user really + * shouldn't have any of these, but it seems like it + * would be nice to copy everything ... + */ + return copy_special(dst, &sb, mt, uid, gid); + } else { + /* + * Create the new file and copy the contents. The new + * file will be owned by the provided UID and GID values. + */ + return copy_file(src, dst, &sb, mt, uid, gid); + } + + return ret; +} + +/* + * The context is not freed in case of error + * because this is a recursive function, will be freed when we + * reach the top level copy_tree() again + */ +static int copy_tree_ctx(struct copy_ctx *cctx, + const char *src_root, + const char *dst_root, + uid_t uid, + gid_t gid) +{ + DIR *src_dir; + int ret; + struct dirent *result; + struct dirent direntp; + char *src_name, *dst_name; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(cctx); + + src_dir = opendir(src_root); + if (src_dir == NULL) { + ret = errno; + DEBUG(1, ("Cannot open the source directory %s: [%d][%s].\n", + src_root, ret, strerror(ret))); + goto fail; + } + + while (readdir_r(src_dir, &direntp, &result) == 0) { + if (result == NULL) { + /* End of directory */ + break; + } + + if (strcmp (direntp.d_name, ".") == 0 || + strcmp (direntp.d_name, "..") == 0) { + continue; + } + + /* build src and dst paths */ + src_name = talloc_asprintf(tmp_ctx, "%s/%s", src_root, direntp.d_name); + dst_name = talloc_asprintf(tmp_ctx, "%s/%s", dst_root, direntp.d_name); + if (dst_name == NULL || src_name == NULL) { + ret = ENOMEM; + goto fail; + } + + /* copy */ + ret = copy_entry(cctx, src_name, dst_name, uid, gid); + if (ret != EOK) { + DEBUG(1, ("Cannot copy '%s' to '%s', error %d\n", + src_name, dst_name, ret)); + goto fail; + } + talloc_free(src_name); + talloc_free(dst_name); + } + + ret = closedir(src_dir); + if (ret != 0) { + ret = errno; + goto fail; + } + +fail: + talloc_free(tmp_ctx); + return ret; +} + +int copy_tree(const char *src_root, const char *dst_root, + uid_t uid, gid_t gid) +{ + int ret = EOK; + struct copy_ctx *cctx = NULL; + struct stat s_src; + + cctx = talloc_zero(NULL, struct copy_ctx); + + ret = lstat(src_root, &s_src); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot lstat the source directory '%s': [%d][%s]\n", + src_root, ret, strerror(ret))); + goto fail; + } + + cctx->src_orig = src_root; + cctx->dst_orig = dst_root; + cctx->src_dev = s_src.st_dev; + + ret = copy_tree_ctx(cctx, src_root, dst_root, uid, gid); + if (ret != EOK) { + DEBUG(1, ("copy_tree_ctx failed: [%d][%s]\n", ret, strerror(ret))); + goto fail; + } + +fail: + reset_selinux_file_context(); + talloc_free(cctx); + return ret; +} + diff --git a/server/tools/tools_util.h b/server/tools/tools_util.h index 2a1ee25e8..a643e739f 100644 --- a/server/tools/tools_util.h +++ b/server/tools/tools_util.h @@ -95,4 +95,14 @@ int remove_homedir(TALLOC_CTX *mem_ctx, const char *username, uid_t uid, bool force); +/* from files.c */ +int remove_tree(const char *root); + +int copy_tree(const char *src_root, + const char *dst_root, + uid_t uid, gid_t gid); + +int selinux_file_context(const char *dst_name); +int reset_selinux_file_context(void); + #endif /* __TOOLS_UTIL_H__ */ diff --git a/server/util/files.c b/server/util/files.c deleted file mode 100644 index ce73fc8be..000000000 --- a/server/util/files.c +++ /dev/null @@ -1,735 +0,0 @@ -/* - Authors: - Jakub Hrozek - - Copyright (C) 2009 Red Hat - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -/* - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright (c) 1991 - 1994, Julianne Frances Haugh - * Copyright (c) 1996 - 2001, Marek Michałkiewicz - * Copyright (c) 2003 - 2006, Tomasz Kłoczko - * Copyright (c) 2007 - 2008, Nicolas François - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. The name of the copyright holders or contributors may not be used to - * endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" -#include "util/util.h" - -#ifdef HAVE_SELINUX -#include -#endif - -int copy_tree(const char *src_root, const char *dst_root, - uid_t uid, gid_t gid); - -struct copy_ctx { - const char *src_orig; - const char *dst_orig; - dev_t src_dev; -}; - -#ifdef HAVE_SELINUX -/* - * selinux_file_context - Set the security context before any file or - * directory creation. - * - * selinux_file_context () should be called before any creation of file, - * symlink, directory, ... - * - * Callers may have to Reset SELinux to create files with default - * contexts: - * reset_selinux_file_context(); - */ -int selinux_file_context(const char *dst_name) -{ - security_context_t scontext = NULL; - - if (is_selinux_enabled() == 1) { - /* Get the default security context for this file */ - if (matchpathcon(dst_name, 0, &scontext) < 0) { - if (security_getenforce () != 0) { - return 1; - } - } - /* Set the security context for the next created file */ - if (setfscreatecon(scontext) < 0) { - if (security_getenforce() != 0) { - return 1; - } - } - freecon(scontext); - } - - return 0; -} - -int reset_selinux_file_context(void) -{ - setfscreatecon(NULL); - return EOK; -} - -#else /* HAVE_SELINUX */ -int selinux_file_context(const char *dst_name) -{ - return EOK; -} - -int reset_selinux_file_context(void) -{ - return EOK; -} -#endif /* HAVE_SELINUX */ - -/* wrapper in order not to create a temporary context in - * every iteration */ -static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx, - dev_t parent_dev, - const char *root); - -int remove_tree(const char *root) -{ - TALLOC_CTX *tmp_ctx = NULL; - int ret; - - tmp_ctx = talloc_new(NULL); - if (!tmp_ctx) { - return ENOMEM; - } - - ret = remove_tree_with_ctx(tmp_ctx, 0, root); - talloc_free(tmp_ctx); - return ret; -} - -/* - * The context is not freed in case of error - * because this is a recursive function, will be freed when we - * reach the top level remove_tree() again - */ -static int remove_tree_with_ctx(TALLOC_CTX *mem_ctx, - dev_t parent_dev, - const char *root) -{ - char *fullpath = NULL; - struct dirent *result; - struct dirent direntp; - struct stat statres; - DIR *rootdir = NULL; - int ret; - - rootdir = opendir(root); - if (rootdir == NULL) { - ret = errno; - DEBUG(1, ("Cannot open directory %s [%d][%s]", - root, ret, strerror(ret))); - goto fail; - } - - while (readdir_r(rootdir, &direntp, &result) == 0) { - if (result == NULL) { - /* End of directory */ - break; - } - - if (strcmp (direntp.d_name, ".") == 0 || - strcmp (direntp.d_name, "..") == 0) { - continue; - } - - fullpath = talloc_asprintf(mem_ctx, "%s/%s", root, direntp.d_name); - if (fullpath == NULL) { - ret = ENOMEM; - goto fail; - } - - ret = lstat(fullpath, &statres); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot stat %s: [%d][%s]\n", - fullpath, ret, strerror(ret))); - goto fail; - } - - if (S_ISDIR(statres.st_mode)) { - /* if directory, recursively descend, but check if on the same FS */ - if (parent_dev && parent_dev != statres.st_dev) { - DEBUG(1, ("Directory %s is on different filesystem, " - "will not follow\n", fullpath)); - ret = EFAULT; - goto fail; - } - - ret = remove_tree_with_ctx(mem_ctx, statres.st_dev, fullpath); - if (ret != EOK) { - DEBUG(1, ("Removing subdirectory %s failed: [%d][%s]\n", - fullpath, ret, strerror(ret))); - goto fail; - } - } else { - ret = unlink(fullpath); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Removing file %s failed: [%d][%s]\n", - fullpath, ret, strerror(ret))); - goto fail; - } - } - - talloc_free(fullpath); - } - - ret = closedir(rootdir); - if (ret != 0) { - ret = errno; - goto fail; - } - - ret = rmdir(root); - if (ret != 0) { - ret = errno; - goto fail; - } - -fail: - return ret; -} - -static int copy_dir(const char *src, const char *dst, - const struct stat *statp, const struct timeval mt[2], - uid_t uid, gid_t gid) -{ - int ret = 0; - - /* - * Create a new target directory, make it owned by - * the user and then recursively copy that directory. - */ - selinux_file_context(dst); - - ret = mkdir(dst, statp->st_mode); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot mkdir directory '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - ret = chown(dst, uid, gid); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot chown directory '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - ret = chmod(dst, statp->st_mode); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot chmod directory '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - ret = copy_tree(src, dst, uid, gid); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot copy directory from '%s' to '%s': [%d][%s].\n", - src, dst, ret, strerror(ret))); - return ret; - } - - ret = utimes(dst, mt); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot set utimes on a directory '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - return EOK; -} - -static char *talloc_readlink(TALLOC_CTX *mem_ctx, const char *filename) -{ - size_t size = 1024; - ssize_t nchars; - char *buffer; - - buffer = talloc_array(mem_ctx, char, size); - if (!buffer) { - return NULL; - } - - while (1) { - nchars = readlink(filename, buffer, size); - if (nchars < 0) { - return NULL; - } - - if ((size_t) nchars < size) { - /* The buffer was large enough */ - break; - } - - /* Try again with a bigger buffer */ - size *= 2; - buffer = talloc_realloc(mem_ctx, buffer, char, size); - if (!buffer) { - return NULL; - } - } - - /* readlink does not nul-terminate */ - buffer[nchars] = '\0'; - return buffer; -} - -static int copy_symlink(struct copy_ctx *cctx, - const char *src, - const char *dst, - const struct stat *statp, - const struct timeval mt[], - uid_t uid, gid_t gid) -{ - int ret; - char *oldlink; - char *tmp; - TALLOC_CTX *tmp_ctx = NULL; - - tmp_ctx = talloc_new(cctx); - if (!tmp_ctx) { - return ENOMEM; - } - - /* - * Get the name of the file which the link points - * to. If that name begins with the original - * source directory name, that part of the link - * name will be replaced with the original - * destination directory name. - */ - oldlink = talloc_readlink(tmp_ctx, src); - if (oldlink == NULL) { - ret = ENOMEM; - goto done; - } - - /* If src was a link to an entry of the src_orig directory itself, - * create a link to the corresponding entry in the dst_orig - * directory. - * FIXME: This may change a relative link to an absolute link - */ - if (strncmp(oldlink, cctx->src_orig, strlen(cctx->src_orig)) == 0) { - tmp = talloc_asprintf(tmp_ctx, "%s%s", cctx->dst_orig, oldlink + strlen(cctx->src_orig)); - if (tmp == NULL) { - ret = ENOMEM; - goto done; - } - - talloc_free(oldlink); - oldlink = tmp; - } - - selinux_file_context(dst); - - ret = symlink(oldlink, dst); - if (ret != 0) { - ret = errno; - DEBUG(1, ("symlink() failed on file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto done; - } - - ret = lchown(dst, uid, gid); - if (ret != 0) { - ret = errno; - DEBUG(1, ("lchown() failed on file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto done; - } - -done: - talloc_free(tmp_ctx); - return ret; -} - -static int copy_special(const char *dst, - const struct stat *statp, - const struct timeval mt[], - uid_t uid, gid_t gid) -{ - int ret = 0; - - selinux_file_context(dst); - - ret = mknod(dst, statp->st_mode & ~07777, statp->st_rdev); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot mknod special file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - ret = chown(dst, uid, gid); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot chown special file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - ret = chmod(dst, statp->st_mode & 07777); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot chmod special file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - ret = utimes(dst, mt); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot call utimes on special file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - return ret; - } - - return EOK; -} - -static int copy_file(const char *src, - const char *dst, - const struct stat *statp, - const struct timeval mt[], - uid_t uid, gid_t gid) -{ - int ret; - int ifd = -1; - int ofd = -1; - char buf[1024]; - ssize_t cnt, written, offset; - struct stat fstatbuf; - - ifd = open(src, O_RDONLY); - if (ifd < 0) { - ret = errno; - DEBUG(1, ("Cannot open() source file '%s': [%d][%s].\n", - src, ret, strerror(ret))); - goto fail; - } - - ret = fstat(ifd, &fstatbuf); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot fstat() source file '%s': [%d][%s].\n", - src, ret, strerror(ret))); - goto fail; - } - - if (statp->st_dev != fstatbuf.st_dev || - statp->st_ino != fstatbuf.st_ino) { - DEBUG(1, ("File %s was modified between lstat and open.\n", src)); - ret = EIO; - goto fail; - } - - selinux_file_context(dst); - - ofd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, statp->st_mode & 07777); - if (ofd < 0) { - ret = errno; - DEBUG(1, ("Cannot open() destination file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - - ret = fchown(ofd, uid, gid); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot fchown() destination file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - - ret = fchmod(ofd, statp->st_mode & 07777); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot fchmod() destination file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - - while ((cnt = read(ifd, buf, sizeof(buf))) > 0) { - offset = 0; - while (cnt > 0) { - written = write(ofd, buf+offset, (size_t)cnt); - if (written == -1) { - ret = errno; - DEBUG(1, ("Cannot write() to source file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - offset += written; - cnt -= written; - } - } - if (cnt == -1) { - ret = errno; - DEBUG(1, ("Cannot read() from source file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - - - ret = close(ifd); - ifd = -1; - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot close() source file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - - ret = close(ofd); - ifd = -1; - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot close() destination file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - - ret = utimes(dst, mt); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot call utimes() on destination file '%s': [%d][%s].\n", - dst, ret, strerror(ret))); - goto fail; - } - - return EOK; - - /* Reachable by jump only */ -fail: - if (ifd != -1) close(ifd); - if (ofd != -1) close(ofd); - return ret; -} - -/* - * The context is not freed in case of error - * because this is a recursive function, will be freed when we - * reach the top level copy_tree() again - */ -static int copy_entry(struct copy_ctx *cctx, - const char *src, - const char *dst, - uid_t uid, - gid_t gid) -{ - int ret = EOK; - struct stat sb; - struct timeval mt[2]; - - ret = lstat(src, &sb); - if (ret == -1) { - ret = errno; - DEBUG(1, ("Cannot lstat() the source file '%s': [%d][%s].\n", - src, ret, strerror(ret))); - return ret; - } - - mt[0].tv_sec = sb.st_atime; - mt[0].tv_usec = 0; - - mt[1].tv_sec = sb.st_mtime; - mt[1].tv_usec = 0; - - if (S_ISLNK (sb.st_mode)) { - ret = copy_symlink(cctx, src, dst, &sb, mt, uid, gid); - if (ret != EOK) { - DEBUG(1, ("Cannot copy symlink '%s' to '%s': [%d][%s]\n", - src, dst, ret, strerror(ret))); - } - return ret; - } - - if (S_ISDIR(sb.st_mode)) { - /* Check if we're still on the same FS */ - if (sb.st_dev != cctx->src_dev) { - DEBUG(2, ("Will not descend to other FS\n")); - /* Skip this without error */ - return EOK; - } - return copy_dir(src, dst, &sb, mt, uid, gid); - } else if (!S_ISREG(sb.st_mode)) { - /* - * Deal with FIFOs and special files. The user really - * shouldn't have any of these, but it seems like it - * would be nice to copy everything ... - */ - return copy_special(dst, &sb, mt, uid, gid); - } else { - /* - * Create the new file and copy the contents. The new - * file will be owned by the provided UID and GID values. - */ - return copy_file(src, dst, &sb, mt, uid, gid); - } - - return ret; -} - -/* - * The context is not freed in case of error - * because this is a recursive function, will be freed when we - * reach the top level copy_tree() again - */ -static int copy_tree_ctx(struct copy_ctx *cctx, - const char *src_root, - const char *dst_root, - uid_t uid, - gid_t gid) -{ - DIR *src_dir; - int ret; - struct dirent *result; - struct dirent direntp; - char *src_name, *dst_name; - TALLOC_CTX *tmp_ctx; - - tmp_ctx = talloc_new(cctx); - - src_dir = opendir(src_root); - if (src_dir == NULL) { - ret = errno; - DEBUG(1, ("Cannot open the source directory %s: [%d][%s].\n", - src_root, ret, strerror(ret))); - goto fail; - } - - while (readdir_r(src_dir, &direntp, &result) == 0) { - if (result == NULL) { - /* End of directory */ - break; - } - - if (strcmp (direntp.d_name, ".") == 0 || - strcmp (direntp.d_name, "..") == 0) { - continue; - } - - /* build src and dst paths */ - src_name = talloc_asprintf(tmp_ctx, "%s/%s", src_root, direntp.d_name); - dst_name = talloc_asprintf(tmp_ctx, "%s/%s", dst_root, direntp.d_name); - if (dst_name == NULL || src_name == NULL) { - ret = ENOMEM; - goto fail; - } - - /* copy */ - ret = copy_entry(cctx, src_name, dst_name, uid, gid); - if (ret != EOK) { - DEBUG(1, ("Cannot copy '%s' to '%s', error %d\n", - src_name, dst_name, ret)); - goto fail; - } - talloc_free(src_name); - talloc_free(dst_name); - } - - ret = closedir(src_dir); - if (ret != 0) { - ret = errno; - goto fail; - } - -fail: - talloc_free(tmp_ctx); - return ret; -} - -int copy_tree(const char *src_root, const char *dst_root, - uid_t uid, gid_t gid) -{ - int ret = EOK; - struct copy_ctx *cctx = NULL; - struct stat s_src; - - cctx = talloc_zero(NULL, struct copy_ctx); - - ret = lstat(src_root, &s_src); - if (ret != 0) { - ret = errno; - DEBUG(1, ("Cannot lstat the source directory '%s': [%d][%s]\n", - src_root, ret, strerror(ret))); - goto fail; - } - - cctx->src_orig = src_root; - cctx->dst_orig = dst_root; - cctx->src_dev = s_src.st_dev; - - ret = copy_tree_ctx(cctx, src_root, dst_root, uid, gid); - if (ret != EOK) { - DEBUG(1, ("copy_tree_ctx failed: [%d][%s]\n", ret, strerror(ret))); - goto fail; - } - -fail: - reset_selinux_file_context(); - talloc_free(cctx); - return ret; -} - diff --git a/server/util/util.h b/server/util/util.h index 70dba3721..9a27ae559 100644 --- a/server/util/util.h +++ b/server/util/util.h @@ -205,14 +205,4 @@ int backup_file(const char *src, int dbglvl); errno_t check_and_open_readonly(const char *filename, int *fd, const uid_t uid, const gid_t gid, const mode_t mode); -/* from files.c */ -int remove_tree(const char *root); - -int copy_tree(const char *src_root, - const char *dst_root, - uid_t uid, gid_t gid); - -int selinux_file_context(const char *dst_name); -int reset_selinux_file_context(void); - #endif /* __SSSD_UTIL_H__ */ -- cgit