>From efe8ec0c1df8847d81a3a0a49efb1637eb0ab2d8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Mon, 5 Oct 2009 19:45:03 +0200
Subject: [PATCH] User home directories management

Create and populate user directories on useradd, delete them on userdel

Fixes: #212
---
 server/Makefile.am           |   42 ++-
 server/conf_macros.m4        |   17 +
 server/confdb/confdb.h       |    5 +
 server/configure.ac          |    6 +
 server/external/selinux.m4   |   13 +
 server/man/sss_useradd.8.xml |   45 +++
 server/man/sss_userdel.8.xml |   36 ++
 server/man/sssd.conf.5.xml   |   73 +++++
 server/python/pysss.c        |  133 +++++++-
 server/tests/files-tests.c   |  321 ++++++++++++++++++
 server/tests/python-test.py  |   66 ++++-
 server/tools/sss_sync_ops.c  |  126 +++++++-
 server/tools/sss_sync_ops.h  |   23 ++-
 server/tools/sss_useradd.c   |   81 ++++-
 server/tools/sss_userdel.c   |   70 ++++-
 server/tools/tools_util.c    |  223 +++++++++++++
 server/tools/tools_util.h    |   19 ++
 server/util/files.c          |  735 ++++++++++++++++++++++++++++++++++++++++++
 server/util/util.h           |   10 +
 19 files changed, 1984 insertions(+), 60 deletions(-)
 create mode 100644 server/external/selinux.m4
 create mode 100644 server/tests/files-tests.c
 create mode 100644 server/util/files.c

diff --git a/server/Makefile.am b/server/Makefile.am
index 81f438d..38d7d54 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 #
@@ -405,6 +413,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 410914e..0990e50 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 0894327..4d4e8c2 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 580dda3..a182e06 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 0000000..0c5d529
--- /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 023b569..1abb361 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 9907728..2b6d923 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 4b8a92f..9baed08 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 a9a949e..8011ed6 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 0000000..206879f
--- /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 fddf9c3..e1eaab2 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 932a712..2bea4f0 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 3988992..383319a 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 f805b24..5a579aa 100644
--- a/server/tools/sss_useradd.c
+++ b/server/tools/sss_useradd.c
@@ -33,15 +33,6 @@
 #include "tools/sss_sync_ops.h"
 #include "util/sssd-i18n.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);
@@ -116,7 +107,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 },
@@ -126,6 +119,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;
@@ -148,12 +144,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;
-            }
         }
     }
 
@@ -233,7 +235,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;
@@ -264,6 +267,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 d14ef3d..d4088cb 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 17cc3aa..bcb8d5c 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 a56fc5e..bc9199a 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 0000000..ce73fc8
--- /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 a9d2553..f93f04b 100644
--- a/server/util/util.h
+++ b/server/util/util.h
@@ -199,4 +199,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__ */
-- 
1.6.2.5

