summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/Makefile.am42
-rw-r--r--server/conf_macros.m417
-rw-r--r--server/confdb/confdb.h5
-rw-r--r--server/configure.ac6
-rw-r--r--server/external/selinux.m413
-rw-r--r--server/man/sss_useradd.8.xml45
-rw-r--r--server/man/sss_userdel.8.xml36
-rw-r--r--server/man/sssd.conf.5.xml73
-rw-r--r--server/python/pysss.c133
-rw-r--r--server/tests/files-tests.c321
-rw-r--r--server/tests/python-test.py66
-rw-r--r--server/tools/sss_sync_ops.c126
-rw-r--r--server/tools/sss_sync_ops.h23
-rw-r--r--server/tools/sss_useradd.c81
-rw-r--r--server/tools/sss_userdel.c70
-rw-r--r--server/tools/tools_util.c223
-rw-r--r--server/tools/tools_util.h19
-rw-r--r--server/util/files.c735
-rw-r--r--server/util/util.h10
19 files changed, 1984 insertions, 60 deletions
diff --git a/server/Makefile.am b/server/Makefile.am
index 8c6cc48ab..63020c8d0 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 410914e7e..0990e507a 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 089432729..4d4e8c297 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 580dda346..a182e06a3 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 000000000..0c5d5294e
--- /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 023b569ea..1abb36129 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 990772844..2b6d923e4 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 4b8a92f8b..9baed088e 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 a9a949ef2..8011ed67e 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 000000000..206879f27
--- /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 fddf9c311..e1eaab2d1 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 932a71222..2bea4f07c 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 3988992e9..383319a8f 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 d05f5ae94..94de68fd9 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 d14ef3da3..d4088cb51 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 17cc3aa7f..bcb8d5c1e 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 92fba20d0..2a1ee25e8 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 000000000..ce73fc8be
--- /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 9a27ae559..70dba3721 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__ */