diff options
-rw-r--r-- | server/Makefile.am | 42 | ||||
-rw-r--r-- | server/conf_macros.m4 | 17 | ||||
-rw-r--r-- | server/confdb/confdb.h | 5 | ||||
-rw-r--r-- | server/configure.ac | 6 | ||||
-rw-r--r-- | server/external/selinux.m4 | 13 | ||||
-rw-r--r-- | server/man/sss_useradd.8.xml | 45 | ||||
-rw-r--r-- | server/man/sss_userdel.8.xml | 36 | ||||
-rw-r--r-- | server/man/sssd.conf.5.xml | 73 | ||||
-rw-r--r-- | server/python/pysss.c | 133 | ||||
-rw-r--r-- | server/tests/files-tests.c | 321 | ||||
-rw-r--r-- | server/tests/python-test.py | 66 | ||||
-rw-r--r-- | server/tools/sss_sync_ops.c | 126 | ||||
-rw-r--r-- | server/tools/sss_sync_ops.h | 23 | ||||
-rw-r--r-- | server/tools/sss_useradd.c | 81 | ||||
-rw-r--r-- | server/tools/sss_userdel.c | 70 | ||||
-rw-r--r-- | server/tools/tools_util.c | 223 | ||||
-rw-r--r-- | server/tools/tools_util.h | 19 | ||||
-rw-r--r-- | server/util/files.c | 735 | ||||
-rw-r--r-- | server/util/util.h | 10 |
19 files changed, 1984 insertions, 60 deletions
diff --git a/server/Makefile.am b/server/Makefile.am index 8c6cc48a..63020c8d 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -67,7 +67,8 @@ if HAVE_CHECK strtonum-tests \ resolv-tests \ krb5-utils-tests \ - check_and_open-tests + check_and_open-tests \ + files-tests endif check_PROGRAMS = \ @@ -150,7 +151,6 @@ AM_CPPFLAGS = -Wall \ -DVARDIR=\"$(localstatedir)\" \ -DSHLIBEXT=\"$(SHLIBEXT)\" \ -DSSSD_LIBEXEC_PATH=\"$(sssdlibexecdir)\" \ - -DSHADOW_UTILS_PATH=\"$(shadow_utils_path)\" \ -DSSSD_INTROSPECT_PATH=\"$(dbusinstropectdir)\" \ -DSSSD_CONF_DIR=\"$(sssdconfdir)\" \ -DSSS_NSS_SOCKET_NAME=\"$(pipepath)/nss\" \ @@ -183,6 +183,7 @@ SSSD_UTIL_OBJ = \ util/backup_file.c \ util/strtonum.c \ util/check_and_open.c \ + util/files.c \ $(SSSD_DEBUG_OBJ) SSSD_RESPONDER_OBJ = \ @@ -232,6 +233,13 @@ PYTHON_BINDINGS_LIBS = \ $(NSS_LIBS) \ libsss_crypt.la +TOOLS_LIBS = \ + $(SSSD_LIBS) + +if BUILD_SELINUX + TOOLS_LIBS += $(SELINUX_LIBS) +endif + dist_noinst_HEADERS = \ monitor/monitor.h \ util/nss_sha512crypt.h \ @@ -327,42 +335,42 @@ sss_useradd_SOURCES = \ $(SSSD_UTIL_OBJ) \ $(SSSD_TOOLS_OBJ) sss_useradd_LDADD = \ - $(SSSD_LIBS) + $(TOOLS_LIBS) sss_userdel_SOURCES = \ tools/sss_userdel.c \ $(SSSD_UTIL_OBJ) \ $(SSSD_TOOLS_OBJ) sss_userdel_LDADD = \ - $(SSSD_LIBS) + $(TOOLS_LIBS) sss_groupadd_SOURCES = \ tools/sss_groupadd.c \ $(SSSD_UTIL_OBJ) \ $(SSSD_TOOLS_OBJ) sss_groupadd_LDADD = \ - $(SSSD_LIBS) + $(TOOLS_LIBS) sss_groupdel_SOURCES = \ tools/sss_groupdel.c \ $(SSSD_UTIL_OBJ) \ $(SSSD_TOOLS_OBJ) sss_groupdel_LDADD = \ - $(SSSD_LIBS) + $(TOOLS_LIBS) sss_usermod_SOURCES = \ tools/sss_usermod.c \ $(SSSD_UTIL_OBJ) \ $(SSSD_TOOLS_OBJ) sss_usermod_LDADD = \ - $(SSSD_LIBS) + $(TOOLS_LIBS) sss_groupmod_SOURCES = \ tools/sss_groupmod.c \ $(SSSD_UTIL_OBJ) \ $(SSSD_TOOLS_OBJ) sss_groupmod_LDADD = \ - $(SSSD_LIBS) + $(TOOLS_LIBS) ################# # Feature Tests # @@ -407,6 +415,24 @@ check_and_open_tests_CFLAGS = \ $(CHECK_CFLAGS) check_and_open_tests_LDADD = \ $(CHECK_LIBS) + +FILES_TESTS_LIBS = \ + $(CHECK_LIBS) \ + $(POPT_LIBS) \ + $(TALLOC_LIBS) +if BUILD_SELINUX + FILES_TESTS_LIBS += $(SELINUX_LIBS) +endif + +files_tests_SOURCES = \ + $(SSSD_DEBUG_OBJ) \ + tests/files-tests.c \ + util/check_and_open.c \ + util/files.c +files_tests_CFLAGS = \ + $(CHECK_CFLAGS) +files_tests_LDADD = \ + $(FILES_TESTS_LIBS) endif stress_tests_SOURCES = \ diff --git a/server/conf_macros.m4 b/server/conf_macros.m4 index 410914e7..0990e507 100644 --- a/server/conf_macros.m4 +++ b/server/conf_macros.m4 @@ -171,3 +171,20 @@ AC_DEFUN([WITH_PYTHON_BINDINGS], AM_CONDITIONAL([BUILD_PYTHON_BINDINGS], [test x"$with_python_bindings" = xyes]) ]) +AC_DEFUN([WITH_SELINUX], + [ AC_ARG_WITH([selinux], + [AC_HELP_STRING([--with-selinux], + [Whether to build with SELinux support [yes]] + ) + ], + [], + with_selinux=yes + ) + if test x"$with_selinux" == xyes; then + HAVE_SELINUX=1 + AC_SUBST(HAVE_SELINUX) + AC_DEFINE_UNQUOTED(HAVE_SELINUX, 1, [Build with SELinux support]) + fi + AM_CONDITIONAL([BUILD_SELINUX], [test x"$with_selinux" = xyes]) + ]) + diff --git a/server/confdb/confdb.h b/server/confdb/confdb.h index 08943272..4d4e8c29 100644 --- a/server/confdb/confdb.h +++ b/server/confdb/confdb.h @@ -89,6 +89,11 @@ /* Local Provider */ #define CONFDB_LOCAL_DEFAULT_SHELL "default_shell" #define CONFDB_LOCAL_DEFAULT_BASEDIR "base_directory" +#define CONFDB_LOCAL_CREATE_HOMEDIR "create_homedir" +#define CONFDB_LOCAL_REMOVE_HOMEDIR "remove_homedir" +#define CONFDB_LOCAL_UMASK "homedir_umask" +#define CONFDB_LOCAL_SKEL_DIR "skel_dir" +#define CONFDB_LOCAL_MAIL_DIR "mail_dir" /* Proxy Provider */ #define CONFDB_PROXY_LIBNAME "proxy_lib_name" diff --git a/server/configure.ac b/server/configure.ac index 580dda34..a182e06a 100644 --- a/server/configure.ac +++ b/server/configure.ac @@ -52,6 +52,7 @@ WITH_MANPAGES WITH_XML_CATALOG WITH_KRB5_PLUGIN_PATH WITH_PYTHON_BINDINGS +WITH_SELINUX m4_include([external/pkg.m4]) m4_include([external/libpopt.m4]) @@ -67,6 +68,7 @@ m4_include([external/libcares.m4]) m4_include([external/docbook.m4]) m4_include([external/sizes.m4]) m4_include([external/python.m4]) +m4_include([external/selinux.m4]) m4_include([util/signal.m4]) PKG_CHECK_MODULES([DBUS],[dbus-1]) @@ -101,6 +103,10 @@ if test x$HAVE_PYTHON_BINDINGS != x; then AM_PYTHON_CONFIG fi +if test x$HAVE_SELINUX != x; then + AM_CHECK_SELINUX +fi + AC_CHECK_HEADERS([sys/inotify.h]) PKG_CHECK_MODULES([CHECK], [check], [have_check=1], [have_check=]) diff --git a/server/external/selinux.m4 b/server/external/selinux.m4 new file mode 100644 index 00000000..0c5d5294 --- /dev/null +++ b/server/external/selinux.m4 @@ -0,0 +1,13 @@ +dnl A macro to check the availability of SELinux +AC_DEFUN([AM_CHECK_SELINUX], +[ + AC_CHECK_HEADERS(selinux/selinux.h, + [AC_CHECK_LIB(selinux, is_selinux_enabled, + [SELINUX_LIBS="-lselinux"], + [AC_MSG_ERROR([SELinux library is missing])] + ) + ], + [AC_MSG_ERROR([SELinux headers are missing])]) + AC_SUBST(SELINUX_LIBS) +]) + diff --git a/server/man/sss_useradd.8.xml b/server/man/sss_useradd.8.xml index 023b569e..1abb3612 100644 --- a/server/man/sss_useradd.8.xml +++ b/server/man/sss_useradd.8.xml @@ -115,6 +115,51 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term> + <option>-m</option>,<option>--create-home</option> + </term> + <listitem> + <para> + Create the user's home directory if it does not + exist. The files and directories contained in the + skeleton directory (which can be defined with the + -k option or in the config file) will be copied + to the home directory. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-M</option>,<option>--no-create-home</option> + </term> + <listitem> + <para> + Do not create the user's home directory. Overrides + configuration settings. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-k</option>,<option>--skel</option> + <replaceable>SKELDIR</replaceable> + </term> + <listitem> + <para> + The skeleton directory, which contains files + and directories to be copied in the user's home + directory, when the home directory is + created by <command>sss_useradd</command>. + </para> + <para> + This option is only valid if the <option>-m</option> + (or <option>--create-home</option>) option is + specified, or creation of home directories is set to TRUE + in the configuration. + </para> + </listitem> + </varlistentry> <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" /> </variablelist> </refsect1> diff --git a/server/man/sss_userdel.8.xml b/server/man/sss_userdel.8.xml index 99077284..2b6d923e 100644 --- a/server/man/sss_userdel.8.xml +++ b/server/man/sss_userdel.8.xml @@ -40,6 +40,42 @@ <title>OPTIONS</title> <variablelist remap='IP'> <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="include/param_help.xml" /> + <varlistentry> + <term> + <option>-r</option>,<option>--remove</option> + </term> + <listitem> + <para> + Files in the user's home directory will be + removed along with the home directory itself and + the user's mail spool. Overrides the configuration. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-R</option>,<option>--no-remove</option> + </term> + <listitem> + <para> + Files in the user's home directory will NOT be + removed along with the home directory itself and + the user's mail spool. Overrides the configuration. + </para> + </listitem> + </varlistentry> + <varlistentry> + <term> + <option>-f</option>,<option>--force</option> + </term> + <listitem> + <para> + This option forces <command>sss_userdel</command> + to remove the user's home directory and mail spool, + even if they are not owned by the specified user. + </para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/server/man/sssd.conf.5.xml b/server/man/sssd.conf.5.xml index 4b8a92f8..9baed088 100644 --- a/server/man/sssd.conf.5.xml +++ b/server/man/sssd.conf.5.xml @@ -603,6 +603,79 @@ </para> </listitem> </varlistentry> + <varlistentry> + <term>create_homedir (bool)</term> + <listitem> + <para> + Indicate if a home directory should be created by default for new users. + Can be overriden on command line. + </para> + <para> + Default: TRUE + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>remove_homedir (bool)</term> + <listitem> + <para> + Indicate if a home directory should be removed by default for deleted users. + Can be overriden on command line. + </para> + <para> + Default: TRUE + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>homedir_umask (integer)</term> + <listitem> + <para> + Used by + <citerefentry> + <refentrytitle>sss_useradd</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry> to specify the default permissions on a newly created + home directory. + </para> + <para> + Default: 077 + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>skel_dir (string)</term> + <listitem> + <para> + The skeleton directory, which contains files + and directories to be copied in the user's + home directory, when the home directory is + created by + <citerefentry> + <refentrytitle>sss_useradd</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry> + </para> + <para> + Default: <filename>/etc/skel</filename> + </para> + </listitem> + </varlistentry> + <varlistentry> + <term>mail_dir (string)</term> + <listitem> + <para> + The mail spool directory. This is needed to + manipulate the mailbox when its corresponding + user account is modified or deleted. + If not specified, a default + value is used. + </para> + <para> + Default: <filename>/var/mail</filename> + </para> + </listitem> + </varlistentry> </variablelist> </refsect2> diff --git a/server/python/pysss.c b/server/python/pysss.c index a9a949ef..8011ed67 100644 --- a/server/python/pysss.c +++ b/server/python/pysss.c @@ -149,13 +149,15 @@ struct tools_ctx *init_ctx(TALLOC_CTX *mem_ctx, PyDoc_STRVAR(py_sss_useradd__doc__, "Add a user named ``username``.\n\n" ":param username: name of the user\n\n" - ":param kwargs: Keyword arguments ro customize the operation\n\n" + ":param kwargs: Keyword arguments that customize the operation\n\n" "* useradd can be customized further with keyword arguments:\n" " * ``uid``: The UID of the user\n" " * ``gid``: The GID of the user\n" " * ``gecos``: The comment string\n" " * ``homedir``: Home directory\n" " * ``shell``: Login shell\n" + " * ``skel``: Specify an alternative skeleton directory\n" + " * ``create_home``: (bool) Force creation of home directory on or off\n" " * ``groups``: List of groups the user is member of\n"); @@ -169,15 +171,19 @@ static PyObject *py_sss_useradd(PySssLocalObject *self, const char *gecos = NULL; const char *home = NULL; const char *shell = NULL; + const char *skel = NULL; char *username = NULL; int ret; const char * const kwlist[] = { "username", "uid", "gid", "gecos", - "homedir", "shell", "groups", NULL }; + "homedir", "shell", "skel", + "create_home", "groups", NULL }; PyObject *py_groups = Py_None; + PyObject *py_create_home = Py_None; + int create_home = 0; /* parse arguments */ if (!PyArg_ParseTupleAndKeywords(args, kwds, - discard_const_p(char, "s|kksssO!"), + discard_const_p(char, "s|kkssssO!O!"), discard_const_p(char *, kwlist), &username, &uid, @@ -185,6 +191,9 @@ static PyObject *py_sss_useradd(PySssLocalObject *self, &gecos, &home, &shell, + &skel, + &PyBool_Type, + &py_create_home, &PyList_Type, &py_groups)) { goto fail; @@ -204,6 +213,14 @@ static PyObject *py_sss_useradd(PySssLocalObject *self, } } + /* user-wise the parameter is only bool - do or don't, + * however we must have a third state - undecided, pick default */ + if (py_create_home == Py_True) { + create_home = DO_CREATE_HOME; + } else if (py_create_home == Py_False) { + create_home = DO_NOT_CREATE_HOME; + } + tctx->octx->name = username; tctx->octx->uid = uid; @@ -211,7 +228,9 @@ static PyObject *py_sss_useradd(PySssLocalObject *self, ret = useradd_defaults(tctx, self->confdb, tctx->octx, gecos, - home, shell); + home, shell, + create_home, + skel); if (ret != EOK) { PyErr_SetSssError(ret); goto fail; @@ -242,6 +261,43 @@ static PyObject *py_sss_useradd(PySssLocalObject *self, goto fail; } + /* Create user's home directory and/or mail spool */ + if (tctx->octx->create_homedir) { + /* We need to know the UID and GID of the user, if + * sysdb did assign it automatically, do a lookup */ + if (tctx->octx->uid == 0 || tctx->octx->gid == 0) { + ret = sysdb_getpwnam_sync(tctx, + tctx->ev, + tctx->sysdb, + tctx->octx->name, + tctx->local, + &tctx->octx); + if (ret != EOK) { + PyErr_SetSssError(ret); + goto fail; + } + } + + ret = create_homedir(tctx, + tctx->octx->skeldir, + tctx->octx->home, + tctx->octx->name, + tctx->octx->uid, + tctx->octx->gid, + tctx->octx->umask); + if (ret != EOK) { + PyErr_SetSssError(ret); + goto fail; + } + + /* failure here should not be fatal */ + create_mail_spool(tctx, + tctx->octx->name, + tctx->octx->maildir, + tctx->octx->uid, + tctx->octx->gid); + } + talloc_zfree(tctx); Py_RETURN_NONE; @@ -255,7 +311,11 @@ fail: */ PyDoc_STRVAR(py_sss_userdel__doc__, "Remove the user named ``username``.\n\n" - ":param username: Name of user being removed\n"); + ":param username: Name of user being removed\n" + ":param kwargs: Keyword arguments that customize the operation\n\n" + "* userdel can be customized further with keyword arguments:\n" + " * ``force``: (bool) Force removal of files not owned by the user\n" + " * ``remove``: (bool) Toggle removing home directory and mail spool\n"); static PyObject *py_sss_userdel(PySssLocalObject *self, PyObject *args, @@ -264,8 +324,19 @@ static PyObject *py_sss_userdel(PySssLocalObject *self, struct tools_ctx *tctx = NULL; char *username = NULL; int ret; - - if(!PyArg_ParseTuple(args, discard_const_p(char, "s"), &username)) { + PyObject *py_remove = Py_None; + int remove_home = 0; + PyObject *py_force = Py_None; + const char * const kwlist[] = { "username", "remove", "force", NULL }; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, + discard_const_p(char, "s|O!O!"), + discard_const_p(char *, kwlist), + &username, + &PyBool_Type, + &py_remove, + &PyBool_Type, + &py_force)) { goto fail; } @@ -277,6 +348,37 @@ static PyObject *py_sss_userdel(PySssLocalObject *self, tctx->octx->name = username; + if (py_remove == Py_True) { + remove_home = DO_REMOVE_HOME; + } else if (py_remove == Py_False) { + remove_home = DO_NOT_REMOVE_HOME; + } + + /* + * Fills in defaults for ops_ctx user did not specify. + */ + ret = userdel_defaults(tctx, + tctx->confdb, + tctx->octx, + remove_home); + if (ret != EOK) { + PyErr_SetSssError(ret); + goto fail; + } + + if (tctx->octx->remove_homedir) { + ret = sysdb_getpwnam_sync(tctx, + tctx->ev, + tctx->sysdb, + tctx->octx->name, + tctx->local, + &tctx->octx); + if (ret != EOK) { + PyErr_SetSssError(ret); + goto fail; + } + } + /* Delete the user within a transaction */ start_transaction(tctx); if (tctx->error != EOK) { @@ -301,6 +403,19 @@ static PyObject *py_sss_userdel(PySssLocalObject *self, goto fail; } + if (tctx->octx->remove_homedir) { + ret = remove_homedir(tctx, + tctx->octx->home, + tctx->octx->maildir, + tctx->octx->name, + tctx->octx->uid, + (py_force == Py_True)); + if (ret != EOK) { + PyErr_SetSssError(ret); + goto fail; + } + } + talloc_zfree(tctx); Py_RETURN_NONE; @@ -315,7 +430,7 @@ fail: PyDoc_STRVAR(py_sss_usermod__doc__, "Modify a user.\n\n" ":param username: Name of user being modified\n\n" - ":param kwargs: Keyword arguments ro customize the operation\n\n" + ":param kwargs: Keyword arguments that customize the operation\n\n" "* usermod can be customized further with keyword arguments:\n" " * ``uid``: The UID of the user\n" " * ``gid``: The GID of the user\n" @@ -754,7 +869,7 @@ static PyMethodDef sss_local_methods[] = { METH_KEYWORDS, py_sss_useradd__doc__ }, { "userdel", (PyCFunction) py_sss_userdel, - METH_VARARGS, py_sss_userdel__doc__ + METH_KEYWORDS, py_sss_userdel__doc__ }, { "usermod", (PyCFunction) py_sss_usermod, METH_KEYWORDS, py_sss_usermod__doc__ diff --git a/server/tests/files-tests.c b/server/tests/files-tests.c new file mode 100644 index 00000000..206879f2 --- /dev/null +++ b/server/tests/files-tests.c @@ -0,0 +1,321 @@ +/* + * Authors: + * Jakub Hrozek <jhrozek@redhat.com> + * + * Copyright (C) 2008 Red Hat + * see file 'COPYING' for use and warranty information + * + * 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; version 3 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdlib.h> +#include <check.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> +#include <talloc.h> +#include <popt.h> + +#include "config.h" +#include "util/util.h" + +static char tpl_dir[] = "file-tests-dir-XXXXXX"; +static char *dir_path; +static char *dst_path; +static uid_t uid; +static gid_t gid; +static TALLOC_CTX *test_ctx = NULL; + +static void setup_files_test(void) +{ + /* create a temporary directory that we fill with stuff later on */ + test_ctx = talloc_new(NULL); + dir_path = mkdtemp(talloc_strdup(test_ctx, tpl_dir)); + dst_path = mkdtemp(talloc_strdup(test_ctx, tpl_dir)); + + uid = getuid(); + gid = getgid(); +} + +static void teardown_files_test(void) +{ + char *cmd = NULL; + + /* OK this is crude but since the functions to remove tree are under test.. */ + if (dir_path && test_ctx) { + cmd = talloc_asprintf(test_ctx, "/bin/rm -rf %s\n", dir_path); + system(cmd); + } + if (dst_path && test_ctx) { + cmd = talloc_asprintf(test_ctx, "/bin/rm -rf %s\n", dst_path); + system(cmd); + } + + /* clean up */ + talloc_zfree(test_ctx); +} + +static int create_simple_file(const char *name, const char *content) +{ + int fd; + ssize_t size; + int ret; + + fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0700); + fail_if(fd == -1, "Cannot create simple file\n"); + + size = write(fd, "abc", 3); + fail_if(size == -1, "Cannot write to file\n"); + + ret = fsync(fd); + fail_if(ret == -1, "Cannot sync file\n"); + + ret = close(fd); + fail_if(ret == -1, "Cannot close file\n"); + + return ret; +} + +START_TEST(test_remove_tree) +{ + int ret; + char origpath[PATH_MAX+1]; + + errno = 0; + getcwd(origpath, PATH_MAX); + fail_unless(errno == 0, "Cannot getcwd\n"); + + DEBUG(5, ("About to delete %s\n", dir_path)); + + /* create a file */ + ret = chdir(dir_path); + fail_if(ret == -1, "Cannot chdir1\n"); + + ret = create_simple_file("bar", "bar"); + fail_if(ret == -1, "Cannot create file1\n"); + + /* create a subdir and file inside it */ + ret = mkdir("subdir", 0700); + fail_if(ret == -1, "Cannot create subdir\n"); + + ret = chdir("subdir"); + fail_if(ret == -1, "Cannot chdir\n"); + + ret = create_simple_file("foo", "foo"); + fail_if(ret == -1, "Cannot create file\n"); + + /* create another subdir, empty this time */ + ret = mkdir("subdir2", 0700); + fail_if(ret == -1, "Cannot create subdir\n"); + + ret = chdir(origpath); + fail_if(ret == -1, "Cannot chdir2\n"); + + /* go back */ + ret = chdir(origpath); + fail_if(ret == -1, "Cannot chdir\n"); + + /* and finally wipe it out.. */ + ret = remove_tree(dir_path); + fail_unless(ret == EOK, "remove_tree failed\n"); + + /* check if really gone */ + ret = access(dir_path, F_OK); + fail_unless(ret == -1, "directory still there after remove_tree\n"); +} +END_TEST + +START_TEST(test_simple_copy) +{ + int ret; + char origpath[PATH_MAX+1]; + char *tmp; + int fd = -1; + + errno = 0; + getcwd(origpath, PATH_MAX); + fail_unless(errno == 0, "Cannot getcwd\n"); + + /* create a file */ + ret = chdir(dir_path); + fail_if(ret == -1, "Cannot chdir1\n"); + + ret = create_simple_file("bar", "bar"); + fail_if(ret == -1, "Cannot create file1\n"); + + /* create a subdir and file inside it */ + ret = mkdir("subdir", 0700); + fail_if(ret == -1, "Cannot create subdir\n"); + + ret = chdir("subdir"); + fail_if(ret == -1, "Cannot chdir\n"); + + ret = create_simple_file("foo", "foo"); + fail_if(ret == -1, "Cannot create file\n"); + + /* go back */ + ret = chdir(origpath); + fail_if(ret == -1, "Cannot chdir\n"); + + /* and finally copy.. */ + DEBUG(5, ("Will copy from '%s' to '%s'\n", dir_path, dst_path)); + ret = copy_tree(dir_path, dst_path, uid, gid); + fail_unless(ret == EOK, "copy_tree failed\n"); + + /* check if really copied */ + ret = access(dst_path, F_OK); + fail_unless(ret == 0, "destination directory not there\n"); + + tmp = talloc_asprintf(test_ctx, "%s/bar", dst_path); + ret = check_and_open_readonly(tmp, &fd, uid, gid, 0700); + fail_unless(ret == EOK, "Cannot open %s\n"); + close(fd); + talloc_free(tmp); +} +END_TEST + +START_TEST(test_copy_symlink) +{ + int ret; + char origpath[PATH_MAX+1]; + char *tmp; + struct stat statbuf; + + errno = 0; + getcwd(origpath, PATH_MAX); + fail_unless(errno == 0, "Cannot getcwd\n"); + + /* create a subdir */ + ret = chdir(dir_path); + fail_if(ret == -1, "Cannot chdir\n"); + + ret = create_simple_file("footarget", "foo"); + fail_if(ret == -1, "Cannot create file\n"); + + ret = symlink("footarget", "foolink"); + fail_if(ret == -1, "Cannot create symlink\n"); + + /* go back */ + ret = chdir(origpath); + fail_if(ret == -1, "Cannot chdir\n"); + + /* and finally copy.. */ + DEBUG(5, ("Will copy from '%s' to '%s'\n", dir_path, dst_path)); + ret = copy_tree(dir_path, dst_path, uid, gid); + fail_unless(ret == EOK, "copy_tree failed\n"); + + /* check if really copied */ + ret = access(dst_path, F_OK); + fail_unless(ret == 0, "destination directory not there\n"); + + tmp = talloc_asprintf(test_ctx, "%s/foolink", dst_path); + ret = lstat(tmp, &statbuf); + fail_unless(ret == 0, "cannot stat the symlink %s\n", tmp); + fail_unless(S_ISLNK(statbuf.st_mode), "%s not a symlink?\n", tmp); + talloc_free(tmp); +} +END_TEST + +START_TEST(test_copy_node) +{ + int ret; + char origpath[PATH_MAX+1]; + char *tmp; + struct stat statbuf; + + errno = 0; + getcwd(origpath, PATH_MAX); + fail_unless(errno == 0, "Cannot getcwd\n"); + + /* create a node */ + ret = chdir(dir_path); + fail_if(ret == -1, "Cannot chdir\n"); + + ret = mknod("testnode", S_IFIFO | S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH, 0); + fail_unless(ret == 0, "cannot stat /dev/null: %s", strerror(errno)); + + /* go back */ + ret = chdir(origpath); + fail_if(ret == -1, "Cannot chdir\n"); + + /* and finally copy.. */ + DEBUG(5, ("Will copy from '%s' to '%s'\n", dir_path, dst_path)); + ret = copy_tree(dir_path, dst_path, uid, gid); + fail_unless(ret == EOK, "copy_tree failed\n"); + + /* check if really copied */ + ret = access(dst_path, F_OK); + fail_unless(ret == 0, "destination directory not there\n"); + + tmp = talloc_asprintf(test_ctx, "%s/testnode", dst_path); + ret = lstat(tmp, &statbuf); + fail_unless(ret == 0, "cannot stat the node %s\n", tmp); + fail_unless(S_ISFIFO(statbuf.st_mode), "%s not a char device??\n", tmp); + talloc_free(tmp); +} +END_TEST + +static Suite *files_suite(void) +{ + Suite *s = suite_create("files_suite"); + + TCase *tc_files = tcase_create("files"); + tcase_add_checked_fixture(tc_files, + setup_files_test, + teardown_files_test); + + tcase_add_test(tc_files, test_remove_tree); + tcase_add_test(tc_files, test_simple_copy); + tcase_add_test(tc_files, test_copy_symlink); + tcase_add_test(tc_files, test_copy_node); + suite_add_tcase(s, tc_files); + + return s; +} + +int main(int argc, char *argv[]) +{ + int number_failed; + int opt; + poptContext pc; + int debug = 0; + + struct poptOption long_options[] = { + POPT_AUTOHELP + { "debug-level", 'd', POPT_ARG_INT, &debug, 0, "Set debug level", NULL }, + POPT_TABLEEND + }; + + pc = poptGetContext(argv[0], argc, (const char **) argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + poptFreeContext(pc); + debug_level = debug; + + Suite *s = files_suite(); + SRunner *sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/server/tests/python-test.py b/server/tests/python-test.py index fddf9c31..e1eaab2d 100644 --- a/server/tests/python-test.py +++ b/server/tests/python-test.py @@ -20,6 +20,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import os +import tempfile +import shutil import unittest import commands import errno @@ -120,9 +123,15 @@ class LocalTest(unittest.TestCase): def add_user(self, username): self._run_and_check("sss_useradd %s" % (username)) + def add_user_not_home(self, username): + self._run_and_check("sss_useradd -M %s" % (username)) + def remove_user(self, username): self._run_and_check("sss_userdel %s" % (username)) + def remove_user_not_home(self, username): + self._run_and_check("sss_userdel -R %s" % (username)) + class SanityTest(unittest.TestCase): def testInstantiate(self): "Test that the local backed binding can be instantiated" @@ -139,18 +148,51 @@ class UseraddTest(LocalTest): self.username = "testUseradd" self.local.useradd(self.username) self.validate_user(self.username) + # check home directory was created with default name + self.assertEquals(os.access("/home/%s" % self.username, os.F_OK), True) def testUseraddWithParams(self): "Test adding a local user with modified parameters" self.username = "testUseraddWithParams" self.local.useradd(self.username, gecos="foo bar", - homedir="/people/foobar", + homedir="/home/foobar", shell="/bin/zsh") self.validate_user(self.username, gecos="foo bar", - homeDirectory="/people/foobar", + homeDirectory="/home/foobar", loginShell="/bin/zsh") + # check home directory was created with nondefault name + self.assertEquals(os.access("/home/foobar", os.F_OK), True) + + def testUseraddNoHomedir(self): + "Test adding a local user without creating his home dir" + self.username = "testUseraddNoHomedir" + self.local.useradd(self.username, create_home = False) + self.validate_user(self.username) + # check home directory was not created + self.assertEquals(os.access("/home/%s" % self.username, os.F_OK), False) + self.local.userdel(self.username, remove = False) + self.username = None # fool tearDown into not removing the user + + def testUseraddAlternateSkeldir(self): + "Test adding a local user and init his homedir from a custom location" + self.username = "testUseraddAlternateSkeldir" + + skeldir = tempfile.mkdtemp() + fd, path = tempfile.mkstemp(dir=skeldir) + fdo = os.fdopen(fd) + fdo.flush() + fdo.close + self.assertEquals(os.access(path, os.F_OK), True) + filename = os.path.basename(path) + + try: + self.local.useradd(self.username, skel = skeldir) + self.validate_user(self.username) + self.assertEquals(os.access("/home/%s/%s"%(self.username,filename), os.F_OK), True) + finally: + shutil.rmtree(skeldir) def testUseraddToGroups(self): "Test adding a local user with group membership" @@ -208,9 +250,21 @@ class UseraddTestNegative(LocalTest): class UserdelTest(LocalTest): def testUserdel(self): self.add_user("testUserdel") + self.assertEquals(os.access("/home/testUserdel", os.F_OK), True) self.validate_user("testUserdel") self.local.userdel("testUserdel") self.validate_no_user("testUserdel") + self.assertEquals(os.access("/home/testUserdel", os.F_OK), False) + + def testUserdelNotHomedir(self): + self.add_user("testUserdel") + self.assertEquals(os.access("/home/testUserdel", os.F_OK), True) + self.validate_user("testUserdel") + self.local.userdel("testUserdel", remove=False) + self.validate_no_user("testUserdel") + self.assertEquals(os.access("/home/testUserdel", os.F_OK), True) + shutil.rmtree("/home/testUserdel") + os.remove("/var/mail/testUserdel") def testUserdelNegative(self): self.validate_no_user("testUserdelNegative") @@ -225,20 +279,20 @@ class UsermodTest(LocalTest): def setUp(self): self.local = pysss.local() self.username = "UsermodTest" - self.add_user(self.username) + self.add_user_not_home(self.username) def tearDown(self): - self.remove_user(self.username) + self.remove_user_not_home(self.username) def testUsermod(self): "Test modifying user attributes" self.local.usermod(self.username, gecos="foo bar", - homedir="/people/foobar", + homedir="/home/foobar", shell="/bin/zsh") self.validate_user(self.username, gecos="foo bar", - homeDirectory="/people/foobar", + homeDirectory="/home/foobar", loginShell="/bin/zsh") def testUsermodUID(self): diff --git a/server/tools/sss_sync_ops.c b/server/tools/sss_sync_ops.c index 932a7122..2bea4f07 100644 --- a/server/tools/sss_sync_ops.c +++ b/server/tools/sss_sync_ops.c @@ -20,6 +20,7 @@ #include <tevent.h> #include <talloc.h> +#include <sys/types.h> #include "util/util.h" #include "db/sysdb.h" @@ -28,6 +29,12 @@ /* Default settings for user attributes */ #define DFL_SHELL_VAL "/bin/bash" #define DFL_BASEDIR_VAL "/home" +#define DFL_CREATE_HOMEDIR "TRUE" +#define DFL_REMOVE_HOMEDIR "TRUE" +#define DFL_UMASK 077 +#define DFL_SKEL_DIR "/etc/skel" +#define DFL_MAIL_DIR "/var/spool/mail" + #define VAR_CHECK(var, val, attr, msg) do { \ if (var != (val)) { \ @@ -1111,6 +1118,47 @@ static int group_mod_recv(struct tevent_req *req) return sync_ops_recv(req); } +int userdel_defaults(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb, + struct ops_ctx *data, + int remove_home) +{ + int ret; + char *conf_path; + bool dfl_remove_home; + + conf_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, data->domain->name); + if (!conf_path) { + return ENOMEM; + } + + /* remove homedir on user creation? */ + if (!remove_home) { + ret = confdb_get_bool(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_REMOVE_HOMEDIR, + DFL_REMOVE_HOMEDIR, &dfl_remove_home); + if (ret != EOK) { + goto done; + } + data->remove_homedir = dfl_remove_home; + } else { + data->remove_homedir = (remove_home == DO_REMOVE_HOME); + } + + /* a directory to remove mail spools from */ + ret = confdb_get_string(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_MAIL_DIR, + DFL_MAIL_DIR, &data->maildir); + if (ret != EOK) { + goto done; + } + + ret = EOK; +done: + talloc_free(conf_path); + return ret; +} + /* * Default values for add operations */ @@ -1119,11 +1167,12 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, struct ops_ctx *data, const char *gecos, const char *homedir, - const char *shell) + const char *shell, + int create_home, + const char *skeldir) { int ret; char *basedir = NULL; - char *dfl_shell = NULL; char *conf_path = NULL; conf_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, data->domain->name); @@ -1131,18 +1180,17 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, return ENOMEM; } + /* gecos */ data->gecos = talloc_strdup(mem_ctx, gecos ? gecos : data->name); if (!data->gecos) { ret = ENOMEM; goto done; } + DEBUG(7, ("Gecos: %s\n", data->gecos)); + /* homedir */ if (homedir) { data->home = talloc_strdup(data, homedir); - if (data->home == NULL) { - ret = ENOMEM; - goto done; - } } else { ret = confdb_get_string(confdb, mem_ctx, conf_path, CONFDB_LOCAL_DEFAULT_BASEDIR, @@ -1151,34 +1199,80 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, goto done; } data->home = talloc_asprintf(mem_ctx, "%s/%s", basedir, data->name); - if (!data->home) { - ret = ENOMEM; - goto done; - } } if (!data->home) { ret = ENOMEM; goto done; } + DEBUG(7, ("Homedir: %s\n", data->home)); + /* default shell */ if (!shell) { ret = confdb_get_string(confdb, mem_ctx, conf_path, CONFDB_LOCAL_DEFAULT_SHELL, - DFL_SHELL_VAL, &dfl_shell); + DFL_SHELL_VAL, &data->shell); if (ret != EOK) { goto done; } - shell = dfl_shell; + } else { + data->shell = talloc_strdup(mem_ctx, shell); + if (!data->shell) { + ret = ENOMEM; + goto done; + } } - data->shell = talloc_strdup(mem_ctx, shell); - if (!data->shell) { - ret = ENOMEM; + DEBUG(7, ("Shell: %s\n", data->shell)); + + /* create homedir on user creation? */ + if (!create_home) { + ret = confdb_get_bool(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_CREATE_HOMEDIR, + DFL_CREATE_HOMEDIR, &data->create_homedir); + if (ret != EOK) { + goto done; + } + } else { + data->create_homedir = (create_home == DO_CREATE_HOME); + } + DEBUG(7, ("Auto create homedir: %s\n", data->create_homedir?"True":"False")); + + /* umask to create homedirs */ + ret = confdb_get_int(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_UMASK, + DFL_UMASK, (int *) &data->umask); + if (ret != EOK) { + goto done; + } + DEBUG(7, ("Umask: %o\n", data->umask)); + + /* a directory to create mail spools in */ + ret = confdb_get_string(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_MAIL_DIR, + DFL_MAIL_DIR, &data->maildir); + if (ret != EOK) { goto done; } + DEBUG(7, ("Mail dir: %s\n", data->maildir)); + + /* skeleton dir */ + if (!skeldir) { + ret = confdb_get_string(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_SKEL_DIR, + DFL_SKEL_DIR, &data->skeldir); + if (ret != EOK) { + goto done; + } + } else { + data->skeldir = talloc_strdup(mem_ctx, skeldir); + if (!data->skeldir) { + ret = ENOMEM; + goto done; + } + } + DEBUG(7, ("Skeleton dir: %s\n", data->skeldir)); ret = EOK; done: - talloc_free(dfl_shell); talloc_free(basedir); talloc_free(conf_path); return ret; diff --git a/server/tools/sss_sync_ops.h b/server/tools/sss_sync_ops.h index 3988992e..383319a8 100644 --- a/server/tools/sss_sync_ops.h +++ b/server/tools/sss_sync_ops.h @@ -27,6 +27,13 @@ #define DO_LOCK 1 #define DO_UNLOCK 2 +/* 0 = not set, pick default */ +#define DO_CREATE_HOME 1 +#define DO_NOT_CREATE_HOME 2 +#define DO_REMOVE_HOME 1 +#define DO_NOT_REMOVE_HOME 2 +#define DO_FORCE_REMOVAL 1 + struct ops_ctx { struct sss_domain_info *domain; @@ -38,6 +45,12 @@ struct ops_ctx { char *shell; int lock; + bool create_homedir; + bool remove_homedir; + mode_t umask; + char *skeldir; + char *maildir; + char **addgroups; char **rmgroups; }; @@ -48,7 +61,15 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, struct ops_ctx *data, const char *gecos, const char *homedir, - const char *shell); + const char *shell, + int create_home, + const char *skeldir); + +/* default values for remove operations */ +int userdel_defaults(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb, + struct ops_ctx *data, + int remove_home); /* synchronous operations */ int useradd(TALLOC_CTX *mem_ctx, diff --git a/server/tools/sss_useradd.c b/server/tools/sss_useradd.c index d05f5ae9..94de68fd 100644 --- a/server/tools/sss_useradd.c +++ b/server/tools/sss_useradd.c @@ -32,15 +32,6 @@ #include "tools/tools_util.h" #include "tools/sss_sync_ops.h" -/* Default settings for user attributes */ -#define CONFDB_DFL_SECTION "config/user_defaults" - -#define DFL_SHELL_ATTR "defaultShell" -#define DFL_BASEDIR_ATTR "baseDirectory" - -#define DFL_SHELL_VAL "/bin/bash" -#define DFL_BASEDIR_VAL "/home" - static void get_gid_callback(void *ptr, int error, struct ldb_result *res) { struct tools_ctx *tctx = talloc_get_type(ptr, struct tools_ctx); @@ -115,7 +106,9 @@ int main(int argc, const char **argv) const char *pc_home = NULL; char *pc_shell = NULL; int pc_debug = 0; + int pc_create_home = 0; const char *pc_username = NULL; + const char *pc_skeldir = NULL; struct poptOption long_options[] = { POPT_AUTOHELP { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL }, @@ -125,6 +118,9 @@ int main(int argc, const char **argv) { "home", 'h', POPT_ARG_STRING, &pc_home, 0, _("Home directory"), NULL }, { "shell", 's', POPT_ARG_STRING, &pc_shell, 0, _("Login shell"), NULL }, { "groups", 'G', POPT_ARG_STRING, NULL, 'G', _("Groups"), NULL }, + { "create-home", 'm', POPT_ARG_NONE, NULL, 'm', _("Create user's directory if it does not exist"), NULL }, + { "no-create-home", 'M', POPT_ARG_NONE, NULL, 'M', _("Never create user's directory, overrides config"), NULL }, + { "skel", 'k', POPT_ARG_STRING, &pc_skeldir, 0, _("Specify an alternative skeleton directory") }, POPT_TABLEEND }; poptContext pc = NULL; @@ -147,12 +143,18 @@ int main(int argc, const char **argv) pc = poptGetContext(NULL, argc, argv, long_options, 0); poptSetOtherOptionHelp(pc, "USERNAME"); while ((ret = poptGetNextOpt(pc)) > 0) { - if (ret == 'G') { - groups = poptGetOptArg(pc); - if (!groups) { - ret = -1; + switch (ret) { + case 'G': + groups = poptGetOptArg(pc); + if (!groups) goto fini; + + case 'm': + pc_create_home = DO_CREATE_HOME; + break; + + case 'M': + pc_create_home = DO_NOT_CREATE_HOME; break; - } } } @@ -232,7 +234,8 @@ int main(int argc, const char **argv) * Fills in defaults for ops_ctx user did not specify. */ ret = useradd_defaults(tctx, tctx->confdb, tctx->octx, - pc_gecos, pc_home, pc_shell); + pc_gecos, pc_home, pc_shell, + pc_create_home, pc_skeldir); if (ret != EOK) { ERROR("Cannot set default values\n"); ret = EXIT_FAILURE; @@ -263,6 +266,54 @@ int main(int argc, const char **argv) end_transaction(tctx); + /* Create user's home directory and/or mail spool */ + if (tctx->octx->create_homedir) { + /* We need to know the UID and GID of the user, if + * sysdb did assign it automatically, do a lookup */ + if (tctx->octx->uid == 0 || tctx->octx->gid == 0) { + ret = sysdb_getpwnam_sync(tctx, + tctx->ev, + tctx->sysdb, + tctx->octx->name, + tctx->local, + &tctx->octx); + if (ret != EOK) { + ERROR("Cannot get info about the user\n"); + ret = EXIT_FAILURE; + goto fini; + } + } + + ret = create_homedir(tctx, + tctx->octx->skeldir, + tctx->octx->home, + tctx->octx->name, + tctx->octx->uid, + tctx->octx->gid, + tctx->octx->umask); + if (ret == EEXIST) { + ERROR("User's home directory already exists, not copying " + "data from skeldir\n"); + } else if (ret != EOK) { + ERROR("Cannot create user's home directory: %s\n", strerror(ret)); + ret = EXIT_FAILURE; + goto fini; + } + + ret = create_mail_spool(tctx, + tctx->octx->name, + tctx->octx->maildir, + tctx->octx->uid, + tctx->octx->gid); + if (ret != EOK) { + ERROR("Cannot create user's mail spool: %s\n", strerror(ret)); + DEBUG(1, ("Cannot create user's mail spool: [%d][%s].\n", + ret, strerror(ret))); + ret = EXIT_FAILURE; + goto fini; + } + } + done: if (tctx->error) { switch (tctx->error) { diff --git a/server/tools/sss_userdel.c b/server/tools/sss_userdel.c index d14ef3da..d4088cb5 100644 --- a/server/tools/sss_userdel.c +++ b/server/tools/sss_userdel.c @@ -36,11 +36,16 @@ int main(int argc, const char **argv) const char *pc_username = NULL; int pc_debug = 0; + int pc_remove = 0; + int pc_force = 0; poptContext pc = NULL; struct poptOption long_options[] = { POPT_AUTOHELP { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL }, + { "remove", 'r', POPT_ARG_NONE, NULL, 'r', _("Remove home directory and mail spool"), NULL }, + { "no-remove", 'R', POPT_ARG_NONE, NULL, 'R', _("Do not remove home directory and mail spool"), NULL }, + { "force", 'f', POPT_ARG_NONE, NULL, 'f', _("Force removal of files not owned by the user"), NULL }, POPT_TABLEEND }; @@ -57,14 +62,30 @@ int main(int argc, const char **argv) /* parse parameters */ pc = poptGetContext(NULL, argc, argv, long_options, 0); poptSetOtherOptionHelp(pc, "USERNAME"); - if ((ret = poptGetNextOpt(pc)) < -1) { + while ((ret = poptGetNextOpt(pc)) > 0) { + switch (ret) { + case 'r': + pc_remove = DO_REMOVE_HOME; + break; + + case 'R': + pc_remove = DO_NOT_REMOVE_HOME; + break; + + case 'f': + pc_force = DO_FORCE_REMOVAL; + break; + } + } + + debug_level = pc_debug; + + if (ret != -1) { usage(pc, poptStrerror(ret)); ret = EXIT_FAILURE; goto fini; } - debug_level = pc_debug; - pc_username = poptGetArg(pc); if (pc_username == NULL) { usage(pc, _("Specify user to delete\n")); @@ -90,6 +111,29 @@ int main(int argc, const char **argv) goto fini; } + /* + * Fills in defaults for ops_ctx user did not specify. + */ + ret = userdel_defaults(tctx, tctx->confdb, tctx->octx, pc_remove); + if (ret != EOK) { + ERROR("Cannot set default values\n"); + ret = EXIT_FAILURE; + goto fini; + } + + if (tctx->octx->remove_homedir) { + ret = sysdb_getpwnam_sync(tctx, + tctx->ev, + tctx->sysdb, + tctx->octx->name, + tctx->local, + &tctx->octx); + if (ret != EOK) { + /* Error message will be printed in the switch */ + goto done; + } + } + start_transaction(tctx); if (tctx->error != EOK) { goto done; @@ -107,9 +151,25 @@ int main(int argc, const char **argv) end_transaction(tctx); + if (tctx->octx->remove_homedir) { + ret = remove_homedir(tctx, + tctx->octx->home, + tctx->octx->maildir, + tctx->octx->name, + tctx->octx->uid, + pc_force); + if (ret == EPERM) { + ERROR("Not removing home dir - not owned by user\n"); + } else if (ret != EOK) { + ERROR("Cannot remove homedir: %s\n", strerror(ret)); + ret = EXIT_FAILURE; + goto fini; + } + } + + ret = tctx->error; done: - if (tctx->error) { - ret = tctx->error; + if (ret) { DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret))); switch (ret) { case ENOENT: diff --git a/server/tools/tools_util.c b/server/tools/tools_util.c index 17cc3aa7..bcb8d5c1 100644 --- a/server/tools/tools_util.c +++ b/server/tools/tools_util.c @@ -23,6 +23,14 @@ #include <tevent.h> #include <popt.h> #include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "config.h" +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif #include "util/util.h" #include "confdb/confdb.h" @@ -294,3 +302,218 @@ fini: return ret; } +/* + * Check is path is owned by uid + * returns 0 - owns + * -1 - does not own + * >0 - an error occured, error code + */ +static int is_owner(uid_t uid, const char *path) +{ + struct stat statres; + int ret; + + ret = stat(path, &statres); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot stat %s: [%d][%s]\n", path, ret, strerror(ret))); + return ret; + } + + if (statres.st_uid == uid) { + return EOK; + } + return -1; +} + +static int remove_mail_spool(TALLOC_CTX *mem_ctx, + const char *maildir, + const char *username, + uid_t uid, + bool force) +{ + int ret; + char *spool_file; + + spool_file = talloc_asprintf(mem_ctx, "%s/%s", maildir, username); + if (spool_file == NULL) { + ret = ENOMEM; + goto fail; + } + + if (force == false) { + /* Check the owner of the mail spool */ + ret = is_owner(uid, spool_file); + switch (ret) { + case 0: + break; + case -1: + DEBUG(3, ("%s not owned by %d, not removing\n", + spool_file, uid)); + ret = EACCES; + /* FALLTHROUGH */ + default: + goto fail; + } + } + + ret = unlink(spool_file); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot remove() the spool file %s: [%d][%s]\n", + spool_file, ret, strerror(ret))); + goto fail; + } + +fail: + talloc_free(spool_file); + return ret; +} + +int remove_homedir(TALLOC_CTX *mem_ctx, + const char *homedir, + const char *maildir, + const char *username, + uid_t uid, bool force) +{ + int ret; + + ret = remove_mail_spool(mem_ctx, maildir, username, uid, force); + if (ret != EOK) { + DEBUG(1, ("Cannot remove user's mail spool\n")); + /* Should this be fatal? I don't think so. Maybe convert to ERROR? */ + } + + if (force == false && is_owner(uid, homedir) == -1) { + DEBUG(1, ("Not removing home dir - not owned by user\n")); + return EPERM; + } + + /* Remove the tree */ + ret = remove_tree(homedir); + if (ret != EOK) { + DEBUG(1, ("Cannot remove homedir %s: %d\n", + homedir, ret)); + return ret; + } + + return EOK; +} + +/* The reason for not putting this into create_homedir + * is better granularity when it comes to reporting error + * messages and tracebacks in pysss + */ +int create_mail_spool(TALLOC_CTX *mem_ctx, + const char *username, + const char *maildir, + uid_t uid, gid_t gid) +{ + char *spool_file = NULL; + int fd; + int ret; + + spool_file = talloc_asprintf(mem_ctx, "%s/%s", maildir, username); + if (spool_file == NULL) { + ret = ENOMEM; + goto fail; + } + + selinux_file_context(spool_file); + + fd = open(spool_file, O_CREAT | O_WRONLY | O_EXCL, 0); + if (fd < 0) { + ret = errno; + DEBUG(1, ("Cannot open() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = fchmod(fd, 0600); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fchmod() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = fchown(fd, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fchown() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = fsync(fd); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fsync() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = close(fd); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot close() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + +fail: + reset_selinux_file_context(); + talloc_free(spool_file); + return ret; +} + +int create_homedir(TALLOC_CTX *mem_ctx, + const char *skeldir, + const char *homedir, + const char *username, + uid_t uid, + gid_t gid, + mode_t default_umask) +{ + int ret; + + selinux_file_context(homedir); + + ret = mkdir(homedir, 0); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot create user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + ret = chown(homedir, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chown user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + ret = chmod(homedir, 0777 & ~default_umask); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chmod user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + reset_selinux_file_context(); + + ret = copy_tree(skeldir, homedir, uid, gid); + if (ret != EOK) { + DEBUG(1, ("Cannot populate user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + +done: + reset_selinux_file_context(); + return ret; +} + diff --git a/server/tools/tools_util.h b/server/tools/tools_util.h index 92fba20d..2a1ee25e 100644 --- a/server/tools/tools_util.h +++ b/server/tools/tools_util.h @@ -76,4 +76,23 @@ int check_group_names(struct tools_ctx *tctx, char **grouplist, char **badgroup); +int create_homedir(TALLOC_CTX *mem_ctx, + const char *skeldir, + const char *homedir, + const char *username, + uid_t uid, + gid_t gid, + mode_t default_umask); + +int create_mail_spool(TALLOC_CTX *mem_ctx, + const char *username, + const char *maildir, + uid_t uid, gid_t gid); + +int remove_homedir(TALLOC_CTX *mem_ctx, + const char *homedir, + const char *maildir, + const char *username, + uid_t uid, bool force); + #endif /* __TOOLS_UTIL_H__ */ diff --git a/server/util/files.c b/server/util/files.c new file mode 100644 index 00000000..ce73fc8b --- /dev/null +++ b/server/util/files.c @@ -0,0 +1,735 @@ +/* + Authors: + Jakub Hrozek <jhrozek@redhat.com> + + 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 <http://www.gnu.org/licenses/>. +*/ + +/* + * 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 <sys/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <dirent.h> +#include <fcntl.h> +#include <errno.h> +#include <talloc.h> + +#include "config.h" +#include "util/util.h" + +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#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 9a27ae55..70dba372 100644 --- a/server/util/util.h +++ b/server/util/util.h @@ -205,4 +205,14 @@ 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__ */ |